From b09ac64b7b2d93efab3998033588f5cb0e470ccf Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Mon, 27 Oct 2008 15:14:03 +0000 Subject: wusb: release mutex in the error path of whci-hcd's whc_do_gencmd() Signed-off-by: David Vrabel --- drivers/usb/host/whci/hw.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c index ac86e59c1225..d498e7203217 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,8 @@ 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; } -- cgit v1.2.3 From 4d2bea4ca0adb4cebfbf89d34869c74081c42577 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Mon, 27 Oct 2008 15:42:31 +0000 Subject: wusb: do a proper channel stop When stopping the WUSB channel the host should send Channel Stop IEs giving the WUSB Channel Time of the last MMC. Both WHCI and HWA hosts provide a channel stop command for this. Signed-off-by: David Vrabel --- drivers/usb/host/hwa-hc.c | 102 +++++++++++++++++++--------------------- drivers/usb/host/whci/whcd.h | 2 +- drivers/usb/host/whci/whci-hc.h | 2 + drivers/usb/host/whci/wusb.c | 15 ++++-- 4 files changed, 63 insertions(+), 58 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 64be4d88df11..0e18989e1658 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -171,11 +171,6 @@ static int hwahc_op_start(struct usb_hcd *usb_hcd) if (result < 0) goto error_set_cluster_id; - result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); - if (result < 0) { - dev_err(dev, "cannot listen to notifications: %d\n", result); - goto error_stop; - } usb_hcd->uses_new_polling = 1; usb_hcd->poll_rh = 1; usb_hcd->state = HC_STATE_RUNNING; @@ -185,8 +180,6 @@ out: d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); return result; -error_stop: - __wa_stop(&hwahc->wa); error_set_cluster_id: wusb_cluster_id_put(wusbhc->cluster_id); error_cluster_id_get: @@ -194,39 +187,6 @@ error_cluster_id_get: } -/* - * FIXME: break this function up - */ -static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) -{ - int result; - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - - /* Set up a Host Info WUSB Information Element */ - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); - result = -ENOSPC; - - result = __wa_set_feature(&hwahc->wa, WA_ENABLE); - if (result < 0) { - dev_err(dev, "error commanding HC to start: %d\n", result); - goto error_stop; - } - result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); - if (result < 0) { - dev_err(dev, "error waiting for HC to start: %d\n", result); - goto error_stop; - } - result = 0; -out: - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return result; - -error_stop: - result = __wa_clear_feature(&hwahc->wa, WA_ENABLE); - goto out; -} - static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) { struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); @@ -246,18 +206,6 @@ static int hwahc_op_resume(struct usb_hcd *usb_hcd) return -ENOSYS; } -static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) -{ - int result; - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); - /* Nothing for now */ - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return; -} - /* * No need to abort pipes, as when this is called, all the children * has been disconnected and that has done it [through @@ -275,8 +223,6 @@ static void hwahc_op_stop(struct usb_hcd *usb_hcd) d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); wusbhc_stop(wusbhc); - wa_nep_disarm(&hwahc->wa); - result = __wa_stop(&hwahc->wa); wusb_cluster_id_put(wusbhc->cluster_id); mutex_unlock(&wusbhc->mutex); d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); @@ -325,6 +271,54 @@ static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, rpipe_ep_disable(&hwahc->wa, ep); } +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + return result; + +error_stop: + __wa_clear_feature(&hwahc->wa, WA_ENABLE); + return result; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc, int delay) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + int ret; + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_CHAN_STOP, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + delay * 1000, + iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (ret == 0) + msleep(delay); + + wa_nep_disarm(&hwahc->wa); + __wa_stop(&hwahc->wa); +} + /* * Set the UWB MAS allocation for the WUSB cluster * diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h index 1d2a53bd39fd..1bbb8cb6bf80 100644 --- a/drivers/usb/host/whci/whcd.h +++ b/drivers/usb/host/whci/whcd.h @@ -136,7 +136,7 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); /* 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); diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h index bff1eb7a35cf..51df7e313b38 100644 --- a/drivers/usb/host/whci/whci-hc.h +++ b/drivers/usb/host/whci/whci-hc.h @@ -410,6 +410,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 66e4ddcd961d..2befd475def4 100644 --- a/drivers/usb/host/whci/wusb.c +++ b/drivers/usb/host/whci/wusb.c @@ -64,8 +64,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 +79,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, -- cgit v1.2.3 From f88518d122f1b007f47a46aff37ca2885126a923 Mon Sep 17 00:00:00 2001 From: Huang Weiyi Date: Fri, 31 Oct 2008 22:49:58 +0800 Subject: wusb: remove unused #include The file(s) below do not use LINUX_VERSION_CODE nor KERNEL_VERSION. drivers/usb/host/hwa-hc.c drivers/usb/host/whci/hcd.c drivers/usb/host/whci/int.c drivers/usb/host/whci/wusb.c drivers/usb/wusbcore/cbaf.c This patch removes the said #include . Signed-off-by: Huang Weiyi Signed-off-by: David Vrabel --- drivers/usb/host/hwa-hc.c | 1 - drivers/usb/host/whci/hcd.c | 1 - drivers/usb/host/whci/int.c | 1 - drivers/usb/host/whci/wusb.c | 1 - 4 files changed, 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 0e18989e1658..2827353e97e1 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -54,7 +54,6 @@ * DWA). */ #include -#include #include #include #include diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c index ef3ad4dca945..de1e07271b81 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include #include #include #include diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c index fce01174aa9b..6aae70028101 100644 --- a/drivers/usb/host/whci/int.c +++ b/drivers/usb/host/whci/int.c @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include #include #include #include diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c index 2befd475def4..540021a0971e 100644 --- a/drivers/usb/host/whci/wusb.c +++ b/drivers/usb/host/whci/wusb.c @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include #include #include #include -- cgit v1.2.3 From 6fae35f9cea92793a98b2d9ab21235e5ae035581 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Mon, 17 Nov 2008 15:53:42 +0000 Subject: uwb: add basic radio manager The UWB radio manager coordinates the use of the radio between the PALs that may be using it. PALs request use of the radio with uwb_radio_start() and the radio manager will start beaconing if its not already doing so. When the last PAL has called uwb_radio_stop() beaconing will be stopped. In the future, the radio manager will have a more sophisticated channel selection algorithm, probably following the Channel Selection Policy from the WiMedia Alliance when it is finalized. For now, channel 9 (BG1, TFC1) is selected. The user may override the channel selected by the radio manager and may force the radio to stop beaconing. The WUSB Host Controller PAL makes use of this and there are two new debug PAL commands that can be used for testing. Signed-off-by: David Vrabel --- drivers/usb/host/hwa-hc.c | 1 - drivers/usb/host/whci/hcd.c | 2 -- 2 files changed, 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 2827353e97e1..2a4d36fa70b0 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -221,7 +221,6 @@ static void hwahc_op_stop(struct usb_hcd *usb_hcd) d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); - wusbhc_stop(wusbhc); wusb_cluster_id_put(wusbhc->cluster_id); mutex_unlock(&wusbhc->mutex); d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c index de1e07271b81..f599f89d3be1 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -91,8 +91,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); -- cgit v1.2.3 From 56968d0c1a920eb165c06318f5c458724e1df0af Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Tue, 25 Nov 2008 14:23:40 +0000 Subject: wusb: whci-hcd shouldn't do ASL/PZL updates while channel is inactive ASL/PZL updates while the WUSB channel is inactive (i.e., the PZL and ASL are stopped) may not complete. This causes hangs when removing the whci-hcd module if a device is still connected (removing the device does an endpoint_disable which results in an ASL update to remove the qset). If the WUSB channel is inactive the update can simply be skipped as the WHC doesn't care about the state of the ASL/PZL. Signed-off-by: David Vrabel --- drivers/usb/host/whci/asl.c | 21 ++++++++++++++++++--- drivers/usb/host/whci/pzl.c | 21 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index 4d7078e50572..ba99a7a3f81a 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -179,11 +179,26 @@ 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; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); + } + mutex_unlock(&wusbhc->mutex); } /** diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c index 8d62df0c330b..34d3a0aeab2b 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -195,11 +195,26 @@ 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; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); + } + mutex_unlock(&wusbhc->mutex); } static void update_pzl_hw_view(struct whc *whc) -- cgit v1.2.3 From dcc7461eef7341e84e2f7274f904ce01a43b2506 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 26 Nov 2008 13:36:59 +0000 Subject: wusb: add debug files for ASL, PZL and DI to the whci-hcd driver Add asl, pzl and di debugfs files to uwb/uwbN/wusbhc for WHCI host controller. These dump the current ASL, PZL and DI buffer. Signed-off-by: David Vrabel --- drivers/usb/host/whci/Kbuild | 1 + drivers/usb/host/whci/asl.c | 25 ------ drivers/usb/host/whci/debug.c | 189 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/whci/hcd.c | 3 + drivers/usb/host/whci/pzl.c | 28 ------- drivers/usb/host/whci/qset.c | 40 --------- drivers/usb/host/whci/whcd.h | 9 +- drivers/usb/host/whci/wusb.c | 27 ------ 8 files changed, 201 insertions(+), 121 deletions(-) create mode 100644 drivers/usb/host/whci/debug.c (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild index 26a3871ea0f9..11e5040b8337 100644 --- a/drivers/usb/host/whci/Kbuild +++ b/drivers/usb/host/whci/Kbuild @@ -2,6 +2,7 @@ obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o whci-hcd-y := \ asl.o \ + debug.o \ hcd.o \ hw.o \ init.o \ diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index ba99a7a3f81a..577c0d29849d 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -19,32 +19,11 @@ #include #include #include -#define D_LOCAL 0 -#include #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) { @@ -217,8 +196,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. @@ -232,8 +209,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) { diff --git a/drivers/usb/host/whci/debug.c b/drivers/usb/host/whci/debug.c new file mode 100644 index 000000000000..cf2d45946c57 --- /dev/null +++ b/drivers/usb/host/whci/debug.c @@ -0,0 +1,189 @@ +/* + * 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 . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +struct whc_dbg { + struct dentry *di_f; + struct dentry *asl_f; + struct dentry *pzl_f; +}; + +void qset_print(struct seq_file *s, struct whc_qset *qset) +{ + struct whc_std *std; + struct urb *urb = NULL; + int i; + + seq_printf(s, "qset %08x\n", (u32)qset->qset_dma); + 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\n", qset->qh.status, qset->qh.err_count); + 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 struct file_operations di_fops = { + .open = di_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct file_operations asl_fops = { + .open = asl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static 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 f599f89d3be1..1569afd6245b 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -273,6 +273,8 @@ static int whc_probe(struct umc_dev *umc) goto error_wusbhc_b_create; } + whc_dbg_init(whc); + return 0; error_wusbhc_b_create: @@ -296,6 +298,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); diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c index 34d3a0aeab2b..2ae5abf69a6a 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -19,35 +19,11 @@ #include #include #include -#define D_LOCAL 0 -#include #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) { @@ -250,8 +226,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) @@ -263,8 +237,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) { diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c index 0420037d2e18..7be74314ee12 100644 --- a/drivers/usb/host/whci/qset.c +++ b/drivers/usb/host/whci/qset.c @@ -24,46 +24,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; diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h index 1bbb8cb6bf80..0f3540f04f53 100644 --- a/drivers/usb/host/whci/whcd.h +++ b/drivers/usb/host/whci/whcd.h @@ -21,6 +21,7 @@ #define __WHCD_H #include +#include #include #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)) @@ -190,8 +194,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/wusb.c b/drivers/usb/host/whci/wusb.c index 540021a0971e..f24efdebad17 100644 --- a/drivers/usb/host/whci/wusb.c +++ b/drivers/usb/host/whci/wusb.c @@ -18,43 +18,16 @@ #include #include #include -#define D_LOCAL 1 -#include #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, -- cgit v1.2.3 From bce83697c5fe84a7a5d38c96fbbe43b4bc028c3e Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Mon, 22 Dec 2008 18:22:50 +0000 Subject: uwb: use dev_dbg() for debug messages Instead of the home-grown d_fnstart(), d_fnend() and d_printf() macros, use dev_dbg() or remove the message entirely. Signed-off-by: David Vrabel --- drivers/usb/host/hwa-hc.c | 55 ++++++----------------------------------------- 1 file changed, 7 insertions(+), 48 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 2a4d36fa70b0..8582236e4cad 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -62,16 +62,12 @@ #include "../wusbcore/wa-hc.h" #include "../wusbcore/wusbhc.h" -#define D_LOCAL 0 -#include - struct hwahc { struct wusbhc wusbhc; /* has to be 1st */ struct wahc wa; - u8 buffer[16]; /* for misc usb transactions */ }; -/** +/* * FIXME should be wusbhc * * NOTE: we need to cache the Cluster ID because later...there is no @@ -125,7 +121,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); struct device *dev = &hwahc->wa.usb_iface->dev; - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); wa_nep_disarm(&hwahc->wa); result = __wa_set_feature(&hwahc->wa, WA_RESET); @@ -133,7 +128,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) dev_err(dev, "error commanding HC to reset: %d\n", result); goto error_unlock; } - d_printf(3, dev, "reset: waiting for device to change state\n"); result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); if (result < 0) { dev_err(dev, "error waiting for HC to reset: %d\n", result); @@ -141,7 +135,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) } error_unlock: mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); return result; } @@ -154,15 +147,9 @@ static int hwahc_op_start(struct usb_hcd *usb_hcd) int result; struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - /* Set up a Host Info WUSB Information Element */ - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); result = -ENOSPC; mutex_lock(&wusbhc->mutex); - /* Start the numbering from the top so that the bottom - * range of the unauth addr space is used for devices, - * the top for HCs; use 0xfe - RC# */ addr = wusb_cluster_id_get(); if (addr == 0) goto error_cluster_id_get; @@ -176,7 +163,6 @@ static int hwahc_op_start(struct usb_hcd *usb_hcd) result = 0; out: mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); return result; error_set_cluster_id: @@ -213,18 +199,11 @@ static int hwahc_op_resume(struct usb_hcd *usb_hcd) */ static void hwahc_op_stop(struct usb_hcd *usb_hcd) { - int result; struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct wahc *wa = &hwahc->wa; - struct device *dev = &wa->usb_iface->dev; - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); wusb_cluster_id_put(wusbhc->cluster_id); mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return; } static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) @@ -573,11 +552,11 @@ static int wa_fill_descr(struct wahc *wa) itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); while (itr_size >= sizeof(*hdr)) { hdr = (struct usb_descriptor_header *) itr; - d_printf(3, dev, "Extra device descriptor: " - "type %02x/%u bytes @ %zu (%zu left)\n", - hdr->bDescriptorType, hdr->bLength, - (itr - usb_dev->rawdescriptors[actconfig_idx]), - itr_size); + dev_dbg(dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) goto found; itr += hdr->bLength; @@ -786,7 +765,6 @@ static void hwahc_destroy(struct hwahc *hwahc) { struct wusbhc *wusbhc = &hwahc->wusbhc; - d_fnstart(1, NULL, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); __wa_destroy(&hwahc->wa); wusbhc_destroy(&hwahc->wusbhc); @@ -796,7 +774,6 @@ static void hwahc_destroy(struct hwahc *hwahc) usb_put_intf(hwahc->wa.usb_iface); usb_put_dev(hwahc->wa.usb_dev); mutex_unlock(&wusbhc->mutex); - d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc); } static void hwahc_init(struct hwahc *hwahc) @@ -813,7 +790,6 @@ static int hwahc_probe(struct usb_interface *usb_iface, struct hwahc *hwahc; struct device *dev = &usb_iface->dev; - d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id); result = -ENOMEM; usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); if (usb_hcd == NULL) { @@ -840,7 +816,6 @@ static int hwahc_probe(struct usb_interface *usb_iface, dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); goto error_wusbhc_b_create; } - d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id); return 0; error_wusbhc_b_create: @@ -850,7 +825,6 @@ error_add_hcd: error_hwahc_create: usb_put_hcd(usb_hcd); error_alloc: - d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result); return result; } @@ -864,16 +838,12 @@ static void hwahc_disconnect(struct usb_interface *usb_iface) wusbhc = usb_hcd_to_wusbhc(usb_hcd); hwahc = container_of(wusbhc, struct hwahc, wusbhc); - d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface); wusbhc_b_destroy(&hwahc->wusbhc); usb_remove_hcd(usb_hcd); hwahc_destroy(hwahc); usb_put_hcd(usb_hcd); - d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc, - usb_iface); } -/** USB device ID's that we handle */ static struct usb_device_id hwahc_id_table[] = { /* FIXME: use class labels for this */ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, @@ -890,18 +860,7 @@ static struct usb_driver hwahc_driver = { static int __init hwahc_driver_init(void) { - int result; - result = usb_register(&hwahc_driver); - if (result < 0) { - printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n", - result); - goto error_usb_register; - } - return 0; - -error_usb_register: - return result; - + return usb_register(&hwahc_driver); } module_init(hwahc_driver_init); -- cgit v1.2.3 From a5e6ced58d423cb09c4fc0087dcfdb0b5deb5e1c Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 7 Jan 2009 10:54:22 +0000 Subject: wusb: timeout when waiting for ASL/PZL updates in whci-hcd Timeout if an ASL or PZL update doesn't not complete and reset the hardware. Signed-off-by: David Vrabel --- drivers/usb/host/whci/asl.c | 9 +++++++-- drivers/usb/host/whci/hw.c | 15 +++++++++++++++ drivers/usb/host/whci/pzl.c | 9 +++++++-- drivers/usb/host/whci/whcd.h | 1 + 4 files changed, 30 insertions(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index 577c0d29849d..2291c5f5af51 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -170,12 +170,17 @@ void asl_stop(struct whc *whc) void asl_update(struct whc *whc, uint32_t wusbcmd) { struct wusbhc *wusbhc = &whc->wusbhc; + long t; mutex_lock(&wusbhc->mutex); if (wusbhc->active) { whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->async_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); + 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); } diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c index d498e7203217..6afa2e379160 100644 --- a/drivers/usb/host/whci/hw.c +++ b/drivers/usb/host/whci/hw.c @@ -87,3 +87,18 @@ out: 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/pzl.c b/drivers/usb/host/whci/pzl.c index 2ae5abf69a6a..7dc85a0bee7c 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -183,12 +183,17 @@ void pzl_stop(struct whc *whc) void pzl_update(struct whc *whc, uint32_t wusbcmd) { struct wusbhc *wusbhc = &whc->wusbhc; + long t; mutex_lock(&wusbhc->mutex); if (wusbhc->active) { whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->periodic_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); + 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); } diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h index 0f3540f04f53..d3543a181dc9 100644 --- a/drivers/usb/host/whci/whcd.h +++ b/drivers/usb/host/whci/whcd.h @@ -137,6 +137,7 @@ 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); -- cgit v1.2.3 From e64a5219296fff11a0da0322b43012265cd3b327 Mon Sep 17 00:00:00 2001 From: roel kluin Date: Tue, 21 Oct 2008 00:47:23 -0400 Subject: USB: clean up redundant tests on unsigned temp, bytes and param->{length,sglen,vary} are unsigned so these tests do nothing. Signed-off-by: Roel Kluin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-dbg.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 0cb53ca8d343..7f4ace73d44a 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -455,9 +455,7 @@ static void qh_lines ( (scratch >> 16) & 0x7fff, scratch, td->urb); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; @@ -466,9 +464,7 @@ static void qh_lines ( } temp = snprintf (next, size, "\n"); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; -- cgit v1.2.3 From 8e8ce4b642ce4beb865fb1d67cd8ea883d9ae7d7 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 20 Oct 2008 21:46:01 -0700 Subject: USB: use pci_ioremap_bar() in drivers/usb Use the newly introduced pci_ioremap_bar() function in drivers/usb. pci_ioremap_bar() just takes a pci device and a bar number, with the goal of making it really hard to get wrong, while also having a central place to stick sanity checks. Signed-off-by: Arjan van de Ven Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/pci-quirks.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index ae6e70edd745..15e4f1d92c79 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -172,9 +172,9 @@ static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ #ifndef __hppa__ @@ -221,9 +221,9 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; cap_length = readb(base); op_reg_base = base + cap_length; -- cgit v1.2.3 From 3a4e72cbf2ac4435630a2b03bd25e60ef5967e99 Mon Sep 17 00:00:00 2001 From: Vikram Pandita Date: Fri, 24 Oct 2008 23:41:30 +0530 Subject: USB: Avoid 20ms delay in EHCI resume For function ehci_bus_resume() - Added flag resume_needed No need to wait for 20ms if no port was suspended - Change mdelay to msleep - release and reacquire the spinlock around mdelay Signed-off-by: vikram pandita Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 218f9660d7ee..38650707dfe0 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -194,6 +194,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) u32 temp; u32 power_okay; int i; + u8 resume_needed = 0; if (time_before (jiffies, ehci->next_statechange)) msleep(5); @@ -228,7 +229,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* Some controller/firmware combinations need a delay during which * they set up the port statuses. See Bugzilla #8190. */ - mdelay(8); + spin_unlock_irq(&ehci->lock); + msleep(8); + spin_lock_irq(&ehci->lock); /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); @@ -236,12 +239,21 @@ static int ehci_bus_resume (struct usb_hcd *hcd) temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && - (temp & PORT_SUSPEND)) + (temp & PORT_SUSPEND)) { temp |= PORT_RESUME; + resume_needed = 1; + } ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } + + /* msleep for 20ms only if code is trying to resume port */ + if (resume_needed) { + spin_unlock_irq(&ehci->lock); + msleep(20); + spin_lock_irq(&ehci->lock); + } + i = HCS_N_PORTS (ehci->hcs_params); - mdelay (20); while (i--) { temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (test_bit(i, &ehci->bus_suspended) && -- cgit v1.2.3 From b92a78e582b1a45649143dc86e526f5824092478 Mon Sep 17 00:00:00 2001 From: Rodolfo Giometti Date: Thu, 23 Oct 2008 10:08:07 +0200 Subject: usb host: Oxford OXU210HP HCD driver. This driver implements the support for Oxford OXU210HP USB high-speed host, no peripheral nor OTG. Signed-off-by: Rodolfo Giometti Cc: Kan Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 13 + drivers/usb/host/Makefile | 1 + drivers/usb/host/oxu210hp-hcd.c | 3985 +++++++++++++++++++++++++++++++++++++++ drivers/usb/host/oxu210hp.h | 447 +++++ 4 files changed, 4446 insertions(+) create mode 100644 drivers/usb/host/oxu210hp-hcd.c create mode 100644 drivers/usb/host/oxu210hp.h (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index f3a75a929e0a..2b476b6b3d4d 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -96,6 +96,19 @@ config USB_EHCI_HCD_PPC_OF Enables support for the USB controller present on the PowerPC OpenFirmware platform bus. +config USB_OXU210HP_HCD + tristate "OXU210HP HCD support" + depends on USB + ---help--- + The OXU210HP is an USB host/OTG/device controller. Enable this + option if your board has this chip. If unsure, say N. + + This driver does not support isochronous transfers and doesn't + implement OTG nor USB device controllers. + + To compile this driver as a module, choose M here: the + module will be called oxu210hp-hcd. + config USB_ISP116X_HCD tristate "ISP116X HCD support" depends on USB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 23be22224044..e5f3f20787e4 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_USB_WHCI_HCD) += whci/ obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o +obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c new file mode 100644 index 000000000000..251123c29d81 --- /dev/null +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -0,0 +1,3985 @@ +/* + * Copyright (c) 2008 Rodolfo Giometti + * Copyright (c) 2008 Eurotech S.p.A. + * + * This code is *strongly* based on EHCI-HCD code by David Brownell since + * the chip is a quasi-EHCI compatible. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core/hcd.h" + +#include +#include +#include + +#include +#include + +#include "oxu210hp.h" + +#define DRIVER_VERSION "0.0.50" + +/* + * Main defines + */ + +#define oxu_dbg(oxu, fmt, args...) \ + dev_dbg(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_err(oxu, fmt, args...) \ + dev_err(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_info(oxu, fmt, args...) \ + dev_info(oxu_to_hcd(oxu)->self.controller , fmt , ## args) + +static inline struct usb_hcd *oxu_to_hcd(struct oxu_hcd *oxu) +{ + return container_of((void *) oxu, struct usb_hcd, hcd_priv); +} + +static inline struct oxu_hcd *hcd_to_oxu(struct usb_hcd *hcd) +{ + return (struct oxu_hcd *) (hcd->hcd_priv); +} + +/* + * Debug stuff + */ + +#undef OXU_URB_TRACE +#undef OXU_VERBOSE_DEBUG + +#ifdef OXU_VERBOSE_DEBUG +#define oxu_vdbg oxu_dbg +#else +#define oxu_vdbg(oxu, fmt, args...) /* Nop */ +#endif + +#ifdef DEBUG + +static int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf(buf, len, "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf(buf, len, "%s%sintrenable %02x%s%s%s%s%s%s", + label, label[0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings[] = + { "1024", "512", "256", "??" }; + +static int dbg_command_buf(char *buf, unsigned len, + const char *label, u32 command) +{ + return scnprintf(buf, len, + "%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s", + label, label[0] ? " " : "", command, + (command & CMD_PARK) ? "park" : "(park)", + CMD_PARK_CNT(command), + (command >> 16) & 0x3f, + (command & CMD_LRESET) ? " LReset" : "", + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings[(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int dbg_port_buf(char *buf, unsigned len, const char *label, + int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: + sig = "se0"; + break; + case 1 << 10: + sig = "k"; /* low speed */ + break; + case 2 << 10: + sig = "j"; + break; + default: + sig = "?"; + break; + } + + return scnprintf(buf, len, + "%s%sport %d status %06x%s%s sig=%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", port, status, + (status & PORT_POWER) ? " POWER" : "", + (status & PORT_OWNER) ? " OWNER" : "", + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_OCC) ? " OCC" : "", + (status & PORT_OC) ? " OC" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : "" + ); +} + +#else + +static inline int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_command_buf(char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_port_buf(char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(oxu, label, status) { \ + char _buf[80]; \ + dbg_status_buf(_buf, sizeof _buf, label, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_cmd(oxu, label, command) { \ + char _buf[80]; \ + dbg_command_buf(_buf, sizeof _buf, label, command); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_port(oxu, label, port, status) { \ + char _buf[80]; \ + dbg_port_buf(_buf, sizeof _buf, label, port, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +/* + * Module parameters + */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh; /* 0 to 6 */ +module_param(log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC(log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* Initial park setting: slower than hw default */ +static unsigned park; +module_param(park, uint, S_IRUGO); +MODULE_PARM_DESC(park, "park setting; 1-3 back-to-back async packets"); + +/* For flakey hardware, ignore overcurrent indicators */ +static int ignore_oc; +module_param(ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC(ignore_oc, "ignore bogus hardware overcurrent indications"); + + +static void ehci_work(struct oxu_hcd *oxu); +static int oxu_hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + +/* + * Local functions + */ + +/* Low level read/write registers functions */ +static inline u32 oxu_readl(void *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void oxu_writel(void *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +static inline void timer_action_done(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + clear_bit(action, &oxu->actions); +} + +static inline void timer_action(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + if (!test_and_set_bit(action, &oxu->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + /* all timings except IAA watchdog can be overridden. + * async queue SHRINK often precedes IAA. while it's ready + * to go OFF neither can matter, and afterwards the IO + * watchdog stops unless there's still periodic traffic. + */ + if (action != TIMER_IAA_WATCHDOG + && t > oxu->watchdog.expires + && timer_pending(&oxu->watchdog)) + return; + mod_timer(&oxu->watchdog, t); + } +} + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake(struct oxu_hcd *oxu, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* Force HC to halt state from unknown (EHCI spec section 2.3) */ +static int ehci_halt(struct oxu_hcd *oxu) +{ + u32 temp = readl(&oxu->regs->status); + + /* disable any irqs left enabled by previous code */ + writel(0, &oxu->regs->intr_enable); + + if ((temp & STS_HALT) != 0) + return 0; + + temp = readl(&oxu->regs->command); + temp &= ~CMD_RUN; + writel(temp, &oxu->regs->command); + return handshake(oxu, &oxu->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* Put TDI/ARC silicon into EHCI mode */ +static void tdi_reset(struct oxu_hcd *oxu) +{ + u32 __iomem *reg_ptr; + u32 tmp; + + reg_ptr = (u32 __iomem *)(((u8 __iomem *)oxu->regs) + 0x68); + tmp = readl(reg_ptr); + tmp |= 0x3; + writel(tmp, reg_ptr); +} + +/* Reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct oxu_hcd *oxu) +{ + int retval; + u32 command = readl(&oxu->regs->command); + + command |= CMD_RESET; + dbg_cmd(oxu, "reset", command); + writel(command, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + oxu->next_statechange = jiffies; + retval = handshake(oxu, &oxu->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + tdi_reset(oxu); + + return retval; +} + +/* Idle the controller (from running) */ +static void ehci_quiesce(struct oxu_hcd *oxu) +{ + u32 temp; + +#ifdef DEBUG + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + BUG(); +#endif + + /* wait for any schedule enables/disables to take effect */ + temp = readl(&oxu->regs->command) << 10; + temp &= STS_ASS | STS_PSS; + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + temp, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } + + /* then disable anything that's still active */ + temp = readl(&oxu->regs->command); + temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel(temp, &oxu->regs->command); + + /* hardware can take 16 microframes to turn off ... */ + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + 0, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } +} + +static int check_reset_complete(struct oxu_hcd *oxu, int index, + u32 __iomem *status_reg, int port_status) +{ + if (!(port_status & PORT_CONNECT)) { + oxu->reset_done[index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + oxu_dbg(oxu, "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else + oxu_dbg(oxu, "port %d high speed\n", index + 1); + + return port_status; +} + +static void ehci_hub_descriptor(struct oxu_hcd *oxu, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(oxu->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* oxu 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->bitmap[0], 0, temp); + memset(&desc->bitmap[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC(oxu->hcs_params)) + temp |= 0x0001; /* per-port power control */ + else + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp); +} + + +/* Allocate an OXU210HP on-chip memory data buffer + * + * An on-chip memory data buffer is required for each OXU210HP USB transfer. + * Each transfer descriptor has one or more on-chip memory data buffers. + * + * Data buffers are allocated from a fix sized pool of data blocks. + * To minimise fragmentation and give reasonable memory utlisation, + * data buffers are allocated with sizes the power of 2 multiples of + * the block size, starting on an address a multiple of the allocated size. + * + * FIXME: callers of this function require a buffer to be allocated for + * len=0. This is a waste of on-chip memory and should be fix. Then this + * function should be changed to not allocate a buffer for len=0. + */ +static int oxu_buf_alloc(struct oxu_hcd *oxu, struct ehci_qtd *qtd, int len) +{ + int n_blocks; /* minium blocks needed to hold len */ + int a_blocks; /* blocks allocated */ + int i, j; + + /* Don't allocte bigger than supported */ + if (len > BUFFER_SIZE * BUFFER_NUM) { + oxu_err(oxu, "buffer too big (%d)\n", len); + return -ENOMEM; + } + + spin_lock(&oxu->mem_lock); + + /* Number of blocks needed to hold len */ + n_blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE; + + /* Round the number of blocks up to the power of 2 */ + for (a_blocks = 1; a_blocks < n_blocks; a_blocks <<= 1) + ; + + /* Find a suitable available data buffer */ + for (i = 0; i < BUFFER_NUM; + i += max(a_blocks, (int)oxu->db_used[i])) { + + /* Check all the required blocks are available */ + for (j = 0; j < a_blocks; j++) + if (oxu->db_used[i + j]) + break; + + if (j != a_blocks) + continue; + + /* Allocate blocks found! */ + qtd->buffer = (void *) &oxu->mem->db_pool[i]; + qtd->buffer_dma = virt_to_phys(qtd->buffer); + + qtd->qtd_buffer_len = BUFFER_SIZE * a_blocks; + oxu->db_used[i] = a_blocks; + + spin_unlock(&oxu->mem_lock); + + return 0; + } + + /* Failed */ + + spin_unlock(&oxu->mem_lock); + + return -ENOMEM; +} + +static void oxu_buf_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = (qtd->buffer - (void *) &oxu->mem->db_pool[0]) + / BUFFER_SIZE; + oxu->db_used[index] = 0; + qtd->qtd_buffer_len = 0; + qtd->buffer_dma = 0; + qtd->buffer = NULL; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static inline void ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma) +{ + memset(qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); +} + +static inline void oxu_qtd_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + if (qtd->buffer) + oxu_buf_free(oxu, qtd); + + spin_lock(&oxu->mem_lock); + + index = qtd - &oxu->mem->qtd_pool[0]; + oxu->qtd_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct ehci_qtd *ehci_qtd_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qtd *qtd = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QTD_NUM; i++) + if (!oxu->qtd_used[i]) + break; + + if (i < QTD_NUM) { + qtd = (struct ehci_qtd *) &oxu->mem->qtd_pool[i]; + memset(qtd, 0, sizeof *qtd); + + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); + + qtd->qtd_dma = virt_to_phys(qtd); + + oxu->qtd_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qtd; +} + +static void oxu_qh_free(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = qh - &oxu->mem->qh_pool[0]; + oxu->qh_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static void qh_destroy(struct kref *kref) +{ + struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref); + struct oxu_hcd *oxu = qh->oxu; + + /* clean qtds first, and know this is not linked */ + if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { + oxu_dbg(oxu, "unused qh not empty!\n"); + BUG(); + } + if (qh->dummy) + oxu_qtd_free(oxu, qh->dummy); + oxu_qh_free(oxu, qh); +} + +static struct ehci_qh *oxu_qh_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qh *qh = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QHEAD_NUM; i++) + if (!oxu->qh_used[i]) + break; + + if (i < QHEAD_NUM) { + qh = (struct ehci_qh *) &oxu->mem->qh_pool[i]; + memset(qh, 0, sizeof *qh); + + kref_init(&qh->kref); + qh->oxu = oxu; + qh->qh_dma = virt_to_phys(qh); + INIT_LIST_HEAD(&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = ehci_qtd_alloc(oxu); + if (qh->dummy == NULL) { + oxu_dbg(oxu, "no dummy td\n"); + oxu->qh_used[i] = 0; + + return NULL; + } + + oxu->qh_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_get(struct ehci_qh *qh) +{ + kref_get(&qh->kref); + return qh; +} + +static inline void qh_put(struct ehci_qh *qh) +{ + kref_put(&qh->kref, qh_destroy); +} + +static void oxu_murb_free(struct oxu_hcd *oxu, struct oxu_murb *murb) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = murb - &oxu->murb_pool[0]; + oxu->murb_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct oxu_murb *oxu_murb_alloc(struct oxu_hcd *oxu) + +{ + int i; + struct oxu_murb *murb = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < MURB_NUM; i++) + if (!oxu->murb_used[i]) + break; + + if (i < MURB_NUM) { + murb = &(oxu->murb_pool)[i]; + + oxu->murb_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return murb; +} + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ +static void ehci_mem_cleanup(struct oxu_hcd *oxu) +{ + kfree(oxu->murb_pool); + oxu->murb_pool = NULL; + + if (oxu->async) + qh_put(oxu->async); + oxu->async = NULL; + + del_timer(&oxu->urb_timer); + + oxu->periodic = NULL; + + /* shadow periodic table */ + kfree(oxu->pshadow); + oxu->pshadow = NULL; +} + +/* Remember to add cleanup code (above) if you add anything here. + */ +static int ehci_mem_init(struct oxu_hcd *oxu, gfp_t flags) +{ + int i; + + for (i = 0; i < oxu->periodic_size; i++) + oxu->mem->frame_list[i] = EHCI_LIST_END; + for (i = 0; i < QHEAD_NUM; i++) + oxu->qh_used[i] = 0; + for (i = 0; i < QTD_NUM; i++) + oxu->qtd_used[i] = 0; + + oxu->murb_pool = kcalloc(MURB_NUM, sizeof(struct oxu_murb), flags); + if (!oxu->murb_pool) + goto fail; + + for (i = 0; i < MURB_NUM; i++) + oxu->murb_used[i] = 0; + + oxu->async = oxu_qh_alloc(oxu); + if (!oxu->async) + goto fail; + + oxu->periodic = (__le32 *) &oxu->mem->frame_list; + oxu->periodic_dma = virt_to_phys(oxu->periodic); + + for (i = 0; i < oxu->periodic_size; i++) + oxu->periodic[i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + oxu->pshadow = kcalloc(oxu->periodic_size, sizeof(void *), flags); + if (oxu->pshadow != NULL) + return 0; + +fail: + oxu_dbg(oxu, "couldn't init memory\n"); + ehci_mem_cleanup(oxu); + return -ENOMEM; +} + +/* Fill a qtd, returning how much of the buffer we were able to queue up. + */ +static int qtd_fill(struct ehci_qtd *qtd, dma_addr_t buf, size_t len, + int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[0] = cpu_to_le32((u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely(len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[i] = cpu_to_le32((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_le32((count << 16) | token); + qtd->length = count; + + return count; +} + +static inline void qh_update(struct oxu_hcd *oxu, + struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + qh->hw_qtd_next = QTD_NEXT(qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(qh->hw_info1 & cpu_to_le32(1 << 14))) { + unsigned is_out, epnum; + + is_out = !(qtd->hw_token & cpu_to_le32(1 << 8)); + epnum = (le32_to_cpup(&qh->hw_info1) >> 8) & 0x0f; + if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { + qh->hw_token &= ~__constant_cpu_to_le32(QTD_TOGGLE); + usb_settoggle(qh->dev, epnum, is_out, 1); + } + } + + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + wmb(); + qh->hw_token &= __constant_cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); +} + +/* If it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void qh_refresh(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *qtd; + + if (list_empty(&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry(qh->qtd_list.next, + struct ehci_qtd, qtd_list); + /* first qtd may already be partially processed */ + if (cpu_to_le32(qtd->qtd_dma) == qh->hw_current) + qtd = NULL; + } + + if (qtd) + qh_update(oxu, qh, qtd); +} + +static void qtd_copy_status(struct oxu_hcd *oxu, struct urb *urb, + size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely(QTD_PID(token) != 2)) + urb->actual_length += length - QTD_LENGTH(token); + + /* don't modify error codes */ + if (unlikely(urb->status != -EINPROGRESS)) + return; + + /* force cleanup after short read; not always an error */ + if (unlikely(IS_SHORT_READ(token))) + urb->status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + urb->status = -EOVERFLOW; + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + urb->status = -EPROTO; + } else if (token & QTD_STS_DBE) { + urb->status = (QTD_PID(token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad crc, wrong PID, etc; retried */ + if (QTD_CERR(token)) + urb->status = -EPIPE; + else { + oxu_dbg(oxu, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + urb->status = -EPROTO; + } + /* CERR nonzero + no errors + halt --> stall */ + } else if (QTD_CERR(token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + + oxu_vdbg(oxu, "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + token, urb->status); + } +} + +static void ehci_urb_done(struct oxu_hcd *oxu, struct urb *urb) +__releases(oxu->lock) +__acquires(oxu->lock) +{ + if (likely(urb->hcpriv != NULL)) { + struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw_info2 & __constant_cpu_to_le32(QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs--; + } + qh_put(qh); + } + + urb->hcpriv = NULL; + switch (urb->status) { + case -EINPROGRESS: /* success */ + urb->status = 0; + default: /* fault */ + break; + case -EREMOTEIO: /* fault or normal */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + urb->status = 0; + break; + case -ECONNRESET: /* canceled */ + case -ENOENT: + break; + } + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + urb->status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + spin_unlock(&oxu->lock); + usb_hcd_giveback_urb(oxu_to_hcd(oxu), urb, urb->status); + spin_lock(&oxu->lock); +} + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh); +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh); + +#define HALT_BIT __constant_cpu_to_le32(QTD_STS_HALT) + +/* Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns number of completions called, + * indicating how much "real" work we did. + */ +static unsigned qh_completions(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *last = NULL, *end = qh->dummy; + struct list_head *entry, *tmp; + int stopped; + unsigned count = 0; + int do_status = 0; + u8 state; + struct oxu_murb *murb = NULL; + + if (unlikely(list_empty(&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe(entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + /* Clean up any state from previous QTD ...*/ + if (last) { + if (likely(last->urb != urb)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + } + oxu_qtd_free(oxu, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb(); + token = le32_to_cpu(qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + if ((token & QTD_STS_ACTIVE) == 0) { + + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + */ + } else if (IS_SHORT_READ(token) && + !(qtd->hw_alt_next & EHCI_LIST_END)) { + stopped = 1; + goto halt; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely(!stopped && + HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) { + break; + + } else { + stopped = 1; + + if (unlikely(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) + urb->status = -ESHUTDOWN; + + /* ignore active urbs unless some previous qtd + * for the urb faulted (including short read) or + * its urb was canceled. we may patch qh or qtds. + */ + if (likely(urb->status == -EINPROGRESS)) + continue; + + /* issue status after short control reads */ + if (unlikely(do_status != 0) + && QTD_PID(token) == 0 /* OUT */) { + do_status = 0; + continue; + } + + /* token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_le32(qtd->qtd_dma) + == qh->hw_current) + token = le32_to_cpu(qh->hw_token); + + /* force halt for unlinked or blocked qh, so we'll + * patch the qh later and so that completions can't + * activate it while we "know" it's stopped. + */ + if ((HALT_BIT & qh->hw_token) == 0) { +halt: + qh->hw_token |= HALT_BIT; + wmb(); + } + } + + /* Remove it from the queue */ + qtd_copy_status(oxu, urb->complete ? + urb : ((struct oxu_murb *) urb)->main, + qtd->length, token); + if ((usb_pipein(qtd->urb->pipe)) && + (NULL != qtd->transfer_buffer)) + memcpy(qtd->transfer_buffer, qtd->buffer, qtd->length); + do_status = (urb->status == -EREMOTEIO) + && usb_pipecontrol(urb->pipe); + + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry(qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + list_del(&qtd->qtd_list); + last = qtd; + } + + /* last urb's completion might still need calling */ + if (likely(last != NULL)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_qtd_free(oxu, last); + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(oxu, qh); + break; + case QH_STATE_LINKED: + /* should be rare for periodic transfers, + * except maybe high bandwidth ... + */ + if ((__constant_cpu_to_le32(QH_SMASK) + & qh->hw_info2) != 0) { + intr_deschedule(oxu, qh); + (void) qh_schedule(oxu, qh); + } else + unlink_async(oxu, qh); + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/* High bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +/* ... and packet size, for any kind of endpoint descriptor */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* Reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list) +{ + struct list_head *entry, *temp; + + list_for_each_safe(entry, temp, qtd_list) { + struct ehci_qtd *qtd; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + list_del(&qtd->qtd_list); + oxu_qtd_free(oxu, qtd); + } +} + +/* Create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, + struct urb *urb, + struct list_head *head, + gfp_t flags) +{ + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + int is_input; + u32 token; + void *transfer_buf = NULL; + int ret; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein(urb->pipe); + if (!urb->transfer_buffer && urb->transfer_buffer_length && is_input) + urb->transfer_buffer = phys_to_virt(urb->transfer_dma); + + if (usb_pipecontrol(urb->pipe)) { + /* SETUP pid */ + ret = oxu_buf_alloc(oxu, qtd, sizeof(struct usb_ctrlrequest)); + if (ret) + goto cleanup; + + qtd_fill(qtd, qtd->buffer_dma, sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + memcpy(qtd->buffer, qtd->urb->setup_packet, + sizeof(struct usb_ctrlrequest)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * Data transfer stage: buffer setup + */ + + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + + buf = qtd->buffer_dma; + transfer_buf = urb->transfer_buffer; + + if (!is_input) + memcpy(qtd->buffer, qtd->urb->transfer_buffer, len); + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(qtd, buf, len, token, maxpacket); + qtd->transfer_buffer = transfer_buf; + len -= this_qtd_len; + buf += this_qtd_len; + transfer_buf += this_qtd_len; + if (is_input) + qtd->hw_alt_next = oxu->async->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + if (likely(len > 0)) { + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* unless the bulk/interrupt caller wants a chance to clean + * up after short reads, hc should advance qh past this urb + */ + if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol(urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END; + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely(urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + qtd->hw_token |= __constant_cpu_to_le32(QTD_IOC); + return head; + +cleanup: + qtd_list_free(oxu, urb, head); + return NULL; +} + +/* Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh *qh_make(struct oxu_hcd *oxu, + struct urb *urb, gfp_t flags) +{ + struct ehci_qh *qh = oxu_qh_alloc(oxu); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint(urb->pipe) << 8; + info1 |= usb_pipedevice(urb->pipe) << 0; + + is_input = usb_pipein(urb->pipe); + type = usb_pipetype(urb->pipe); + maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + dbg("intr period %d uframes, NYET!", + urb->interval); + goto done; + } + } else { + struct usb_tt *tt = urb->dev->tt; + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time(urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ + qh->c_usecs = qh->usecs + HS_USECS(0); + qh->usecs = HS_USECS(1); + } else { /* SPLIT+DATA, gap, CSPLIT */ + qh->usecs += HS_USECS(1); + qh->c_usecs = HS_USECS(0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US(think_time + + usb_calc_bus_time(urb->dev->speed, + is_input, 0, max_packet(maxp))); + qh->period = urb->interval; + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (EHCI_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet(maxp) << 16; + info2 |= hb_mult(maxp) << 30; + } + break; + default: + dbg("bogus dev %p speed %d", urb->dev, urb->dev->speed); +done: + qh_put(qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32(info1); + qh->hw_info2 = cpu_to_le32(info2); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); + qh_refresh(oxu, qh); + return qh; +} + +/* Move qh (and its qtds) onto async queue; maybe enable queue. + */ +static void qh_link_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + __le32 dma = QH_NEXT(qh->qh_dma); + struct ehci_qh *head; + + /* (re)start the async schedule? */ + head = oxu->async; + timer_action_done(oxu, TIMER_ASYNC_OFF); + if (!head->qh_next.qh) { + u32 cmd = readl(&oxu->regs->command); + + if (!(cmd & CMD_ASE)) { + /* in case a clear of CMD_ASE didn't take yet */ + (void)handshake(oxu, &oxu->regs->status, + STS_ASS, 0, 150); + cmd |= CMD_ASE | CMD_RUN; + writel(cmd, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } + } + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + if (qh->qh_state == QH_STATE_IDLE) + qh_refresh(oxu, qh); + + /* splice right after start */ + qh->qh_next = head->qh_next; + qh->hw_next = head->hw_next; + wmb(); + + head->qh_next.qh = qh; + head->hw_next = dma; + + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +#define QH_ADDR_MASK __constant_cpu_to_le32(0x7f) + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct ehci_qh *qh_append_tds(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list, + int epnum, void **ptr) +{ + struct ehci_qh *qh = NULL; + + qh = (struct ehci_qh *) *ptr; + if (unlikely(qh == NULL)) { + /* can't sleep here, we have oxu->lock... */ + qh = qh_make(oxu, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely(qh != NULL)) { + struct ehci_qtd *qtd; + + if (unlikely(list_empty(qtd_list))) + qtd = NULL; + else + qtd = list_entry(qtd_list->next, struct ehci_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely(epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice(urb->pipe) == 0) + qh->hw_info1 &= ~QH_ADDR_MASK; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely(qtd != NULL)) { + struct ehci_qtd *dummy; + dma_addr_t dma; + __le32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT; + wmb(); + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del(&qtd->qtd_list); + list_add(&dummy->qtd_list, qtd_list); + list_splice(qtd_list, qh->qtd_list.prev); + + ehci_qtd_init(qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry(qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(dma); + + /* let the hc process these next qtds */ + dummy->hw_token = (token & ~(0x80)); + wmb(); + dummy->hw_token = token; + + urb->hcpriv = qh_get(qh); + } + } + return qh; +} + +static int submit_async(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + struct ehci_qtd *qtd; + int epnum; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc = 0; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); +#endif + + spin_lock_irqsave(&oxu->lock, flags); + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + rc = -ESHUTDOWN; + goto done; + } + + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely(qh->qh_state == QH_STATE_IDLE)) + qh_link_async(oxu, qh_get(qh)); +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (unlikely(qh == NULL)) + qtd_list_free(oxu, urb, qtd_list); + return rc; +} + +/* The async qh for the qtds being reclaimed are now unlinked from the HC */ + +static void end_unlink_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh = oxu->reclaim; + struct ehci_qh *next; + + timer_action_done(oxu, TIMER_IAA_WATCHDOG); + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + qh_put(qh); /* refcount from reclaim */ + + /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ + next = qh->reclaim; + oxu->reclaim = next; + oxu->reclaim_ready = 0; + qh->reclaim = NULL; + + qh_completions(oxu, qh); + + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + qh_link_async(oxu, qh); + else { + qh_put(qh); /* refcount from async list */ + + /* it's not free to turn the async schedule on/off; leave it + * active but idle for a while once it empties. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) + && oxu->async->qh_next.qh == NULL) + timer_action(oxu, TIMER_ASYNC_OFF); + } + + if (next) { + oxu->reclaim = NULL; + start_unlink_async(oxu, next); + } +} + +/* makes sure the async qh will become idle */ +/* caller must own oxu->lock */ + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int cmd = readl(&oxu->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + assert_spin_locked(&oxu->lock); + if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)) + BUG(); +#endif + + /* stop async schedule right now? */ + if (unlikely(qh == oxu->async)) { + /* can't get here without STS_ASS set */ + if (oxu_to_hcd(oxu)->state != HC_STATE_HALT + && !oxu->reclaim) { + /* ... and CMD_IAAD clear */ + writel(cmd & ~CMD_ASE, &oxu->regs->command); + wmb(); + /* handshake later, if we need to */ + timer_action_done(oxu, TIMER_ASYNC_OFF); + } + return; + } + + qh->qh_state = QH_STATE_UNLINK; + oxu->reclaim = qh = qh_get(qh); + + prev = oxu->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + wmb(); + + if (unlikely(oxu_to_hcd(oxu)->state == HC_STATE_HALT)) { + /* if (unlikely(qh->reclaim != 0)) + * this will recurse, probably not much + */ + end_unlink_async(oxu); + return; + } + + oxu->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel(cmd, &oxu->regs->command); + (void) readl(&oxu->regs->command); + timer_action(oxu, TIMER_IAA_WATCHDOG); +} + +static void scan_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; + + if (!++(oxu->stamp)) + oxu->stamp++; + timer_action_done(oxu, TIMER_ASYNC_SHRINK); +rescan: + qh = oxu->async->qh_next.qh; + if (likely(qh != NULL)) { + do { + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list) + && qh->stamp != oxu->stamp) { + int temp; + + /* unlinks could happen here; completion + * reporting drops the lock. rescan using + * the latest schedule, but don't rescan + * qhs we already finished (no looping). + */ + qh = qh_get(qh); + qh->stamp = oxu->stamp; + temp = qh_completions(oxu, qh); + qh_put(qh); + if (temp != 0) + goto rescan; + } + + /* unlink idle entries, reducing HC PCI usage as well + * as HCD schedule-scanning costs. delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + * (plus, avoids some kind of re-activation race.) + */ + if (list_empty(&qh->qtd_list)) { + if (qh->stamp == oxu->stamp) + action = TIMER_ASYNC_SHRINK; + else if (!oxu->reclaim + && qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); + } + + qh = qh->qh_next.qh; + } while (qh); + } + if (action == TIMER_ASYNC_SHRINK) + timer_action(oxu, TIMER_ASYNC_SHRINK); +} + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow *periodic_next_shadow(union ehci_shadow *periodic, + __le32 tag) +{ + switch (tag) { + default: + case Q_TYPE_QH: + return &periodic->qh->qh_next; + } +} + +/* caller must hold oxu->lock */ +static void periodic_unlink(struct oxu_hcd *oxu, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &oxu->pshadow[frame]; + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(prev_p, Q_NEXT_TYPE(*hw_p)); + hw_p = here.hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(&here, Q_NEXT_TYPE(*hw_p)); + *hw_p = *here.hw_next; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short periodic_usecs(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe) +{ + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow *q = &oxu->pshadow[frame]; + unsigned usecs = 0; + + while (q->ptr) { + switch (Q_NEXT_TYPE(*hw_p)) { + case Q_TYPE_QH: + default: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &q->qh->hw_next; + q = &q->qh->qh_next; + break; + } + } +#ifdef DEBUG + if (usecs > 100) + oxu_err(oxu, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +static int enable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, 0, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) | CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... PSS happens later */ + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + + /* make sure ehci_work scans these */ + oxu->next_uframe = readl(&oxu->regs->frame_index) + % (oxu->periodic_size << 3); + return 0; +} + +static int disable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, STS_PSS, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) & ~CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... */ + + oxu->next_uframe = -1; + return 0; +} + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; oxu 0.96+) + */ +static int qh_link_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg(&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) { + union ehci_shadow *prev = &oxu->pshadow[i]; + __le32 *hw_p = &oxu->periodic[i]; + union ehci_shadow here = *prev; + __le32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(*hw_p); + if (type == Q_TYPE_QH) + break; + prev = periodic_next_shadow(prev, type); + hw_p = &here.qh->hw_next; + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw_next = *hw_p; + wmb(); + prev->qh = qh; + *hw_p = QH_NEXT(qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh_get(qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + /* maybe enable periodic schedule processing */ + if (!oxu->periodic_sched++) + return enable_periodic(oxu); + + return 0; +} + +static void qh_unlink_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period; + + /* FIXME: + * IF this isn't high speed + * and this qh is active in the current uframe + * (and overlay token SplitXstate is false?) + * THEN + * qh->hw_info1 |= __constant_cpu_to_le32(1 << 7 "ignore"); + */ + + /* high bandwidth, or otherwise part of every microframe */ + period = qh->period; + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) + periodic_unlink(oxu, i, qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg(&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + qh_put(qh); + + /* maybe turn off periodic schedule */ + oxu->periodic_sched--; + if (!oxu->periodic_sched) + (void) disable_periodic(oxu); +} + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned wait; + + qh_unlink_periodic(oxu, qh); + + /* simple/paranoid: always delay, expecting the HC needs to read + * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and + * expect khubd to clean up after any CSPLITs we won't issue. + * active high speed queues may need bigger delays... + */ + if (list_empty(&qh->qtd_list) + || (__constant_cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) + wait = 2; + else + wait = 55; /* worst case: 3 * 1024 */ + + udelay(wait); + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + wmb(); +} + +static int check_period(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + unsigned period, unsigned usecs) +{ + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* + * 80% periodic == 100 usec/uframe available + * convert "usecs we need" to "max already claimed" + */ + usecs = 100 - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely(period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < oxu->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < oxu->periodic_size); + } + + return 1; +} + +static int check_intr_schedule(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + const struct ehci_qh *qh, __le32 *c_maskp) +{ + int retval = -ENOSPC; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(oxu, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int status; + unsigned uframe; + __le32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + + qh_refresh(oxu, qh); + qh->hw_next = EHCI_LIST_END; + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(le32_to_cpup(&qh->hw_info2) & QH_SMASK); + status = check_intr_schedule(oxu, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + frame = qh->period - 1; + do { + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(oxu, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } while (status && frame--); + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule(oxu, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + qh->hw_info2 &= __constant_cpu_to_le32(~(QH_CMASK | QH_SMASK)); + qh->hw_info2 |= qh->period + ? cpu_to_le32(1 << uframe) + : __constant_cpu_to_le32(QH_SMASK); + qh->hw_info2 |= c_mask; + } else + oxu_dbg(oxu, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + status = qh_link_periodic(oxu, qh); +done: + return status; +} + +static int intr_submit(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + unsigned epnum; + unsigned long flags; + struct ehci_qh *qh; + int status = 0; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave(&oxu->lock, flags); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + status = -ESHUTDOWN; + goto done; + } + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD(&empty); + qh = qh_append_tds(oxu, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + status = qh_schedule(oxu, qh); + if (status != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON(qh == NULL); + + /* ... update usbfs periodic stats */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs++; + +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (status) + qtd_list_free(oxu, urb, qtd_list); + + return status; +} + +static inline int itd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "iso support is missing!\n"); + return -ENOSYS; +} + +static inline int sitd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "split iso support is missing!\n"); + return -ENOSYS; +} + +static void scan_periodic(struct oxu_hcd *oxu) +{ + unsigned frame, clock, now_uframe, mod; + unsigned modified; + + mod = oxu->periodic_size << 3; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + now_uframe = oxu->next_uframe; + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + clock = readl(&oxu->regs->frame_index); + else + clock = now_uframe + mod - 1; + clock %= mod; + + for (;;) { + union ehci_shadow q, *q_p; + __le32 type, *hw_p; + unsigned uframes; + + /* don't scan past the live uframe */ + frame = now_uframe >> 3; + if (frame == (clock >> 3)) + uframes = now_uframe & 0x07; + else { + /* safe to scan the whole frame at once */ + now_uframe |= 0x07; + uframes = 8; + } + +restart: + /* scan each element in frame's queue for completions */ + q_p = &oxu->pshadow[frame]; + hw_p = &oxu->periodic[frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(*hw_p); + modified = 0; + + while (q.ptr != NULL) { + union ehci_shadow temp; + int live; + + live = HC_IS_RUNNING(oxu_to_hcd(oxu)->state); + switch (type) { + case Q_TYPE_QH: + /* handle any completions */ + temp.qh = qh_get(q.qh); + type = Q_NEXT_TYPE(q.qh->hw_next); + q = q.qh->qh_next; + modified = qh_completions(oxu, temp.qh); + if (unlikely(list_empty(&temp.qh->qtd_list))) + intr_deschedule(oxu, temp.qh); + qh_put(temp.qh); + break; + default: + dbg("corrupt type %d frame %d shadow %p", + type, frame, q.ptr); + q.ptr = NULL; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified)) + goto restart; + } + + /* Stop when we catch up to the HC */ + + /* FIXME: this assumes we won't get lapped when + * latencies climb; that should be rare, but... + * detect it, and just go all the way around. + * FLR might help detect this case, so long as latencies + * don't exceed periodic_size msec (default 1.024 sec). + */ + + /* FIXME: likewise assumes HC doesn't halt mid-scan */ + + if (now_uframe == clock) { + unsigned now; + + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + break; + oxu->next_uframe = now_uframe; + now = readl(&oxu->regs->frame_index) % mod; + if (now_uframe == now) + break; + + /* rescan the rest of this frame, then ... */ + clock = now; + } else { + now_uframe++; + now_uframe %= mod; + } + } +} + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void ehci_turn_off_all_ports(struct oxu_hcd *oxu) +{ + int port = HCS_N_PORTS(oxu->hcs_params); + + while (port--) + writel(PORT_RWC_BITS, &oxu->regs->port_status[port]); +} + +static void ehci_port_power(struct oxu_hcd *oxu, int is_on) +{ + unsigned port; + + if (!HCS_PPC(oxu->hcs_params)) + return; + + oxu_dbg(oxu, "...power%s ports...\n", is_on ? "up" : "down"); + for (port = HCS_N_PORTS(oxu->hcs_params); port > 0; ) + (void) oxu_hub_control(oxu_to_hcd(oxu), + is_on ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port--, NULL, 0); + msleep(20); +} + +/* Called from some interrupts, timers, and so on. + * It calls driver completion functions, after dropping oxu->lock. + */ +static void ehci_work(struct oxu_hcd *oxu) +{ + timer_action_done(oxu, TIMER_IO_WATCHDOG); + if (oxu->reclaim_ready) + end_unlink_async(oxu); + + /* another CPU may drop oxu->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (oxu->scanning) + return; + oxu->scanning = 1; + scan_async(oxu); + if (oxu->next_uframe != -1) + scan_periodic(oxu); + oxu->scanning = 0; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && + (oxu->async->qh_next.ptr != NULL || + oxu->periodic_sched != 0)) + timer_action(oxu, TIMER_IO_WATCHDOG); +} + +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + /* if we need to use IAA and it's busy, defer */ + if (qh->qh_state == QH_STATE_LINKED + && oxu->reclaim + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) { + struct ehci_qh *last; + + for (last = oxu->reclaim; + last->reclaim; + last = last->reclaim) + continue; + qh->qh_state = QH_STATE_UNLINK_WAIT; + last->reclaim = qh; + + /* bypass IAA if the hc can't care */ + } else if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && oxu->reclaim) + end_unlink_async(oxu); + + /* something else might have unlinked the qh by now */ + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); +} + +/* + * USB host controller methods + */ + +static irqreturn_t oxu210_hcd_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 status, pcd_status = 0; + int bh; + + spin_lock(&oxu->lock); + + status = readl(&oxu->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + oxu_dbg(oxu, "device removed\n"); + goto dead; + } + + status &= INTR_MASK; + if (!status) { /* irq sharing? */ + spin_unlock(&oxu->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + writel(status, &oxu->regs->status); + readl(&oxu->regs->command); /* unblock posted write */ + bh = 0; + +#ifdef OXU_VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status(oxu, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + oxu->reclaim_ready = 1; + bh = 1; + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + unsigned i = HCS_N_PORTS(oxu->hcs_params); + pcd_status = status; + + /* resume root hub? */ + if (!(readl(&oxu->regs->command) & CMD_RUN)) + usb_hcd_resume_root_hub(hcd); + + while (i--) { + int pstatus = readl(&oxu->regs->port_status[i]); + + if (pstatus & PORT_OWNER) + continue; + if (!(pstatus & PORT_RESUME) + || oxu->reset_done[i] != 0) + continue; + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. + */ + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + oxu_dbg(oxu, "port %d remote wakeup\n", i + 1); + mod_timer(&hcd->rh_timer, oxu->reset_done[i]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely((status & STS_FATAL) != 0)) { + /* bogus "fatal" IRQs appear on some chips... why? */ + status = readl(&oxu->regs->status); + dbg_cmd(oxu, "fatal", readl(&oxu->regs->command)); + dbg_status(oxu, "fatal", status); + if (status & STS_HALT) { + oxu_err(oxu, "fatal error\n"); +dead: + ehci_reset(oxu); + writel(0, &oxu->regs->configured_flag); + /* generic layer kills/unlinks all urbs, then + * uses oxu_stop to clean up the rest + */ + bh = 1; + } + } + + if (bh) + ehci_work(oxu); + spin_unlock(&oxu->lock); + if (pcd_status & STS_PCD) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +static irqreturn_t oxu_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret = IRQ_HANDLED; + + u32 status = oxu_readl(hcd->regs, OXU_CHIPIRQSTATUS); + u32 enable = oxu_readl(hcd->regs, OXU_CHIPIRQEN_SET); + + /* Disable all interrupt */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_CLR, enable); + + if ((oxu->is_otg && (status & OXU_USBOTGI)) || + (!oxu->is_otg && (status & OXU_USBSPHI))) + oxu210_hcd_irq(hcd); + else + ret = IRQ_NONE; + + /* Enable all interrupt back */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_SET, enable); + + return ret; +} + +static void oxu_watchdog(unsigned long param) +{ + struct oxu_hcd *oxu = (struct oxu_hcd *) param; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + + /* lost IAA irqs wedge things badly; seen with a vt8235 */ + if (oxu->reclaim) { + u32 status = readl(&oxu->regs->status); + if (status & STS_IAA) { + oxu_vdbg(oxu, "lost IAA\n"); + writel(STS_IAA, &oxu->regs->status); + oxu->reclaim_ready = 1; + } + } + + /* stop async processing after it's idled a bit */ + if (test_bit(TIMER_ASYNC_OFF, &oxu->actions)) + start_unlink_async(oxu, oxu->async); + + /* oxu could run by timer, without IRQs ... */ + ehci_work(oxu); + + spin_unlock_irqrestore(&oxu->lock, flags); +} + +/* One-time init, only for memory state. + */ +static int oxu_hcd_init(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int retval; + u32 hcc_params; + + spin_lock_init(&oxu->lock); + + init_timer(&oxu->watchdog); + oxu->watchdog.function = oxu_watchdog; + oxu->watchdog.data = (unsigned long) oxu; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + oxu->periodic_size = DEFAULT_I_TDPS; + retval = ehci_mem_init(oxu, GFP_KERNEL); + if (retval < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_ISOC_CACHE(hcc_params)) /* full frame cache */ + oxu->i_thresh = 8; + else /* N microframes cached */ + oxu->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + oxu->reclaim = NULL; + oxu->reclaim_ready = 0; + oxu->next_uframe = -1; + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + oxu->async->qh_next.qh = NULL; + oxu->async->hw_next = QH_NEXT(oxu->async->qh_dma); + oxu->async->hw_info1 = cpu_to_le32(QH_HEAD); + oxu->async->hw_token = cpu_to_le32(QTD_STS_HALT); + oxu->async->hw_qtd_next = EHCI_LIST_END; + oxu->async->qh_state = QH_STATE_LINKED; + oxu->async->hw_alt_next = QTD_NEXT(oxu->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + oxu_dbg(oxu, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (EHCI_TUNE_FLS << 2); + } + oxu->command = temp; + + return 0; +} + +/* Called during probe() after chip reset completes. + */ +static int oxu_reset(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret; + + spin_lock_init(&oxu->mem_lock); + INIT_LIST_HEAD(&oxu->urb_list); + oxu->urb_len = 0; + + /* FIMXE */ + hcd->self.controller->dma_mask = 0UL; + + if (oxu->is_otg) { + oxu->caps = hcd->regs + OXU_OTG_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_OTG_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_SPH_MEM; + } else { + oxu->caps = hcd->regs + OXU_SPH_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_SPH_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_OTG_MEM; + } + + oxu->hcs_params = readl(&oxu->caps->hcs_params); + oxu->sbrn = 0x20; + + ret = oxu_hcd_init(hcd); + if (ret) + return ret; + + return 0; +} + +static int oxu_run(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int retval; + u32 temp, hcc_params; + + hcd->uses_new_polling = 1; + hcd->poll_rh = 0; + + /* EHCI spec section 4.1 */ + retval = ehci_reset(oxu); + if (retval != 0) { + ehci_mem_cleanup(oxu); + return retval; + } + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* hcc_params controls whether oxu->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) + writel(0, &oxu->regs->segment); + + oxu->command &= ~(CMD_LRESET | CMD_IAAD | CMD_PSE | + CMD_ASE | CMD_RESET); + oxu->command |= CMD_RUN; + writel(oxu->command, &oxu->regs->command); + dbg_cmd(oxu, "init", oxu->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + */ + hcd->state = HC_STATE_RUNNING; + writel(FLAG_CF, &oxu->regs->configured_flag); + readl(&oxu->regs->command); /* unblock posted writes */ + + temp = HC_VERSION(readl(&oxu->caps->hc_capbase)); + oxu_info(oxu, "USB %x.%x started, quasi-EHCI %x.%02x, driver %s%s\n", + ((oxu->sbrn & 0xf0)>>4), (oxu->sbrn & 0x0f), + temp >> 8, temp & 0xff, DRIVER_VERSION, + ignore_oc ? ", overcurrent ignored" : ""); + + writel(INTR_MASK, &oxu->regs->intr_enable); /* Turn On Interrupts */ + + return 0; +} + +static void oxu_stop(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + /* Turn off port power on all root hub ports. */ + ehci_port_power(oxu, 0); + + /* no more interrupts ... */ + del_timer_sync(&oxu->watchdog); + + spin_lock_irq(&oxu->lock); + if (HC_IS_RUNNING(hcd->state)) + ehci_quiesce(oxu); + + ehci_reset(oxu); + writel(0, &oxu->regs->intr_enable); + spin_unlock_irq(&oxu->lock); + + /* let companion controllers work when we aren't */ + writel(0, &oxu->regs->configured_flag); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq(&oxu->lock); + if (oxu->async) + ehci_work(oxu); + spin_unlock_irq(&oxu->lock); + ehci_mem_cleanup(oxu); + + dbg_status(oxu, "oxu_stop completed", readl(&oxu->regs->status)); +} + +/* Kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void oxu_shutdown(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + (void) ehci_halt(oxu); + ehci_turn_off_all_ports(oxu); + + /* make BIOS/etc use companion controller during reboot */ + writel(0, &oxu->regs->configured_flag); + + /* unblock posted writes */ + readl(&oxu->regs->configured_flag); +} + +/* Non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD(&qtd_list); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(oxu, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(oxu, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit(oxu, urb, mem_flags); + else + return sitd_submit(oxu, urb, mem_flags); + } +} + +/* This function is responsible for breaking URBs with big data size + * into smaller size and processing small urbs in sequence. + */ +static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int num, rem; + int transfer_buffer_length; + void *transfer_buffer; + struct urb *murb; + int i, ret; + + /* If not bulk pipe just enqueue the URB */ + if (!usb_pipebulk(urb->pipe)) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Otherwise we should verify the USB transfer buffer size! */ + transfer_buffer = urb->transfer_buffer; + transfer_buffer_length = urb->transfer_buffer_length; + + num = urb->transfer_buffer_length / 4096; + rem = urb->transfer_buffer_length % 4096; + if (rem != 0) + num++; + + /* If URB is smaller than 4096 bytes just enqueue it! */ + if (num == 1) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Ok, we have more job to do! :) */ + + for (i = 0; i < num - 1; i++) { + /* Get free micro URB poll till a free urb is recieved */ + + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = 4096; + murb->transfer_buffer = transfer_buffer + i * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 0; + + /* This loop is to guarantee urb to be processed when there's + * not enough resources at a particular time by retrying. + */ + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + } + + /* Last urb requires special handling */ + + /* Get free micro URB poll till a free urb is recieved */ + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = rem > 0 ? rem : 4096; + murb->transfer_buffer = transfer_buffer + (num - 1) * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 1; + + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + + return ret; +} + +/* Remove from hardware lists. + * Completions normally happen asynchronously + */ +static int oxu_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + unlink_async(oxu, qh); + break; + + case PIPE_INTERRUPT: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + intr_deschedule(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_IDLE: + qh_completions(oxu, qh); + break; + default: + oxu_dbg(oxu, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(hcd->state)) { + int status; + + status = qh_schedule(oxu, qh); + spin_unlock_irqrestore(&oxu->lock, flags); + + if (status != 0) { + /* shouldn't happen often, but ... + * FIXME kill those tds' urbs + */ + err("can't reschedule qh %p, err %d", + qh, status); + } + return status; + } + break; + } +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return 0; +} + +/* Bulk qh holds the data toggle */ +static void oxu_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + unsigned long flags; + struct ehci_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave(&oxu->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw_info1 == 0) { + oxu_vdbg(oxu, "iso delay\n"); + goto idle_timeout; + } + + if (!HC_IS_RUNNING(hcd->state)) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + for (tmp = oxu->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty */ + if (!tmp) + goto nogood; + unlink_async(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ +idle_timeout: + spin_unlock_irqrestore(&oxu->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (list_empty(&qh->qtd_list)) { + qh_put(qh); + break; + } + /* else FALL THROUGH */ + default: +nogood: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + oxu_err(oxu, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty(&qh->qtd_list) ? "" : "(has tds)"); + break; + } + ep->hcpriv = NULL; +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return; +} + +static int oxu_get_frame(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + return (readl(&oxu->regs->frame_index) >> 3) % + oxu->periodic_size; +} + +/* Build "status change" packet (one or two bytes) from HC registers */ +static int oxu_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp, mask, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + ports = HCS_N_PORTS(oxu->hcs_params); + if (ports > 7) { + buf[1] = 0; + retval++; + } + + /* Some boards (mostly VIA?) report bogus overcurrent indications, + * causing massive log spam unless we completely ignore them. It + * may be relevant that VIA VT8235 controlers, where PORT_POWER is + * always set, seem to clear PORT_OCC and PORT_CSC when writing to + * PORT_POWER; that's surprising, but maybe within-spec. + */ + if (!ignore_oc) + mask = PORT_CSC | PORT_PEC | PORT_OCC; + else + mask = PORT_CSC | PORT_PEC; + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave(&oxu->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl(&oxu->regs->port_status[i]); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if (!(temp & PORT_CONNECT)) + oxu->reset_done[i] = 0; + if ((temp & mask) != 0 || ((temp & PORT_RESUME) != 0 && + time_after_eq(jiffies, oxu->reset_done[i]))) { + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + status = STS_PCD; + } + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore(&oxu->lock, flags); + return status ? retval : 0; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int oxu_port_speed(struct oxu_hcd *oxu, + unsigned int portsc) +{ + switch ((portsc >> 26) & 3) { + case 0: + return 0; + case 1: + return 1 << USB_PORT_FEAT_LOWSPEED; + case 2: + default: + return 1 << USB_PORT_FEAT_HIGHSPEED; + } +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +static int oxu_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ports = HCS_N_PORTS(oxu->hcs_params); + u32 __iomem *status_reg = &oxu->regs->port_status[wIndex - 1]; + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&oxu->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_FEAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel(temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + writel((temp & ~PORT_RWC_BITS) | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + writel(temp | PORT_RESUME, status_reg); + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel((temp & ~PORT_RWC_BITS) | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel((temp & ~PORT_RWC_BITS) | PORT_OCC, status_reg); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor(oxu, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = readl(status_reg); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + if (temp & PORT_PEC) + status |= 1 << USB_PORT_FEAT_C_ENABLE; + if ((temp & PORT_OCC) && !ignore_oc) + status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!oxu->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&oxu_to_hcd(oxu)->rh_timer, + oxu->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_SUSPEND; + oxu->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = readl(status_reg); + writel(temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + retval = handshake(oxu, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + oxu_err(oxu, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_RESET; + oxu->reset_done[wIndex] = 0; + + /* force reset to complete */ + writel(temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(oxu, status_reg, + PORT_RESET, 0, 750); + if (retval != 0) { + oxu_err(oxu, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(oxu, wIndex, status_reg, + readl(status_reg)); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &oxu->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + writel(temp, status_reg); + oxu_dbg(oxu, "port %d --> companion\n", wIndex + 1); + temp = readl(status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + /* status may be from integrated TT */ + status |= oxu_port_speed(oxu, temp); + } + if (temp & PORT_PE) + status |= 1 << USB_PORT_FEAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= 1 << USB_PORT_FEAT_SUSPEND; + if (temp & PORT_OC) + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + if (temp & PORT_RESET) + status |= 1 << USB_PORT_FEAT_RESET; + if (temp & PORT_POWER) + status |= 1 << USB_PORT_FEAT_POWER; + +#ifndef OXU_VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port(oxu, "GetStatus", wIndex + 1, temp); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + if (temp & PORT_OWNER) + break; + + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + if (device_may_wakeup(&hcd->self.root_hub->dev)) + temp |= PORT_WAKE_BITS; + writel(temp | PORT_SUSPEND, status_reg); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp | PORT_POWER, status_reg); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + oxu_vdbg(oxu, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(50); + writel(temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + ehci_quiesce(oxu); + ehci_halt(oxu); + temp |= selector << 16; + writel(temp, status_reg); + break; + + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&oxu->lock, flags); + return retval; +} + +#ifdef CONFIG_PM + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int port; + int mask; + + oxu_dbg(oxu, "suspend root hub\n"); + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + + port = HCS_N_PORTS(oxu->hcs_params); + spin_lock_irq(&oxu->lock); + + /* stop schedules, clean any completed work */ + if (HC_IS_RUNNING(hcd->state)) { + ehci_quiesce(oxu); + hcd->state = HC_STATE_QUIESCING; + } + oxu->command = readl(&oxu->regs->command); + if (oxu->reclaim) + oxu->reclaim_ready = 1; + ehci_work(oxu); + + /* Unlike other USB host controller types, EHCI doesn't have + * any notion of "global" or bus-wide suspend. The driver has + * to manually suspend all the active unsuspended ports, and + * then manually resume them in the bus_resume() routine. + */ + oxu->bus_suspended = 0; + while (port--) { + u32 __iomem *reg = &oxu->regs->port_status[port]; + u32 t1 = readl(reg) & ~PORT_RWC_BITS; + u32 t2 = t1; + + /* keep track of which ports we suspend */ + if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && + !(t1 & PORT_SUSPEND)) { + t2 |= PORT_SUSPEND; + set_bit(port, &oxu->bus_suspended); + } + + /* enable remote wakeup on all ports */ + if (device_may_wakeup(&hcd->self.root_hub->dev)) + t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E; + else + t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); + + if (t1 != t2) { + oxu_vdbg(oxu, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + writel(t2, reg); + } + } + + /* turn off now-idle HC */ + del_timer_sync(&oxu->watchdog); + ehci_halt(oxu); + hcd->state = HC_STATE_SUSPENDED; + + /* allow remote wakeup */ + mask = INTR_MASK; + if (!device_may_wakeup(&hcd->self.root_hub->dev)) + mask &= ~STS_PCD; + writel(mask, &oxu->regs->intr_enable); + readl(&oxu->regs->intr_enable); + + oxu->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irq(&oxu->lock); + return 0; +} + +/* Caller has locked the root hub, and should reset/reinit on error */ +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int i; + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + spin_lock_irq(&oxu->lock); + + /* Ideally and we've got a real resume here, and no port's power + * was lost. (For PCI, that means Vaux was maintained.) But we + * could instead be restoring a swsusp snapshot -- so that BIOS was + * the last user of the controller, not reset/pm hardware keeping + * state we gave to it. + */ + temp = readl(&oxu->regs->intr_enable); + oxu_dbg(oxu, "resume root hub%s\n", temp ? "" : " after power loss"); + + /* at least some APM implementations will try to deliver + * IRQs right away, so delay them until we're ready. + */ + writel(0, &oxu->regs->intr_enable); + + /* re-init operational registers */ + writel(0, &oxu->regs->segment); + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* restore CMD_RUN, framelist size, and irq threshold */ + writel(oxu->command, &oxu->regs->command); + + /* Some controller/firmware combinations need a delay during which + * they set up the port statuses. See Bugzilla #8190. */ + mdelay(8); + + /* manually resume the ports we suspended during bus_suspend() */ + i = HCS_N_PORTS(oxu->hcs_params); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + temp &= ~(PORT_RWC_BITS + | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + temp |= PORT_RESUME; + } + writel(temp, &oxu->regs->port_status[i]); + } + i = HCS_N_PORTS(oxu->hcs_params); + mdelay(20); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + temp &= ~(PORT_RWC_BITS | PORT_RESUME); + writel(temp, &oxu->regs->port_status[i]); + oxu_vdbg(oxu, "resumed port %d\n", i + 1); + } + } + (void) readl(&oxu->regs->command); + + /* maybe re-activate the schedule(s) */ + temp = 0; + if (oxu->async->qh_next.qh) + temp |= CMD_ASE; + if (oxu->periodic_sched) + temp |= CMD_PSE; + if (temp) { + oxu->command |= temp; + writel(oxu->command, &oxu->regs->command); + } + + oxu->next_statechange = jiffies + msecs_to_jiffies(5); + hcd->state = HC_STATE_RUNNING; + + /* Now we can safely re-enable irqs */ + writel(INTR_MASK, &oxu->regs->intr_enable); + + spin_unlock_irq(&oxu->lock); + return 0; +} + +#else + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + return 0; +} + +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct hc_driver oxu_hc_driver = { + .description = "oxu210hp_hcd", + .product_desc = "oxu210hp HCD", + .hcd_priv_size = sizeof(struct oxu_hcd), + + /* + * Generic hardware linkage + */ + .irq = oxu_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * Basic lifecycle operations + */ + .reset = oxu_reset, + .start = oxu_run, + .stop = oxu_stop, + .shutdown = oxu_shutdown, + + /* + * Managing i/o requests and associated device resources + */ + .urb_enqueue = oxu_urb_enqueue, + .urb_dequeue = oxu_urb_dequeue, + .endpoint_disable = oxu_endpoint_disable, + + /* + * Scheduling support + */ + .get_frame_number = oxu_get_frame, + + /* + * Root hub support + */ + .hub_status_data = oxu_hub_status_data, + .hub_control = oxu_hub_control, + .bus_suspend = oxu_bus_suspend, + .bus_resume = oxu_bus_resume, +}; + +/* + * Module stuff + */ + +static void oxu_configuration(struct platform_device *pdev, void *base) +{ + u32 tmp; + + /* Initialize top level registers. + * First write ever + */ + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + oxu_writel(base, OXU_SOFTRESET, OXU_SRESET); + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + + tmp = oxu_readl(base, OXU_PIOBURSTREADCTRL); + oxu_writel(base, OXU_PIOBURSTREADCTRL, tmp | 0x0040); + + oxu_writel(base, OXU_ASO, OXU_SPHPOEN | OXU_OVRCCURPUPDEN | + OXU_COMPARATOR | OXU_ASO_OP); + + tmp = oxu_readl(base, OXU_CLKCTRL_SET); + oxu_writel(base, OXU_CLKCTRL_SET, tmp | OXU_SYSCLKEN | OXU_USBOTGCLKEN); + + /* Clear all top interrupt enable */ + oxu_writel(base, OXU_CHIPIRQEN_CLR, 0xff); + + /* Clear all top interrupt status */ + oxu_writel(base, OXU_CHIPIRQSTATUS, 0xff); + + /* Enable all needed top interrupt except OTG SPH core */ + oxu_writel(base, OXU_CHIPIRQEN_SET, OXU_USBSPHLPWUI | OXU_USBOTGLPWUI); +} + +static int oxu_verify_id(struct platform_device *pdev, void *base) +{ + u32 id; + char *bo[] = { + "reserved", + "128-pin LQFP", + "84-pin TFBGA", + "reserved", + }; + + /* Read controller signature register to find a match */ + id = oxu_readl(base, OXU_DEVICEID); + dev_info(&pdev->dev, "device ID %x\n", id); + if ((id & OXU_REV_MASK) != (OXU_REV_2100 << OXU_REV_SHIFT)) + return -1; + + dev_info(&pdev->dev, "found device %x %s (%04x:%04x)\n", + id >> OXU_REV_SHIFT, + bo[(id & OXU_BO_MASK) >> OXU_BO_SHIFT], + (id & OXU_MAJ_REV_MASK) >> OXU_MAJ_REV_SHIFT, + (id & OXU_MIN_REV_MASK) >> OXU_MIN_REV_SHIFT); + + return 0; +} + +static const struct hc_driver oxu_hc_driver; +static struct usb_hcd *oxu_create(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq, int otg) +{ + struct device *dev = &pdev->dev; + + struct usb_hcd *hcd; + struct oxu_hcd *oxu; + int ret; + + /* Set endian mode and host mode */ + oxu_writel(base + (otg ? OXU_OTG_CORE_OFFSET : OXU_SPH_CORE_OFFSET), + OXU_USBMODE, + OXU_CM_HOST_ONLY | OXU_ES_LITTLE | OXU_VBPS); + + hcd = usb_create_hcd(&oxu_hc_driver, dev, + otg ? "oxu210hp_otg" : "oxu210hp_sph"); + if (!hcd) + return ERR_PTR(-ENOMEM); + + hcd->rsrc_start = memstart; + hcd->rsrc_len = memlen; + hcd->regs = base; + hcd->irq = irq; + hcd->state = HC_STATE_HALT; + + oxu = hcd_to_oxu(hcd); + oxu->is_otg = otg; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret < 0) + return ERR_PTR(ret); + + return hcd; +} + +static int oxu_init(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + struct usb_hcd *hcd; + int ret; + + /* First time configuration at start up */ + oxu_configuration(pdev, base); + + ret = oxu_verify_id(pdev, base); + if (ret) { + dev_err(&pdev->dev, "no devices found!\n"); + return -ENODEV; + } + + /* Create the OTG controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 1); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create OTG controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_otg; + } + info->hcd[0] = hcd; + + /* Create the SPH host controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 0); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create SPH controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_sph; + } + info->hcd[1] = hcd; + + oxu_writel(base, OXU_CHIPIRQEN_SET, + oxu_readl(base, OXU_CHIPIRQEN_SET) | 3); + + return 0; + +error_create_sph: + usb_remove_hcd(info->hcd[0]); + usb_put_hcd(info->hcd[0]); + +error_create_otg: + return ret; +} + +static int oxu_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + void *base; + unsigned long memstart, memlen; + int irq, ret; + struct oxu_info *info; + + if (usb_disabled()) + return -ENODEV; + + /* + * Get the platform resources + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "no IRQ! Check %s setup!\n", pdev->dev.bus_id); + return -ENODEV; + } + irq = res->start; + dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no registers address! Check %s setup!\n", + pdev->dev.bus_id); + return -ENODEV; + } + memstart = res->start; + memlen = res->end - res->start + 1; + dev_dbg(&pdev->dev, "MEM resource %lx-%lx\n", memstart, memlen); + if (!request_mem_region(memstart, memlen, + oxu_hc_driver.description)) { + dev_dbg(&pdev->dev, "memory area already in use\n"); + return -EBUSY; + } + + ret = set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (ret) { + dev_err(&pdev->dev, "error setting irq type\n"); + ret = -EFAULT; + goto error_set_irq_type; + } + + base = ioremap(memstart, memlen); + if (!base) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + ret = -EFAULT; + goto error_ioremap; + } + + /* Allocate a driver data struct to hold useful info for both + * SPH & OTG devices + */ + info = kzalloc(sizeof(struct oxu_info), GFP_KERNEL); + if (!info) { + dev_dbg(&pdev->dev, "error allocating memory\n"); + ret = -EFAULT; + goto error_alloc; + } + platform_set_drvdata(pdev, info); + + ret = oxu_init(pdev, memstart, memlen, base, irq); + if (ret < 0) { + dev_dbg(&pdev->dev, "cannot init USB devices\n"); + goto error_init; + } + + dev_info(&pdev->dev, "devices enabled and running\n"); + platform_set_drvdata(pdev, info); + + return 0; + +error_init: + kfree(info); + platform_set_drvdata(pdev, NULL); + +error_alloc: + iounmap(base); + +error_set_irq_type: +error_ioremap: + release_mem_region(memstart, memlen); + + dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, ret); + return ret; +} + +static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) +{ + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +static int oxu_drv_remove(struct platform_device *pdev) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + unsigned long memstart = info->hcd[0]->rsrc_start, + memlen = info->hcd[0]->rsrc_len; + void *base = info->hcd[0]->regs; + + oxu_remove(pdev, info->hcd[0]); + oxu_remove(pdev, info->hcd[1]); + + iounmap(base); + release_mem_region(memstart, memlen); + + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void oxu_drv_shutdown(struct platform_device *pdev) +{ + oxu_drv_remove(pdev); +} + +#if 0 +/* FIXME: TODO */ +static int oxu_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} + +static int oxu_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} +#else +#define oxu_drv_suspend NULL +#define oxu_drv_resume NULL +#endif + +static struct platform_driver oxu_driver = { + .probe = oxu_drv_probe, + .remove = oxu_drv_remove, + .shutdown = oxu_drv_shutdown, + .suspend = oxu_drv_suspend, + .resume = oxu_drv_resume, + .driver = { + .name = "oxu210hp-hcd", + .bus = &platform_bus_type + } +}; + +static int __init oxu_module_init(void) +{ + int retval = 0; + + retval = platform_driver_register(&oxu_driver); + if (retval < 0) + return retval; + + return retval; +} + +static void __exit oxu_module_cleanup(void) +{ + platform_driver_unregister(&oxu_driver); +} + +module_init(oxu_module_init); +module_exit(oxu_module_cleanup); + +MODULE_DESCRIPTION("Oxford OXU210HP HCD driver - ver. " DRIVER_VERSION); +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h new file mode 100644 index 000000000000..8910e271cc7d --- /dev/null +++ b/drivers/usb/host/oxu210hp.h @@ -0,0 +1,447 @@ +/* + * Host interface registers + */ + +#define OXU_DEVICEID 0x00 + #define OXU_REV_MASK 0xffff0000 + #define OXU_REV_SHIFT 16 + #define OXU_REV_2100 0x2100 + #define OXU_BO_SHIFT 8 + #define OXU_BO_MASK (0x3 << OXU_BO_SHIFT) + #define OXU_MAJ_REV_SHIFT 4 + #define OXU_MAJ_REV_MASK (0xf << OXU_MAJ_REV_SHIFT) + #define OXU_MIN_REV_SHIFT 0 + #define OXU_MIN_REV_MASK (0xf << OXU_MIN_REV_SHIFT) +#define OXU_HOSTIFCONFIG 0x04 +#define OXU_SOFTRESET 0x08 + #define OXU_SRESET (1 << 0) + +#define OXU_PIOBURSTREADCTRL 0x0C + +#define OXU_CHIPIRQSTATUS 0x10 +#define OXU_CHIPIRQEN_SET 0x14 +#define OXU_CHIPIRQEN_CLR 0x18 + #define OXU_USBSPHLPWUI 0x00000080 + #define OXU_USBOTGLPWUI 0x00000040 + #define OXU_USBSPHI 0x00000002 + #define OXU_USBOTGI 0x00000001 + +#define OXU_CLKCTRL_SET 0x1C + #define OXU_SYSCLKEN 0x00000008 + #define OXU_USBSPHCLKEN 0x00000002 + #define OXU_USBOTGCLKEN 0x00000001 + +#define OXU_ASO 0x68 + #define OXU_SPHPOEN 0x00000100 + #define OXU_OVRCCURPUPDEN 0x00000800 + #define OXU_ASO_OP (1 << 10) + #define OXU_COMPARATOR 0x000004000 + +#define OXU_USBMODE 0x1A8 + #define OXU_VBPS 0x00000020 + #define OXU_ES_LITTLE 0x00000000 + #define OXU_CM_HOST_ONLY 0x00000003 + +/* + * Proper EHCI structs & defines + */ + +/* Magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +struct oxu_hcd; + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + */ + u32 hc_capbase; +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved[9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status[0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +#define PORT_LED_OFF (0<<14) +#define PORT_LED_AMBER (1<<14) +#define PORT_LED_GREEN (2<<14) +#define PORT_LED_MASK (3<<14) +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) +} __attribute__ ((packed)); + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct ehci_dbg_port { + u32 control; +#define DBGP_OWNER (1<<30) +#define DBGP_ENABLED (1<<28) +#define DBGP_DONE (1<<16) +#define DBGP_INUSE (1<<10) +#define DBGP_ERRCODE(x) (((x)>>7)&0x07) +# define DBGP_ERR_BAD 1 +# define DBGP_ERR_SIGNAL 2 +#define DBGP_ERROR (1<<6) +#define DBGP_GO (1<<5) +#define DBGP_OUT (1<<4) +#define DBGP_LEN(x) (((x)>>0)&0x0f) + u32 pids; +#define DBGP_PID_GET(x) (((x)>>16)&0xff) +#define DBGP_PID_SET(data, tok) (((data)<<8)|(tok)) + u32 data03; + u32 data47; + u32 address; +#define DBGP_EPADDR(dev, ep) (((dev)<<8)|(ep)) +} __attribute__ ((packed)); + + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.5.1 */ + __le32 hw_alt_next; /* see EHCI 3.5.2 */ + __le32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + __le32 hw_buf[5]; /* see EHCI 3.5.4 */ + __le32 hw_buf_hi[5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ + + u32 qtd_buffer_len; + void *buffer; + dma_addr_t buffer_dma; + void *transfer_buffer; + void *transfer_dma; +} __attribute__ ((aligned(32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK __constant_cpu_to_le32 (~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) + +/* Type tag from {qh, itd, sitd, fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + __le32 *hw_next; /* (all types) */ + void *ptr; +}; + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.6.1 */ + __le32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + __le32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + __le32 hw_qtd_next; + __le32 hw_alt_next; + __le32 hw_token; + __le32 hw_buf[5]; + __le32 hw_buf_hi[5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct ehci_qtd *dummy; + struct ehci_qh *reclaim; /* next to reclaim */ + + struct oxu_hcd *oxu; + struct kref kref; + unsigned stamp; + + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + struct usb_device *dev; /* access to TT */ +} __attribute__ ((aligned(32))); + +/* + * Proper OXU210HP structs + */ + +#define OXU_OTG_CORE_OFFSET 0x00400 +#define OXU_OTG_CAP_OFFSET (OXU_OTG_CORE_OFFSET + 0x100) +#define OXU_SPH_CORE_OFFSET 0x00800 +#define OXU_SPH_CAP_OFFSET (OXU_SPH_CORE_OFFSET + 0x100) + +#define OXU_OTG_MEM 0xE000 +#define OXU_SPH_MEM 0x16000 + +/* Only how many elements & element structure are specifies here. */ +/* 2 host controllers are enabled - total size <= 28 kbytes */ +#define DEFAULT_I_TDPS 1024 +#define QHEAD_NUM 16 +#define QTD_NUM 32 +#define SITD_NUM 8 +#define MURB_NUM 8 + +#define BUFFER_NUM 8 +#define BUFFER_SIZE 512 + +struct oxu_info { + struct usb_hcd *hcd[2]; +}; + +struct oxu_buf { + u8 buffer[BUFFER_SIZE]; +} __attribute__ ((aligned(BUFFER_SIZE))); + +struct oxu_onchip_mem { + struct oxu_buf db_pool[BUFFER_NUM]; + + u32 frame_list[DEFAULT_I_TDPS]; + struct ehci_qh qh_pool[QHEAD_NUM]; + struct ehci_qtd qtd_pool[QTD_NUM]; +} __attribute__ ((aligned(4 << 10))); + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct oxu_murb { + struct urb urb; + struct urb *main; + u8 last; +}; + +struct oxu_hcd { /* one per controller */ + unsigned int is_otg:1; + + u8 qh_used[QHEAD_NUM]; + u8 qtd_used[QTD_NUM]; + u8 db_used[BUFFER_NUM]; + u8 murb_used[MURB_NUM]; + + struct oxu_onchip_mem __iomem *mem; + spinlock_t mem_lock; + + struct timer_list urb_timer; + + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + unsigned reclaim_ready:1; + unsigned scanning:1; + + /* periodic schedule support */ + unsigned periodic_size; + __le32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_uframe; /* scan periodic, start here */ + unsigned periodic_sched; /* periodic activity count */ + + /* per root hub port */ + unsigned long reset_done[EHCI_MAX_ROOT_PORTS]; + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + * already suspended at the + * start of a bus suspend + */ + unsigned long companion_ports;/* which ports are dedicated + * to the companion controller + */ + + struct timer_list watchdog; + unsigned long actions; + unsigned stamp; + unsigned long next_statechange; + u32 command; + + /* SILICON QUIRKS */ + struct list_head urb_list; /* this is the head to urb + * queue that didn't get enough + * resources + */ + struct oxu_murb *murb_pool; /* murb per split big urb */ + unsigned urb_len; + + u8 sbrn; /* packed release number */ +}; + +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ +#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ +#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ + +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +#include -- cgit v1.2.3 From d859bffc662482f06ba5603e6f592de4e18cf135 Mon Sep 17 00:00:00 2001 From: Steven Noonan Date: Wed, 5 Nov 2008 12:41:24 -0800 Subject: USB: EHCI pci-quirks.c: don't wait so long for BIOS handoff Instead of waiting a painful 5000ms, quirk_usb_disable_ehci() now does a 1000ms loop to wait for the BIOS to acknowledge the handoff. The five second delay is really quite irritating to have to deal with every boot up, and I very seriously doubt any non-broken bios takes more than a second to do the actual handoff. Signed-off-by: Steven Noonan Cc: Alan Stern Cc: David Brownell Cc: Jeff Garzik Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/pci-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 15e4f1d92c79..75b69847918e 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -271,7 +271,7 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) /* if boot firmware now owns EHCI, spin till * it hands it over. */ - msec = 5000; + msec = 1000; while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { tried_handoff = 1; msleep(10); -- cgit v1.2.3 From 796bcae7361c28cf825780f6f1aac9dd3411394e Mon Sep 17 00:00:00 2001 From: Vitaly Bordug Date: Sun, 9 Nov 2008 19:43:30 +0100 Subject: USB: powerpc: Workaround for the PPC440EPX USBH_23 errata [take 3] A published errata for ppc440epx states, that when running Linux with both EHCI and OHCI modules loaded, the EHCI module experiences a fatal error when a high-speed device is connected to the USB2.0, and functions normally if OHCI module is not loaded. There used to be recommendation to use only hi-speed or full-speed devices with specific conditions, when respective module was unloaded. Later, it was observed that ohci suspend is enough to keep things going, and it was turned into workaround, as explained below. Quote from original descriprion: The 440EPx USB 2.0 Host controller is an EHCI compliant controller. In USB 2.0 Host controllers, each EHCI controller has one or more companion controllers, which may be OHCI or UHCI. An USB 2.0 Host controller will contain one or more ports. For each port, only one of the controllers is connected at any one time. In the 440EPx, there is only one OHCI companion controller, and only one USB 2.0 Host port. All ports on an USB 2.0 controller default to the companion controller. If you load only an ohci driver, it will have control of the ports and any deviceplugged in will operate, although high speed devices will be forced to operate at full speed. When an ehci driver is loaded, it explicitly takes control of the ports. If there is a device connected, and / or every time there is a new device connected, the ehci driver determines if the device is high speed or not. If it is high speed, the driver retains control of the port. If it is not, the driver explicitly gives the companion controller control of the port. The is a software workaround that uses Initial version of the software workaround was posted to linux-usb-devel: http://www.mail-archive.com/linux-usb-devel@lists.sourceforge.net/msg54019.html and later available from amcc.com: http://www.amcc.com/Embedded/Downloads/download.html?cat=1&family=15&ins=2 The patch below is generally based on the latter, but reworked to powerpc/of_device USB drivers, and uses a few devicetree inquiries to get rid of (some) hardcoded defines. Signed-off-by: Vitaly Bordug Signed-off-by: Stefan Roese Cc: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 9 ++++++++- drivers/usb/host/ehci-ppc-of.c | 45 +++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/ehci.h | 34 +++++++++++++++++++++++++++++++ drivers/usb/host/ohci-ppc-of.c | 25 +++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 38650707dfe0..97a53a48a3d8 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -434,8 +434,15 @@ static int check_reset_complete ( port_status &= ~PORT_RWC_BITS; ehci_writel(ehci, port_status, status_reg); - } else + /* ensure 440EPX ohci controller state is operational */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 1); + } else { ehci_dbg (ehci, "port %d high speed\n", index + 1); + /* ensure 440EPx ohci controller state is suspended */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 0); + } return port_status; } diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index b018deed2e8f..ef732b704f53 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -107,11 +107,13 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) { struct device_node *dn = op->node; struct usb_hcd *hcd; - struct ehci_hcd *ehci; + struct ehci_hcd *ehci = NULL; struct resource res; int irq; int rv; + struct device_node *np; + if (usb_disabled()) return -ENODEV; @@ -149,6 +151,20 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) } ehci = hcd_to_ehci(hcd); + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + /* claim we really affected by usb23 erratum */ + if (!of_address_to_resource(np, 0, &res)) + ehci->ohci_hcctrl_reg = ioremap(res.start + + OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + if (!ehci->ohci_hcctrl_reg) { + pr_debug(__FILE__ ": ioremap for ohci hcctrl failed\n"); + } else { + ehci->has_amcc_usb23 = 1; + } + } if (of_get_property(dn, "big-endian", NULL)) { ehci->big_endian_mmio = 1; @@ -181,6 +197,9 @@ err_ioremap: irq_dispose_mapping(irq); err_irq: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + if (ehci->has_amcc_usb23) + iounmap(ehci->ohci_hcctrl_reg); err_rmr: usb_put_hcd(hcd); @@ -191,6 +210,11 @@ err_rmr: static int ehci_hcd_ppc_of_remove(struct of_device *op) { struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + struct device_node *np; + struct resource res; + dev_set_drvdata(&op->dev, NULL); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); @@ -201,6 +225,25 @@ static int ehci_hcd_ppc_of_remove(struct of_device *op) irq_dispose_mapping(hcd->irq); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + /* use request_mem_region to test if the ohci driver is loaded. if so + * ensure the ohci core is operational. + */ + if (ehci->has_amcc_usb23) { + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) + if (!request_mem_region(res.start, + 0x4, hcd_name)) + set_ohci_hcfs(ehci, 1); + else + release_mem_region(res.start, 0x4); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + of_node_put(np); + } + + iounmap(ehci->ohci_hcctrl_reg); + } usb_put_hcd(hcd); return 0; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index c7d4b5a06bdb..fb7054ccf4fc 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -120,6 +120,16 @@ struct ehci_hcd { /* one per controller */ unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; + unsigned has_amcc_usb23:1; + + /* required for usb32 quirk */ + #define OHCI_CTRL_HCFS (3 << 6) + #define OHCI_USB_OPER (2 << 6) + #define OHCI_USB_SUSPEND (3 << 6) + + #define OHCI_HCCTRL_OFFSET 0x4 + #define OHCI_HCCTRL_LEN 0x4 + __hc32 *ohci_hcctrl_reg; u8 sbrn; /* packed release number */ @@ -636,6 +646,30 @@ static inline void ehci_writel(const struct ehci_hcd *ehci, #endif } +/* + * On certain ppc-44x SoC there is a HW issue, that could only worked around with + * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. + * Other common bits are dependant on has_amcc_usb23 quirk flag. + */ +#ifdef CONFIG_44x +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ + u32 hc_control; + + hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS); + if (operational) + hc_control |= OHCI_USB_OPER; + else + hc_control |= OHCI_USB_SUSPEND; + + writel_be(hc_control, ehci->ohci_hcctrl_reg); + (void) readl_be(ehci->ohci_hcctrl_reg); +} +#else +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ } +#endif + /*-------------------------------------------------------------------------*/ /* diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 7ac53264ead3..68a301710297 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -91,6 +91,7 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) int rv; int is_bigendian; + struct device_node *np; if (usb_disabled()) return -ENODEV; @@ -147,6 +148,30 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) if (rv == 0) return 0; + /* by now, 440epx is known to show usb_23 erratum */ + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx"); + + /* Work around - At this point ohci_run has executed, the + * controller is running, everything, the root ports, etc., is + * set up. If the ehci driver is loaded, put the ohci core in + * the suspended state. The ehci driver will bring it out of + * suspended state when / if a non-high speed USB device is + * attached to the USB Host port. If the ehci driver is not + * loaded, do nothing. request_mem_region is used to test if + * the ehci driver is loaded. + */ + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) { + if (!request_mem_region(res.start, 0x4, hcd_name)) { + writel_be((readl_be(&ohci->regs->control) | + OHCI_USB_SUSPEND), &ohci->regs->control); + (void) readl_be(&ohci->regs->control); + } else + release_mem_region(res.start, 0x4); + } else + pr_debug(__FILE__ ": cannot get ehci offset from fdt\n"); + } + iounmap(hcd->regs); err_ioremap: irq_dispose_mapping(irq); -- cgit v1.2.3 From 427c4f333474f5447f62387c1fb060e586c1a781 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Fri, 7 Nov 2008 01:52:53 +0100 Subject: usb: struct device - replace bus_id with dev_name(), dev_set_name() Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-tmio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index f9f134af0bd1..8dabe8e31d8c 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -201,7 +201,7 @@ static int __devinit ohci_hcd_tmio_drv_probe(struct platform_device *dev) if (!cell) return -EINVAL; - hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev->dev.bus_id); + hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev)); if (!hcd) { ret = -ENOMEM; goto err_usb_create_hcd; -- cgit v1.2.3 From 74c71ebd8d7c7a513022851a02bb52b9fa7e0dcb Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Fri, 7 Nov 2008 01:47:15 +0100 Subject: oxu210hp-hcd.c: struct device - replace bus_id with dev_name(), dev_set_name() Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/oxu210hp-hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 251123c29d81..75548f7c716b 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -3825,7 +3825,7 @@ static int oxu_drv_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(&pdev->dev, - "no IRQ! Check %s setup!\n", pdev->dev.bus_id); + "no IRQ! Check %s setup!\n", dev_name(&pdev->dev)); return -ENODEV; } irq = res->start; @@ -3834,7 +3834,7 @@ static int oxu_drv_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no registers address! Check %s setup!\n", - pdev->dev.bus_id); + dev_name(&pdev->dev)); return -ENODEV; } memstart = res->start; @@ -3893,7 +3893,7 @@ error_set_irq_type: error_ioremap: release_mem_region(memstart, memlen); - dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, ret); + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret); return ret; } -- cgit v1.2.3 From 3a407e7391767898027eb7030c41c78ebc7a785d Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 13 Nov 2008 18:57:53 +0100 Subject: USB: Convert ohci-pnx4008 to a new-style i2c driver The legacy i2c binding model will go away soon, convert ohci-pnx4008 to use the new binding model instead. Signed-off-by: Jean Delvare Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-pnx4008.c | 85 +++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 49 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c index e306ca6aef3d..100bf3d8437c 100644 --- a/drivers/usb/host/ohci-pnx4008.c +++ b/drivers/usb/host/ohci-pnx4008.c @@ -106,65 +106,34 @@ extern int ocpi_enable(void); static struct clk *usb_clk; -static int isp1301_probe(struct i2c_adapter *adap); -static int isp1301_detach(struct i2c_client *client); - static const unsigned short normal_i2c[] = { ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END }; -static const unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; - -static struct i2c_client_address_data addr_data = { - .normal_i2c = normal_i2c, - .probe = dummy_i2c_addrlist, - .ignore = dummy_i2c_addrlist, -}; - -struct i2c_driver isp1301_driver = { - .driver = { - .name = "isp1301_pnx", - }, - .attach_adapter = isp1301_probe, - .detach_client = isp1301_detach, -}; -static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind) +static int isp1301_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - struct i2c_client *c; - int err; - - c = kzalloc(sizeof(*c), GFP_KERNEL); - if (!c) - return -ENOMEM; - - strlcpy(c->name, "isp1301_pnx", I2C_NAME_SIZE); - c->flags = 0; - c->addr = addr; - c->adapter = adap; - c->driver = &isp1301_driver; - - err = i2c_attach_client(c); - if (err) { - kfree(c); - return err; - } - - isp1301_i2c_client = c; - return 0; } -static int isp1301_probe(struct i2c_adapter *adap) +static int isp1301_remove(struct i2c_client *client) { - return i2c_probe(adap, &addr_data, isp1301_attach); -} - -static int isp1301_detach(struct i2c_client *client) -{ - i2c_detach_client(client); - kfree(isp1301_i2c_client); return 0; } +const struct i2c_device_id isp1301_id[] = { + { "isp1301_pnx", 0 }, + { } +}; + +struct i2c_driver isp1301_driver = { + .driver = { + .name = "isp1301_pnx", + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + static void i2c_write(u8 buf, u8 subaddr) { char tmpbuf[2]; @@ -328,6 +297,8 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) struct usb_hcd *hcd = 0; struct ohci_hcd *ohci; const struct hc_driver *driver = &ohci_pnx4008_hc_driver; + struct i2c_adapter *i2c_adap; + struct i2c_board_info i2c_info; int ret = 0, irq; @@ -351,9 +322,20 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) ret = i2c_add_driver(&isp1301_driver); if (ret < 0) { - err("failed to connect I2C to ISP1301 USB Transceiver"); + err("failed to add ISP1301 driver"); goto out; } + i2c_adap = i2c_get_adapter(2); + memset(&i2c_info, 0, sizeof(struct i2c_board_info)); + strlcpy(i2c_info.name, "isp1301_pnx", I2C_NAME_SIZE); + isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, + normal_i2c); + i2c_put_adapter(i2c_adap); + if (!isp1301_i2c_client) { + err("failed to connect I2C to ISP1301 USB Transceiver"); + ret = -ENODEV; + goto out_i2c_driver; + } isp1301_configure(); @@ -429,6 +411,9 @@ out3: out2: clk_put(usb_clk); out1: + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; +out_i2c_driver: i2c_del_driver(&isp1301_driver); out: return ret; @@ -445,6 +430,8 @@ static int usb_hcd_pnx4008_remove(struct platform_device *pdev) pnx4008_unset_usb_bits(); clk_disable(usb_clk); clk_put(usb_clk); + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; i2c_del_driver(&isp1301_driver); platform_set_drvdata(pdev, NULL); -- cgit v1.2.3 From 0a2e5b9b8a3d205b565dec18d6fe39ef1aed75cc Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Thu, 13 Nov 2008 15:46:37 +0900 Subject: USB: m66592 and r8a66597 resource changes Use the more common platform_get_resource() together with index instead of depending on the resource name and platform_get_resource_by_name(). Replace the resource_len() implementation with resource_size(). Signed-off-by: Magnus Damm Acked-by: Yoshihiro Shimoda Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/r8a66597-hcd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index c21f14e0666a..319041205b57 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2275,7 +2275,6 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev) return 0; } -#define resource_len(r) (((r)->end - (r)->start) + 1) static int __init r8a66597_probe(struct platform_device *pdev) { #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) @@ -2296,11 +2295,10 @@ static int __init r8a66597_probe(struct platform_device *pdev) goto clean_up; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - (char *)hcd_name); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENODEV; - dev_err(&pdev->dev, "platform_get_resource_byname error.\n"); + dev_err(&pdev->dev, "platform_get_resource error.\n"); goto clean_up; } @@ -2315,7 +2313,7 @@ static int __init r8a66597_probe(struct platform_device *pdev) irq = ires->start; irq_trigger = ires->flags & IRQF_TRIGGER_MASK; - reg = ioremap(res->start, resource_len(res)); + reg = ioremap(res->start, resource_size(res)); if (reg == NULL) { ret = -ENOMEM; dev_err(&pdev->dev, "ioremap error.\n"); -- cgit v1.2.3 From 6c0735687d37e25a65866823881bcbf39a6a023f Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Sun, 30 Nov 2008 16:50:04 +0100 Subject: USB: isp1760: use a specific PLX bridge instead of any bdridge this driver can't handle (of course) any brdige class devices. So we now are just active on one specific bridge which should be only the isp1761 chip behind a PLX bridge. Signed-off-by: Sebastian Andrzej Siewior Tested-by: Karl Bongers Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1760-if.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b87ca7cf4b37..a53114c367cd 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -268,12 +268,16 @@ static void isp1761_pci_shutdown(struct pci_dev *dev) printk(KERN_ERR "ips1761_pci_shutdown\n"); } -static const struct pci_device_id isp1760_plx [] = { { - /* handle any USB 2.0 EHCI controller */ - PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_OTHER << 8) | (0x06 << 16)), ~0), - .driver_data = 0, -}, -{ /* end: all zeroes */ } +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } }; MODULE_DEVICE_TABLE(pci, isp1760_plx); -- cgit v1.2.3 From 6013bbbab0dcbc43bcf9dd70beeab2a0b1ec5ea7 Mon Sep 17 00:00:00 2001 From: Karl Bongers Date: Mon, 1 Dec 2008 11:47:40 +0100 Subject: USB: isp1760: Fix probe in PCI glue code Contains fixes so probe on x86 PCI runs, apparently I'm first to try this. Several fixes to memory access to probe host scratch register. Previously would bug check on chip_addr var used uninitialized. Scratch reg write failed in one instance due to 16-bit initial access mode, so added "& 0x0000ffff" to the readl as fix. Includes some general cleanup - remove global vars, organize memory map resource use. Signed-off-by: Karl Bongers Signed-off-by: Sebastian Andrzej Siewior Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1760-if.c | 97 ++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 42 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index a53114c367cd..25453066fda5 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -129,23 +129,23 @@ static struct of_platform_driver isp1760_of_driver = { #endif #ifdef CONFIG_PCI -static u32 nxp_pci_io_base; -static u32 iolength; -static u32 pci_mem_phy0; -static u32 length; -static u8 __iomem *chip_addr; -static u8 __iomem *iobase; - static int __devinit isp1761_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { u8 latency, limit; __u32 reg_data; int retry_count; - int length; - int status = 1; struct usb_hcd *hcd; unsigned int devflags = 0; + int ret_status = 0; + + resource_size_t pci_mem_phy0; + resource_size_t memlength; + + u8 __iomem *chip_addr; + u8 __iomem *iobase; + resource_size_t nxp_pci_io_base; + resource_size_t iolength; if (usb_disabled()) return -ENODEV; @@ -168,26 +168,30 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, iobase = ioremap_nocache(nxp_pci_io_base, iolength); if (!iobase) { printk(KERN_ERR "ioremap #1\n"); - release_mem_region(nxp_pci_io_base, iolength); - return -ENOMEM; + ret_status = -ENOMEM; + goto cleanup1; } /* Grab the PLX PCI shared memory of the ISP 1761 we need */ pci_mem_phy0 = pci_resource_start(dev, 3); - length = pci_resource_len(dev, 3); - - if (length < 0xffff) { - printk(KERN_ERR "memory length for this resource is less than " - "required\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -ENOMEM; + memlength = pci_resource_len(dev, 3); + if (memlength < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + ret_status = -ENOMEM; + goto cleanup2; } - if (!request_mem_region(pci_mem_phy0, length, "ISP-PCI")) { + if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { printk(KERN_ERR "host controller already in use\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -EBUSY; + ret_status = -EBUSY; + goto cleanup2; + } + + /* map available memory */ + chip_addr = ioremap_nocache(pci_mem_phy0,memlength); + if (!chip_addr) { + printk(KERN_ERR "Error ioremap failed\n"); + ret_status = -ENOMEM; + goto cleanup3; } /* bad pci latencies can contribute to overruns */ @@ -210,39 +214,54 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, * */ writel(0xface, chip_addr + HC_SCRATCH_REG); udelay(100); - reg_data = readl(chip_addr + HC_SCRATCH_REG); + reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; retry_count--; } + iounmap(chip_addr); + /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not */ if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - goto clean; + ret_status = -ENOMEM; + goto cleanup3; } pci_set_master(dev); - status = readl(iobase + 0x68); - status |= 0x900; - writel(status, iobase + 0x68); + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - hcd = isp1760_register(pci_mem_phy0, length, dev->irq, + hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev), devflags); - if (!IS_ERR(hcd)) { - pci_set_drvdata(dev, hcd); - return 0; + if (IS_ERR(hcd)) { + ret_status = -ENODEV; + goto cleanup3; } -clean: - status = -ENODEV; + + /* done with PLX IO access */ iounmap(iobase); - release_mem_region(pci_mem_phy0, length); release_mem_region(nxp_pci_io_base, iolength); - return status; + + pci_set_drvdata(dev, hcd); + return 0; + +cleanup3: + release_mem_region(pci_mem_phy0, memlength); +cleanup2: + iounmap(iobase); +cleanup1: + release_mem_region(nxp_pci_io_base, iolength); + return ret_status; } + static void isp1761_pci_remove(struct pci_dev *dev) { struct usb_hcd *hcd; @@ -255,12 +274,6 @@ static void isp1761_pci_remove(struct pci_dev *dev) usb_put_hcd(hcd); pci_disable_device(dev); - - iounmap(iobase); - iounmap(chip_addr); - - release_mem_region(nxp_pci_io_base, iolength); - release_mem_region(pci_mem_phy0, length); } static void isp1761_pci_shutdown(struct pci_dev *dev) -- cgit v1.2.3 From 42c65396d4f10f25bdab13f8e2f33fe63fa94418 Mon Sep 17 00:00:00 2001 From: Thomas Hommel Date: Thu, 18 Dec 2008 10:31:40 +0100 Subject: USB: isp1760: don't auto disable Port1 on ISP1761 There is no need to disable port 1 on ISP1761. That port could be used as an OTG port which would require a different init sequence. However we don't have OTG support (yet) so we can use it as a normal USB port. This patch allows port 1 to be used a normal Port on the ISP1761. Signed-off-by: Thomas Hommel Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1760-hcd.c | 13 ++++++------- drivers/usb/host/isp1760-hcd.h | 1 - drivers/usb/host/isp1760-if.c | 3 --- 3 files changed, 6 insertions(+), 11 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 8017f1cf78e2..b899f1a59c26 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -435,14 +435,13 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) /* * PORT 1 Control register of the ISP1760 is the OTG control - * register on ISP1761. + * register on ISP1761. Since there is no OTG or device controller + * support in this driver, we use port 1 as a "normal" USB host port on + * both chips. */ - if (!(priv->devflags & ISP1760_FLAG_ISP1761) && - !(priv->devflags & ISP1760_FLAG_PORT1_DIS)) { - isp1760_writel(PORT1_POWER | PORT1_INIT2, - hcd->regs + HC_PORT1_CTRL); - mdelay(10); - } + isp1760_writel(PORT1_POWER | PORT1_INIT2, + hcd->regs + HC_PORT1_CTRL); + mdelay(10); priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 4377277667d9..a9daea587962 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -135,7 +135,6 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, * indicate the most "atypical" case, so that a devflags of 0 is * a sane default configuration. */ -#define ISP1760_FLAG_PORT1_DIS 0x00000001 /* Port 1 disabled */ #define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ #define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ #define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 25453066fda5..4cf7ca428b33 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -60,9 +60,6 @@ static int of_isp1760_probe(struct of_device *dev, if (of_device_is_compatible(dp, "nxp,usb-isp1761")) devflags |= ISP1760_FLAG_ISP1761; - if (of_get_property(dp, "port1-disable", NULL) != NULL) - devflags |= ISP1760_FLAG_PORT1_DIS; - /* Some systems wire up only 16 of the 32 data lines */ prop = of_get_property(dp, "bus-width", NULL); if (prop && *prop == 16) -- cgit v1.2.3 From a0d4922da2e4ccb0973095d8d29f36f6b1b5f703 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 17 Dec 2008 15:06:03 -0500 Subject: USB: fix up suspend and resume for PCI host controllers This patch (as1192) rearranges the USB PCI host controller suspend and resume and resume routines: Use pci_wake_from_d3() for enabling and disabling wakeup, instead of pci_enable_wake(). Carry out the actual state change while interrupts are disabled. Change the order of the preparations to agree with the general recommendation for PCI devices, instead of messing around with the wakeup settings while the device is in D3. In .suspend: Call the underlying driver to disable IRQ generation; pci_wake_from_d3(device_may_wakeup()); pci_disable_device(); In .suspend_late: pci_save_state(); pci_set_power_state(D3hot); (for PPC_PMAC) Disable ASIC clocks In .resume_early: (for PPC_PMAC) Enable ASIC clocks pci_set_power_state(D0); pci_restore_state(); In .resume: pci_enable_device(); pci_set_master(); pci_wake_from_d3(0); Call the underlying driver to reenable IRQ generation Add the necessary .suspend_late and .resume_early method pointers to the PCI host controller drivers. Signed-off-by: Alan Stern CC: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-pci.c | 2 ++ drivers/usb/host/ohci-pci.c | 2 ++ drivers/usb/host/uhci-hcd.c | 2 ++ 3 files changed, 6 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 36864f958444..6af47a0937b8 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -428,6 +428,8 @@ static struct pci_driver ehci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif .shutdown = usb_hcd_pci_shutdown, diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index a9c2ae36c7ad..8380cc2e961a 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -487,6 +487,8 @@ static struct pci_driver ohci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index cf5e4cf7ea42..4e221060f58c 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,6 +942,8 @@ static struct pci_driver uhci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif /* PM */ }; -- cgit v1.2.3 From 6fd9086a518d4f14213a32fe6c9ac17fabebbc1e Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 17 Dec 2008 17:20:38 -0500 Subject: USB: automatically enable wakeup for PCI host controllers This patch (as1193b) enables wakeup during initialization for all PCI host controllers, and it removes some code (and comments!) that are no longer needed now that the PCI core automatically initializes wakeup settings for all new devices. The idea is that the bus should initialize wakeup, and the bus glue or controller driver should enable it. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-pci.c | 10 +++++++--- drivers/usb/host/ohci-hcd.c | 12 +++++++----- drivers/usb/host/ohci-pci.c | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 6af47a0937b8..bdc6e86e1f8b 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -219,15 +219,19 @@ static int ehci_pci_setup(struct usb_hcd *hcd) /* Serial Bus Release Number is at PCI 0x60 offset */ pci_read_config_byte(pdev, 0x60, &ehci->sbrn); - /* Workaround current PCI init glitch: wakeup bits aren't - * being set from PCI PM capability. + /* Keep this around for a while just in case some EHCI + * implementation uses legacy PCI PM support. This test + * can be removed on 17 Dec 2009 if the dev_warn() hasn't + * been triggered by then. */ if (!device_can_wakeup(&pdev->dev)) { u16 port_wake; pci_read_config_word(pdev, 0x62, &port_wake); - if (port_wake & 0x0001) + if (port_wake & 0x0001) { + dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); device_init_wakeup(&pdev->dev, 1); + } } #ifdef CONFIG_USB_SUSPEND diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 8aa3f4556a32..65a9609f4ad6 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -589,13 +589,15 @@ static int ohci_run (struct ohci_hcd *ohci) /* also: power/overcurrent flags in roothub.a */ } - /* Reset USB nearly "by the book". RemoteWakeupConnected was - * saved if boot firmware (BIOS/SMM/...) told us it's connected, - * or if bus glue did the same (e.g. for PCI add-in cards with - * PCI PM support). + /* Reset USB nearly "by the book". RemoteWakeupConnected has + * to be checked in case boot firmware (BIOS/SMM/...) has set up + * wakeup in a way the bus isn't aware of (e.g., legacy PCI PM). + * If the bus glue detected wakeup capability then it should + * already be enabled. Either way, if wakeup should be enabled + * but isn't, we'll enable it now. */ if ((ohci->hc_control & OHCI_CTRL_RWC) != 0 - && !device_may_wakeup(hcd->self.controller)) + && !device_can_wakeup(hcd->self.controller)) device_init_wakeup(hcd->self.controller, 1); switch (ohci->hc_control & OHCI_CTRL_HCFS) { diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 8380cc2e961a..8b28ae7865ba 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -355,9 +355,9 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) /* RWC may not be set for add-in PCI cards, since boot * firmware probably ignored them. This transfers PCI - * PM wakeup capabilities (once the PCI layer is fixed). + * PM wakeup capabilities. */ - if (device_may_wakeup(&pdev->dev)) + if (device_can_wakeup(&pdev->dev)) ohci->hc_control |= OHCI_CTRL_RWC; } #endif /* CONFIG_PM */ -- cgit v1.2.3 From 953a7e8476bbd7367cebdb868c326ba42968bc13 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 18 Jan 2009 12:52:38 +0000 Subject: [ARM] omap: ensure OMAP drivers pass a struct device to clk_get() Signed-off-by: Russell King --- drivers/usb/host/ohci-omap.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 4bbddb73abd9..f3aaba35e912 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -315,14 +315,14 @@ static int usb_hcd_omap_probe (const struct hc_driver *driver, return -ENODEV; } - usb_host_ck = clk_get(0, "usb_hhc_ck"); + usb_host_ck = clk_get(&pdev->dev, "usb_hhc_ck"); if (IS_ERR(usb_host_ck)) return PTR_ERR(usb_host_ck); if (!cpu_is_omap15xx()) - usb_dc_ck = clk_get(0, "usb_dc_ck"); + usb_dc_ck = clk_get(&pdev->dev, "usb_dc_ck"); else - usb_dc_ck = clk_get(0, "lb_ck"); + usb_dc_ck = clk_get(&pdev->dev, "lb_ck"); if (IS_ERR(usb_dc_ck)) { clk_put(usb_host_ck); -- cgit v1.2.3 From bcca06efea883bdf3803a0bb0ffa60f26730387d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 13 Jan 2009 11:35:54 -0500 Subject: USB: don't enable wakeup by default for PCI host controllers This patch (as1199) changes the initial wakeup settings for PCI USB host controllers. The controllers are marked as capable of waking the system, but wakeup is not enabled by default. It turns out that enabling wakeup for USB host controllers has a lot of bad consequences. As the simplest example, if a USB mouse or keyboard is unplugged immediately after the computer is put to sleep, the unplug will cause the system to wake back up again! We are better off marking them as wakeup-capable and leaving wakeup disabled. Signed-off-by: Alan Stern Reported-by: Rafael J. Wysocki CC: David Brownell Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-pci.c | 2 +- drivers/usb/host/ohci-hcd.c | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index bdc6e86e1f8b..9faa5c8fe025 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -230,7 +230,7 @@ static int ehci_pci_setup(struct usb_hcd *hcd) pci_read_config_word(pdev, 0x62, &port_wake); if (port_wake & 0x0001) { dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); - device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_capable(&pdev->dev, 1); } } diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 65a9609f4ad6..5cf5f1eca4f4 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -593,12 +593,10 @@ static int ohci_run (struct ohci_hcd *ohci) * to be checked in case boot firmware (BIOS/SMM/...) has set up * wakeup in a way the bus isn't aware of (e.g., legacy PCI PM). * If the bus glue detected wakeup capability then it should - * already be enabled. Either way, if wakeup should be enabled - * but isn't, we'll enable it now. + * already be enabled; if so we'll just enable it again. */ - if ((ohci->hc_control & OHCI_CTRL_RWC) != 0 - && !device_can_wakeup(hcd->self.controller)) - device_init_wakeup(hcd->self.controller, 1); + if ((ohci->hc_control & OHCI_CTRL_RWC) != 0) + device_set_wakeup_capable(hcd->self.controller, 1); switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_OPER: -- cgit v1.2.3 From a15d95a003fae154121733f049dd25e9c13dbef3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 20 Jan 2009 01:26:56 +0100 Subject: USB: Fix suspend-resume of PCI USB controllers Commit a0d4922da2e4ccb0973095d8d29f36f6b1b5f703 (USB: fix up suspend and resume for PCI host controllers) attempted to fix the suspend-resume of PCI USB controllers, but unfortunately it did that incorrectly and interrupts are left enabled by the USB controllers' ->suspend_late() callback as a result. This leads to serious problems during suspend which are very difficult to debug. Fix the issue by removing the ->suspend_late() callback of PCI USB controllers and moving the code from there to the ->suspend() callback executed with interrupts enabled. Additionally, make the ->resume() callback of PCI USB controllers execute pci_enable_wake(dev, PCI_D0, false) to disable wake-up from the full power state (PCI_D0). Signed-off-by: Rafael J. Wysocki Tested-by: Andrey Borzenkov Tested-by: "Jeff Chua" Cc: Alan Stern Cc: Andrew Morton Cc: Christian Borntraeger Cc: "Zdenek Kabelac" Cc: Ingo Molnar Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-pci.c | 1 - drivers/usb/host/ohci-pci.c | 1 - drivers/usb/host/uhci-hcd.c | 1 - 3 files changed, 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 9faa5c8fe025..bb21fb0a4969 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -432,7 +432,6 @@ static struct pci_driver ehci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 8b28ae7865ba..5d625c3fd423 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -487,7 +487,6 @@ static struct pci_driver ohci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 4e221060f58c..944f7e0ca4df 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,7 +942,6 @@ static struct pci_driver uhci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif /* PM */ -- cgit v1.2.3 From 2bf5fa13fc8e34d7b86307b99f64a24cb7a83852 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Sat, 24 Jan 2009 17:55:57 -0800 Subject: USB: omap1 ohci buildfix (otg related) > > drivers/built-in.o: In function `ohci_omap_init': > > hid-quirks.c:(.text+0x6c608): undefined reference to `otg_get_transceiver' > > drivers/built-in.o: In function `omap_udc_probe': > > hid-quirks.c:(.init.text+0x34c0): undefined reference to `otg_get_transceiver' > > hid-quirks.c:(.init.text+0x3d40): undefined reference to `otg_put_transceiver' Reported-by: Russell King Signed-off-by: David Brownell Acked-by: Tony Lindgren Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 2b476b6b3d4d..5a988f4cdf87 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -140,6 +140,7 @@ config USB_OHCI_HCD tristate "OHCI HCD support" depends on USB && USB_ARCH_HAS_OHCI select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 + select USB_OTG_UTILS if ARCH_OMAP ---help--- The Open Host Controller Interface (OHCI) is a standard for accessing USB 1.1 host controller hardware. It does more in hardware than Intel's -- cgit v1.2.3 From 236dd4d18f293e3c9798f35c08272196826a980d Mon Sep 17 00:00:00 2001 From: Anton Vorontsov Date: Sat, 10 Jan 2009 05:03:21 +0300 Subject: USB: Driver for Freescale QUICC Engine USB Host Controller This patch adds support for the FHCI USB controller, as found in the Freescale MPC836x and MPC832x processors. It can support Full or Low speed modes. Quite a lot the hardware is doing by itself (SOF generation, CRC generation and checking), though scheduling and retransmission is on software's shoulders. This controller does not integrate the root hub, so this driver also fakes one-port hub. External hub is required to support more than one device. Signed-off-by: Anton Vorontsov Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 17 + drivers/usb/host/Makefile | 6 + drivers/usb/host/fhci-dbg.c | 139 +++++++ drivers/usb/host/fhci-hcd.c | 836 +++++++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-hub.c | 345 ++++++++++++++++ drivers/usb/host/fhci-mem.c | 113 ++++++ drivers/usb/host/fhci-q.c | 284 ++++++++++++++ drivers/usb/host/fhci-sched.c | 888 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-tds.c | 626 +++++++++++++++++++++++++++++ drivers/usb/host/fhci.h | 607 +++++++++++++++++++++++++++++ 10 files changed, 3861 insertions(+) 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-sched.c create mode 100644 drivers/usb/host/fhci-tds.c create mode 100644 drivers/usb/host/fhci.h (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5a988f4cdf87..2c63bfb1f8d9 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -239,6 +239,23 @@ config USB_UHCI_HCD To compile this driver as a module, choose M here: the module will be called uhci-hcd. +config USB_FHCI_HCD + tristate "Freescale QE USB Host Controller support" + depends on USB && OF_GPIO && QE_GPIO && QUICC_ENGINE + select FSL_GTM + select QE_USB + help + This driver enables support for Freescale QE USB Host Controller + (as found on MPC8360 and MPC8323 processors), the driver supports + Full and Low Speed USB. + +config FHCI_DEBUG + bool "Freescale QE USB Host Controller debug support" + depends on USB_FHCI_HCD && DEBUG_FS + help + Say "y" to see some FHCI debug information and statistics + throught debugfs. + config USB_U132_HCD tristate "Elan U132 Adapter Host Controller" depends on USB && USB_FTDI_ELAN diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e5f3f20787e4..f163571e33d8 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -7,6 +7,11 @@ ifeq ($(CONFIG_USB_DEBUG),y) endif isp1760-objs := isp1760-hcd.o isp1760-if.o +fhci-objs := fhci-hcd.o fhci-hub.o fhci-q.o fhci-mem.o \ + fhci-tds.o fhci-sched.o +ifeq ($(CONFIG_FHCI_DEBUG),y) +fhci-objs += fhci-dbg.o +endif obj-$(CONFIG_USB_WHCI_HCD) += whci/ @@ -17,6 +22,7 @@ obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o +obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c new file mode 100644 index 000000000000..34e14edf390b --- /dev/null +++ b/drivers/usb/host/fhci-dbg.c @@ -0,0 +1,139 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +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, +}; + +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) { + WARN_ON(1); + 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); + + WARN_ON(!fhci->dfs_regs || !fhci->dfs_irq_stat); +} + +void fhci_dfs_destroy(struct fhci_hcd *fhci) +{ + if (!fhci->dfs_root) + return; + + if (fhci->dfs_irq_stat) + debugfs_remove(fhci->dfs_irq_stat); + + if (fhci->dfs_regs) + debugfs_remove(fhci->dfs_regs); + + debugfs_remove(fhci->dfs_root); +} diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c new file mode 100644 index 000000000000..ba622cc8a9ba --- /dev/null +++ b/drivers/usb/host/fhci-hcd.c @@ -0,0 +1,836 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +void fhci_start_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + /* clear frame_n */ + out_be16(&fhci->pram->frame_num, 0); + + out_be16(&fhci->regs->usb_sof_tmr, 0); + setbits8(&fhci->regs->usb_mod, USB_MODE_SFTE); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_stop_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + clrbits8(&fhci->regs->usb_mod, USB_MODE_SFTE); + gtm_stop_timer16(fhci->timer); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +u16 fhci_get_sof_timer_count(struct fhci_usb *usb) +{ + return be16_to_cpu(in_be16(&usb->fhci->regs->usb_sof_tmr) / 12); +} + +/* initialize the endpoint zero */ +static u32 endpoint_zero_init(struct fhci_usb *usb, + enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + u32 rc; + + rc = fhci_create_ep(usb, data_mem, ring_len); + if (rc) + return rc; + + /* inilialize endpoint registers */ + fhci_init_ep_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 */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + out_be16(&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_nosync(fhci->timer->irq); + + /* disable the usb interrupt */ + disable_irq_nosync(fhci_to_hcd(fhci)->irq); + out_be16(&usb->fhci->regs->usb_mask, 0); + } + usb->intr_nesting_cnt++; +} + +/* enable the USB controller */ +static u32 fhci_usb_enable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + out_be16(&usb->fhci->regs->usb_event, 0xffff); + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + setbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + mdelay(100); + + return 0; +} + +/* disable the USB controller */ +static u32 fhci_usb_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_usb_disable_interrupt(usb); + fhci_port_disable(fhci); + + /* disable the usb controller */ + if (usb->port_status == FHCI_PORT_FULL || + usb->port_status == FHCI_PORT_LOW) + fhci_device_disconnected_interrupt(fhci); + + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + 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; +} + +static void fhci_mem_free(struct fhci_hcd *fhci) +{ + struct ed *ed; + struct ed *next_ed; + struct td *td; + struct td *next_td; + + list_for_each_entry_safe(ed, next_ed, &fhci->empty_eds, node) { + list_del(&ed->node); + kfree(ed); + } + + list_for_each_entry_safe(td, next_td, &fhci->empty_tds, node) { + list_del(&td->node); + kfree(td); + } + + kfree(fhci->vroot_hub); + fhci->vroot_hub = NULL; + + kfree(fhci->hc_list); + fhci->hc_list = NULL; +} + +static int fhci_mem_init(struct fhci_hcd *fhci) +{ + int i; + + fhci->hc_list = kzalloc(sizeof(*fhci->hc_list), GFP_KERNEL); + if (!fhci->hc_list) + goto err; + + 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) + goto err; + + 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; + + td = kmalloc(sizeof(*td), GFP_KERNEL); + if (!td) + goto err; + fhci_recycle_empty_td(fhci, td); + } + for (i = 0; i < MAX_EDS; i++) { + struct ed *ed; + + ed = kmalloc(sizeof(*ed), GFP_KERNEL); + if (!ed) + goto err; + fhci_recycle_empty_ed(fhci, ed); + } + + fhci->active_urbs = 0; + return 0; +err: + fhci_mem_free(fhci); + return -ENOMEM; +} + +/* destroy the fhci_usb structure */ +static void fhci_usb_free(void *lld) +{ + struct fhci_usb *usb = lld; + struct fhci_hcd *fhci = usb->fhci; + + if (usb) { + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + fhci_ep0_free(usb); + kfree(usb->actual_frame); + kfree(usb); + } +} + +/* initialize the USB */ +static int fhci_usb_init(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + memset_io(usb->fhci->pram, 0, FHCI_PRAM_SIZE); + + usb->port_status = FHCI_PORT_DISABLED; + usb->max_frame_usage = FRAME_TIME_USAGE; + usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION; + + 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 */ + out_be16(&fhci->pram->frame_num, 0); + + /* clear rx state */ + out_be32(&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); + + out_8(&usb->fhci->regs->usb_mod, USB_MODE_HOST | USB_MODE_EN); + + /* clearing the mask register */ + out_be16(&usb->fhci->regs->usb_mask, 0); + + /* initialing the event register */ + out_be16(&usb->fhci->regs->usb_event, 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 */ +static 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 = fhci_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); + if (ret) { + fhci_err(fhci, "failed to allocate memory\n"); + goto err; + } + + fhci->usb_lld = fhci_create_lld(fhci); + if (!fhci->usb_lld) { + fhci_err(fhci, "low level driver config failed\n"); + ret = -ENOMEM; + goto err; + } + + ret = fhci_usb_init(fhci); + if (ret) { + fhci_err(fhci, "low level driver initialize failed\n"); + goto err; + } + + 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; +err: + fhci_mem_free(fhci); + return ret; +} + +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 ret; + int i; + int size = 0; + struct urb_priv *urb_priv; + unsigned long flags; + + 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; + } + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto err; + + /* 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; + + fhci_queue_urb(fhci, urb); +err: + if (ret) { + kfree(urb_priv->tds); + kfree(urb_priv); + } + spin_unlock_irqrestore(&fhci->lock, flags); + return ret; +} + +/* 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; + int ret = -EINVAL; + unsigned long flags; + + if (!urb || !urb->dev || !urb->dev->bus) + goto out; + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto out2; + + 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 out2; + + urb_priv->state = URB_DEL; + + /* already pending? */ + urb_priv->ed->state = FHCI_ED_URB_DEL; + } else { + fhci_urb_complete_free(fhci, urb); + } + +out2: + spin_unlock_irqrestore(&fhci->lock, flags); +out: + return ret; +} + +static void fhci_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fhci_hcd *fhci; + struct ed *ed; + unsigned long flags; + + fhci = hcd_to_fhci(hcd); + spin_lock_irqsave(&fhci->lock, flags); + ed = ep->hcpriv; + if (ed) { + while (ed->td_head != NULL) { + struct td *td = fhci_remove_td_from_ed(ed); + fhci_urb_complete_free(fhci, td->urb); + } + fhci_recycle_empty_ed(fhci, ed); + ep->hcpriv = NULL; + } + spin_unlock_irqrestore(&fhci->lock, flags); +} + +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 | HCD_MEMORY, + + /* 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, +}; + +static int __devinit of_fhci_probe(struct of_device *ofdev, + const struct of_device_id *ofid) +{ + struct device *dev = &ofdev->dev; + struct device_node *node = ofdev->node; + struct usb_hcd *hcd; + struct fhci_hcd *fhci; + struct resource usb_regs; + unsigned long pram_addr; + unsigned int usb_irq; + const char *sprop; + const u32 *iprop; + int size; + int ret; + int i; + int j; + + if (usb_disabled()) + return -ENODEV; + + sprop = of_get_property(node, "mode", NULL); + if (sprop && strcmp(sprop, "host")) + return -ENODEV; + + hcd = usb_create_hcd(&fhci_driver, dev, dev->bus_id); + if (!hcd) { + dev_err(dev, "could not create hcd\n"); + return -ENOMEM; + } + + fhci = hcd_to_fhci(hcd); + hcd->self.controller = dev; + dev_set_drvdata(dev, hcd); + + iprop = of_get_property(node, "hub-power-budget", &size); + if (iprop && size == sizeof(*iprop)) + hcd->power_budget = *iprop; + + /* FHCI registers. */ + ret = of_address_to_resource(node, 0, &usb_regs); + if (ret) { + dev_err(dev, "could not get regs\n"); + goto err_regs; + } + + hcd->regs = ioremap(usb_regs.start, usb_regs.end - usb_regs.start + 1); + if (!hcd->regs) { + dev_err(dev, "could not ioremap regs\n"); + ret = -ENOMEM; + goto err_regs; + } + fhci->regs = hcd->regs; + + /* Parameter RAM. */ + iprop = of_get_property(node, "reg", &size); + if (!iprop || size < sizeof(*iprop) * 4) { + dev_err(dev, "can't get pram offset\n"); + ret = -EINVAL; + goto err_pram; + } + + pram_addr = cpm_muram_alloc_fixed(iprop[2], FHCI_PRAM_SIZE); + if (IS_ERR_VALUE(pram_addr)) { + dev_err(dev, "failed to allocate usb pram\n"); + ret = -ENOMEM; + goto err_pram; + } + fhci->pram = cpm_muram_addr(pram_addr); + + /* GPIOs and pins */ + for (i = 0; i < NUM_GPIOS; i++) { + int gpio; + enum of_gpio_flags flags; + + gpio = of_get_gpio_flags(node, i, &flags); + fhci->gpios[i] = gpio; + fhci->alow_gpios[i] = flags & OF_GPIO_ACTIVE_LOW; + + if (!gpio_is_valid(gpio)) { + if (i < 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", i == GPIO_SPEED ? + "speed" : "power"); + continue; + } + } + + ret = gpio_request(gpio, dev->bus_id); + if (ret) { + dev_err(dev, "failed to request gpio %d", i); + goto err_gpios; + } + + if (i >= GPIO_SPEED) { + ret = gpio_direction_output(gpio, 0); + if (ret) { + dev_err(dev, "failed to set gpio %d as " + "an output\n", i); + i++; + goto err_gpios; + } + } + } + + for (j = 0; j < NUM_PINS; j++) { + fhci->pins[j] = qe_pin_request(ofdev->node, j); + if (IS_ERR(fhci->pins[j])) { + ret = PTR_ERR(fhci->pins[j]); + dev_err(dev, "can't get pin %d: %d\n", j, ret); + goto err_pins; + } + } + + /* Frame limit timer and its interrupt. */ + fhci->timer = gtm_get_timer16(); + if (IS_ERR(fhci->timer)) { + ret = PTR_ERR(fhci->timer); + dev_err(dev, "failed to request qe timer: %i", ret); + goto err_get_timer; + } + + 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; + } + + /* USB Host interrupt. */ + usb_irq = irq_of_parse_and_map(node, 0); + if (usb_irq == NO_IRQ) { + dev_err(dev, "could not get usb irq\n"); + ret = -EINVAL; + goto err_usb_irq; + } + + /* Clocks. */ + sprop = of_get_property(node, "fsl,fullspeed-clock", NULL); + if (sprop) { + fhci->fullspeed_clk = qe_clock_source(sprop); + if (fhci->fullspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong fullspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + sprop = of_get_property(node, "fsl,lowspeed-clock", NULL); + if (sprop) { + fhci->lowspeed_clk = qe_clock_source(sprop); + if (fhci->lowspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong lowspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + if (fhci->fullspeed_clk == QE_CLK_NONE && + fhci->lowspeed_clk == QE_CLK_NONE) { + dev_err(dev, "no clocks specified\n"); + ret = -EINVAL; + goto err_clocks; + } + + dev_info(dev, "at 0x%p, irq %d\n", hcd->regs, usb_irq); + + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + + /* Start with full-speed, if possible. */ + if (fhci->fullspeed_clk != QE_CLK_NONE) { + fhci_config_transceiver(fhci, FHCI_PORT_FULL); + qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + } else { + fhci_config_transceiver(fhci, FHCI_PORT_LOW); + qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + } + + /* Clear and disable any pending interrupts. */ + out_be16(&fhci->regs->usb_event, 0xffff); + out_be16(&fhci->regs->usb_mask, 0); + + ret = usb_add_hcd(hcd, usb_irq, IRQF_DISABLED); + if (ret < 0) + goto err_add_hcd; + + fhci_dfs_create(fhci); + + return 0; + +err_add_hcd: +err_clocks: + irq_dispose_mapping(usb_irq); +err_usb_irq: + free_irq(fhci->timer->irq, hcd); +err_timer_irq: + gtm_put_timer16(fhci->timer); +err_get_timer: +err_pins: + while (--j >= 0) + qe_pin_free(fhci->pins[j]); +err_gpios: + while (--i >= 0) { + if (gpio_is_valid(fhci->gpios[i])) + gpio_free(fhci->gpios[i]); + } + cpm_muram_free(pram_addr); +err_pram: + iounmap(hcd->regs); +err_regs: + 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); + int i; + int j; + + usb_remove_hcd(hcd); + free_irq(fhci->timer->irq, hcd); + gtm_put_timer16(fhci->timer); + cpm_muram_free(cpm_muram_offset(fhci->pram)); + for (i = 0; i < NUM_GPIOS; i++) { + if (!gpio_is_valid(fhci->gpios[i])) + continue; + gpio_free(fhci->gpios[i]); + } + for (j = 0; j < NUM_PINS; j++) + qe_pin_free(fhci->pins[j]); + fhci_dfs_destroy(fhci); + usb_put_hcd(hcd); + return 0; +} + +static int __devexit of_fhci_remove(struct of_device *ofdev) +{ + return fhci_remove(&ofdev->dev); +} + +static struct of_device_id of_fhci_match[] = { + { .compatible = "fsl,mpc8323-qe-usb", }, + {}, +}; +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 , " + "Jerry Huang , " + "Anton Vorontsov "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c new file mode 100644 index 000000000000..0cfaedc3e124 --- /dev/null +++ b/drivers/usb/host/fhci-hub.c @@ -0,0 +1,345 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +/* 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_gpio_set_value(struct fhci_hcd *fhci, int gpio_nr, bool on) +{ + int gpio = fhci->gpios[gpio_nr]; + bool alow = fhci->alow_gpios[gpio_nr]; + + if (!gpio_is_valid(gpio)) + return; + + gpio_set_value(gpio, on ^ alow); + mdelay(5); +} + +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status) +{ + fhci_dbg(fhci, "-> %s: %d\n", __func__, status); + + switch (status) { + case FHCI_PORT_POWER_OFF: + fhci_gpio_set_value(fhci, GPIO_POWER, false); + break; + case FHCI_PORT_DISABLED: + case FHCI_PORT_WAITING: + fhci_gpio_set_value(fhci, GPIO_POWER, true); + break; + case FHCI_PORT_LOW: + fhci_gpio_set_value(fhci, GPIO_SPEED, false); + break; + case FHCI_PORT_FULL: + fhci_gpio_set_value(fhci, GPIO_SPEED, true); + break; + default: + WARN_ON(1); + break; + } + + fhci_dbg(fhci, "<- %s: %d\n", __func__, status); +} + +/* disable the USB port by clearing the EN bit in the USBMOD register */ +void fhci_port_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = (struct fhci_usb *)fhci->usb_lld; + enum fhci_port_status port_status; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + + fhci_flush_all_transmissions(usb); + + 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; + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + + /* check if during the disconnection process attached new device */ + if (port_status == FHCI_PORT_WAITING) + fhci_device_connected_interrupt(fhci); + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + fhci_usb_enable_interrupt((struct fhci_usb *)fhci->usb_lld); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* enable the USB port by setting the EN bit in the USBMOD register */ +void fhci_port_enable(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_config_transceiver(fhci, usb->port_status); + + if ((usb->port_status != FHCI_PORT_FULL) && + (usb->port_status != FHCI_PORT_LOW)) + fhci_start_sof_timer(fhci); + + usb->vroot_hub->port.wPortStatus |= USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_io_port_generate_reset(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + gpio_direction_output(fhci->gpios[GPIO_USBOE], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTP], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTN], 0); + + mdelay(5); + + qe_pin_set_dedicated(fhci->pins[PIN_USBOE]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTP]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTN]); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* generate the RESET condition on the bus */ +void fhci_port_reset(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + u8 mode; + u16 mask; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + /* disable the USB controller */ + mode = in_8(&fhci->regs->usb_mod); + out_8(&fhci->regs->usb_mod, mode & (~USB_MODE_EN)); + + /* disable idle interrupts */ + mask = in_be16(&fhci->regs->usb_mask); + out_be16(&fhci->regs->usb_mask, mask & (~USB_E_IDLE_MASK)); + + fhci_io_port_generate_reset(fhci); + + /* enable interrupt on this endpoint */ + out_be16(&fhci->regs->usb_mask, mask); + + /* enable the USB controller */ + mode = in_8(&fhci->regs->usb_mod); + out_8(&fhci->regs->usb_mod, mode | USB_MODE_EN); + fhci_start_sof_timer(fhci); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int ret = 0; + unsigned long flags; + + fhci_dbg(fhci, "-> %s\n", __func__); + + spin_lock_irqsave(&fhci->lock, flags); + + 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)) { + *buf = 1 << 1; + ret = 1; + fhci_dbg(fhci, "-- %s\n", __func__); + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + fhci_dbg(fhci, "<- %s\n", __func__); + + return ret; +} + +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; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + fhci_dbg(fhci, "-> %s\n", __func__); + + 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; + fhci_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; + fhci_config_transceiver(fhci, FHCI_PORT_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; + break; + 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; + fhci_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; + fhci_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; + fhci_config_transceiver(fhci, FHCI_PORT_WAITING); + break; + default: + goto error; + } + break; + default: +error: + retval = -EPIPE; + } + + fhci_dbg(fhci, "<- %s\n", __func__); + + spin_unlock_irqrestore(&fhci->lock, flags); + + return retval; +} diff --git a/drivers/usb/host/fhci-mem.c b/drivers/usb/host/fhci-mem.c new file mode 100644 index 000000000000..2c0736c99712 --- /dev/null +++ b/drivers/usb/host/fhci-mem.c @@ -0,0 +1,113 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +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; +} + +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td) +{ + init_td(td); + list_add(&td->node, &fhci->empty_tds); +} + +struct ed *fhci_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; +} + +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed) +{ + init_ed(ed); + list_add(&ed->node, &fhci->empty_eds); +} + +struct td *fhci_td_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->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; + + 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 000000000000..b0a1446ba292 --- /dev/null +++ b/drivers/usb/host/fhci-q.c @@ -0,0 +1,284 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +/* 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; +} + +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td) +{ + list_add_tail(&td->frame_lh, &frame->tds_list); +} + +void fhci_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; +} + +struct td *fhci_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; +} + +struct td *fhci_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; +} + +struct td *fhci_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; +} + +struct td *fhci_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; +} + +void fhci_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); + fhci_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 */ +void fhci_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; + } + + usb_hcd_unlink_urb_from_ep(fhci_to_hcd(fhci), urb); + + 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) + */ +void fhci_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 */ +void fhci_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 = fhci_remove_td_from_ed(ed); + /* HC may have partly processed this TD */ + if (td->status != USB_TD_INPROGRESS) + fhci_done_td(urb, td); + + /* URB is done;clean up */ + if (++(urb_priv->tds_cnt) == urb_priv->num_of_tds) + fhci_urb_complete_free(fhci, urb); + } +} diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c new file mode 100644 index 000000000000..bb63b68ddb77 --- /dev/null +++ b/drivers/usb/host/fhci-sched.c @@ -0,0 +1,888 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +static void recycle_frame(struct fhci_usb *usb, struct packet *pkt) +{ + pkt->data = NULL; + pkt->len = 0; + pkt->status = USB_TD_OK; + pkt->info = 0; + pkt->priv_data = NULL; + + cq_put(usb->ep0->empty_frame_Q, pkt); +} + +/* confirm submitted packet */ +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt) +{ + struct td *td; + struct packet *td_pkt; + struct ed *ed; + u32 trans_len; + bool td_done = false; + + td = fhci_remove_td_from_frame(usb->actual_frame); + td_pkt = td->pkt; + trans_len = pkt->len; + td->status = pkt->status; + if (td->type == FHCI_TA_IN && td_pkt->info & PKT_DUMMY_PACKET) { + if ((td->data + td->actual_len) && trans_len) + memcpy(td->data + td->actual_len, pkt->data, + trans_len); + cq_put(usb->ep0->dummy_packets_Q, pkt->data); + } + + recycle_frame(usb, pkt); + + ed = td->ed; + if (ed->mode == FHCI_TF_ISO) { + if (ed->td_list.next->next != &ed->td_list) { + struct td *td_next = + list_entry(ed->td_list.next->next, struct td, + node); + + td_next->start_frame = usb->actual_frame->frame_num; + } + td->actual_len = trans_len; + td_done = true; + } else if ((td->status & USB_TD_ERROR) && + !(td->status & USB_TD_TX_ER_NAK)) { + /* + * There was an error on the transaction (but not NAK). + * If it is fatal error (data underrun, stall, bad pid or 3 + * errors exceeded), mark this TD as done. + */ + if ((td->status & USB_TD_RX_DATA_UNDERUN) || + (td->status & USB_TD_TX_ER_STALL) || + (td->status & USB_TD_RX_ER_PID) || + (++td->error_cnt >= 3)) { + ed->state = FHCI_ED_HALTED; + td_done = true; + + if (td->status & USB_TD_RX_DATA_UNDERUN) { + fhci_dbg(usb->fhci, "td err fu\n"); + td->toggle = !td->toggle; + td->actual_len += trans_len; + } else { + fhci_dbg(usb->fhci, "td err f!u\n"); + } + } else { + fhci_dbg(usb->fhci, "td err !f\n"); + /* it is not a fatal error -retry this transaction */ + td->nak_cnt = 0; + td->error_cnt++; + td->status = USB_TD_OK; + } + } else if (td->status & USB_TD_TX_ER_NAK) { + /* there was a NAK response */ + fhci_vdbg(usb->fhci, "td nack\n"); + td->nak_cnt++; + td->error_cnt = 0; + td->status = USB_TD_OK; + } else { + /* there was no error on transaction */ + td->error_cnt = 0; + td->nak_cnt = 0; + td->toggle = !td->toggle; + td->actual_len += trans_len; + + if (td->len == td->actual_len) + td_done = true; + } + + if (td_done) + fhci_move_td_from_ed_to_done_list(usb, ed); +} + +/* + * Flush all transmitted packets from BDs + * This routine is called when disabling the USB port to flush all + * transmissions that are allready scheduled in the BDs + */ +void fhci_flush_all_transmissions(struct fhci_usb *usb) +{ + u8 mode; + struct td *td; + + mode = in_8(&usb->fhci->regs->usb_mod); + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + fhci_flush_bds(usb); + + while ((td = fhci_peek_td_from_frame(usb->actual_frame)) != NULL) { + struct packet *pkt = td->pkt; + + pkt->status = USB_TD_TX_ER_TIMEOUT; + fhci_transaction_confirm(usb, pkt); + } + + usb->actual_frame->frame_status = FRAME_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_mod, mode | USB_MODE_EN); +} + +/* + * This function forms the packet and transmit the packet. This function + * will handle all endpoint type:ISO,interrupt,control and bulk + */ +static 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; + break; + 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) { + fhci_vdbg(usb->fhci, "not enough space in this frame: " + "%d %d %d\n", usb->actual_frame->total_bytes, len, + 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 - fhci_get_sof_timer_count(usb)))) { + fhci_dbg(usb->fhci, "not enough time in this frame\n"); + return -1; + } + + /* update frame object fields before transmitting */ + pkt = cq_get(usb->ep0->empty_frame_Q); + if (!pkt) { + fhci_dbg(usb->fhci, "there is no empty frame\n"); + 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); + fhci_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; + fhci_transaction_confirm(usb, pkt); + } else if (fhci_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); + fhci_err(usb->fhci, "host transaction failed\n"); + return -1; + } + + return len; +} + +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); + } +} + +/* + * 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 ed *ed; + struct td *td; + int ans = 1; + u32 save_transaction_time = usb->sw_transaction_time; + + list_for_each_entry(ed, list, node) { + td = ed->td_head; + + if (!td || (td && td->status == USB_TD_INPROGRESS)) + continue; + + if (ed->state != FHCI_ED_OPER) { + if (ed->state == FHCI_ED_URB_DEL) { + td->status = USB_TD_OK; + fhci_move_td_from_ed_to_done_list(usb, ed); + ed->state = FHCI_ED_SKIP; + } + continue; + } + + /* + * if it isn't interrupt pipe or it is not iso pipe and the + * interval time passed + */ + if ((list_type == FHCI_TF_INTR || list_type == FHCI_TF_ISO) && + (((usb->actual_frame->frame_num - + td->start_frame) & 0x7ff) < td->interval)) + continue; + + if (add_packet(usb, ed, td) < 0) + continue; + + /* 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; + fhci_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 ((((in_be16(&fhci->pram->frame_num) & 0x07ff) - + usb->actual_frame->frame_num) & 0x7ff) > 5) + fhci_flush_actual_frame(usb); + else + return -EINVAL; + } + + usb->actual_frame->frame_status = FRAME_IS_PREPARED; + usb->actual_frame->frame_num = in_be16(&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. + */ +void fhci_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); +} + +/* 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; + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + } + + gtm_set_exact_timer16(fhci->timer, usb->max_frame_usage, false); + + fhci_host_transmit_actual_frame(usb); + usb->actual_frame->frame_status = FRAME_IS_TRANSMITTED; + + fhci_schedule_transactions(usb); +} + +/* Handles device disconnected interrupt on port */ +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_usb_disable_interrupt(usb); + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + usb->port_status = FHCI_PORT_DISABLED; + + fhci_stop_sof_timer(fhci); + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&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); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* detect a new device connected on the USB port */ +void fhci_device_connected_interrupt(struct fhci_hcd *fhci) +{ + + struct fhci_usb *usb = fhci->usb_lld; + int state; + int ret; + + fhci_dbg(fhci, "-> %s\n", __func__); + + 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_warn(fhci, "Low-Speed device is not supported, " + "try use BRGx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_LOW; + setbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + 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; + fhci_port_enable(usb); + } else if (state == 2) { + ret = qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + if (ret) { + fhci_warn(fhci, "Full-Speed device is not supported, " + "try use CLKx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_FULL; + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + 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); + fhci_port_enable(usb); + } +out: + fhci_usb_enable_interrupt(usb); + fhci_dbg(fhci, "<- %s\n", __func__); +} + +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); + + gtm_set_exact_timer16(fhci->timer, 1000, false); + + if (usb->actual_frame->frame_status == FRAME_IS_TRANSMITTED) { + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + fhci_push_dummy_bd(usb->ep0); + } + + fhci_schedule_transactions(usb); + + spin_unlock(&fhci->lock); + + return IRQ_HANDLED; +} + +/* Cancel transmission on the USB endpoint */ +static void abort_transmission(struct fhci_usb *usb) +{ + fhci_dbg(usb->fhci, "-> %s\n", __func__); + /* issue stop Tx command */ + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + /* flush Tx FIFOs */ + out_8(&usb->fhci->regs->usb_comm, USB_CMD_FLUSH_FIFO | EP_ZERO); + udelay(1000); + /* reset Tx BDs */ + fhci_flush_bds(usb); + /* issue restart Tx command */ + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + fhci_dbg(usb->fhci, "<- %s\n", __func__); +} + +irqreturn_t fhci_irq(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb; + u16 usb_er = 0; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + usb = fhci->usb_lld; + + usb_er |= in_be16(&usb->fhci->regs->usb_event) & + in_be16(&usb->fhci->regs->usb_mask); + + /* clear event bits for next time */ + out_be16(&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)) { + fhci_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; + out_be16(&usb->fhci->regs->usb_event, + usb->saved_msk); + } else if (usb->port_status == FHCI_PORT_DISABLED) { + if (fhci_ioports_check_bus_state(fhci) == 1 && + usb->port_status != FHCI_PORT_LOW && + usb->port_status != FHCI_PORT_FULL) + fhci_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); + } + + if (usb_er & USB_E_TXB_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXB_MASK; + } + + if (usb_er & USB_E_TXE1_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXE1_MASK; + } + + if (usb_er & USB_E_IDLE_MASK) { + if (usb->port_status == FHCI_PORT_DISABLED && + usb->port_status != FHCI_PORT_LOW && + usb->port_status != FHCI_PORT_FULL) { + usb_er &= ~USB_E_RESET_MASK; + fhci_device_connected_interrupt(fhci); + } else if (usb->port_status == + FHCI_PORT_DISCONNECTING) { + /* XXX usb->port_status = FHCI_PORT_WAITING; */ + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_mask, + usb->saved_msk); + } else { + fhci_dbg_isr(fhci, -1); + } + + usb_er &= ~USB_E_IDLE_MASK; + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + return IRQ_HANDLED; +} + + +/* + * 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; + + disable_irq(fhci->timer->irq); + disable_irq(fhci_to_hcd(fhci)->irq); + spin_lock(&fhci->lock); + + td = fhci_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 */ + fhci_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) { + fhci_urb_complete_free(fhci, urb); + } else if (urb_priv->state == URB_DEL && + ed->state == FHCI_ED_SKIP) { + fhci_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; + fhci_del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } + + td = fhci_remove_td_from_done_list(fhci->hc_list); + } + + spin_unlock(&fhci->lock); + enable_irq(fhci->timer->irq); + enable_irq(fhci_to_hcd(fhci)->irq); +} + +DECLARE_TASKLET(fhci_tasklet, process_done_list, 0); + +/* transfer complted callback */ +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci) +{ + if (!fhci->process_done_task->state) + tasklet_schedule(fhci->process_done_task); + return 0; +} + +/* + * 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 + */ +void fhci_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 = fhci_get_empty_ed(fhci); + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->ep_addr = 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; + fhci_dbg(fhci, "new ep speed=%d max_pkt_size=%d\n", + ed->speed, ed->max_pkt_size); + } + + /* 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 = NULL; + + 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 = fhci_td_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 = fhci_td_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 = fhci_td_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 = fhci_td_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 = fhci_td_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 = fhci_td_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 = fhci_td_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 = 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 = fhci_td_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); + } + break; + 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); + break; + default: + break; + } + } + + fhci_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 000000000000..b40332290319 --- /dev/null +++ b/drivers/usb/host/fhci-tds.c @@ -0,0 +1,626 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 +#include +#include +#include +#include +#include +#include "../core/hcd.h" +#include "fhci.h" + +#define DUMMY_BD_BUFFER 0xdeadbeef +#define DUMMY2_BD_BUFFER 0xbaadf00d + +/* Transaction Descriptors bits */ +#define TD_R 0x8000 /* ready bit */ +#define TD_W 0x2000 /* wrap bit */ +#define TD_I 0x1000 /* interrupt on completion */ +#define TD_L 0x0800 /* last */ +#define TD_TC 0x0400 /* transmit CRC */ +#define TD_CNF 0x0200 /* CNF - Must be always 1 */ +#define TD_LSP 0x0100 /* Low-speed transaction */ +#define TD_PID 0x00c0 /* packet id */ +#define TD_RXER 0x0020 /* Rx error or not */ + +#define TD_NAK 0x0010 /* No ack. */ +#define TD_STAL 0x0008 /* Stall 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 { + __be16 status; + __be16 length; + __be32 buf_ptr; + __be16 extra; + __be16 reserved; +}; + +static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, + struct usb_td __iomem *td, + u16 status) +{ + if (status & TD_W) + return base; + else + return ++td; +} + +void fhci_push_dummy_bd(struct endpoint *ep) +{ + if (ep->already_pushed_dummy_bd == false) { + u16 td_status = in_be16(&ep->empty_td->status); + + out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + ep->already_pushed_dummy_bd = true; + } +} + +/* destroy an USB endpoint */ +void fhci_ep0_free(struct fhci_usb *usb) +{ + struct endpoint *ep; + int size; + + ep = usb->ep0; + if (ep) { + if (ep->td_base) + cpm_muram_free(cpm_muram_offset(ep->td_base)); + + if (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 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + struct endpoint *ep; + struct usb_td __iomem *td; + unsigned long ep_offset; + char *err_for = "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) + return -ENOMEM; + + ep_mem_size = ring_len * sizeof(*td) + sizeof(struct fhci_ep_pram); + ep_offset = cpm_muram_alloc(ep_mem_size, 32); + if (IS_ERR_VALUE(ep_offset)) + goto err; + ep->td_base = cpm_muram_addr(ep_offset); + + /* zero all queue pointers */ + ep->conf_frame_Q = cq_new(ring_len + 2); + ep->empty_frame_Q = cq_new(ring_len + 2); + ep->dummy_packets_Q = cq_new(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 __iomem *)ep->td_base + sizeof(*td) * ring_len; + + ep->conf_td = ep->td_base; + ep->empty_td = ep->td_base; + + ep->already_pushed_dummy_bd = false; + + /* initialize tds */ + td = ep->td_base; + for (i = 0; i < ring_len; i++) { + out_be32(&td->buf_ptr, 0); + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be16(&td->extra, 0); + td++; + } + td--; + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + + /* endpoint structure has been created */ + usb->ep0 = ep; + + return 0; +err: + fhci_ep0_free(usb); + kfree(ep); + fhci_err(usb->fhci, "no memory for the %s\n", err_for); + return -ENOMEM; +} + +/* + * initialize the endpoint register according to the given parameters + * + * artuments: + * usb A pointer to the data strucutre of the USB + * ep A pointer to the endpoint structre + * data_mem The data memory partition(BUS) + */ +void fhci_init_ep_registers(struct fhci_usb *usb, struct endpoint *ep, + enum fhci_mem_alloc data_mem) +{ + u8 rt; + + /* set the endpoint registers according to the endpoint */ + out_be16(&usb->fhci->regs->usb_ep[0], + USB_TRANS_CTR | USB_EP_MF | USB_EP_RTE); + out_be16(&usb->fhci->pram->ep_ptr[0], + cpm_muram_offset(ep->ep_pram_ptr)); + + rt = (BUS_MODE_BO_BE | BUS_MODE_GBL); +#ifdef MULTI_DATA_BUS + if (data_mem == MEM_SECONDARY) + rt |= BUS_MODE_DTB; +#endif + out_8(&ep->ep_pram_ptr->rx_func_code, rt); + out_8(&ep->ep_pram_ptr->tx_func_code, rt); + out_be16(&ep->ep_pram_ptr->rx_buff_len, 1028); + out_be16(&ep->ep_pram_ptr->rx_base, 0); + out_be16(&ep->ep_pram_ptr->tx_base, cpm_muram_offset(ep->td_base)); + out_be16(&ep->ep_pram_ptr->rx_bd_ptr, 0); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, cpm_muram_offset(ep->td_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); +} + +/* + * Collect the submitted frames and inform the application about them + * It is also 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 'fhci_transaction_confirm' routine + */ +static void fhci_td_transaction_confirm(struct fhci_usb *usb) +{ + struct endpoint *ep = usb->ep0; + struct packet *pkt; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + u16 td_length; + u32 buf; + + /* + * collect transmitted BDs from the chip. The routine clears all BDs + * with R bit = 0 and the pointer to data buffer is not NULL, that is + * BDs which point to the transmitted data buffer + */ + while (1) { + td = ep->conf_td; + td_status = in_be16(&td->status); + td_length = in_be16(&td->length); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* check if the TD is empty */ + if (!(!(td_status & TD_R) && ((td_status & ~TD_W) || buf))) + break; + /* check if it is a dummy buffer */ + else if ((buf == DUMMY_BD_BUFFER) && !(td_status & ~TD_W)) + break; + + /* mark TD as empty */ + clrbits16(&td->status, ~TD_W); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + /* advance the TD pointer */ + ep->conf_td = next_bd(ep->td_base, ep->conf_td, td_status); + + /* check if it is a dummy buffer(type2) */ + if ((buf == DUMMY2_BD_BUFFER) && !(td_status & ~TD_W)) + continue; + + pkt = cq_get(ep->conf_frame_Q); + if (!pkt) + fhci_err(usb->fhci, "no frame to confirm\n"); + + if (td_status & TD_ERRORS) { + if (td_status & TD_RXER) { + if (td_status & TD_CR) + pkt->status = USB_TD_RX_ER_CRC; + else if (td_status & TD_AB) + pkt->status = USB_TD_RX_ER_BITSTUFF; + else if (td_status & TD_OV) + pkt->status = USB_TD_RX_ER_OVERUN; + else if (td_status & TD_BOV) + pkt->status = USB_TD_RX_DATA_OVERUN; + else if (td_status & TD_NO) + pkt->status = USB_TD_RX_ER_NONOCT; + else + fhci_err(usb->fhci, "illegal error " + "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; + + fhci_transaction_confirm(usb, pkt); + } +} + +/* + * Submitting a data frame to a specified endpoint of a USB device + * The frame is put in the driver's transmit queue for this endpoint + * + * Arguments: + * usb A pointer to the USB structure + * pkt A pointer to the user frame structure + * trans_type Transaction tyep - IN,OUT or SETUP + * dest_addr Device address - 0~127 + * dest_ep Endpoint number of the device - 0~16 + * trans_mode Pipe type - ISO,Interrupt,bulk or control + * dest_speed USB speed - Low speed or FULL speed + * data_toggle Data sequence toggle - 0 or 1 + */ +u32 fhci_host_transaction(struct fhci_usb *usb, + struct packet *pkt, + enum fhci_ta_type trans_type, + u8 dest_addr, + u8 dest_ep, + enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle) +{ + struct endpoint *ep = usb->ep0; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + + fhci_usb_disable_interrupt(usb); + /* start from the next BD that should be filled */ + td = ep->empty_td; + td_status = in_be16(&td->status); + + if (td_status & TD_R && in_be16(&td->length)) { + /* if the TD is not free */ + fhci_usb_enable_interrupt(usb); + return -1; + } + + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + fhci_usb_enable_interrupt(usb); + pkt->priv_data = td; + out_be32(&td->buf_ptr, virt_to_phys(pkt->data)); + /* sets up transaction parameters - addr,endp,dir,and type */ + extra_data = (dest_ep << TD_ENDP_SHIFT) | dest_addr; + switch (trans_type) { + case FHCI_TA_IN: + extra_data |= TD_TOK_IN; + break; + case FHCI_TA_OUT: + extra_data |= TD_TOK_OUT; + break; + case FHCI_TA_SETUP: + extra_data |= TD_TOK_SETUP; + break; + } + if (trans_mode == FHCI_TF_ISO) + extra_data |= TD_ISO; + out_be16(&td->extra, extra_data); + + /* sets up the buffer descriptor */ + td_status = ((td_status & TD_W) | TD_R | TD_L | TD_I | TD_CNF); + if (!(pkt->info & PKT_NO_CRC)) + td_status |= TD_TC; + + switch (trans_type) { + case FHCI_TA_IN: + if (data_toggle) + pkt->info |= PKT_PID_DATA1; + else + pkt->info |= PKT_PID_DATA0; + break; + default: + if (data_toggle) { + td_status |= TD_PID_DATA1; + pkt->info |= PKT_PID_DATA1; + } else { + td_status |= TD_PID_DATA0; + pkt->info |= PKT_PID_DATA0; + } + break; + } + + if ((dest_speed == FHCI_LOW_SPEED) && + (usb->port_status == FHCI_PORT_FULL)) + td_status |= TD_LSP; + + out_be16(&td->status, td_status); + + /* set up buffer length */ + if (trans_type == FHCI_TA_IN) + out_be16(&td->length, pkt->len + CRC_SIZE); + else + out_be16(&td->length, pkt->len); + + /* put the frame to the confirmation queue */ + cq_put(ep->conf_frame_Q, pkt); + + if (cq_howmany(ep->conf_frame_Q) == 1) + out_8(&usb->fhci->regs->usb_comm, USB_CMD_STR_FIFO); + + return 0; +} + +/* Reset the Tx BD ring */ +void fhci_flush_bds(struct fhci_usb *usb) +{ + u16 extra_data; + u16 td_status; + u32 buf; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + td = ep->td_base; + while (1) { + td_status = in_be16(&td->status); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* if the TD is not empty - we'll confirm it as Timeout */ + if (td_status & TD_R) + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + /* if this TD is dummy - let's skip this TD */ + else if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) + out_be32(&td->buf_ptr, DUMMY2_BD_BUFFER); + /* if this is the last TD - break */ + if (td_status & TD_W) + break; + + td++; + } + + fhci_td_transaction_confirm(usb); + + td = ep->td_base; + do { + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + td++; + } while (!(in_be16(&td->status) & TD_W)); + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; +} + +/* + * Flush all transmitted packets from TDs in the actual frame. + * This routine is called when something wrong with the controller and + * we want to get rid of the actual frame and start again next frame + */ +void fhci_flush_actual_frame(struct fhci_usb *usb) +{ + u8 mode; + u16 tb_ptr; + u16 extra_data; + u16 td_status; + u32 buf_ptr; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + /* disable the USB controller */ + mode = in_8(&usb->fhci->regs->usb_mod); + out_8(&usb->fhci->regs->usb_mod, mode & ~USB_MODE_EN); + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + do { + if (td_status & TD_R) { + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + } else { + out_be32(&td->buf_ptr, 0); + ep->already_pushed_dummy_bd = false; + break; + } + + /* advance the TD pointer */ + td = next_bd(ep->td_base, td, td_status); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + } while ((td_status & TD_R) || buf_ptr); + + fhci_td_transaction_confirm(usb); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; + + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_mod, mode | USB_MODE_EN); +} + +/* handles Tx confirm and Tx error interrupt */ +void fhci_tx_conf_interrupt(struct fhci_usb *usb) +{ + fhci_td_transaction_confirm(usb); + + /* + * Schedule another transaction to this frame only if we have + * already confirmed all transaction in the frame. + */ + if (((fhci_get_sof_timer_count(usb) < usb->max_frame_usage) || + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION)) && + (list_empty(&usb->actual_frame->tds_list))) + fhci_schedule_transactions(usb); +} + +void fhci_host_transmit_actual_frame(struct fhci_usb *usb) +{ + u16 tb_ptr; + u16 td_status; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + + if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) { + struct usb_td __iomem *old_td = td; + + ep->already_pushed_dummy_bd = false; + td_status = in_be16(&td->status); + /* gets the next TD in the ring */ + td = next_bd(ep->td_base, td, td_status); + tb_ptr = cpm_muram_offset(td); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, tb_ptr); + + /* start transmit only if we have something in the TDs */ + if (in_be16(&td->status) & TD_R) + out_8(&usb->fhci->regs->usb_comm, USB_CMD_STR_FIFO); + + if (in_be32(&ep->conf_td->buf_ptr) == DUMMY_BD_BUFFER) { + out_be32(&old_td->buf_ptr, 0); + ep->conf_td = next_bd(ep->td_base, ep->conf_td, + td_status); + } else { + out_be32(&old_td->buf_ptr, DUMMY2_BD_BUFFER); + } + } +} diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h new file mode 100644 index 000000000000..7116284ed21a --- /dev/null +++ b/drivers/usb/host/fhci.h @@ -0,0 +1,607 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish + * Jerry Huang + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../core/hcd.h" + +#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 + +/* 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]; + __be16 usb_ep[4]; /* endpoint register */ + u8 reserved2[4]; + __be16 usb_event; /* event register */ + u8 reserved3[2]; + __be16 usb_mask; /* mask register */ + u8 reserved4[1]; + u8 usb_status; /* status register */ + __be16 usb_sof_tmr; /* Start Of Frame timer */ + u8 reserved5[2]; + __be16 usb_frame_num; /* frame number register */ + u8 reserved6[1]; +}; + +/* Freescale USB HOST */ +struct fhci_pram { + __be16 ep_ptr[4]; /* Endpoint porter reg */ + __be32 rx_state; /* Rx internal state */ + __be32 rx_ptr; /* Rx internal data pointer */ + __be16 frame_num; /* Frame number */ + __be16 rx_cnt; /* Rx byte count */ + __be32 rx_temp; /* Rx temp */ + __be32 rx_data_temp; /* Rx data temp */ + __be16 rx_u_ptr; /* Rx microcode return address temp */ + u8 reserved1[2]; /* reserved area */ + __be32 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 { + __be16 rx_base; /* Rx BD base address */ + __be16 tx_base; /* Tx BD base address */ + u8 rx_func_code; /* Rx function code */ + u8 tx_func_code; /* Tx function code */ + __be16 rx_buff_len; /* Rx buffer length */ + __be16 rx_bd_ptr; /* Rx BD pointer */ + __be16 tx_bd_ptr; /* Tx BD pointer */ + __be32 tx_state; /* Tx internal state */ + __be32 tx_ptr; /* Tx internal data pointer */ + __be16 tx_crc; /* temp transmit CRC */ + __be16 tx_cnt; /* Tx byte count */ + __be32 tx_temp; /* Tx temp */ + __be16 tx_u_ptr; /* Tx microcode return address temp */ + __be16 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_POWER, + NUM_GPIOS, +}; + +enum fhci_pins { + PIN_USBOE = 0, + PIN_USBTP, + PIN_USBTN, + NUM_PINS, +}; + +struct fhci_hcd { + enum qe_clock fullspeed_clk; + enum qe_clock lowspeed_clk; + struct qe_pin *pins[NUM_PINS]; + int gpios[NUM_GPIOS]; + bool alow_gpios[NUM_GPIOS]; + + struct fhci_regs __iomem *regs; /* I/O memory used to communicate */ + struct fhci_pram __iomem *pram; /* Parameter RAM */ + struct gtm_timer *timer; + + 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_POWER_OFF = 0, + FHCI_PORT_DISABLED, + 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 next trans. within this TD */ + u16 iso_index; /* ISO transaction index */ + u16 start_frame; /* start frame time stamp */ + u16 interval; /* interval between trans. (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 __iomem *priv_data; /* private data of the driver (TDs or BDs) */ +}; + +/* 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 endpoint { + /* Pointer to ep parameter RAM */ + struct fhci_ep_pram __iomem *ep_pram_ptr; + + /* Host transactions */ + struct usb_td __iomem *td_base; /* first TD in the ring */ + struct usb_td __iomem *conf_td; /* next TD for confirm after transac */ + struct usb_td __iomem *empty_td;/* next TD for new transaction req. */ + struct kfifo *empty_frame_Q; /* Empty frames list to use */ + struct kfifo *conf_frame_Q; /* frames passed to TDs,waiting for tx */ + struct kfifo *dummy_packets_Q;/* dummy packets for the CRC overun */ + + bool already_pushed_dummy_bd; +}; + +/* 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 */ + 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 inline u16 get_frame_num(struct fhci_hcd *fhci) +{ + return in_be16(&fhci->pram->frame_num) & 0x07ff; +} + +#define fhci_dbg(fhci, fmt, args...) \ + dev_dbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_vdbg(fhci, fmt, args...) \ + dev_vdbg(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 inline struct fhci_hcd *hcd_to_fhci(struct usb_hcd *hcd) +{ + return (struct fhci_hcd *)hcd->hcd_priv; +} + +static inline struct usb_hcd *fhci_to_hcd(struct fhci_hcd *fhci) +{ + return container_of((void *)fhci, struct usb_hcd, hcd_priv); +} + +/* fifo of pointers */ +static inline struct kfifo *cq_new(int size) +{ + return kfifo_alloc(size * sizeof(void *), GFP_KERNEL, NULL); +} + +static inline void cq_delete(struct kfifo *kfifo) +{ + kfifo_free(kfifo); +} + +static inline unsigned int cq_howmany(struct kfifo *kfifo) +{ + return __kfifo_len(kfifo) / sizeof(void *); +} + +static inline int cq_put(struct kfifo *kfifo, void *p) +{ + return __kfifo_put(kfifo, (void *)&p, sizeof(p)); +} + +static inline void *cq_get(struct kfifo *kfifo) +{ + void *p = NULL; + + __kfifo_get(kfifo, (void *)&p, sizeof(p)); + return p; +} + +/* fhci-hcd.c */ +void fhci_start_sof_timer(struct fhci_hcd *fhci); +void fhci_stop_sof_timer(struct fhci_hcd *fhci); +u16 fhci_get_sof_timer_count(struct fhci_usb *usb); +void fhci_usb_enable_interrupt(struct fhci_usb *usb); +void fhci_usb_disable_interrupt(struct fhci_usb *usb); +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci); + +/* fhci-mem.c */ +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td); +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed); +struct ed *fhci_get_empty_ed(struct fhci_hcd *fhci); +struct td *fhci_td_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); +void fhci_add_tds_to_ed(struct ed *ed, struct td **td_list, int number); + +/* fhci-hub.c */ +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status); +void fhci_port_disable(struct fhci_hcd *fhci); +void fhci_port_enable(void *lld); +void fhci_io_port_generate_reset(struct fhci_hcd *fhci); +void fhci_port_reset(void *lld); +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf); +int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); + +/* fhci-tds.c */ +void fhci_flush_bds(struct fhci_usb *usb); +void fhci_flush_actual_frame(struct fhci_usb *usb); +u32 fhci_host_transaction(struct fhci_usb *usb, struct packet *pkt, + enum fhci_ta_type trans_type, u8 dest_addr, + u8 dest_ep, enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle); +void fhci_host_transmit_actual_frame(struct fhci_usb *usb); +void fhci_tx_conf_interrupt(struct fhci_usb *usb); +void fhci_push_dummy_bd(struct endpoint *ep); +u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len); +void fhci_init_ep_registers(struct fhci_usb *usb, + struct endpoint *ep, + enum fhci_mem_alloc data_mem); +void fhci_ep0_free(struct fhci_usb *usb); + +/* fhci-sched.c */ +extern struct tasklet_struct fhci_tasklet; +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt); +void fhci_flush_all_transmissions(struct fhci_usb *usb); +void fhci_schedule_transactions(struct fhci_usb *usb); +void fhci_device_connected_interrupt(struct fhci_hcd *fhci); +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci); +void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb); +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci); +irqreturn_t fhci_irq(struct usb_hcd *hcd); +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd); + +/* fhci-q.h */ +void fhci_urb_complete_free(struct fhci_hcd *fhci, struct urb *urb); +struct td *fhci_remove_td_from_ed(struct ed *ed); +struct td *fhci_remove_td_from_frame(struct fhci_time_frame *frame); +void fhci_move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed); +struct td *fhci_peek_td_from_frame(struct fhci_time_frame *frame); +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td); +struct td *fhci_remove_td_from_done_list(struct fhci_controller_list *p_list); +void fhci_done_td(struct urb *urb, struct td *td); +void fhci_del_ed_list(struct fhci_hcd *fhci, struct ed *ed); + +#ifdef CONFIG_FHCI_DEBUG + +void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er); +void fhci_dfs_destroy(struct fhci_hcd *fhci); +void fhci_dfs_create(struct fhci_hcd *fhci); + +#else + +static inline void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) {} +static inline void fhci_dfs_destroy(struct fhci_hcd *fhci) {} +static inline void fhci_dfs_create(struct fhci_hcd *fhci) {} + +#endif /* CONFIG_FHCI_DEBUG */ + +#endif /* __FHCI_H */ -- cgit v1.2.3