Merge "msm: bam_dmux: add A2 power collapse support" into msm-3.0
diff --git a/arch/arm/mach-msm/bam_dmux.c b/arch/arm/mach-msm/bam_dmux.c
index 5ed4456..304a687 100644
--- a/arch/arm/mach-msm/bam_dmux.c
+++ b/arch/arm/mach-msm/bam_dmux.c
@@ -24,6 +24,7 @@
 #include <linux/sched.h>
 #include <linux/skbuff.h>
 #include <linux/debugfs.h>
+#include <linux/clk.h>
 
 #include <mach/sps.h>
 #include <mach/bam_dmux.h>
@@ -119,6 +120,7 @@
 #define BUFFER_SIZE		2048
 #define NUM_BUFFERS		32
 static struct sps_bam_props a2_props;
+static u32 a2_device_handle;
 static struct sps_pipe *bam_tx_pipe;
 static struct sps_pipe *bam_rx_pipe;
 static struct sps_connect tx_connection;
@@ -155,6 +157,26 @@
 static struct workqueue_struct *bam_mux_rx_workqueue;
 static struct workqueue_struct *bam_mux_tx_workqueue;
 
+/* A2 power collaspe */
+#define UL_TIMEOUT_DELAY 1000	/* in ms */
+static void toggle_apps_ack(void);
+static void reconnect_to_bam(void);
+static void disconnect_to_bam(void);
+static void ul_wakeup(void);
+static void ul_timeout(struct work_struct *work);
+static void vote_dfab(void);
+static void unvote_dfab(void);
+
+static int bam_is_connected;
+static DEFINE_MUTEX(wakeup_lock);
+static struct completion ul_wakeup_ack_completion;
+static struct completion bam_connection_completion;
+static struct delayed_work ul_timeout_work;
+static int ul_packet_written;
+static struct clk *dfab_clk;
+static DEFINE_RWLOCK(ul_wakeup_lock);
+/* End A2 power collaspe */
+
 #define bam_ch_is_open(x)						\
 	(bam_ch[(x)].status == (BAM_CH_LOCAL_OPEN | BAM_CH_REMOTE_OPEN))
 
@@ -316,6 +338,7 @@
 				pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT);
 
 	mutex_unlock(&bam_mux_lock);
+	ul_packet_written = 1;
 	return rc;
 }
 
@@ -365,6 +388,10 @@
 	}
 	spin_unlock_irqrestore(&bam_ch[id].lock, flags);
 
+	read_lock(&ul_wakeup_lock);
+	if (!bam_is_connected)
+		ul_wakeup();
+
 	/* if skb do not have any tailroom for padding,
 	   copy the skb into a new expanded skb */
 	if ((skb->len & 0x3) && (skb_tailroom(skb) < (4 - (skb->len & 0x3)))) {
@@ -421,6 +448,8 @@
 	INIT_WORK(&pkt->work, bam_mux_write_done);
 	rc = sps_transfer_one(bam_tx_pipe, dma_address, skb->len,
 				pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT);
+	ul_packet_written = 1;
+	read_unlock(&ul_wakeup_lock);
 	return rc;
 }
 
@@ -464,6 +493,10 @@
 	bam_ch[id].status |= BAM_CH_LOCAL_OPEN;
 	spin_unlock_irqrestore(&bam_ch[id].lock, flags);
 
+	read_lock(&ul_wakeup_lock);
+	if (!bam_is_connected)
+		ul_wakeup();
+
 	hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
 	hdr->cmd = BAM_MUX_HDR_CMD_OPEN;
 	hdr->reserved = 0;
@@ -472,6 +505,7 @@
 	hdr->pad_len = 0;
 
 	rc = bam_mux_write_cmd((void *)hdr, sizeof(struct bam_mux_hdr));
+	read_unlock(&ul_wakeup_lock);
 
 open_done:
 	DBG("%s: opened ch %d\n", __func__, id);
@@ -491,6 +525,10 @@
 		return -ENODEV;
 	spin_lock_irqsave(&bam_ch[id].lock, flags);
 
+	read_lock(&ul_wakeup_lock);
+	if (!bam_is_connected)
+		ul_wakeup();
+
 	bam_ch[id].notify = NULL;
 	bam_ch[id].priv = NULL;
 	bam_ch[id].status &= ~BAM_CH_LOCAL_OPEN;
@@ -509,6 +547,7 @@
 	hdr->pad_len = 0;
 
 	rc = bam_mux_write_cmd((void *)hdr, sizeof(struct bam_mux_hdr));
+	read_unlock(&ul_wakeup_lock);
 
 	DBG("%s: closed ch %d\n", __func__, id);
 	return rc;
@@ -708,6 +747,101 @@
 
 #endif
 
+static void ul_timeout(struct work_struct *work)
+{
+	write_lock(&ul_wakeup_lock);
+	if (ul_packet_written) {
+		ul_packet_written = 0;
+		schedule_delayed_work(&ul_timeout_work,
+				msecs_to_jiffies(UL_TIMEOUT_DELAY));
+	} else {
+		smsm_change_state(SMSM_APPS_STATE, SMSM_A2_POWER_CONTROL, 0);
+		bam_is_connected = 0;
+	}
+	write_unlock(&ul_wakeup_lock);
+}
+static void ul_wakeup(void)
+{
+	mutex_lock(&wakeup_lock);
+	if (bam_is_connected) { /* bam got connected before lock grabbed */
+		mutex_unlock(&wakeup_lock);
+		return;
+	}
+	INIT_COMPLETION(ul_wakeup_ack_completion);
+	smsm_change_state(SMSM_APPS_STATE, 0, SMSM_A2_POWER_CONTROL);
+	wait_for_completion_interruptible_timeout(&ul_wakeup_ack_completion,
+							HZ);
+	wait_for_completion_interruptible_timeout(&bam_connection_completion,
+							HZ);
+
+	bam_is_connected = 1;
+	schedule_delayed_work(&ul_timeout_work,
+				msecs_to_jiffies(UL_TIMEOUT_DELAY));
+	mutex_unlock(&wakeup_lock);
+}
+
+static void reconnect_to_bam(void)
+{
+	int i;
+
+	vote_dfab();
+	i = sps_device_reset(a2_device_handle);
+	if (i)
+		pr_err("%s: device reset failed rc = %d\n", __func__, i);
+	i = sps_connect(bam_tx_pipe, &tx_connection);
+	if (i)
+		pr_err("%s: tx connection failed rc = %d\n", __func__, i);
+	i = sps_connect(bam_rx_pipe, &rx_connection);
+	if (i)
+		pr_err("%s: rx connection failed rc = %d\n", __func__, i);
+	i = sps_register_event(bam_tx_pipe, &tx_register_event);
+	if (i)
+		pr_err("%s: tx event reg failed rc = %d\n", __func__, i);
+	i = sps_register_event(bam_rx_pipe, &rx_register_event);
+	if (i)
+		pr_err("%s: rx event reg failed rc = %d\n", __func__, i);
+	for (i = 0; i < NUM_BUFFERS; ++i)
+		queue_rx();
+	toggle_apps_ack();
+	complete_all(&bam_connection_completion);
+}
+
+static void disconnect_to_bam(void)
+{
+	struct list_head *node;
+	struct rx_pkt_info *info;
+
+	INIT_COMPLETION(bam_connection_completion);
+	sps_disconnect(bam_tx_pipe);
+	sps_disconnect(bam_rx_pipe);
+	unvote_dfab();
+	__memzero(rx_desc_mem_buf.base, rx_desc_mem_buf.size);
+	__memzero(tx_desc_mem_buf.base, tx_desc_mem_buf.size);
+	while (!list_empty(&bam_rx_pool)) {
+		node = bam_rx_pool.next;
+		list_del(node);
+		info = container_of(node, struct rx_pkt_info, list_node);
+		dma_unmap_single(NULL, info->dma_address, BUFFER_SIZE,
+							DMA_FROM_DEVICE);
+		dev_kfree_skb_any(info->skb);
+		kfree(info);
+	}
+}
+
+static void vote_dfab(void)
+{
+	int rc;
+
+	rc = clk_enable(dfab_clk);
+	if (rc)
+		pr_err("bam_dmux vote for dfab failed rc = %d\n", rc);
+}
+
+static void unvote_dfab(void)
+{
+	clk_disable(dfab_clk);
+}
+
 static void bam_init(void)
 {
 	u32 h;
@@ -716,6 +850,7 @@
 	void *a2_virt_addr;
 	int i;
 
+	vote_dfab();
 	/* init BAM */
 	a2_virt_addr = ioremap_nocache(A2_PHYS_BASE, A2_PHYS_SIZE);
 	if (!a2_virt_addr) {
@@ -735,6 +870,7 @@
 		pr_err("%s: register bam error %d\n", __func__, ret);
 		goto register_bam_failed;
 	}
+	a2_device_handle = h;
 
 	bam_tx_pipe = sps_alloc_endpoint();
 	if (bam_tx_pipe == NULL) {
@@ -836,6 +972,8 @@
 	bam_mux_initialized = 1;
 	for (i = 0; i < NUM_BUFFERS; ++i)
 		queue_rx();
+	toggle_apps_ack();
+	complete_all(&bam_connection_completion);
 	return;
 
 rx_event_reg_failed:
@@ -860,11 +998,22 @@
 	return;
 }
 
+static void toggle_apps_ack(void)
+{
+	static unsigned int clear_bit; /* 0 = set the bit, else clear bit */
+	smsm_change_state(SMSM_APPS_STATE,
+				clear_bit & SMSM_A2_POWER_CONTROL_ACK,
+				~clear_bit & SMSM_A2_POWER_CONTROL_ACK);
+	clear_bit = ~clear_bit;
+}
+
 static void bam_dmux_smsm_cb(void *priv, uint32_t old_state, uint32_t new_state)
 {
 	DBG("%s: smsm activity\n", __func__);
-	if (bam_mux_initialized)
-		pr_err("%s: bam_dmux already initialized\n", __func__);
+	if (bam_mux_initialized && new_state & SMSM_A2_POWER_CONTROL)
+		reconnect_to_bam();
+	else if (bam_mux_initialized && !(new_state & SMSM_A2_POWER_CONTROL))
+		disconnect_to_bam();
 	else if (new_state & SMSM_A2_POWER_CONTROL)
 		bam_init();
 	else
@@ -872,6 +1021,12 @@
 
 }
 
+static void bam_dmux_smsm_ack_cb(void *priv, uint32_t old_state,
+						uint32_t new_state)
+{
+	complete_all(&ul_wakeup_ack_completion);
+}
+
 static int bam_dmux_probe(struct platform_device *pdev)
 {
 	int rc;
@@ -880,6 +1035,16 @@
 	if (bam_mux_initialized)
 		return 0;
 
+	dfab_clk = clk_get(&pdev->dev, "dfab_clk");
+	if (IS_ERR(dfab_clk)) {
+		pr_err("%s: did not get dfab clock\n", __func__);
+		return -EFAULT;
+	}
+
+	rc = clk_set_rate(dfab_clk, 64000000);
+	if (rc)
+		pr_err("%s: unable to set dfab clock rate\n", __func__);
+
 	bam_mux_rx_workqueue = create_singlethread_workqueue("bam_dmux_rx");
 	if (!bam_mux_rx_workqueue)
 		return -ENOMEM;
@@ -904,6 +1069,10 @@
 		}
 	}
 
+	init_completion(&ul_wakeup_ack_completion);
+	init_completion(&bam_connection_completion);
+	INIT_DELAYED_WORK(&ul_timeout_work, ul_timeout);
+
 	rc = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_A2_POWER_CONTROL,
 					bam_dmux_smsm_cb, NULL);
 
@@ -914,6 +1083,22 @@
 		return -ENOMEM;
 	}
 
+	rc = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_A2_POWER_CONTROL_ACK,
+					bam_dmux_smsm_ack_cb, NULL);
+
+	if (rc) {
+		destroy_workqueue(bam_mux_rx_workqueue);
+		destroy_workqueue(bam_mux_tx_workqueue);
+		smsm_state_cb_deregister(SMSM_MODEM_STATE,
+					SMSM_A2_POWER_CONTROL,
+					bam_dmux_smsm_cb, NULL);
+		pr_err("%s: smsm ack cb register failed, rc: %d\n", __func__,
+				rc);
+		for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc)
+			platform_device_put(bam_ch[rc].pdev);
+		return -ENOMEM;
+	}
+
 	return 0;
 }