msm: bam_dmux: add A2 power collapse support
The A2 hardware consumes a significant ammount of power when idle. Add
support to disconnect and reconnect to the A2 when it powers up and down.
Also add support to vote against power collapse when the A2 is needed for
uplink data transmission.
Change-Id: I37c56c91ef2e0f6ba1e16a3c8cf6e5fe10817896
Signed-off-by: Jeffrey Hugo <jhugo@codeaurora.org>
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;
}