libertas: Add auto deep sleep support for SD8385/SD8686/SD8688

Add timer based auto deep sleep feature in libertas driver which can be
configured using iwconfig command. This is tested on SD8688, SD8686 cards
with firmware versions 10.38.1.p25, 9.70.4.p0 respectively on 32-bit and 64-bit
platforms. Tests have been done for USB/CS cards to make sure that the patch
won't break USB/CS code. We didn't test the if_spi driver.

Signed-off-by: Amitkumar Karwar <akarwar@marvell.com>
Signed-off-by: Bing Zhao <bzhao@marvell.com>
Acked-by: Dan Williams <dcbw@redhat.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/libertas/main.c b/drivers/net/wireless/libertas/main.c
index 87b4e49..9b2a917 100644
--- a/drivers/net/wireless/libertas/main.c
+++ b/drivers/net/wireless/libertas/main.c
@@ -574,8 +574,10 @@
 	priv->dnld_sent = DNLD_RES_RECEIVED;
 
 	/* Wake main thread if commands are pending */
-	if (!priv->cur_cmd || priv->tx_pending_len > 0)
-		wake_up_interruptible(&priv->waitq);
+	if (!priv->cur_cmd || priv->tx_pending_len > 0) {
+		if (!priv->wakeup_dev_required)
+			wake_up_interruptible(&priv->waitq);
+	}
 
 	spin_unlock_irqrestore(&priv->driver_lock, flags);
 	lbs_deb_leave(LBS_DEB_THREAD);
@@ -770,7 +772,8 @@
 			shouldsleep = 0;	/* We have a command response */
 		else if (priv->cur_cmd)
 			shouldsleep = 1;	/* Can't send a command; one already running */
-		else if (!list_empty(&priv->cmdpendingq))
+		else if (!list_empty(&priv->cmdpendingq) &&
+					!(priv->wakeup_dev_required))
 			shouldsleep = 0;	/* We have a command to send */
 		else if (__kfifo_len(priv->event_fifo))
 			shouldsleep = 0;	/* We have an event to process */
@@ -822,6 +825,26 @@
 		}
 		spin_unlock_irq(&priv->driver_lock);
 
+		/* Process hardware events, e.g. card removed, link lost */
+		spin_lock_irq(&priv->driver_lock);
+		while (__kfifo_len(priv->event_fifo)) {
+			u32 event;
+			__kfifo_get(priv->event_fifo, (unsigned char *) &event,
+				sizeof(event));
+			spin_unlock_irq(&priv->driver_lock);
+			lbs_process_event(priv, event);
+			spin_lock_irq(&priv->driver_lock);
+		}
+		spin_unlock_irq(&priv->driver_lock);
+
+		if (priv->wakeup_dev_required) {
+			lbs_deb_thread("Waking up device...\n");
+			/* Wake up device */
+			if (priv->exit_deep_sleep(priv))
+				lbs_deb_thread("Wakeup device failed\n");
+			continue;
+		}
+
 		/* command timeout stuff */
 		if (priv->cmd_timed_out && priv->cur_cmd) {
 			struct cmd_ctrl_node *cmdnode = priv->cur_cmd;
@@ -849,18 +872,7 @@
 		}
 		priv->cmd_timed_out = 0;
 
-		/* Process hardware events, e.g. card removed, link lost */
-		spin_lock_irq(&priv->driver_lock);
-		while (__kfifo_len(priv->event_fifo)) {
-			u32 event;
 
-			__kfifo_get(priv->event_fifo, (unsigned char *) &event,
-				sizeof(event));
-			spin_unlock_irq(&priv->driver_lock);
-			lbs_process_event(priv, event);
-			spin_lock_irq(&priv->driver_lock);
-		}
-		spin_unlock_irq(&priv->driver_lock);
 
 		if (!priv->fw_ready)
 			continue;
@@ -894,6 +906,9 @@
 		    (priv->psstate == PS_STATE_PRE_SLEEP))
 			continue;
 
+		if (priv->is_deep_sleep)
+			continue;
+
 		/* Execute the next command */
 		if (!priv->dnld_sent && !priv->cur_cmd)
 			lbs_execute_next_command(priv);
@@ -928,6 +943,7 @@
 	}
 
 	del_timer(&priv->command_timer);
+	del_timer(&priv->auto_deepsleep_timer);
 	wake_up_all(&priv->cmd_pending);
 
 	lbs_deb_leave(LBS_DEB_THREAD);
@@ -1050,6 +1066,60 @@
 	lbs_deb_leave(LBS_DEB_CMD);
 }
 
+/**
+ *  This function put the device back to deep sleep mode when timer expires
+ *  and no activity (command, event, data etc.) is detected.
+ */
+static void auto_deepsleep_timer_fn(unsigned long data)
+{
+	struct lbs_private *priv = (struct lbs_private *)data;
+	int ret;
+
+	lbs_deb_enter(LBS_DEB_CMD);
+
+	if (priv->is_activity_detected) {
+		priv->is_activity_detected = 0;
+	} else {
+		if (priv->is_auto_deep_sleep_enabled &&
+				(!priv->wakeup_dev_required) &&
+				(priv->connect_status != LBS_CONNECTED)) {
+			lbs_deb_main("Entering auto deep sleep mode...\n");
+			ret = lbs_prepare_and_send_command(priv,
+					CMD_802_11_DEEP_SLEEP, 0,
+					0, 0, NULL);
+		}
+	}
+	mod_timer(&priv->auto_deepsleep_timer , jiffies +
+				(priv->auto_deep_sleep_timeout * HZ)/1000);
+	lbs_deb_leave(LBS_DEB_CMD);
+}
+
+int lbs_enter_auto_deep_sleep(struct lbs_private *priv)
+{
+	lbs_deb_enter(LBS_DEB_SDIO);
+
+	priv->is_auto_deep_sleep_enabled = 1;
+	if (priv->is_deep_sleep)
+		priv->wakeup_dev_required = 1;
+	mod_timer(&priv->auto_deepsleep_timer ,
+			jiffies + (priv->auto_deep_sleep_timeout * HZ)/1000);
+
+	lbs_deb_leave(LBS_DEB_SDIO);
+	return 0;
+}
+
+int lbs_exit_auto_deep_sleep(struct lbs_private *priv)
+{
+	lbs_deb_enter(LBS_DEB_SDIO);
+
+	priv->is_auto_deep_sleep_enabled = 0;
+	priv->auto_deep_sleep_timeout = 0;
+	del_timer(&priv->auto_deepsleep_timer);
+
+	lbs_deb_leave(LBS_DEB_SDIO);
+	return 0;
+}
+
 static void lbs_sync_channel_worker(struct work_struct *work)
 {
 	struct lbs_private *priv = container_of(work, struct lbs_private,
@@ -1099,11 +1169,17 @@
 	priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE;
 	priv->psmode = LBS802_11POWERMODECAM;
 	priv->psstate = PS_STATE_FULL_POWER;
+	priv->is_deep_sleep = 0;
+	priv->is_auto_deep_sleep_enabled = 0;
+	priv->wakeup_dev_required = 0;
+	init_waitqueue_head(&priv->ds_awake_q);
 
 	mutex_init(&priv->lock);
 
 	setup_timer(&priv->command_timer, command_timer_fn,
 		(unsigned long)priv);
+	setup_timer(&priv->auto_deepsleep_timer, auto_deepsleep_timer_fn,
+			(unsigned long)priv);
 
 	INIT_LIST_HEAD(&priv->cmdfreeq);
 	INIT_LIST_HEAD(&priv->cmdpendingq);
@@ -1142,6 +1218,7 @@
 	if (priv->event_fifo)
 		kfifo_free(priv->event_fifo);
 	del_timer(&priv->command_timer);
+	del_timer(&priv->auto_deepsleep_timer);
 	kfree(priv->networks);
 	priv->networks = NULL;
 
@@ -1272,6 +1349,11 @@
 	wrqu.ap_addr.sa_family = ARPHRD_ETHER;
 	wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL);
 
+	if (priv->is_deep_sleep) {
+		priv->is_deep_sleep = 0;
+		wake_up_interruptible(&priv->ds_awake_q);
+	}
+
 	/* Stop the thread servicing the interrupts */
 	priv->surpriseremoved = 1;
 	kthread_stop(priv->main_thread);
@@ -1392,6 +1474,7 @@
 
 	/* Delete the timeout of the currently processing command */
 	del_timer_sync(&priv->command_timer);
+	del_timer_sync(&priv->auto_deepsleep_timer);
 
 	/* Flush pending command nodes */
 	spin_lock_irqsave(&priv->driver_lock, flags);