aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host/whci
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/whci')
-rw-r--r--drivers/usb/host/whci/Kbuild3
-rw-r--r--drivers/usb/host/whci/asl.c106
-rw-r--r--drivers/usb/host/whci/debug.c203
-rw-r--r--drivers/usb/host/whci/hcd.c54
-rw-r--r--drivers/usb/host/whci/hw.c23
-rw-r--r--drivers/usb/host/whci/init.c3
-rw-r--r--drivers/usb/host/whci/int.c2
-rw-r--r--drivers/usb/host/whci/pzl.c111
-rw-r--r--drivers/usb/host/whci/qset.c424
-rw-r--r--drivers/usb/host/whci/whcd.h22
-rw-r--r--drivers/usb/host/whci/whci-hc.h18
-rw-r--r--drivers/usb/host/whci/wusb.c44
12 files changed, 783 insertions, 230 deletions
diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild
index 26a3871ea0f..26df0138079 100644
--- a/drivers/usb/host/whci/Kbuild
+++ b/drivers/usb/host/whci/Kbuild
@@ -2,7 +2,8 @@ obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
whci-hcd-y := \
asl.o \
- hcd.o \
+ debug.o \
+ hcd.o \
hw.o \
init.o \
int.o \
diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c
index 4d7078e5057..77324930603 100644
--- a/drivers/usb/host/whci/asl.c
+++ b/drivers/usb/host/whci/asl.c
@@ -16,35 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
+#include <linux/gfp.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include <linux/usb.h>
-#define D_LOCAL 0
-#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
-#if D_LOCAL >= 4
-static void dump_asl(struct whc *whc, const char *tag)
-{
- struct device *dev = &whc->umc->dev;
- struct whc_qset *qset;
-
- d_printf(4, dev, "ASL %s\n", tag);
-
- list_for_each_entry(qset, &whc->async_list, list_node) {
- dump_qset(qset, dev);
- }
-}
-#else
-static inline void dump_asl(struct whc *whc, const char *tag)
-{
-}
-#endif
-
-
static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
struct whc_qset **next, struct whc_qset **prev)
{
@@ -136,6 +116,10 @@ static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
if (status & QTD_STS_HALTED) {
/* Ug, an error. */
process_halted_qtd(whc, qset, td);
+ /* A halted qTD always triggers an update
+ because the qset was either removed or
+ reactivated. */
+ update |= WHC_UPDATE_UPDATED;
goto done;
}
@@ -143,7 +127,8 @@ static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
process_inactive_qtd(whc, qset, td);
}
- update |= qset_add_qtds(whc, qset);
+ if (!qset->remove)
+ update |= qset_add_qtds(whc, qset);
done:
/*
@@ -179,11 +164,31 @@ void asl_stop(struct whc *whc)
1000, "stop ASL");
}
+/**
+ * asl_update - request an ASL update and wait for the hardware to be synced
+ * @whc: the WHCI HC
+ * @wusbcmd: WUSBCMD value to start the update.
+ *
+ * If the WUSB HC is inactive (i.e., the ASL is stopped) then the
+ * update must be skipped as the hardware may not respond to update
+ * requests.
+ */
void asl_update(struct whc *whc, uint32_t wusbcmd)
{
- whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
- wait_event(whc->async_list_wq,
- (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0);
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ long t;
+
+ mutex_lock(&wusbhc->mutex);
+ if (wusbhc->active) {
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ t = wait_event_timeout(
+ whc->async_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0,
+ msecs_to_jiffies(1000));
+ if (t == 0)
+ whc_hw_error(whc, "ASL update timeout");
+ }
+ mutex_unlock(&wusbhc->mutex);
}
/**
@@ -202,8 +207,6 @@ void scan_async_work(struct work_struct *work)
spin_lock_irq(&whc->lock);
- dump_asl(whc, "before processing");
-
/*
* Transerve the software list backwards so new qsets can be
* safely inserted into the ASL without making it non-circular.
@@ -217,8 +220,6 @@ void scan_async_work(struct work_struct *work)
update |= process_qset(whc, qset);
}
- dump_asl(whc, "after processing");
-
spin_unlock_irq(&whc->lock);
if (update) {
@@ -231,14 +232,24 @@ void scan_async_work(struct work_struct *work)
/*
* Now that the ASL is updated, complete the removal of any
* removed qsets.
+ *
+ * If the qset was to be reset, do so and reinsert it into the
+ * ASL if it has pending transfers.
*/
- spin_lock(&whc->lock);
+ spin_lock_irq(&whc->lock);
list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
qset_remove_complete(whc, qset);
+ if (qset->reset) {
+ qset_reset(whc, qset);
+ if (!list_empty(&qset->stds)) {
+ asl_qset_insert_begin(whc, qset);
+ queue_work(whc->workqueue, &whc->async_work);
+ }
+ }
}
- spin_unlock(&whc->lock);
+ spin_unlock_irq(&whc->lock);
}
/**
@@ -259,23 +270,29 @@ int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
spin_lock_irqsave(&whc->lock, flags);
+ err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (err < 0) {
+ spin_unlock_irqrestore(&whc->lock, flags);
+ return err;
+ }
+
qset = get_qset(whc, urb, GFP_ATOMIC);
if (qset == NULL)
err = -ENOMEM;
else
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
if (!err) {
- usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
- if (!qset->in_sw_list)
+ if (!qset->in_sw_list && !qset->remove)
asl_qset_insert_begin(whc, qset);
- }
+ } else
+ usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
spin_unlock_irqrestore(&whc->lock, flags);
if (!err)
queue_work(whc->workqueue, &whc->async_work);
- return 0;
+ return err;
}
/**
@@ -293,6 +310,7 @@ int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
struct whc_urb *wurb = urb->hcpriv;
struct whc_qset *qset = wurb->qset;
struct whc_std *std, *t;
+ bool has_qtd = false;
int ret;
unsigned long flags;
@@ -303,17 +321,21 @@ int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
goto out;
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
- if (std->urb == urb)
+ if (std->urb == urb) {
+ if (std->qtd)
+ has_qtd = true;
qset_free_std(whc, std);
- else
+ } else
std->qtd = NULL; /* so this std is re-added when the qset is */
}
- asl_qset_remove(whc, qset);
- wurb->status = status;
- wurb->is_async = true;
- queue_work(whc->workqueue, &wurb->dequeue_work);
-
+ if (has_qtd) {
+ asl_qset_remove(whc, qset);
+ wurb->status = status;
+ wurb->is_async = true;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+ } else
+ qset_remove_urb(whc, qset, urb, status);
out:
spin_unlock_irqrestore(&whc->lock, flags);
diff --git a/drivers/usb/host/whci/debug.c b/drivers/usb/host/whci/debug.c
new file mode 100644
index 00000000000..ba61dae9e4d
--- /dev/null
+++ b/drivers/usb/host/whci/debug.c
@@ -0,0 +1,203 @@
+/*
+ * Wireless Host Controller (WHC) debug.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/export.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+struct whc_dbg {
+ struct dentry *di_f;
+ struct dentry *asl_f;
+ struct dentry *pzl_f;
+};
+
+static void qset_print(struct seq_file *s, struct whc_qset *qset)
+{
+ static const char *qh_type[] = {
+ "ctrl", "isoc", "bulk", "intr", "rsvd", "rsvd", "rsvd", "lpintr", };
+ struct whc_std *std;
+ struct urb *urb = NULL;
+ int i;
+
+ seq_printf(s, "qset %08x", (u32)qset->qset_dma);
+ if (&qset->list_node == qset->whc->async_list.prev) {
+ seq_printf(s, " (dummy)\n");
+ } else {
+ seq_printf(s, " ep%d%s-%s maxpkt: %d\n",
+ qset->qh.info1 & 0x0f,
+ (qset->qh.info1 >> 4) & 0x1 ? "in" : "out",
+ qh_type[(qset->qh.info1 >> 5) & 0x7],
+ (qset->qh.info1 >> 16) & 0xffff);
+ }
+ seq_printf(s, " -> %08x\n", (u32)qset->qh.link);
+ seq_printf(s, " info: %08x %08x %08x\n",
+ qset->qh.info1, qset->qh.info2, qset->qh.info3);
+ seq_printf(s, " sts: %04x errs: %d curwin: %08x\n",
+ qset->qh.status, qset->qh.err_count, qset->qh.cur_window);
+ seq_printf(s, " TD: sts: %08x opts: %08x\n",
+ qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
+
+ for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
+ seq_printf(s, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
+ i == qset->td_start ? 'S' : ' ',
+ i == qset->td_end ? 'E' : ' ',
+ i, qset->qtd[i].status, qset->qtd[i].options,
+ (u32)qset->qtd[i].page_list_ptr);
+ }
+ seq_printf(s, " ntds: %d\n", qset->ntds);
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (urb != std->urb) {
+ urb = std->urb;
+ seq_printf(s, " urb %p transferred: %d bytes\n", urb,
+ urb->actual_length);
+ }
+ if (std->qtd)
+ seq_printf(s, " sTD[%td]: %zu bytes @ %08x\n",
+ std->qtd - &qset->qtd[0],
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ else
+ seq_printf(s, " sTD[-]: %zd bytes @ %08x\n",
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ }
+}
+
+static int di_print(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ char buf[72];
+ int d;
+
+ for (d = 0; d < whc->n_devices; d++) {
+ struct di_buf_entry *di = &whc->di_buf[d];
+
+ bitmap_scnprintf(buf, sizeof(buf),
+ (unsigned long *)di->availability_info, UWB_NUM_MAS);
+
+ seq_printf(s, "DI[%d]\n", d);
+ seq_printf(s, " availability: %s\n", buf);
+ seq_printf(s, " %c%c key idx: %d dev addr: %d\n",
+ (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
+ (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
+ (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
+ (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
+ }
+ return 0;
+}
+
+static int asl_print(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ struct whc_qset *qset;
+
+ list_for_each_entry(qset, &whc->async_list, list_node) {
+ qset_print(s, qset);
+ }
+
+ return 0;
+}
+
+static int pzl_print(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ struct whc_qset *qset;
+ int period;
+
+ for (period = 0; period < 5; period++) {
+ seq_printf(s, "Period %d\n", period);
+ list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
+ qset_print(s, qset);
+ }
+ }
+ return 0;
+}
+
+static int di_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, di_print, inode->i_private);
+}
+
+static int asl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, asl_print, inode->i_private);
+}
+
+static int pzl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pzl_print, inode->i_private);
+}
+
+static const struct file_operations di_fops = {
+ .open = di_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static const struct file_operations asl_fops = {
+ .open = asl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static const struct file_operations pzl_fops = {
+ .open = pzl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+void whc_dbg_init(struct whc *whc)
+{
+ if (whc->wusbhc.pal.debugfs_dir == NULL)
+ return;
+
+ whc->dbg = kzalloc(sizeof(struct whc_dbg), GFP_KERNEL);
+ if (whc->dbg == NULL)
+ return;
+
+ whc->dbg->di_f = debugfs_create_file("di", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &di_fops);
+ whc->dbg->asl_f = debugfs_create_file("asl", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &asl_fops);
+ whc->dbg->pzl_f = debugfs_create_file("pzl", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &pzl_fops);
+}
+
+void whc_dbg_clean_up(struct whc *whc)
+{
+ if (whc->dbg) {
+ debugfs_remove(whc->dbg->pzl_f);
+ debugfs_remove(whc->dbg->asl_f);
+ debugfs_remove(whc->dbg->di_f);
+ kfree(whc->dbg);
+ }
+}
diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c
index ef3ad4dca94..d7b363a418d 100644
--- a/drivers/usb/host/whci/hcd.c
+++ b/drivers/usb/host/whci/hcd.c
@@ -15,9 +15,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/module.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
@@ -69,7 +69,7 @@ static int whc_start(struct usb_hcd *usb_hcd)
whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
usb_hcd->uses_new_polling = 1;
- usb_hcd->poll_rh = 1;
+ set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags);
usb_hcd->state = HC_STATE_RUNNING;
out:
@@ -92,8 +92,6 @@ static void whc_stop(struct usb_hcd *usb_hcd)
mutex_lock(&wusbhc->mutex);
- wusbhc_stop(wusbhc);
-
/* stop HC */
le_writel(0, whc->base + WUSBINTR);
whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
@@ -136,7 +134,7 @@ static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
default:
ret = asl_urb_enqueue(whc, urb, mem_flags);
break;
- };
+ }
return ret;
}
@@ -162,7 +160,7 @@ static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
default:
ret = asl_urb_dequeue(whc, urb, status);
break;
- };
+ }
return ret;
}
@@ -189,6 +187,32 @@ static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
}
}
+static void whc_endpoint_reset(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct whc_qset *qset;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ qset = ep->hcpriv;
+ if (qset) {
+ qset->remove = 1;
+ qset->reset = 1;
+
+ if (usb_endpoint_xfer_bulk(&ep->desc)
+ || usb_endpoint_xfer_control(&ep->desc))
+ queue_work(whc->workqueue, &whc->async_work);
+ else
+ queue_work(whc->workqueue, &whc->periodic_work);
+ }
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+
static struct hc_driver whc_hc_driver = {
.description = "whci-hcd",
.product_desc = "Wireless host controller",
@@ -203,29 +227,29 @@ static struct hc_driver whc_hc_driver = {
.urb_enqueue = whc_urb_enqueue,
.urb_dequeue = whc_urb_dequeue,
.endpoint_disable = whc_endpoint_disable,
+ .endpoint_reset = whc_endpoint_reset,
.hub_status_data = wusbhc_rh_status_data,
.hub_control = wusbhc_rh_control,
- .bus_suspend = wusbhc_rh_suspend,
- .bus_resume = wusbhc_rh_resume,
.start_port_reset = wusbhc_rh_start_port_reset,
};
static int whc_probe(struct umc_dev *umc)
{
- int ret = -ENOMEM;
+ int ret;
struct usb_hcd *usb_hcd;
- struct wusbhc *wusbhc = NULL;
- struct whc *whc = NULL;
+ struct wusbhc *wusbhc;
+ struct whc *whc;
struct device *dev = &umc->dev;
usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
if (usb_hcd == NULL) {
dev_err(dev, "unable to create hcd\n");
- goto error;
+ return -ENOMEM;
}
usb_hcd->wireless = 1;
+ usb_hcd->self.sg_tablesize = 2048; /* somewhat arbitrary */
wusbhc = usb_hcd_to_wusbhc(usb_hcd);
whc = wusbhc_to_whc(wusbhc);
@@ -269,6 +293,7 @@ static int whc_probe(struct umc_dev *umc)
dev_err(dev, "cannot add HCD: %d\n", ret);
goto error_usb_add_hcd;
}
+ device_wakeup_enable(usb_hcd->self.controller);
ret = wusbhc_b_create(wusbhc);
if (ret) {
@@ -276,6 +301,8 @@ static int whc_probe(struct umc_dev *umc)
goto error_wusbhc_b_create;
}
+ whc_dbg_init(whc);
+
return 0;
error_wusbhc_b_create:
@@ -299,6 +326,7 @@ static void whc_remove(struct umc_dev *umc)
struct whc *whc = wusbhc_to_whc(wusbhc);
if (usb_hcd) {
+ whc_dbg_clean_up(whc);
wusbhc_b_destroy(wusbhc);
usb_remove_hcd(usb_hcd);
wusbhc_destroy(wusbhc);
@@ -328,7 +356,7 @@ static void __exit whci_hc_driver_exit(void)
module_exit(whci_hc_driver_exit);
/* PCI device ID's that we handle (so it gets loaded) */
-static struct pci_device_id whci_hcd_id_table[] = {
+static struct pci_device_id __used whci_hcd_id_table[] = {
{ PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
{ /* empty last entry */ }
};
diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c
index ac86e59c122..6afa2e37916 100644
--- a/drivers/usb/host/whci/hw.c
+++ b/drivers/usb/host/whci/hw.c
@@ -50,6 +50,7 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
unsigned long flags;
dma_addr_t dma_addr;
int t;
+ int ret = 0;
mutex_lock(&whc->mutex);
@@ -61,7 +62,8 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
le_readl(whc->base + WUSBGENCMDSTS),
le_readl(whc->base + WUSBGENCMDPARAMS));
- return -ETIMEDOUT;
+ ret = -ETIMEDOUT;
+ goto out;
}
if (addr) {
@@ -80,8 +82,23 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
whc->base + WUSBGENCMDSTS);
spin_unlock_irqrestore(&whc->lock, flags);
-
+out:
mutex_unlock(&whc->mutex);
- return 0;
+ return ret;
+}
+
+/**
+ * whc_hw_error - recover from a hardware error
+ * @whc: the WHCI HC that broke.
+ * @reason: a description of the failure.
+ *
+ * Recover from broken hardware with a full reset.
+ */
+void whc_hw_error(struct whc *whc, const char *reason)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+
+ dev_err(&whc->umc->dev, "hardware error: %s\n", reason);
+ wusbhc_reset_all(wusbhc);
}
diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c
index 34a783cb013..d3e13b640d4 100644
--- a/drivers/usb/host/whci/init.c
+++ b/drivers/usb/host/whci/init.c
@@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
+#include <linux/gfp.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
@@ -177,7 +178,7 @@ void whc_clean_up(struct whc *whc)
if (whc->qset_pool)
dma_pool_destroy(whc->qset_pool);
- len = whc->umc->resource.end - whc->umc->resource.start + 1;
+ len = resource_size(&whc->umc->resource);
if (whc->base)
iounmap(whc->base);
if (whc->base_phys)
diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c
index fce01174aa9..0c086b2790d 100644
--- a/drivers/usb/host/whci/int.c
+++ b/drivers/usb/host/whci/int.c
@@ -15,9 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <linux/version.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/uwb/umc.h>
#include "../../wusbcore/wusbhc.h"
diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c
index 8d62df0c330..33c5580b4d2 100644
--- a/drivers/usb/host/whci/pzl.c
+++ b/drivers/usb/host/whci/pzl.c
@@ -16,38 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
+#include <linux/gfp.h>
#include <linux/dma-mapping.h>
#include <linux/uwb/umc.h>
#include <linux/usb.h>
-#define D_LOCAL 0
-#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
-#if D_LOCAL >= 4
-static void dump_pzl(struct whc *whc, const char *tag)
-{
- struct device *dev = &whc->umc->dev;
- struct whc_qset *qset;
- int period = 0;
-
- d_printf(4, dev, "PZL %s\n", tag);
-
- for (period = 0; period < 5; period++) {
- d_printf(4, dev, "Period %d\n", period);
- list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
- dump_qset(qset, dev);
- }
- }
-}
-#else
-static inline void dump_pzl(struct whc *whc, const char *tag)
-{
-}
-#endif
-
static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
{
switch (period) {
@@ -145,6 +122,10 @@ static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
if (status & QTD_STS_HALTED) {
/* Ug, an error. */
process_halted_qtd(whc, qset, td);
+ /* A halted qTD always triggers an update
+ because the qset was either removed or
+ reactivated. */
+ update |= WHC_UPDATE_UPDATED;
goto done;
}
@@ -152,7 +133,8 @@ static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
process_inactive_qtd(whc, qset, td);
}
- update |= qset_add_qtds(whc, qset);
+ if (!qset->remove)
+ update |= qset_add_qtds(whc, qset);
done:
/*
@@ -195,11 +177,31 @@ void pzl_stop(struct whc *whc)
1000, "stop PZL");
}
+/**
+ * pzl_update - request a PZL update and wait for the hardware to be synced
+ * @whc: the WHCI HC
+ * @wusbcmd: WUSBCMD value to start the update.
+ *
+ * If the WUSB HC is inactive (i.e., the PZL is stopped) then the
+ * update must be skipped as the hardware may not respond to update
+ * requests.
+ */
void pzl_update(struct whc *whc, uint32_t wusbcmd)
{
- whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
- wait_event(whc->periodic_list_wq,
- (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0);
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ long t;
+
+ mutex_lock(&wusbhc->mutex);
+ if (wusbhc->active) {
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ t = wait_event_timeout(
+ whc->periodic_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0,
+ msecs_to_jiffies(1000));
+ if (t == 0)
+ whc_hw_error(whc, "PZL update timeout");
+ }
+ mutex_unlock(&wusbhc->mutex);
}
static void update_pzl_hw_view(struct whc *whc)
@@ -235,8 +237,6 @@ void scan_periodic_work(struct work_struct *work)
spin_lock_irq(&whc->lock);
- dump_pzl(whc, "before processing");
-
for (period = 4; period >= 0; period--) {
list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
if (!qset->in_hw_list)
@@ -248,8 +248,6 @@ void scan_periodic_work(struct work_struct *work)
if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
update_pzl_hw_view(whc);
- dump_pzl(whc, "after processing");
-
spin_unlock_irq(&whc->lock);
if (update) {
@@ -262,14 +260,24 @@ void scan_periodic_work(struct work_struct *work)
/*
* Now that the PZL is updated, complete the removal of any
* removed qsets.
+ *
+ * If the qset was to be reset, do so and reinsert it into the
+ * PZL if it has pending transfers.
*/
- spin_lock(&whc->lock);
+ spin_lock_irq(&whc->lock);
list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
qset_remove_complete(whc, qset);
+ if (qset->reset) {
+ qset_reset(whc, qset);
+ if (!list_empty(&qset->stds)) {
+ qset_insert_in_sw_list(whc, qset);
+ queue_work(whc->workqueue, &whc->periodic_work);
+ }
+ }
}
- spin_unlock(&whc->lock);
+ spin_unlock_irq(&whc->lock);
}
/**
@@ -290,23 +298,29 @@ int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
spin_lock_irqsave(&whc->lock, flags);
+ err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (err < 0) {
+ spin_unlock_irqrestore(&whc->lock, flags);
+ return err;
+ }
+
qset = get_qset(whc, urb, GFP_ATOMIC);
if (qset == NULL)
err = -ENOMEM;
else
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
if (!err) {
- usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
- if (!qset->in_sw_list)
+ if (!qset->in_sw_list && !qset->remove)
qset_insert_in_sw_list(whc, qset);
- }
+ } else
+ usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
spin_unlock_irqrestore(&whc->lock, flags);
if (!err)
queue_work(whc->workqueue, &whc->periodic_work);
- return 0;
+ return err;
}
/**
@@ -324,6 +338,7 @@ int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
struct whc_urb *wurb = urb->hcpriv;
struct whc_qset *qset = wurb->qset;
struct whc_std *std, *t;
+ bool has_qtd = false;
int ret;
unsigned long flags;
@@ -334,17 +349,22 @@ int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
goto out;
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
- if (std->urb == urb)
+ if (std->urb == urb) {
+ if (std->qtd)
+ has_qtd = true;
qset_free_std(whc, std);
- else
+ } else
std->qtd = NULL; /* so this std is re-added when the qset is */
}
- pzl_qset_remove(whc, qset);
- wurb->status = status;
- wurb->is_async = false;
- queue_work(whc->workqueue, &wurb->dequeue_work);
-
+ if (has_qtd) {
+ pzl_qset_remove(whc, qset);
+ update_pzl_hw_view(whc);
+ wurb->status = status;
+ wurb->is_async = false;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+ } else
+ qset_remove_urb(whc, qset, urb, status);
out:
spin_unlock_irqrestore(&whc->lock, flags);
@@ -361,7 +381,6 @@ void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
qset_delete(whc, qset);
}
-
/**
* pzl_init - initialize the periodic zone list
* @whc: the WHCI host controller
diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c
index 0420037d2e1..dc31c425ce0 100644
--- a/drivers/usb/host/whci/qset.c
+++ b/drivers/usb/host/whci/qset.c
@@ -17,6 +17,7 @@
*/
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
+#include <linux/slab.h>
#include <linux/uwb/umc.h>
#include <linux/usb.h>
@@ -24,46 +25,6 @@
#include "whcd.h"
-void dump_qset(struct whc_qset *qset, struct device *dev)
-{
- struct whc_std *std;
- struct urb *urb = NULL;
- int i;
-
- dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma);
- dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link);
- dev_dbg(dev, " info: %08x %08x %08x\n",
- qset->qh.info1, qset->qh.info2, qset->qh.info3);
- dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count);
- dev_dbg(dev, " TD: sts: %08x opts: %08x\n",
- qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
-
- for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
- dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
- i == qset->td_start ? 'S' : ' ',
- i == qset->td_end ? 'E' : ' ',
- i, qset->qtd[i].status, qset->qtd[i].options,
- (u32)qset->qtd[i].page_list_ptr);
- }
- dev_dbg(dev, " ntds: %d\n", qset->ntds);
- list_for_each_entry(std, &qset->stds, list_node) {
- if (urb != std->urb) {
- urb = std->urb;
- dev_dbg(dev, " urb %p transferred: %d bytes\n", urb,
- urb->actual_length);
- }
- if (std->qtd)
- dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n",
- std->qtd - &qset->qtd[0],
- std->len, std->num_pointers ?
- (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
- else
- dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n",
- std->len, std->num_pointers ?
- (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
- }
-}
-
struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
{
struct whc_qset *qset;
@@ -89,16 +50,19 @@ struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
* state
* @urb: an urb for a transfer to this endpoint
*/
-static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
+static void qset_fill_qh(struct whc *whc, struct whc_qset *qset, struct urb *urb)
{
struct usb_device *usb_dev = urb->dev;
+ struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
struct usb_wireless_ep_comp_descriptor *epcd;
bool is_out;
+ uint8_t phy_rate;
is_out = usb_pipeout(urb->pipe);
- epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
+ qset->max_packet = le16_to_cpu(urb->ep->desc.wMaxPacketSize);
+ epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
if (epcd) {
qset->max_seq = epcd->bMaxSequence;
qset->max_burst = epcd->bMaxBurst;
@@ -107,12 +71,28 @@ static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
qset->max_burst = 1;
}
+ /*
+ * Initial PHY rate is 53.3 Mbit/s for control endpoints or
+ * the maximum supported by the device for other endpoints
+ * (unless limited by the user).
+ */
+ if (usb_pipecontrol(urb->pipe))
+ phy_rate = UWB_PHY_RATE_53;
+ else {
+ uint16_t phy_rates;
+
+ phy_rates = le16_to_cpu(wusb_dev->wusb_cap_descr->wPHYRates);
+ phy_rate = fls(phy_rates) - 1;
+ if (phy_rate > whc->wusbhc.phy_rate)
+ phy_rate = whc->wusbhc.phy_rate;
+ }
+
qset->qh.info1 = cpu_to_le32(
QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
| (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
| usb_pipe_to_qh_type(urb->pipe)
| QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
- | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out))
+ | QH_INFO1_MAX_PKT_LEN(qset->max_packet)
);
qset->qh.info2 = cpu_to_le32(
QH_INFO2_BURST(qset->max_burst)
@@ -126,24 +106,27 @@ static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
* strength and can presumably guess the Tx power required
* from that? */
qset->qh.info3 = cpu_to_le32(
- QH_INFO3_TX_RATE_53_3
+ QH_INFO3_TX_RATE(phy_rate)
| QH_INFO3_TX_PWR(0) /* 0 == max power */
);
+
+ qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
}
/**
* qset_clear - clear fields in a qset so it may be reinserted into a
- * schedule
+ * schedule.
+ *
+ * The sequence number and current window are not cleared (see
+ * qset_reset()).
*/
void qset_clear(struct whc *whc, struct whc_qset *qset)
{
qset->td_start = qset->td_end = qset->ntds = 0;
- qset->remove = 0;
- qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
- qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start));
+ qset->qh.link = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
+ qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK;
qset->qh.err_count = 0;
- qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
qset->qh.scratch[0] = 0;
qset->qh.scratch[1] = 0;
qset->qh.scratch[2] = 0;
@@ -154,6 +137,20 @@ void qset_clear(struct whc *whc, struct whc_qset *qset)
}
/**
+ * qset_reset - reset endpoint state in a qset.
+ *
+ * Clears the sequence number and current window. This qset must not
+ * be in the ASL or PZL.
+ */
+void qset_reset(struct whc *whc, struct whc_qset *qset)
+{
+ qset->reset = 0;
+
+ qset->qh.status &= ~QH_STATUS_SEQ_MASK;
+ qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
+}
+
+/**
* get_qset - get the qset for an async endpoint
*
* A new qset is created if one does not already exist.
@@ -171,13 +168,14 @@ struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
qset->ep = urb->ep;
urb->ep->hcpriv = qset;
- qset_fill_qh(qset, urb);
+ qset_fill_qh(whc, qset, urb);
}
return qset;
}
void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
{
+ qset->remove = 0;
list_del_init(&qset->list_node);
complete(&qset->remove_complete);
}
@@ -263,6 +261,36 @@ static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
qset->ntds--;
}
+static void qset_copy_bounce_to_sg(struct whc *whc, struct whc_std *std)
+{
+ struct scatterlist *sg;
+ void *bounce;
+ size_t remaining, offset;
+
+ bounce = std->bounce_buf;
+ remaining = std->len;
+
+ sg = std->bounce_sg;
+ offset = std->bounce_offset;
+
+ while (remaining) {
+ size_t len;
+
+ len = min(sg->length - offset, remaining);
+ memcpy(sg_virt(sg) + offset, bounce, len);
+
+ bounce += len;
+ remaining -= len;
+
+ offset += len;
+ if (offset >= sg->length) {
+ sg = sg_next(sg);
+ offset = 0;
+ }
+ }
+
+}
+
/**
* qset_free_std - remove an sTD and free it.
* @whc: the WHCI host controller
@@ -271,13 +299,29 @@ static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
void qset_free_std(struct whc *whc, struct whc_std *std)
{
list_del(&std->list_node);
- if (std->num_pointers) {
- dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
- std->num_pointers * sizeof(struct whc_page_list_entry),
- DMA_TO_DEVICE);
+ if (std->bounce_buf) {
+ bool is_out = usb_pipeout(std->urb->pipe);
+ dma_addr_t dma_addr;
+
+ if (std->num_pointers)
+ dma_addr = le64_to_cpu(std->pl_virt[0].buf_ptr);
+ else
+ dma_addr = std->dma_addr;
+
+ dma_unmap_single(whc->wusbhc.dev, dma_addr,
+ std->len, is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (!is_out)
+ qset_copy_bounce_to_sg(whc, std);
+ kfree(std->bounce_buf);
+ }
+ if (std->pl_virt) {
+ if (std->dma_addr)
+ dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
+ std->num_pointers * sizeof(struct whc_page_list_entry),
+ DMA_TO_DEVICE);
kfree(std->pl_virt);
+ std->pl_virt = NULL;
}
-
kfree(std);
}
@@ -315,12 +359,17 @@ static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_f
{
dma_addr_t dma_addr = std->dma_addr;
dma_addr_t sp, ep;
- size_t std_len = std->len;
size_t pl_len;
int p;
- sp = ALIGN(dma_addr, WHCI_PAGE_SIZE);
- ep = dma_addr + std_len;
+ /* Short buffers don't need a page list. */
+ if (std->len <= WHCI_PAGE_SIZE) {
+ std->num_pointers = 0;
+ return 0;
+ }
+
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
+ ep = dma_addr + std->len;
std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
@@ -331,7 +380,7 @@ static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_f
for (p = 0; p < std->num_pointers; p++) {
std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
- dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE);
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
}
return 0;
@@ -361,6 +410,221 @@ static void urb_dequeue_work(struct work_struct *work)
spin_unlock_irqrestore(&whc->lock, flags);
}
+static struct whc_std *qset_new_std(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_std *std;
+
+ std = kzalloc(sizeof(struct whc_std), mem_flags);
+ if (std == NULL)
+ return NULL;
+
+ std->urb = urb;
+ std->qtd = NULL;
+
+ INIT_LIST_HEAD(&std->list_node);
+ list_add_tail(&std->list_node, &qset->stds);
+
+ return std;
+}
+
+static int qset_add_urb_sg(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags)
+{
+ size_t remaining;
+ struct scatterlist *sg;
+ int i;
+ int ntds = 0;
+ struct whc_std *std = NULL;
+ struct whc_page_list_entry *new_pl_virt;
+ dma_addr_t prev_end = 0;
+ size_t pl_len;
+ int p = 0;
+
+ remaining = urb->transfer_buffer_length;
+
+ for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) {
+ dma_addr_t dma_addr;
+ size_t dma_remaining;
+ dma_addr_t sp, ep;
+ int num_pointers;
+
+ if (remaining == 0) {
+ break;
+ }
+
+ dma_addr = sg_dma_address(sg);
+ dma_remaining = min_t(size_t, sg_dma_len(sg), remaining);
+
+ while (dma_remaining) {
+ size_t dma_len;
+
+ /*
+ * We can use the previous std (if it exists) provided that:
+ * - the previous one ended on a page boundary.
+ * - the current one begins on a page boundary.
+ * - the previous one isn't full.
+ *
+ * If a new std is needed but the previous one
+ * was not a whole number of packets then this
+ * sg list cannot be mapped onto multiple
+ * qTDs. Return an error and let the caller
+ * sort it out.
+ */
+ if (!std
+ || (prev_end & (WHCI_PAGE_SIZE-1))
+ || (dma_addr & (WHCI_PAGE_SIZE-1))
+ || std->len + WHCI_PAGE_SIZE > QTD_MAX_XFER_SIZE) {
+ if (std && std->len % qset->max_packet != 0)
+ return -EINVAL;
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL) {
+ return -ENOMEM;
+ }
+ ntds++;
+ p = 0;
+ }
+
+ dma_len = dma_remaining;
+
+ /*
+ * If the remainder of this element doesn't
+ * fit in a single qTD, limit the qTD to a
+ * whole number of packets. This allows the
+ * remainder to go into the next qTD.
+ */
+ if (std->len + dma_len > QTD_MAX_XFER_SIZE) {
+ dma_len = (QTD_MAX_XFER_SIZE / qset->max_packet)
+ * qset->max_packet - std->len;
+ }
+
+ std->len += dma_len;
+ std->ntds_remaining = -1; /* filled in later */
+
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
+ ep = dma_addr + dma_len;
+ num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
+ std->num_pointers += num_pointers;
+
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+
+ new_pl_virt = krealloc(std->pl_virt, pl_len, mem_flags);
+ if (new_pl_virt == NULL) {
+ kfree(std->pl_virt);
+ std->pl_virt = NULL;
+ return -ENOMEM;
+ }
+ std->pl_virt = new_pl_virt;
+
+ for (;p < std->num_pointers; p++) {
+ std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
+ }
+
+ prev_end = dma_addr = ep;
+ dma_remaining -= dma_len;
+ remaining -= dma_len;
+ }
+ }
+
+ /* Now the number of stds is know, go back and fill in
+ std->ntds_remaining. */
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (std->ntds_remaining == -1) {
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+ std->ntds_remaining = ntds--;
+ std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt,
+ pl_len, DMA_TO_DEVICE);
+ }
+ }
+ return 0;
+}
+
+/**
+ * qset_add_urb_sg_linearize - add an urb with sg list, copying the data
+ *
+ * If the URB contains an sg list whose elements cannot be directly
+ * mapped to qTDs then the data must be transferred via bounce
+ * buffers.
+ */
+static int qset_add_urb_sg_linearize(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, gfp_t mem_flags)
+{
+ bool is_out = usb_pipeout(urb->pipe);
+ size_t max_std_len;
+ size_t remaining;
+ int ntds = 0;
+ struct whc_std *std = NULL;
+ void *bounce = NULL;
+ struct scatterlist *sg;
+ int i;
+
+ /* limit maximum bounce buffer to 16 * 3.5 KiB ~= 28 k */
+ max_std_len = qset->max_burst * qset->max_packet;
+
+ remaining = urb->transfer_buffer_length;
+
+ for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) {
+ size_t len;
+ size_t sg_remaining;
+ void *orig;
+
+ if (remaining == 0) {
+ break;
+ }
+
+ sg_remaining = min_t(size_t, remaining, sg->length);
+ orig = sg_virt(sg);
+
+ while (sg_remaining) {
+ if (!std || std->len == max_std_len) {
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL)
+ return -ENOMEM;
+ std->bounce_buf = kmalloc(max_std_len, mem_flags);
+ if (std->bounce_buf == NULL)
+ return -ENOMEM;
+ std->bounce_sg = sg;
+ std->bounce_offset = orig - sg_virt(sg);
+ bounce = std->bounce_buf;
+ ntds++;
+ }
+
+ len = min(sg_remaining, max_std_len - std->len);
+
+ if (is_out)
+ memcpy(bounce, orig, len);
+
+ std->len += len;
+ std->ntds_remaining = -1; /* filled in later */
+
+ bounce += len;
+ orig += len;
+ sg_remaining -= len;
+ remaining -= len;
+ }
+ }
+
+ /*
+ * For each of the new sTDs, map the bounce buffers, create
+ * page lists (if necessary), and fill in std->ntds_remaining.
+ */
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (std->ntds_remaining != -1)
+ continue;
+
+ std->dma_addr = dma_map_single(&whc->umc->dev, std->bounce_buf, std->len,
+ is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
+ return -ENOMEM;
+
+ std->ntds_remaining = ntds--;
+ }
+
+ return 0;
+}
+
/**
* qset_add_urb - add an urb to the qset's queue.
*
@@ -375,10 +639,7 @@ int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
int remaining = urb->transfer_buffer_length;
u64 transfer_dma = urb->transfer_dma;
int ntds_remaining;
-
- ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
- if (ntds_remaining == 0)
- ntds_remaining = 1;
+ int ret;
wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
if (wurb == NULL)
@@ -388,32 +649,39 @@ int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
wurb->urb = urb;
INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
+ if (urb->num_sgs) {
+ ret = qset_add_urb_sg(whc, qset, urb, mem_flags);
+ if (ret == -EINVAL) {
+ qset_free_stds(qset, urb);
+ ret = qset_add_urb_sg_linearize(whc, qset, urb, mem_flags);
+ }
+ if (ret < 0)
+ goto err_no_mem;
+ return 0;
+ }
+
+ ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
+ if (ntds_remaining == 0)
+ ntds_remaining = 1;
+
while (ntds_remaining) {
struct whc_std *std;
size_t std_len;
- std = kmalloc(sizeof(struct whc_std), mem_flags);
- if (std == NULL)
- goto err_no_mem;
-
std_len = remaining;
if (std_len > QTD_MAX_XFER_SIZE)
std_len = QTD_MAX_XFER_SIZE;
- std->urb = urb;
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL)
+ goto err_no_mem;
+
std->dma_addr = transfer_dma;
std->len = std_len;
std->ntds_remaining = ntds_remaining;
- std->qtd = NULL;
-
- INIT_LIST_HEAD(&std->list_node);
- list_add_tail(&std->list_node, &qset->stds);
- if (std_len > WHCI_PAGE_SIZE) {
- if (qset_fill_page_list(whc, std, mem_flags) < 0)
- goto err_no_mem;
- } else
- std->num_pointers = 0;
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
+ goto err_no_mem;
ntds_remaining--;
remaining -= std_len;
@@ -474,7 +742,7 @@ static int get_urb_status_from_qtd(struct urb *urb, u32 status)
* process_inactive_qtd - process an inactive (but not halted) qTD.
*
* Update the urb with the transfer bytes from the qTD, if the urb is
- * completely transfered or (in the case of an IN only) the LPF is
+ * completely transferred or (in the case of an IN only) the LPF is
* set, then the transfer is complete and the urb should be returned
* to the system.
*/
diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h
index 1d2a53bd39f..c80c7d93bc4 100644
--- a/drivers/usb/host/whci/whcd.h
+++ b/drivers/usb/host/whci/whcd.h
@@ -21,6 +21,7 @@
#define __WHCD_H
#include <linux/uwb/whci.h>
+#include <linux/uwb/umc.h>
#include <linux/workqueue.h>
#include "whci-hc.h"
@@ -28,6 +29,7 @@
/* Generic command timeout. */
#define WHC_GENCMD_TIMEOUT_MS 100
+struct whc_dbg;
struct whc {
struct wusbhc wusbhc;
@@ -69,6 +71,8 @@ struct whc {
struct list_head periodic_removed_list;
wait_queue_head_t periodic_list_wq;
struct work_struct periodic_work;
+
+ struct whc_dbg *dbg;
};
#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
@@ -80,6 +84,11 @@ struct whc {
* @len: the length of data in the associated TD.
* @ntds_remaining: number of TDs (starting from this one) in this transfer.
*
+ * @bounce_buf: a bounce buffer if the std was from an urb with a sg
+ * list that could not be mapped to qTDs directly.
+ * @bounce_sg: the first scatterlist element bounce_buf is for.
+ * @bounce_offset: the offset into bounce_sg for the start of bounce_buf.
+ *
* Queued URBs may require more TDs than are available in a qset so we
* use a list of these "software TDs" (sTDs) to hold per-TD data.
*/
@@ -93,6 +102,10 @@ struct whc_std {
int num_pointers;
dma_addr_t dma_addr;
struct whc_page_list_entry *pl_virt;
+
+ void *bounce_buf;
+ struct scatterlist *bounce_sg;
+ unsigned bounce_offset;
};
/**
@@ -133,10 +146,11 @@ void whc_clean_up(struct whc *whc);
/* hw.c */
void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
+void whc_hw_error(struct whc *whc, const char *reason);
/* wusb.c */
int whc_wusbhc_start(struct wusbhc *wusbhc);
-void whc_wusbhc_stop(struct wusbhc *wusbhc);
+void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay);
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
u8 handle, struct wuie_hdr *wuie);
int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
@@ -179,6 +193,7 @@ void qset_free(struct whc *whc, struct whc_qset *qset);
struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
void qset_delete(struct whc *whc, struct whc_qset *qset);
void qset_clear(struct whc *whc, struct whc_qset *qset);
+void qset_reset(struct whc *whc, struct whc_qset *qset);
int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
gfp_t mem_flags);
void qset_free_std(struct whc *whc, struct whc_std *std);
@@ -190,8 +205,11 @@ void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
struct whc_qtd *qtd);
enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
-void dump_qset(struct whc_qset *qset, struct device *dev);
void pzl_update(struct whc *whc, uint32_t wusbcmd);
void asl_update(struct whc *whc, uint32_t wusbcmd);
+/* debug.c */
+void whc_dbg_init(struct whc *whc);
+void whc_dbg_clean_up(struct whc *whc);
+
#endif /* #ifndef __WHCD_H */
diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h
index bff1eb7a35c..4d4cbc0730b 100644
--- a/drivers/usb/host/whci/whci-hc.h
+++ b/drivers/usb/host/whci/whci-hc.h
@@ -172,19 +172,13 @@ struct whc_qhead {
#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */
#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */
-#define QH_INFO3_TX_RATE_53_3 (0 << 24)
-#define QH_INFO3_TX_RATE_80 (1 << 24)
-#define QH_INFO3_TX_RATE_106_7 (2 << 24)
-#define QH_INFO3_TX_RATE_160 (3 << 24)
-#define QH_INFO3_TX_RATE_200 (4 << 24)
-#define QH_INFO3_TX_RATE_320 (5 << 24)
-#define QH_INFO3_TX_RATE_400 (6 << 24)
-#define QH_INFO3_TX_RATE_480 (7 << 24)
+#define QH_INFO3_TX_RATE(r) ((r) << 24) /* PHY rate (see [ECMA-368] section 10.3.1.1) */
#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */
#define QH_STATUS_FLOW_CTRL (1 << 15)
#define QH_STATUS_ICUR(i) ((i) << 5)
#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7)
+#define QH_STATUS_SEQ_MASK 0x1f
/**
* usb_pipe_to_qh_type - USB core pipe type to QH transfer type
@@ -263,10 +257,12 @@ struct whc_qset {
unsigned in_sw_list:1;
unsigned in_hw_list:1;
unsigned remove:1;
+ unsigned reset:1;
struct urb *pause_after_urb;
struct completion remove_complete;
- int max_burst;
- int max_seq;
+ uint16_t max_packet;
+ uint8_t max_burst;
+ uint8_t max_seq;
};
static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target)
@@ -410,6 +406,8 @@ struct dn_buf_entry {
# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0)
#define WUSBTIME 0x68
+# define WUSBTIME_CHANNEL_TIME_MASK 0x00ffffff
+
#define WUSBBPST 0x6c
#define WUSBDIBUPDATED 0x70
diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c
index 66e4ddcd961..8d276268286 100644
--- a/drivers/usb/host/whci/wusb.c
+++ b/drivers/usb/host/whci/wusb.c
@@ -15,47 +15,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <linux/version.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/uwb/umc.h>
-#define D_LOCAL 1
-#include <linux/uwb/debug.h>
#include "../../wusbcore/wusbhc.h"
#include "whcd.h"
-#if D_LOCAL >= 1
-static void dump_di(struct whc *whc, int idx)
-{
- struct di_buf_entry *di = &whc->di_buf[idx];
- struct device *dev = &whc->umc->dev;
- char buf[128];
-
- bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS);
-
- d_printf(1, dev, "DI[%d]\n", idx);
- d_printf(1, dev, " availability: %s\n", buf);
- d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n",
- (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
- (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
- (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
- (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
-}
-#else
-static inline void dump_di(struct whc *whc, int idx)
-{
-}
-#endif
-
static int whc_update_di(struct whc *whc, int idx)
{
int offset = idx / 32;
u32 bit = 1 << (idx % 32);
- dump_di(whc, idx);
-
le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
return whci_wait_for(&whc->umc->dev,
@@ -64,8 +35,9 @@ static int whc_update_di(struct whc *whc, int idx)
}
/*
- * WHCI starts and stops MMCs based on there being a valid GTK so
- * these need only start/stop the asynchronous and periodic schedules.
+ * WHCI starts MMCs based on there being a valid GTK so these need
+ * only start/stop the asynchronous and periodic schedules and send a
+ * channel stop command.
*/
int whc_wusbhc_start(struct wusbhc *wusbhc)
@@ -78,12 +50,20 @@ int whc_wusbhc_start(struct wusbhc *wusbhc)
return 0;
}
-void whc_wusbhc_stop(struct wusbhc *wusbhc)
+void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay)
{
struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 stop_time, now_time;
+ int ret;
pzl_stop(whc);
asl_stop(whc);
+
+ now_time = le_readl(whc->base + WUSBTIME) & WUSBTIME_CHANNEL_TIME_MASK;
+ stop_time = (now_time + ((delay * 8) << 7)) & 0x00ffffff;
+ ret = whc_do_gencmd(whc, WUSBGENCMDSTS_CHAN_STOP, stop_time, NULL, 0);
+ if (ret == 0)
+ msleep(delay);
}
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,