greybus: arche-platform: Enable interrupt support on wake/detect line
authorVaibhav Hiremath <vaibhav.hiremath@linaro.org>
Wed, 24 Feb 2016 23:07:36 +0000 (04:37 +0530)
committerGreg Kroah-Hartman <gregkh@google.com>
Fri, 26 Feb 2016 00:24:23 +0000 (16:24 -0800)
This patch enabled interrupt support on events received over wake/detect
line. The driver follows below state machine,

Default: wake/detect line is high (WD_STATE_IDLE)
On Falling edge:
  SVC initiates boot (either cold/standby).
  On ES3, > 30msec = coldboot, else standby boot.
  Driver moves to WD_STATE_BOOT_INIT
On rising edge (> 30msec):
  SVC expects APB to coldboot
  Driver wakes irq thread which kicks off APB  coldboot
  (WD_STATE_COLDBOOT_TRIG)
On rising edge (< 30msec):
  Driver ignores it, do nothing.

After coldboot of APB, HUB configuration work is scheduled after 2 sec,
allowing enough time for APB<->SVC/Switch to linkup (in multiple
iterations)

Testing Done: Tested on DB3.5 platform.

Signed-off-by: Vaibhav Hiremath <vaibhav.hiremath@linaro.org>
Reviewed-by: Michael Scott <michael.scott@linaro.org>
Tested-by: Michael Scott <michael.scott@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/arche-platform.c

index dcc3844..83db892 100644 (file)
 #include <linux/pinctrl/consumer.h>
 #include <linux/platform_device.h>
 #include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/time.h>
 #include "arche_platform.h"
 
 #include <linux/usb/usb3613.h>
 
+#define WD_COLDBOOT_PULSE_WIDTH_MS     30
+
 enum svc_wakedetect_state {
        WD_STATE_IDLE,                  /* Default state = pulled high/low */
        WD_STATE_BOOT_INIT,             /* WD = falling edge (low) */
@@ -49,6 +54,9 @@ struct arche_platform_drvdata {
 
        struct delayed_work delayed_work;
        enum svc_wakedetect_state wake_detect_state;
+       int wake_detect_irq;
+       spinlock_t lock;
+       unsigned long wake_detect_start;
 
        struct device *dev;
 };
@@ -58,6 +66,18 @@ static inline void svc_reset_onoff(unsigned int gpio, bool onoff)
        gpio_set_value(gpio, onoff);
 }
 
+static int apb_cold_boot(struct device *dev, void *data)
+{
+       int ret;
+
+       ret = apb_ctrl_coldboot(dev);
+       if (ret)
+               dev_warn(dev, "failed to coldboot\n");
+
+       /*Child nodes are independent, so do not exit coldboot operation */
+       return 0;
+}
+
 static int apb_fw_flashing_state(struct device *dev, void *data)
 {
        int ret;
@@ -95,6 +115,86 @@ static void hub_conf_delayed_work(struct work_struct *work)
                dev_warn(arche_pdata->dev, "failed to control hub device\n");
 }
 
+static irqreturn_t arche_platform_wd_irq_thread(int irq, void *devid)
+{
+       struct arche_platform_drvdata *arche_pdata = devid;
+       unsigned long flags;
+
+       spin_lock_irqsave(&arche_pdata->lock, flags);
+       if (arche_pdata->wake_detect_state != WD_STATE_COLDBOOT_TRIG) {
+               /* Something is wrong */
+               spin_unlock_irqrestore(&arche_pdata->lock, flags);
+               return IRQ_HANDLED;
+       }
+
+       arche_pdata->wake_detect_state = WD_STATE_COLDBOOT_START;
+       spin_unlock_irqrestore(&arche_pdata->lock, flags);
+
+       /* Bring APB out of reset: cold boot sequence */
+       device_for_each_child(arche_pdata->dev, NULL, apb_cold_boot);
+
+       spin_lock_irqsave(&arche_pdata->lock, flags);
+       /* USB HUB configuration */
+       schedule_delayed_work(&arche_pdata->delayed_work, msecs_to_jiffies(2000));
+       arche_pdata->wake_detect_state = WD_STATE_IDLE;
+       spin_unlock_irqrestore(&arche_pdata->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t arche_platform_wd_irq(int irq, void *devid)
+{
+       struct arche_platform_drvdata *arche_pdata = devid;
+       unsigned long flags;
+
+       spin_lock_irqsave(&arche_pdata->lock, flags);
+
+       if (gpio_get_value(arche_pdata->wake_detect_gpio)) {
+               /* wake/detect rising */
+
+               /*
+                * If wake/detect line goes high after low, within less than
+                * 30msec, then standby boot sequence is initiated, which is not
+                * supported/implemented as of now. So ignore it.
+                */
+               if (arche_pdata->wake_detect_state == WD_STATE_BOOT_INIT) {
+                       if (time_before(jiffies,
+                                       arche_pdata->wake_detect_start +
+                                       msecs_to_jiffies(WD_COLDBOOT_PULSE_WIDTH_MS))) {
+                               /* No harm with cancellation, even if not pending */
+                               cancel_delayed_work(&arche_pdata->delayed_work);
+                               arche_pdata->wake_detect_state = WD_STATE_IDLE;
+                       } else {
+                               /* Check we are not in middle of irq thread already */
+                               if (arche_pdata->wake_detect_state !=
+                                               WD_STATE_COLDBOOT_START) {
+                                       arche_pdata->wake_detect_state =
+                                               WD_STATE_COLDBOOT_TRIG;
+                                       spin_unlock_irqrestore(&arche_pdata->lock, flags);
+                                       return IRQ_WAKE_THREAD;
+                               }
+                       }
+               }
+       } else {
+               /* wake/detect falling */
+               if (arche_pdata->wake_detect_state == WD_STATE_IDLE) {
+                       arche_pdata->wake_detect_start = jiffies;
+                       /* No harm with cancellation even if it is not pending*/
+                       cancel_delayed_work(&arche_pdata->delayed_work);
+                       /*
+                        * In the begining, when wake/detect goes low (first time), we assume
+                        * it is meant for coldboot and set the flag. If wake/detect line stays low
+                        * beyond 30msec, then it is coldboot else fallback to standby boot.
+                        */
+                       arche_pdata->wake_detect_state = WD_STATE_BOOT_INIT;
+               }
+       }
+
+       spin_unlock_irqrestore(&arche_pdata->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
 static int arche_platform_coldboot_seq(struct arche_platform_drvdata *arche_pdata)
 {
        int ret;
@@ -148,6 +248,8 @@ static void arche_platform_fw_flashing_seq(struct arche_platform_drvdata *arche_
 
 static void arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)
 {
+       unsigned long flags;
+
        if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
                return;
 
@@ -156,7 +258,9 @@ static void arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pda
                /* Send disconnect/detach event to SVC */
                gpio_set_value(arche_pdata->wake_detect_gpio, 0);
                usleep_range(100, 200);
+               spin_lock_irqsave(&arche_pdata->lock, flags);
                arche_pdata->wake_detect_state = WD_STATE_IDLE;
+               spin_unlock_irqrestore(&arche_pdata->lock, flags);
 
                clk_disable_unprepare(arche_pdata->svc_ref_clk);
        }
@@ -344,6 +448,22 @@ static int arche_platform_probe(struct platform_device *pdev)
 
        arche_pdata->dev = &pdev->dev;
 
+       spin_lock_init(&arche_pdata->lock);
+       arche_pdata->wake_detect_irq =
+               gpio_to_irq(arche_pdata->wake_detect_gpio);
+
+       ret = devm_request_threaded_irq(dev, arche_pdata->wake_detect_irq,
+                       arche_platform_wd_irq,
+                       arche_platform_wd_irq_thread,
+                       IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+                       dev_name(dev), arche_pdata);
+       if (ret) {
+               dev_err(dev, "failed to request wake detect IRQ %d\n", ret);
+               return ret;
+       }
+       /* Enable it only after  sending wake/detect event */
+       disable_irq(arche_pdata->wake_detect_irq);
+
        ret = device_create_file(dev, &dev_attr_state);
        if (ret) {
                dev_err(dev, "failed to create state file in sysfs\n");