summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/arche-platform.c
diff options
context:
space:
mode:
authorVaibhav Hiremath <vaibhav.hiremath@linaro.org>2016-02-25 04:37:36 +0530
committerGreg Kroah-Hartman <gregkh@google.com>2016-02-25 16:24:23 -0800
commitf760bbfb5c10952739fd0d39869cdd6a43844037 (patch)
tree3094e164073086b4dc23a9c5445c1962499c8118 /drivers/staging/greybus/arche-platform.c
parent685353c12ea33e99d1daba5b3721b9033cdbdb87 (diff)
greybus: arche-platform: Enable interrupt support on wake/detect line
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>
Diffstat (limited to 'drivers/staging/greybus/arche-platform.c')
-rw-r--r--drivers/staging/greybus/arche-platform.c120
1 files changed, 120 insertions, 0 deletions
diff --git a/drivers/staging/greybus/arche-platform.c b/drivers/staging/greybus/arche-platform.c
index dcc3844854c2..83db892a8a5d 100644
--- a/drivers/staging/greybus/arche-platform.c
+++ b/drivers/staging/greybus/arche-platform.c
@@ -17,10 +17,15 @@
#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");