msm: bam_dmux: add support for disabling A2 power collapse

A2 may signal via a special open command that it is not supporting the
A2 power collapse feature.  Add support to disable the A2 power collapse
feature when the A2 requests it.

Change-Id: I3d104ae345758310ecdea7a38a23283a5e3c7849
Signed-off-by: Jeffrey Hugo <jhugo@codeaurora.org>
Signed-off-by: Eric Holmberg <eholmber@codeaurora.org>
diff --git a/arch/arm/mach-msm/bam_dmux.c b/arch/arm/mach-msm/bam_dmux.c
index 6414359..4ccfbb7 100644
--- a/arch/arm/mach-msm/bam_dmux.c
+++ b/arch/arm/mach-msm/bam_dmux.c
@@ -40,9 +40,11 @@
 
 #define BAM_MUX_HDR_MAGIC_NO    0x33fc
 
-#define BAM_MUX_HDR_CMD_DATA    0
-#define BAM_MUX_HDR_CMD_OPEN    1
-#define BAM_MUX_HDR_CMD_CLOSE   2
+#define BAM_MUX_HDR_CMD_DATA		0
+#define BAM_MUX_HDR_CMD_OPEN		1
+#define BAM_MUX_HDR_CMD_CLOSE		2
+#define BAM_MUX_HDR_CMD_STATUS		3 /* unused */
+#define BAM_MUX_HDR_CMD_OPEN_NO_A2_PC	4
 
 #define POLLING_MIN_SLEEP	950	/* 0.95 ms */
 #define POLLING_MAX_SLEEP	1050	/* 1.05 ms */
@@ -188,6 +190,8 @@
 static void vote_dfab(void);
 static void unvote_dfab(void);
 static void kickoff_ul_wakeup_func(struct work_struct *work);
+static void grab_wakelock(void);
+static void release_wakelock(void);
 
 static int bam_is_connected;
 static DEFINE_MUTEX(wakeup_lock);
@@ -201,6 +205,13 @@
 static int bam_connection_is_active;
 static int wait_for_ack;
 static struct wake_lock bam_wakelock;
+static int a2_pc_disabled;
+static DEFINE_MUTEX(dfab_status_lock);
+static int dfab_is_on;
+static int wait_for_dfab;
+static struct completion dfab_unvote_completion;
+static DEFINE_SPINLOCK(wakelock_reference_lock);
+static int wakelock_reference_count;
 /* End A2 power collaspe */
 
 /* subsystem restart */
@@ -262,6 +273,7 @@
 
 	/*
 	 * States
+	 * D: 1 = Power collapse disabled
 	 * R: 1 = in global reset
 	 * P: 1 = BAM is powered up
 	 * A: 1 = BAM initialized and ready for data
@@ -272,8 +284,9 @@
 	 * A: 1 = Uplink ACK received
 	 */
 	len += scnprintf(buff, sizeof(buff),
-		"<DMUX> %u.%09lu %c%c%c %c%c%c%c ",
+		"<DMUX> %u.%09lu %c%c%c%c %c%c%c%c ",
 		(unsigned)t_now, nanosec_rem,
+		a2_pc_disabled ? 'D' : 'd',
 		in_global_reset ? 'R' : 'r',
 		bam_dmux_power_state ? 'P' : 'p',
 		bam_connection_is_active ? 'A' : 'a',
@@ -395,13 +408,28 @@
 	queue_rx();
 }
 
+static inline void handle_bam_mux_cmd_open(struct bam_mux_hdr *rx_hdr)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags);
+	bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN;
+	bam_ch[rx_hdr->ch_id].num_tx_pkts = 0;
+	spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags);
+	queue_rx();
+	ret = platform_device_add(bam_ch[rx_hdr->ch_id].pdev);
+	if (ret)
+		pr_err("%s: platform_device_add() error: %d\n",
+				__func__, ret);
+}
+
 static void handle_bam_mux_cmd(struct work_struct *work)
 {
 	unsigned long flags;
 	struct bam_mux_hdr *rx_hdr;
 	struct rx_pkt_info *info;
 	struct sk_buff *rx_skb;
-	int ret;
 
 	info = container_of(work, struct rx_pkt_info, work);
 	rx_skb = info->skb;
@@ -442,17 +470,22 @@
 		bam_mux_process_data(rx_skb);
 		break;
 	case BAM_MUX_HDR_CMD_OPEN:
-		bam_dmux_log("%s: opening cid %d\n", __func__,
+		bam_dmux_log("%s: opening cid %d PC enabled\n", __func__,
 				rx_hdr->ch_id);
-		spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags);
-		bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN;
-		bam_ch[rx_hdr->ch_id].num_tx_pkts = 0;
-		spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags);
-		queue_rx();
-		ret = platform_device_add(bam_ch[rx_hdr->ch_id].pdev);
-		if (ret)
-			pr_err("%s: platform_device_add() error: %d\n",
-					__func__, ret);
+		handle_bam_mux_cmd_open(rx_hdr);
+		dev_kfree_skb_any(rx_skb);
+		break;
+	case BAM_MUX_HDR_CMD_OPEN_NO_A2_PC:
+		bam_dmux_log("%s: opening cid %d PC disabled\n", __func__,
+				rx_hdr->ch_id);
+
+		if (!a2_pc_disabled) {
+			a2_pc_disabled = 1;
+			schedule_delayed_work(&ul_timeout_work,
+				msecs_to_jiffies(UL_TIMEOUT_DELAY));
+		}
+
+		handle_bam_mux_cmd_open(rx_hdr);
 		dev_kfree_skb_any(rx_skb);
 		break;
 	case BAM_MUX_HDR_CMD_CLOSE:
@@ -905,6 +938,7 @@
 		goto fail;
 	}
 	polling_mode = 0;
+	release_wakelock();
 
 	/* handle any rx packets before interrupt was enabled */
 	while (bam_connection_is_active && !polling_mode) {
@@ -1034,6 +1068,7 @@
 					" not disabled\n", __func__, ret);
 				break;
 			}
+			grab_wakelock();
 			polling_mode = 1;
 			queue_work(bam_mux_rx_workqueue, &rx_timer_work);
 		}
@@ -1106,6 +1141,7 @@
 		i += scnprintf(buff - i, max - i,
 			"<DMUX> timestamp FLAGS [Message]\n"
 			"FLAGS:\n"
+			"\tD: 1 = Power collapse disabled\n"
 			"\tR: 1 = in global reset\n"
 			"\tP: 1 = BAM is powered up\n"
 			"\tA: 1 = BAM initialized and ready for data\n"
@@ -1268,17 +1304,30 @@
 	} else {
 		bam_dmux_log("%s: powerdown\n", __func__);
 		verify_tx_queue_is_empty(__func__);
-		wait_for_ack = 1;
-		INIT_COMPLETION(ul_wakeup_ack_completion);
-		smsm_change_state(SMSM_APPS_STATE, SMSM_A2_POWER_CONTROL, 0);
+
+		if (a2_pc_disabled) {
+			wait_for_dfab = 1;
+			INIT_COMPLETION(dfab_unvote_completion);
+			release_wakelock();
+		} else {
+			wait_for_ack = 1;
+			INIT_COMPLETION(ul_wakeup_ack_completion);
+			power_vote(0);
+		}
 		bam_is_connected = 0;
 		notify_all(BAM_DMUX_UL_DISCONNECTED, (unsigned long)(NULL));
 	}
 	write_unlock_irqrestore(&ul_wakeup_lock, flags);
+	if (a2_pc_disabled && wait_for_dfab) {
+		unvote_dfab();
+		complete_all(&dfab_unvote_completion);
+		wait_for_dfab = 0;
+	}
 }
 static void ul_wakeup(void)
 {
 	int ret;
+	static int called_before;
 
 	mutex_lock(&wakeup_lock);
 	if (bam_is_connected) { /* bam got connected before lock grabbed */
@@ -1287,6 +1336,28 @@
 		return;
 	}
 
+	if (a2_pc_disabled) {
+		/*
+		 * don't grab the wakelock the first time because it is
+		 * already grabbed when a2 powers on
+		 */
+		if (likely(called_before))
+			grab_wakelock();
+		else
+			called_before = 1;
+		if (wait_for_dfab) {
+			ret = wait_for_completion_interruptible_timeout(
+					&dfab_unvote_completion, HZ);
+			BUG_ON(ret == 0);
+		}
+		vote_dfab();
+		schedule_delayed_work(&ul_timeout_work,
+				msecs_to_jiffies(UL_TIMEOUT_DELAY));
+		bam_is_connected = 1;
+		mutex_unlock(&wakeup_lock);
+		return;
+	}
+
 	/*
 	 * must wait for the previous power down request to have been acked
 	 * chances are it already came in and this will just fall through
@@ -1297,6 +1368,7 @@
 		ret = wait_for_completion_interruptible_timeout(
 					&ul_wakeup_ack_completion, HZ);
 		BUG_ON(ret == 0);
+		wait_for_ack = 0;
 	}
 	INIT_COMPLETION(ul_wakeup_ack_completion);
 	power_vote(1);
@@ -1382,14 +1454,66 @@
 {
 	int rc;
 
+	bam_dmux_log("%s\n", __func__);
+	mutex_lock(&dfab_status_lock);
+	if (dfab_is_on) {
+		bam_dmux_log("%s: dfab is already on\n", __func__);
+		mutex_unlock(&dfab_status_lock);
+		return;
+	}
 	rc = clk_enable(dfab_clk);
 	if (rc)
-		pr_err("bam_dmux vote for dfab failed rc = %d\n", rc);
+		DMUX_LOG_KERR("bam_dmux vote for dfab failed rc = %d\n", rc);
+	dfab_is_on = 1;
+	mutex_unlock(&dfab_status_lock);
 }
 
 static void unvote_dfab(void)
 {
+	bam_dmux_log("%s\n", __func__);
+	mutex_lock(&dfab_status_lock);
+	if (!dfab_is_on) {
+		DMUX_LOG_KERR("%s: dfab is already off\n", __func__);
+		dump_stack();
+		mutex_unlock(&dfab_status_lock);
+		return;
+	}
 	clk_disable(dfab_clk);
+	dfab_is_on = 0;
+	mutex_unlock(&dfab_status_lock);
+}
+
+/* reference counting wrapper around wakelock */
+static void grab_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wakelock_reference_lock, flags);
+	bam_dmux_log("%s: ref count = %d\n", __func__,
+						wakelock_reference_count);
+	if (wakelock_reference_count == 0)
+		wake_lock(&bam_wakelock);
+	++wakelock_reference_count;
+	spin_unlock_irqrestore(&wakelock_reference_lock, flags);
+}
+
+static void release_wakelock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wakelock_reference_lock, flags);
+	if (wakelock_reference_count == 0) {
+		DMUX_LOG_KERR("%s: bam_dmux wakelock not locked\n", __func__);
+		dump_stack();
+		spin_unlock_irqrestore(&wakelock_reference_lock, flags);
+		return;
+	}
+	bam_dmux_log("%s: ref count = %d\n", __func__,
+						wakelock_reference_count);
+	--wakelock_reference_count;
+	if (wakelock_reference_count == 0)
+		wake_unlock(&bam_wakelock);
+	spin_unlock_irqrestore(&wakelock_reference_lock, flags);
 }
 
 static int restart_notifier_cb(struct notifier_block *this,
@@ -1407,6 +1531,7 @@
 
 	bam_dmux_log("%s: begin\n", __func__);
 	in_global_reset = 1;
+	a2_pc_disabled = 0;
 	for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) {
 		temp_remote_status = bam_ch_is_remote_open(i);
 		bam_ch[i].status &= ~BAM_CH_REMOTE_OPEN;
@@ -1664,16 +1789,16 @@
 
 	if (bam_mux_initialized && new_state & SMSM_A2_POWER_CONTROL) {
 		bam_dmux_log("%s: reconnect\n", __func__);
-		wake_lock(&bam_wakelock);
+		grab_wakelock();
 		reconnect_to_bam();
 	} else if (bam_mux_initialized &&
 					!(new_state & SMSM_A2_POWER_CONTROL)) {
 		bam_dmux_log("%s: disconnect\n", __func__);
 		disconnect_to_bam();
-		wake_unlock(&bam_wakelock);
+		release_wakelock();
 	} else if (new_state & SMSM_A2_POWER_CONTROL) {
 		bam_dmux_log("%s: init\n", __func__);
-		wake_lock(&bam_wakelock);
+		grab_wakelock();
 		ret = bam_init();
 		if (ret) {
 			ret = bam_init_fallback();
@@ -1740,6 +1865,7 @@
 
 	init_completion(&ul_wakeup_ack_completion);
 	init_completion(&bam_connection_completion);
+	init_completion(&dfab_unvote_completion);
 	INIT_DELAYED_WORK(&ul_timeout_work, ul_timeout);
 	wake_lock_init(&bam_wakelock, WAKE_LOCK_SUSPEND, "bam_dmux_wakelock");