summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/otg/ab5500-usb.c58
1 files changed, 58 insertions, 0 deletions
diff --git a/drivers/usb/otg/ab5500-usb.c b/drivers/usb/otg/ab5500-usb.c
index c57234d92be..1d38576a8f4 100644
--- a/drivers/usb/otg/ab5500-usb.c
+++ b/drivers/usb/otg/ab5500-usb.c
@@ -21,6 +21,7 @@
#include <linux/kernel_stat.h>
#include <mach/gpio.h>
#include <mach/reboot_reasons.h>
+#include <linux/pm_qos_params.h>
/* AB5500 USB macros
*/
@@ -46,6 +47,10 @@
#define AB5500_USB_LINK_STATUS_MASK_V2 0xF8
#define USB_PROBE_DELAY 1000 /* 1 seconds */
+#define USB_LIMIT (200) /* If we have more than 200 irqs per second */
+
+static struct pm_qos_request_list usb_pm_qos_latency;
+static bool usb_pm_qos_is_latency_0;
#define PUBLIC_ID_BACKUPRAM1 (U5500_BACKUPRAM1_BASE + 0x0FC0)
#define MAX_USB_SERIAL_NUMBER_LEN 31
@@ -145,6 +150,42 @@ static void ab5500_usb_wd_workaround(struct ab5500_usb *ab)
udelay(AB5500_WATCHDOG_DELAY_US);
}
+static void ab5500_usb_load(struct work_struct *work)
+{
+ int cpu;
+ unsigned int num_irqs = 0;
+ static unsigned int old_num_irqs = UINT_MAX;
+ struct delayed_work *work_usb_workaround = to_delayed_work(work);
+ struct ab5500_usb *ab = container_of(work_usb_workaround,
+ struct ab5500_usb, work_usb_workaround);
+
+ for_each_online_cpu(cpu)
+ num_irqs += kstat_irqs_cpu(IRQ_DB5500_USBOTG, cpu);
+
+ if ((num_irqs > old_num_irqs) &&
+ (num_irqs - old_num_irqs) > USB_LIMIT) {
+
+ if (!usb_pm_qos_is_latency_0) {
+
+ pm_qos_add_request(&usb_pm_qos_latency,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+ usb_pm_qos_is_latency_0 = true;
+ }
+ } else {
+
+ if (usb_pm_qos_is_latency_0) {
+
+ pm_qos_remove_request(&usb_pm_qos_latency);
+ usb_pm_qos_is_latency_0 = false;
+ }
+ }
+ old_num_irqs = num_irqs;
+
+ schedule_delayed_work_on(0,
+ &ab->work_usb_workaround,
+ msecs_to_jiffies(USB_PROBE_DELAY));
+}
+
static void ab5500_usb_phy_enable(struct ab5500_usb *ab, bool sel_host)
{
u8 bit;
@@ -166,6 +207,11 @@ static void ab5500_usb_phy_enable(struct ab5500_usb *ab, bool sel_host)
}
ux500_restore_context();
+ if (sel_host) {
+ schedule_delayed_work_on(0,
+ &ab->work_usb_workaround,
+ msecs_to_jiffies(USB_PROBE_DELAY));
+ }
abx500_mask_and_set_register_interruptible(ab->dev,
AB5500_BANK_USB,
AB5500_USB_PHY_CTRL_REG,
@@ -191,6 +237,15 @@ static void ab5500_usb_phy_disable(struct ab5500_usb *ab, bool sel_host)
clk_disable(ab->sysclk);
regulator_disable(ab->v_ape);
ab->usb_gpio->disable();
+
+ if (sel_host) {
+ if (usb_pm_qos_is_latency_0) {
+
+ pm_qos_remove_request(&usb_pm_qos_latency);
+ usb_pm_qos_is_latency_0 = false;
+ }
+ cancel_delayed_work_sync(&ab->work_usb_workaround);
+ }
}
#define ab5500_usb_peri_phy_en(ab) ab5500_usb_phy_enable(ab, false)
@@ -724,6 +779,9 @@ static int __devinit ab5500_usb_probe(struct platform_device *pdev)
*/
INIT_DELAYED_WORK(&ab->dwork, ab5500_usb_delayed_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&ab->work_usb_workaround,
+ ab5500_usb_load);
+
err = otg_set_transceiver(&ab->otg);
if (err)
dev_err(&pdev->dev, "Can't register transceiver\n");