blob: 3d13252b9219bb345f18110050a7da8ca1a97d34 [file] [log] [blame]
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/*
* A2 service component
*/
#include <net/ip.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/sched.h>
#include <linux/skbuff.h>
#include <linux/clk.h>
#include <linux/wakelock.h>
#include <linux/msm-sps.h>
#include <mach/msm_smsm.h>
#include <soc/qcom/socinfo.h>
#include <linux/ipa.h>
#include "ipa_i.h"
#define A2_NUM_PIPES 6
#define A2_SUMMING_THRESHOLD 4096
#define BUFFER_SIZE 2048
#define NUM_BUFFERS 32
#define BAM_CH_LOCAL_OPEN 0x1
#define BAM_CH_REMOTE_OPEN 0x2
#define BAM_CH_IN_RESET 0x4
#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_STATUS 3
#define BAM_MUX_HDR_CMD_OPEN_NO_A2_PC 4
#define LOW_WATERMARK 2
#define HIGH_WATERMARK 4
#define A2_MUX_COMPLETION_TIMEOUT (60*HZ)
#define ENABLE_DISCONNECT_ACK 0x1
#define A2_MUX_PADDING_LENGTH(len) (4 - ((len) & 0x3))
struct bam_ch_info {
u32 status;
a2_mux_notify_cb notify_cb;
void *user_data;
spinlock_t lock;
int num_tx_pkts;
int use_wm;
u32 v4_hdr_hdl;
u32 v6_hdr_hdl;
};
struct tx_pkt_info {
struct sk_buff *skb;
char is_cmd;
u32 len;
struct list_head list_node;
unsigned ts_sec;
unsigned long ts_nsec;
};
struct bam_mux_hdr {
u16 magic_num;
u8 reserved;
u8 cmd;
u8 pad_len;
u8 ch_id;
u16 pkt_len;
};
struct a2_mux_context_type {
u32 tethered_prod;
u32 tethered_cons;
u32 embedded_prod;
u32 embedded_cons;
int a2_mux_apps_pc_enabled;
struct work_struct kickoff_ul_wakeup;
struct work_struct kickoff_ul_power_down;
struct work_struct kickoff_ul_request_resource;
struct bam_ch_info bam_ch[A2_MUX_NUM_CHANNELS];
struct list_head bam_tx_pool;
spinlock_t bam_tx_pool_spinlock;
struct workqueue_struct *a2_mux_tx_workqueue;
struct workqueue_struct *a2_mux_rx_workqueue;
int a2_mux_initialized;
bool bam_is_connected;
bool bam_connect_in_progress;
int a2_mux_send_power_vote_on_init_once;
int a2_mux_sw_bridge_is_connected;
bool a2_mux_dl_wakeup;
u32 a2_device_handle;
struct mutex wakeup_lock;
struct completion ul_wakeup_ack_completion;
struct completion bam_connection_completion;
struct completion request_resource_completion;
struct completion dl_wakeup_completion;
rwlock_t ul_wakeup_lock;
int wait_for_ack;
struct wake_lock bam_wakelock;
int a2_pc_disabled;
spinlock_t wakelock_reference_lock;
int wakelock_reference_count;
int a2_pc_disabled_wakelock_skipped;
int disconnect_ack;
struct mutex smsm_cb_lock;
int bam_dmux_uplink_vote;
};
static struct a2_mux_context_type *a2_mux_ctx;
static void handle_a2_mux_cmd(struct sk_buff *rx_skb);
static bool bam_ch_is_open(int index)
{
return a2_mux_ctx->bam_ch[index].status ==
(BAM_CH_LOCAL_OPEN | BAM_CH_REMOTE_OPEN);
}
static bool bam_ch_is_local_open(int index)
{
return a2_mux_ctx->bam_ch[index].status &
BAM_CH_LOCAL_OPEN;
}
static bool bam_ch_is_remote_open(int index)
{
return a2_mux_ctx->bam_ch[index].status &
BAM_CH_REMOTE_OPEN;
}
static bool bam_ch_is_in_reset(int index)
{
return a2_mux_ctx->bam_ch[index].status &
BAM_CH_IN_RESET;
}
static void set_tx_timestamp(struct tx_pkt_info *pkt)
{
unsigned long long t_now;
t_now = sched_clock();
pkt->ts_nsec = do_div(t_now, 1000000000U);
pkt->ts_sec = (unsigned)t_now;
}
static void verify_tx_queue_is_empty(const char *func)
{
unsigned long flags;
struct tx_pkt_info *info;
int reported = 0;
spin_lock_irqsave(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
list_for_each_entry(info, &a2_mux_ctx->bam_tx_pool, list_node) {
if (!reported) {
IPADBG("%s: tx pool not empty\n", func);
reported = 1;
}
IPADBG("%s: node=%p ts=%u.%09lu\n", __func__,
&info->list_node, info->ts_sec, info->ts_nsec);
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
}
static void grab_wakelock(void)
{
unsigned long flags;
spin_lock_irqsave(&a2_mux_ctx->wakelock_reference_lock, flags);
IPADBG("%s: ref count = %d\n",
__func__,
a2_mux_ctx->wakelock_reference_count);
if (a2_mux_ctx->wakelock_reference_count == 0)
wake_lock(&a2_mux_ctx->bam_wakelock);
++a2_mux_ctx->wakelock_reference_count;
spin_unlock_irqrestore(&a2_mux_ctx->wakelock_reference_lock, flags);
}
static void release_wakelock(void)
{
unsigned long flags;
spin_lock_irqsave(&a2_mux_ctx->wakelock_reference_lock, flags);
if (a2_mux_ctx->wakelock_reference_count == 0) {
IPAERR("%s: bam_dmux wakelock not locked\n", __func__);
dump_stack();
spin_unlock_irqrestore(&a2_mux_ctx->wakelock_reference_lock,
flags);
return;
}
IPADBG("%s: ref count = %d\n",
__func__,
a2_mux_ctx->wakelock_reference_count);
--a2_mux_ctx->wakelock_reference_count;
if (a2_mux_ctx->wakelock_reference_count == 0)
wake_unlock(&a2_mux_ctx->bam_wakelock);
spin_unlock_irqrestore(&a2_mux_ctx->wakelock_reference_lock, flags);
}
static void toggle_apps_ack(void)
{
static unsigned int clear_bit; /* 0 = set the bit, else clear bit */
IPADBG("%s: apps ack %d->%d\n", __func__,
clear_bit & 0x1, ~clear_bit & 0x1);
smsm_change_state(SMSM_APPS_STATE,
clear_bit & SMSM_A2_POWER_CONTROL_ACK,
~clear_bit & SMSM_A2_POWER_CONTROL_ACK);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_apps_acks);
clear_bit = ~clear_bit;
}
static void power_vote(int vote)
{
IPADBG("%s: curr=%d, vote=%d\n",
__func__,
a2_mux_ctx->bam_dmux_uplink_vote, vote);
if (a2_mux_ctx->bam_dmux_uplink_vote == vote)
IPADBG("%s: warning - duplicate power vote\n", __func__);
a2_mux_ctx->bam_dmux_uplink_vote = vote;
if (vote) {
smsm_change_state(SMSM_APPS_STATE, 0, SMSM_A2_POWER_CONTROL);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_on_reqs_out);
} else {
smsm_change_state(SMSM_APPS_STATE, SMSM_A2_POWER_CONTROL, 0);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_off_reqs_out);
}
}
static inline void ul_powerdown(void)
{
IPADBG("%s: powerdown\n", __func__);
verify_tx_queue_is_empty(__func__);
if (a2_mux_ctx->a2_pc_disabled)
release_wakelock();
else {
a2_mux_ctx->wait_for_ack = 1;
INIT_COMPLETION(a2_mux_ctx->ul_wakeup_ack_completion);
power_vote(0);
}
}
static void ul_wakeup(void)
{
int ret;
mutex_lock(&a2_mux_ctx->wakeup_lock);
if (a2_mux_ctx->bam_is_connected &&
!a2_mux_ctx->bam_connect_in_progress) {
IPADBG("%s Already awake\n", __func__);
mutex_unlock(&a2_mux_ctx->wakeup_lock);
return;
}
if (a2_mux_ctx->a2_pc_disabled) {
/*
* don't grab the wakelock the first time because it is
* already grabbed when a2 powers on
*/
if (likely(a2_mux_ctx->a2_pc_disabled_wakelock_skipped))
grab_wakelock();
else
a2_mux_ctx->a2_pc_disabled_wakelock_skipped = 1;
mutex_unlock(&a2_mux_ctx->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
* instead of waiting
*/
if (a2_mux_ctx->wait_for_ack) {
IPADBG("%s waiting for previous ack\n", __func__);
ret = wait_for_completion_timeout(
&a2_mux_ctx->ul_wakeup_ack_completion,
A2_MUX_COMPLETION_TIMEOUT);
a2_mux_ctx->wait_for_ack = 0;
if (unlikely(ret == 0)) {
IPAERR("%s previous ack from modem timed out\n",
__func__);
goto bail;
}
}
INIT_COMPLETION(a2_mux_ctx->ul_wakeup_ack_completion);
power_vote(1);
IPADBG("%s waiting for wakeup ack\n", __func__);
ret = wait_for_completion_timeout(&a2_mux_ctx->ul_wakeup_ack_completion,
A2_MUX_COMPLETION_TIMEOUT);
if (unlikely(ret == 0)) {
IPAERR("%s wakup ack from modem timed out\n", __func__);
goto bail;
}
INIT_COMPLETION(a2_mux_ctx->bam_connection_completion);
if (!a2_mux_ctx->a2_mux_sw_bridge_is_connected) {
ret = wait_for_completion_timeout(
&a2_mux_ctx->bam_connection_completion,
A2_MUX_COMPLETION_TIMEOUT);
if (unlikely(ret == 0)) {
IPAERR("%s modem power on timed out\n", __func__);
goto bail;
}
}
IPADBG("%s complete\n", __func__);
mutex_unlock(&a2_mux_ctx->wakeup_lock);
return;
bail:
mutex_unlock(&a2_mux_ctx->wakeup_lock);
BUG();
return;
}
static void a2_mux_write_done(bool is_tethered, struct sk_buff *skb)
{
struct tx_pkt_info *info;
enum a2_mux_logical_channel_id lcid;
unsigned long event_data;
unsigned long flags;
spin_lock_irqsave(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
info = list_first_entry(&a2_mux_ctx->bam_tx_pool,
struct tx_pkt_info, list_node);
if (unlikely(info->skb != skb)) {
struct tx_pkt_info *errant_pkt;
IPAERR("tx_pool mismatch next=%p list_node=%p, ts=%u.%09lu\n",
a2_mux_ctx->bam_tx_pool.next,
&info->list_node,
info->ts_sec, info->ts_nsec
);
list_for_each_entry(errant_pkt,
&a2_mux_ctx->bam_tx_pool, list_node) {
IPAERR("%s: node=%p ts=%u.%09lu\n", __func__,
&errant_pkt->list_node, errant_pkt->ts_sec,
errant_pkt->ts_nsec);
if (errant_pkt->skb == skb)
info = errant_pkt;
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock,
flags);
BUG();
}
list_del(&info->list_node);
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
if (info->is_cmd) {
dev_kfree_skb_any(info->skb);
kfree(info);
return;
}
skb = info->skb;
kfree(info);
event_data = (unsigned long)(skb);
if (is_tethered)
lcid = A2_MUX_TETHERED_0;
else {
struct bam_mux_hdr *hdr = (struct bam_mux_hdr *)skb->data;
lcid = (enum a2_mux_logical_channel_id) hdr->ch_id;
}
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
a2_mux_ctx->bam_ch[lcid].num_tx_pkts--;
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
if (a2_mux_ctx->bam_ch[lcid].notify_cb)
a2_mux_ctx->bam_ch[lcid].notify_cb(
a2_mux_ctx->bam_ch[lcid].user_data, A2_MUX_WRITE_DONE,
event_data);
else
dev_kfree_skb_any(skb);
}
static bool a2_mux_kickoff_ul_power_down(void)
{
bool is_connected;
write_lock(&a2_mux_ctx->ul_wakeup_lock);
if (a2_mux_ctx->bam_connect_in_progress) {
a2_mux_ctx->bam_is_connected = false;
is_connected = true;
} else {
is_connected = a2_mux_ctx->bam_is_connected;
a2_mux_ctx->bam_is_connected = false;
if (is_connected) {
a2_mux_ctx->bam_connect_in_progress = true;
queue_work(a2_mux_ctx->a2_mux_tx_workqueue,
&a2_mux_ctx->kickoff_ul_power_down);
}
}
write_unlock(&a2_mux_ctx->ul_wakeup_lock);
return is_connected;
}
static bool a2_mux_kickoff_ul_wakeup(void)
{
bool is_connected;
write_lock(&a2_mux_ctx->ul_wakeup_lock);
if (a2_mux_ctx->bam_connect_in_progress) {
a2_mux_ctx->bam_is_connected = true;
is_connected = false;
} else {
is_connected = a2_mux_ctx->bam_is_connected;
a2_mux_ctx->bam_is_connected = true;
if (!is_connected) {
a2_mux_ctx->bam_connect_in_progress = true;
queue_work(a2_mux_ctx->a2_mux_tx_workqueue,
&a2_mux_ctx->kickoff_ul_wakeup);
}
}
write_unlock(&a2_mux_ctx->ul_wakeup_lock);
return is_connected;
}
static void kickoff_ul_power_down_func(struct work_struct *work)
{
bool is_connected;
IPADBG("%s: UL active - forcing powerdown\n", __func__);
ul_powerdown();
write_lock(&a2_mux_ctx->ul_wakeup_lock);
is_connected = a2_mux_ctx->bam_is_connected;
a2_mux_ctx->bam_is_connected = false;
a2_mux_ctx->bam_connect_in_progress = false;
write_unlock(&a2_mux_ctx->ul_wakeup_lock);
if (is_connected)
a2_mux_kickoff_ul_wakeup();
else
ipa_rm_notify_completion(IPA_RM_RESOURCE_RELEASED,
IPA_RM_RESOURCE_A2_CONS);
}
static void kickoff_ul_wakeup_func(struct work_struct *work)
{
bool is_connected;
int ret;
ul_wakeup();
write_lock(&a2_mux_ctx->ul_wakeup_lock);
is_connected = a2_mux_ctx->bam_is_connected;
a2_mux_ctx->bam_is_connected = true;
a2_mux_ctx->bam_connect_in_progress = false;
write_unlock(&a2_mux_ctx->ul_wakeup_lock);
if (is_connected)
ipa_rm_notify_completion(IPA_RM_RESOURCE_GRANTED,
IPA_RM_RESOURCE_A2_CONS);
INIT_COMPLETION(a2_mux_ctx->dl_wakeup_completion);
if (!a2_mux_ctx->a2_mux_dl_wakeup) {
ret = wait_for_completion_timeout(
&a2_mux_ctx->dl_wakeup_completion,
A2_MUX_COMPLETION_TIMEOUT);
if (unlikely(ret == 0)) {
IPAERR("%s timeout waiting for A2 PROD granted\n",
__func__);
BUG();
return;
}
}
if (!is_connected)
a2_mux_kickoff_ul_power_down();
}
static void kickoff_ul_request_resource_func(struct work_struct *work)
{
int ret;
INIT_COMPLETION(a2_mux_ctx->request_resource_completion);
ret = ipa_rm_request_resource(IPA_RM_RESOURCE_A2_PROD);
if (ret < 0 && ret != -EINPROGRESS) {
IPAERR("%s: ipa_rm_request_resource failed %d\n", __func__,
ret);
return;
}
if (ret == -EINPROGRESS) {
ret = wait_for_completion_timeout(
&a2_mux_ctx->request_resource_completion,
A2_MUX_COMPLETION_TIMEOUT);
if (unlikely(ret == 0)) {
IPAERR("%s timeout waiting for A2 PROD granted\n",
__func__);
BUG();
return;
}
}
toggle_apps_ack();
a2_mux_ctx->a2_mux_dl_wakeup = true;
complete_all(&a2_mux_ctx->dl_wakeup_completion);
}
static void ipa_embedded_notify(void *priv,
enum ipa_dp_evt_type evt,
unsigned long data)
{
switch (evt) {
case IPA_RECEIVE:
handle_a2_mux_cmd((struct sk_buff *)data);
break;
case IPA_WRITE_DONE:
a2_mux_write_done(false, (struct sk_buff *)data);
break;
default:
IPAERR("%s: Unknown event %d\n", __func__, evt);
break;
}
}
static void ipa_tethered_notify(void *priv,
enum ipa_dp_evt_type evt,
unsigned long data)
{
IPADBG("%s: event = %d\n", __func__, evt);
switch (evt) {
case IPA_RECEIVE:
if (a2_mux_ctx->bam_ch[A2_MUX_TETHERED_0].notify_cb)
a2_mux_ctx->bam_ch[A2_MUX_TETHERED_0].notify_cb(
a2_mux_ctx->bam_ch[A2_MUX_TETHERED_0].user_data,
A2_MUX_RECEIVE,
data);
else
dev_kfree_skb_any((struct sk_buff *)data);
break;
case IPA_WRITE_DONE:
a2_mux_write_done(true, (struct sk_buff *)data);
break;
default:
IPAERR("%s: Unknown event %d\n", __func__, evt);
break;
}
}
static int connect_to_bam(void)
{
int ret;
struct ipa_sys_connect_params connect_params;
IPAERR("%s:\n", __func__);
if (a2_mux_ctx->a2_mux_sw_bridge_is_connected) {
IPAERR("%s: SW bridge is already UP\n",
__func__);
return -EFAULT;
}
if (sps_ctrl_bam_dma_clk(true))
WARN_ON(1);
memset(&connect_params, 0, sizeof(struct ipa_sys_connect_params));
connect_params.client = IPA_CLIENT_A2_TETHERED_CONS;
connect_params.notify = ipa_tethered_notify;
connect_params.desc_fifo_sz = 0x800;
ret = ipa_bridge_setup(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_TETHERED,
&connect_params,
&a2_mux_ctx->tethered_prod);
if (ret) {
IPAERR("%s: IPA bridge tethered UL failed to connect: %d\n",
__func__, ret);
goto bridge_tethered_ul_failed;
}
memset(&connect_params, 0, sizeof(struct ipa_sys_connect_params));
connect_params.ipa_ep_cfg.mode.mode = IPA_DMA;
connect_params.ipa_ep_cfg.mode.dst = IPA_CLIENT_USB_CONS;
connect_params.client = IPA_CLIENT_A2_TETHERED_PROD;
connect_params.notify = ipa_tethered_notify;
connect_params.desc_fifo_sz = 0x800;
ret = ipa_bridge_setup(IPA_BRIDGE_DIR_DL, IPA_BRIDGE_TYPE_TETHERED,
&connect_params,
&a2_mux_ctx->tethered_cons);
if (ret) {
IPAERR("%s: IPA bridge tethered DL failed to connect: %d\n",
__func__, ret);
goto bridge_tethered_dl_failed;
}
memset(&connect_params, 0, sizeof(struct ipa_sys_connect_params));
connect_params.ipa_ep_cfg.hdr.hdr_len = sizeof(struct bam_mux_hdr);
connect_params.ipa_ep_cfg.hdr.hdr_ofst_pkt_size_valid = 1;
connect_params.ipa_ep_cfg.hdr.hdr_ofst_pkt_size = 6;
connect_params.client = IPA_CLIENT_A2_EMBEDDED_CONS;
connect_params.notify = ipa_embedded_notify;
connect_params.desc_fifo_sz = 0x800;
ret = ipa_bridge_setup(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_EMBEDDED,
&connect_params,
&a2_mux_ctx->embedded_prod);
if (ret) {
IPAERR("%s: IPA bridge embedded UL failed to connect: %d\n",
__func__, ret);
goto bridge_embedded_ul_failed;
}
memset(&connect_params, 0, sizeof(struct ipa_sys_connect_params));
connect_params.ipa_ep_cfg.hdr.hdr_len = sizeof(struct bam_mux_hdr);
connect_params.ipa_ep_cfg.hdr.hdr_ofst_metadata_valid = 1;
/* take 4 bytes, the second byte in the metadata is the ch_id*/
connect_params.ipa_ep_cfg.hdr.hdr_ofst_metadata = 4;
connect_params.client = IPA_CLIENT_A2_EMBEDDED_PROD;
connect_params.notify = ipa_embedded_notify;
connect_params.desc_fifo_sz = 0x800;
ret = ipa_bridge_setup(IPA_BRIDGE_DIR_DL, IPA_BRIDGE_TYPE_EMBEDDED,
&connect_params,
&a2_mux_ctx->embedded_cons);
if (ret) {
IPAERR("%s: IPA bridge embedded DL failed to connect: %d\n",
__func__, ret);
goto bridge_embedded_dl_failed;
}
a2_mux_ctx->a2_mux_sw_bridge_is_connected = 1;
complete_all(&a2_mux_ctx->bam_connection_completion);
return 0;
bridge_embedded_dl_failed:
ipa_bridge_teardown(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_EMBEDDED,
a2_mux_ctx->embedded_prod);
bridge_embedded_ul_failed:
ipa_bridge_teardown(IPA_BRIDGE_DIR_DL, IPA_BRIDGE_TYPE_TETHERED,
a2_mux_ctx->tethered_cons);
bridge_tethered_dl_failed:
ipa_bridge_teardown(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_TETHERED,
a2_mux_ctx->tethered_prod);
bridge_tethered_ul_failed:
if (sps_ctrl_bam_dma_clk(false))
WARN_ON(1);
return ret;
}
static int disconnect_to_bam(void)
{
int ret;
IPAERR("%s\n", __func__);
if (!a2_mux_ctx->a2_mux_sw_bridge_is_connected) {
IPAERR("%s: SW bridge is already DOWN\n",
__func__);
return -EFAULT;
}
ret = ipa_bridge_teardown(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_TETHERED,
a2_mux_ctx->tethered_prod);
if (ret) {
IPAERR("%s: IPA bridge tethered UL failed to disconnect: %d\n",
__func__, ret);
return ret;
}
ret = ipa_bridge_teardown(IPA_BRIDGE_DIR_DL, IPA_BRIDGE_TYPE_TETHERED,
a2_mux_ctx->tethered_cons);
if (ret) {
IPAERR("%s: IPA bridge tethered DL failed to disconnect: %d\n",
__func__, ret);
return ret;
}
ret = ipa_bridge_teardown(IPA_BRIDGE_DIR_UL, IPA_BRIDGE_TYPE_EMBEDDED,
a2_mux_ctx->embedded_prod);
if (ret) {
IPAERR("%s: IPA bridge embedded UL failed to disconnect: %d\n",
__func__, ret);
return ret;
}
ret = ipa_bridge_teardown(IPA_BRIDGE_DIR_DL, IPA_BRIDGE_TYPE_EMBEDDED,
a2_mux_ctx->embedded_cons);
if (ret) {
IPAERR("%s: IPA bridge embedded DL failed to disconnect: %d\n",
__func__, ret);
return ret;
}
if (sps_ctrl_bam_dma_clk(false))
WARN_ON(1);
verify_tx_queue_is_empty(__func__);
(void) ipa_rm_release_resource(IPA_RM_RESOURCE_A2_PROD);
if (a2_mux_ctx->disconnect_ack)
toggle_apps_ack();
a2_mux_ctx->a2_mux_dl_wakeup = false;
a2_mux_ctx->a2_mux_sw_bridge_is_connected = 0;
complete_all(&a2_mux_ctx->bam_connection_completion);
return 0;
}
static void a2_mux_smsm_cb(void *priv,
u32 old_state,
u32 new_state)
{
static int last_processed_state;
mutex_lock(&a2_mux_ctx->smsm_cb_lock);
IPADBG("%s: 0x%08x -> 0x%08x\n", __func__, old_state,
new_state);
if (last_processed_state == (new_state & SMSM_A2_POWER_CONTROL)) {
IPADBG("%s: already processed this state\n", __func__);
mutex_unlock(&a2_mux_ctx->smsm_cb_lock);
return;
}
last_processed_state = new_state & SMSM_A2_POWER_CONTROL;
if (new_state & SMSM_A2_POWER_CONTROL) {
IPADBG("%s: MODEM PWR CTRL 1\n", __func__);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_on_reqs_in);
grab_wakelock();
(void) connect_to_bam();
queue_work(a2_mux_ctx->a2_mux_rx_workqueue,
&a2_mux_ctx->kickoff_ul_request_resource);
} else if (!(new_state & SMSM_A2_POWER_CONTROL)) {
IPADBG("%s: MODEM PWR CTRL 0\n", __func__);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_off_reqs_in);
(void) disconnect_to_bam();
release_wakelock();
} else {
IPAERR("%s: unsupported state change\n", __func__);
}
mutex_unlock(&a2_mux_ctx->smsm_cb_lock);
}
static void a2_mux_smsm_ack_cb(void *priv, u32 old_state,
u32 new_state)
{
IPADBG("%s: 0x%08x -> 0x%08x\n", __func__, old_state,
new_state);
IPA_STATS_INC_CNT(ipa_ctx->stats.a2_power_modem_acks);
complete_all(&a2_mux_ctx->ul_wakeup_ack_completion);
}
static int a2_mux_pm_rm_request_resource(void)
{
int result = 0;
bool is_connected;
is_connected = a2_mux_kickoff_ul_wakeup();
if (!is_connected)
result = -EINPROGRESS;
return result;
}
static int a2_mux_pm_rm_release_resource(void)
{
int result = 0;
bool is_connected;
is_connected = a2_mux_kickoff_ul_power_down();
if (is_connected)
result = -EINPROGRESS;
return result;
}
static void a2_mux_pm_rm_notify_cb(void *user_data,
enum ipa_rm_event event,
unsigned long data)
{
switch (event) {
case IPA_RM_RESOURCE_GRANTED:
IPADBG("%s: PROD GRANTED CB\n", __func__);
complete_all(&a2_mux_ctx->request_resource_completion);
break;
case IPA_RM_RESOURCE_RELEASED:
IPADBG("%s: PROD RELEASED CB\n", __func__);
break;
default:
return;
}
}
static int a2_mux_pm_initialize_rm(void)
{
struct ipa_rm_create_params create_params;
int result;
memset(&create_params, 0, sizeof(create_params));
create_params.name = IPA_RM_RESOURCE_A2_PROD;
create_params.reg_params.notify_cb = &a2_mux_pm_rm_notify_cb;
result = ipa_rm_create_resource(&create_params);
if (result)
goto bail;
memset(&create_params, 0, sizeof(create_params));
create_params.name = IPA_RM_RESOURCE_A2_CONS;
create_params.release_resource = &a2_mux_pm_rm_release_resource;
create_params.request_resource = &a2_mux_pm_rm_request_resource;
result = ipa_rm_create_resource(&create_params);
bail:
return result;
}
static void a2_mux_process_data(struct sk_buff *rx_skb)
{
unsigned long flags;
struct bam_mux_hdr *rx_hdr;
unsigned long event_data;
rx_hdr = (struct bam_mux_hdr *)rx_skb->data;
rx_skb->data = (unsigned char *)(rx_hdr + 1);
rx_skb->tail = rx_skb->data + rx_hdr->pkt_len;
rx_skb->len = rx_hdr->pkt_len;
rx_skb->truesize = rx_hdr->pkt_len + sizeof(struct sk_buff);
event_data = (unsigned long)(rx_skb);
spin_lock_irqsave(&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock, flags);
if (a2_mux_ctx->bam_ch[rx_hdr->ch_id].notify_cb)
a2_mux_ctx->bam_ch[rx_hdr->ch_id].notify_cb(
a2_mux_ctx->bam_ch[rx_hdr->ch_id].user_data,
A2_MUX_RECEIVE,
event_data);
else
dev_kfree_skb_any(rx_skb);
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock,
flags);
}
static void handle_a2_mux_cmd_open(struct bam_mux_hdr *rx_hdr)
{
unsigned long flags;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock, flags);
a2_mux_ctx->bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN;
a2_mux_ctx->bam_ch[rx_hdr->ch_id].num_tx_pkts = 0;
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock,
flags);
}
static void handle_a2_mux_cmd(struct sk_buff *rx_skb)
{
unsigned long flags;
struct bam_mux_hdr *rx_hdr;
rx_hdr = (struct bam_mux_hdr *)rx_skb->data;
IPADBG("%s: magic %x reserved %d cmd %d pad %d ch %d len %d\n",
__func__,
rx_hdr->magic_num, rx_hdr->reserved, rx_hdr->cmd,
rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len);
rx_hdr->magic_num = ntohs(rx_hdr->magic_num);
rx_hdr->pkt_len = ntohs(rx_hdr->pkt_len);
IPADBG("%s: converted to host order magic_num=%d, pkt_len=%d\n",
__func__, rx_hdr->magic_num, rx_hdr->pkt_len);
if (rx_hdr->magic_num != BAM_MUX_HDR_MAGIC_NO) {
IPAERR("bad hdr magic %x rvd %d cmd %d pad %d ch %d len %d\n",
rx_hdr->magic_num, rx_hdr->reserved, rx_hdr->cmd,
rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len);
dev_kfree_skb_any(rx_skb);
return;
}
if (rx_hdr->ch_id >= A2_MUX_NUM_CHANNELS) {
IPAERR("bad LCID %d rsvd %d cmd %d pad %d ch %d len %d\n",
rx_hdr->ch_id, rx_hdr->reserved, rx_hdr->cmd,
rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len);
dev_kfree_skb_any(rx_skb);
return;
}
switch (rx_hdr->cmd) {
case BAM_MUX_HDR_CMD_DATA:
a2_mux_process_data(rx_skb);
break;
case BAM_MUX_HDR_CMD_OPEN:
IPADBG("%s: opening cid %d PC enabled\n", __func__,
rx_hdr->ch_id);
handle_a2_mux_cmd_open(rx_hdr);
if (!(rx_hdr->reserved & ENABLE_DISCONNECT_ACK)) {
IPADBG("%s: deactivating disconnect ack\n",
__func__);
a2_mux_ctx->disconnect_ack = 0;
}
dev_kfree_skb_any(rx_skb);
if (a2_mux_ctx->a2_mux_send_power_vote_on_init_once) {
kickoff_ul_wakeup_func(NULL);
a2_mux_ctx->a2_mux_send_power_vote_on_init_once = 0;
}
break;
case BAM_MUX_HDR_CMD_OPEN_NO_A2_PC:
IPADBG("%s: opening cid %d PC disabled\n", __func__,
rx_hdr->ch_id);
if (!a2_mux_ctx->a2_pc_disabled) {
a2_mux_ctx->a2_pc_disabled = 1;
ul_wakeup();
}
handle_a2_mux_cmd_open(rx_hdr);
dev_kfree_skb_any(rx_skb);
break;
case BAM_MUX_HDR_CMD_CLOSE:
/* probably should drop pending write */
IPADBG("%s: closing cid %d\n", __func__,
rx_hdr->ch_id);
spin_lock_irqsave(&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock,
flags);
a2_mux_ctx->bam_ch[rx_hdr->ch_id].status &=
~BAM_CH_REMOTE_OPEN;
spin_unlock_irqrestore(
&a2_mux_ctx->bam_ch[rx_hdr->ch_id].lock, flags);
dev_kfree_skb_any(rx_skb);
break;
default:
IPAERR("bad hdr.magic %x rvd %d cmd %d pad %d ch %d len %d\n",
rx_hdr->magic_num, rx_hdr->reserved,
rx_hdr->cmd, rx_hdr->pad_len, rx_hdr->ch_id,
rx_hdr->pkt_len);
dev_kfree_skb_any(rx_skb);
return;
}
}
static int a2_mux_write_cmd(void *data, u32 len)
{
int rc;
struct tx_pkt_info *pkt;
unsigned long flags;
pkt = kmalloc(sizeof(struct tx_pkt_info), GFP_ATOMIC);
if (pkt == NULL) {
IPAERR("%s: mem alloc for tx_pkt_info failed\n", __func__);
return -ENOMEM;
}
pkt->skb = __dev_alloc_skb(len, GFP_NOWAIT | __GFP_NOWARN);
if (pkt->skb == NULL) {
IPAERR("%s: unable to alloc skb\n\n", __func__);
kfree(pkt);
return -ENOMEM;
}
memcpy(skb_put(pkt->skb, len), data, len);
pkt->len = len;
pkt->is_cmd = 1;
set_tx_timestamp(pkt);
spin_lock_irqsave(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
list_add_tail(&pkt->list_node, &a2_mux_ctx->bam_tx_pool);
rc = ipa_tx_dp(IPA_CLIENT_A2_EMBEDDED_CONS, pkt->skb, NULL);
if (rc) {
IPAERR("%s ipa_tx_dp failed rc=%d\n",
__func__, rc);
list_del(&pkt->list_node);
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock,
flags);
dev_kfree_skb_any(pkt->skb);
kfree(pkt);
} else {
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock,
flags);
}
return rc;
}
/**
* a2_mux_get_client_handles() - provide the tethered/embedded
* pipe handles for post setup configuration
* @lcid: logical channel ID
* @clnt_cons_handle: [out] consumer pipe handle
* @clnt_prod_handle: [out] producer pipe handle
*
* Returns: 0 on success, negative on failure
*/
int a2_mux_get_client_handles(enum a2_mux_logical_channel_id lcid,
unsigned int *clnt_cons_handle,
unsigned int *clnt_prod_handle)
{
if (!a2_mux_ctx->a2_mux_initialized || lcid >= A2_MUX_NUM_CHANNELS
|| lcid < A2_MUX_WWAN_0)
return -ENODEV;
if (!clnt_cons_handle || !clnt_prod_handle)
return -EINVAL;
if (lcid == A2_MUX_TETHERED_0) {
*clnt_prod_handle = a2_mux_ctx->tethered_prod;
*clnt_cons_handle = a2_mux_ctx->tethered_cons;
} else {
*clnt_prod_handle = a2_mux_ctx->embedded_prod;
*clnt_cons_handle = a2_mux_ctx->embedded_cons;
}
return 0;
}
/**
* a2_mux_write() - send the packet to A2,
* add MUX header acc to lcid provided
* @id: logical channel ID
* @skb: SKB to write
*
* Returns: 0 on success, negative on failure
*/
int a2_mux_write(enum a2_mux_logical_channel_id id, struct sk_buff *skb)
{
int rc = 0;
struct bam_mux_hdr *hdr;
unsigned long flags;
struct sk_buff *new_skb = NULL;
struct tx_pkt_info *pkt;
bool is_connected;
if (id >= A2_MUX_NUM_CHANNELS)
return -EINVAL;
if (!skb)
return -EINVAL;
if (!a2_mux_ctx->a2_mux_initialized)
return -ENODEV;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[id].lock, flags);
if (!bam_ch_is_open(id)) {
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[id].lock, flags);
IPAERR("%s: port not open: %d\n",
__func__,
a2_mux_ctx->bam_ch[id].status);
return -ENODEV;
}
if (a2_mux_ctx->bam_ch[id].use_wm &&
(a2_mux_ctx->bam_ch[id].num_tx_pkts >= HIGH_WATERMARK)) {
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[id].lock, flags);
IPAERR("%s: watermark exceeded: %d\n", __func__, id);
return -EAGAIN;
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[id].lock, flags);
read_lock(&a2_mux_ctx->ul_wakeup_lock);
is_connected = a2_mux_ctx->bam_is_connected &&
!a2_mux_ctx->bam_connect_in_progress;
read_unlock(&a2_mux_ctx->ul_wakeup_lock);
if (!is_connected)
return -ENODEV;
if (id != A2_MUX_TETHERED_0) {
/*
* if skb do not have any tailroom for padding
* copy the skb into a new expanded skb
*/
if ((skb->len & 0x3) &&
(skb_tailroom(skb) < A2_MUX_PADDING_LENGTH(skb->len))) {
new_skb = skb_copy_expand(skb, skb_headroom(skb),
A2_MUX_PADDING_LENGTH(skb->len),
GFP_ATOMIC);
if (new_skb == NULL) {
IPAERR("%s: cannot allocate skb\n", __func__);
rc = -ENOMEM;
goto write_fail;
}
dev_kfree_skb_any(skb);
skb = new_skb;
}
hdr = (struct bam_mux_hdr *)skb_push(
skb, sizeof(struct bam_mux_hdr));
/*
* caller should allocate for hdr and padding
* hdr is fine, padding is tricky
*/
hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
hdr->cmd = BAM_MUX_HDR_CMD_DATA;
hdr->reserved = 0;
hdr->ch_id = id;
hdr->pkt_len = skb->len - sizeof(struct bam_mux_hdr);
if (skb->len & 0x3)
skb_put(skb, A2_MUX_PADDING_LENGTH(skb->len));
hdr->pad_len = skb->len - (sizeof(struct bam_mux_hdr) +
hdr->pkt_len);
IPADBG("data %p, tail %p skb len %d pkt len %d pad len %d\n",
skb->data, skb->tail, skb->len,
hdr->pkt_len, hdr->pad_len);
hdr->magic_num = htons(hdr->magic_num);
hdr->pkt_len = htons(hdr->pkt_len);
IPADBG("convert to network order magic_num=%d, pkt_len=%d\n",
hdr->magic_num, hdr->pkt_len);
}
pkt = kmalloc(sizeof(struct tx_pkt_info), GFP_ATOMIC);
if (pkt == NULL) {
IPAERR("%s: mem alloc for tx_pkt_info failed\n", __func__);
rc = -ENOMEM;
goto write_fail2;
}
pkt->skb = skb;
pkt->is_cmd = 0;
set_tx_timestamp(pkt);
spin_lock_irqsave(&a2_mux_ctx->bam_tx_pool_spinlock, flags);
list_add_tail(&pkt->list_node, &a2_mux_ctx->bam_tx_pool);
if (id == A2_MUX_TETHERED_0)
rc = ipa_tx_dp(IPA_CLIENT_A2_TETHERED_CONS, skb, NULL);
else
rc = ipa_tx_dp(IPA_CLIENT_A2_EMBEDDED_CONS, skb, NULL);
if (rc) {
IPAERR("%s ipa_tx_dp failed rc=%d\n",
__func__, rc);
list_del(&pkt->list_node);
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock,
flags);
goto write_fail3;
} else {
spin_unlock_irqrestore(&a2_mux_ctx->bam_tx_pool_spinlock,
flags);
spin_lock_irqsave(&a2_mux_ctx->bam_ch[id].lock, flags);
a2_mux_ctx->bam_ch[id].num_tx_pkts++;
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[id].lock, flags);
}
return 0;
write_fail3:
kfree(pkt);
write_fail2:
if (new_skb)
dev_kfree_skb_any(new_skb);
write_fail:
return rc;
}
/**
* a2_mux_add_hdr() - called when MUX header should
* be added
* @lcid: logical channel ID
*
* Returns: 0 on success, negative on failure
*/
static int a2_mux_add_hdr(enum a2_mux_logical_channel_id lcid)
{
struct ipa_ioc_add_hdr *hdrs;
struct ipa_hdr_add *ipv4_hdr;
struct ipa_hdr_add *ipv6_hdr;
struct bam_mux_hdr *dmux_hdr;
int rc;
IPADBG("%s: ch %d\n", __func__, lcid);
if (lcid < A2_MUX_WWAN_0 || lcid >= A2_MUX_NUM_CHANNELS ||
lcid == A2_MUX_TETHERED_0) {
IPAERR("%s: non valid lcid passed: %d\n", __func__, lcid);
return -EINVAL;
}
hdrs = kzalloc(sizeof(struct ipa_ioc_add_hdr) +
2 * sizeof(struct ipa_hdr_add), GFP_KERNEL);
if (!hdrs) {
IPAERR("%s: hdr allocation fail for ch %d\n", __func__, lcid);
return -ENOMEM;
}
ipv4_hdr = &hdrs->hdr[0];
ipv6_hdr = &hdrs->hdr[1];
dmux_hdr = (struct bam_mux_hdr *)ipv4_hdr->hdr;
snprintf(ipv4_hdr->name, IPA_RESOURCE_NAME_MAX, "%s%d",
A2_MUX_HDR_NAME_V4_PREF, lcid);
dmux_hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
dmux_hdr->cmd = BAM_MUX_HDR_CMD_DATA;
dmux_hdr->reserved = 0;
dmux_hdr->ch_id = lcid;
/* Packet lenght is added by IPA */
dmux_hdr->pkt_len = 0;
dmux_hdr->pad_len = 0;
dmux_hdr->magic_num = htons(dmux_hdr->magic_num);
IPADBG("converted to network order magic_num=%d\n",
dmux_hdr->magic_num);
ipv4_hdr->hdr_len = sizeof(struct bam_mux_hdr);
ipv4_hdr->is_partial = 0;
dmux_hdr = (struct bam_mux_hdr *)ipv6_hdr->hdr;
snprintf(ipv6_hdr->name, IPA_RESOURCE_NAME_MAX, "%s%d",
A2_MUX_HDR_NAME_V6_PREF, lcid);
dmux_hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
dmux_hdr->cmd = BAM_MUX_HDR_CMD_DATA;
dmux_hdr->reserved = 0;
dmux_hdr->ch_id = lcid;
/* Packet lenght is added by IPA */
dmux_hdr->pkt_len = 0;
dmux_hdr->pad_len = 0;
dmux_hdr->magic_num = htons(dmux_hdr->magic_num);
IPADBG("converted to network order magic_num=%d\n",
dmux_hdr->magic_num);
ipv6_hdr->hdr_len = sizeof(struct bam_mux_hdr);
ipv6_hdr->is_partial = 0;
hdrs->commit = 1;
hdrs->num_hdrs = 2;
rc = ipa_add_hdr(hdrs);
if (rc) {
IPAERR("Fail on Header-Insertion(%d)\n", rc);
goto bail;
}
if (ipv4_hdr->status) {
IPAERR("Fail on Header-Insertion ipv4(%d)\n",
ipv4_hdr->status);
rc = ipv4_hdr->status;
goto bail;
}
if (ipv6_hdr->status) {
IPAERR("%s: Fail on Header-Insertion ipv4(%d)\n", __func__,
ipv6_hdr->status);
rc = ipv6_hdr->status;
goto bail;
}
a2_mux_ctx->bam_ch[lcid].v4_hdr_hdl = ipv4_hdr->hdr_hdl;
a2_mux_ctx->bam_ch[lcid].v6_hdr_hdl = ipv6_hdr->hdr_hdl;
rc = 0;
bail:
kfree(hdrs);
return rc;
}
/**
* a2_mux_del_hdr() - called when MUX header should
* be removed
* @lcid: logical channel ID
*
* Returns: 0 on success, negative on failure
*/
static int a2_mux_del_hdr(enum a2_mux_logical_channel_id lcid)
{
struct ipa_ioc_del_hdr *hdrs;
struct ipa_hdr_del *ipv4_hdl;
struct ipa_hdr_del *ipv6_hdl;
int rc;
IPADBG("%s: ch %d\n", __func__, lcid);
if (lcid < A2_MUX_WWAN_0 || lcid >= A2_MUX_NUM_CHANNELS ||
lcid == A2_MUX_TETHERED_0) {
IPAERR("invalid lcid passed: %d\n", lcid);
return -EINVAL;
}
if (a2_mux_ctx->bam_ch[lcid].v4_hdr_hdl == 0 ||
a2_mux_ctx->bam_ch[lcid].v6_hdr_hdl == 0) {
IPADBG("no hdrs for ch %d, exit Del hdrs\n", lcid);
return 0;
}
hdrs = kzalloc(sizeof(struct ipa_ioc_del_hdr) +
2 * sizeof(struct ipa_hdr_del), GFP_KERNEL);
if (!hdrs) {
IPAERR("hdr alloc fail for ch %d\n", lcid);
return -ENOMEM;
}
ipv4_hdl = &hdrs->hdl[0];
ipv6_hdl = &hdrs->hdl[1];
ipv4_hdl->hdl = a2_mux_ctx->bam_ch[lcid].v4_hdr_hdl;
ipv6_hdl->hdl = a2_mux_ctx->bam_ch[lcid].v6_hdr_hdl;
hdrs->commit = 1;
hdrs->num_hdls = 2;
rc = ipa_del_hdr(hdrs);
if (rc) {
IPAERR("Fail on Del Header-Insertion(%d)\n", rc);
goto bail;
}
if (ipv4_hdl->status) {
IPAERR("Fail on Del Header-Insertion ipv4(%d)\n",
ipv4_hdl->status);
rc = ipv4_hdl->status;
goto bail;
}
a2_mux_ctx->bam_ch[lcid].v4_hdr_hdl = 0;
if (ipv6_hdl->status) {
IPAERR("Fail on Del Header-Insertion ipv4(%d)\n",
ipv6_hdl->status);
rc = ipv6_hdl->status;
goto bail;
}
a2_mux_ctx->bam_ch[lcid].v6_hdr_hdl = 0;
rc = 0;
bail:
kfree(hdrs);
return rc;
}
/**
* a2_mux_open_channel() - opens logical channel
* to A2
* @lcid: logical channel ID
* @user_data: user provided data for below CB
* @notify_cb: user provided notification CB
*
* Returns: 0 on success, negative on failure
*/
int a2_mux_open_channel(enum a2_mux_logical_channel_id lcid,
void *user_data,
a2_mux_notify_cb notify_cb)
{
struct bam_mux_hdr *hdr;
unsigned long flags;
int rc = 0;
bool is_connected;
IPADBG("%s: opening ch %d\n", __func__, lcid);
if (!a2_mux_ctx->a2_mux_initialized) {
IPAERR("%s: not inititialized\n", __func__);
return -ENODEV;
}
if (lcid >= A2_MUX_NUM_CHANNELS || lcid < 0) {
IPAERR("%s: invalid channel id %d\n", __func__, lcid);
return -EINVAL;
}
if (notify_cb == NULL) {
IPAERR("%s: notify function is NULL\n", __func__);
return -EINVAL;
}
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
if (bam_ch_is_open(lcid)) {
IPAERR("%s: Already opened %d\n", __func__, lcid);
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
goto open_done;
}
if (!bam_ch_is_remote_open(lcid)) {
IPAERR("%s: Remote not open; ch: %d\n", __func__, lcid);
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
return -ENODEV;
}
a2_mux_ctx->bam_ch[lcid].notify_cb = notify_cb;
a2_mux_ctx->bam_ch[lcid].user_data = user_data;
a2_mux_ctx->bam_ch[lcid].status |= BAM_CH_LOCAL_OPEN;
a2_mux_ctx->bam_ch[lcid].num_tx_pkts = 0;
a2_mux_ctx->bam_ch[lcid].use_wm = 0;
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
read_lock(&a2_mux_ctx->ul_wakeup_lock);
is_connected = a2_mux_ctx->bam_is_connected &&
!a2_mux_ctx->bam_connect_in_progress;
read_unlock(&a2_mux_ctx->ul_wakeup_lock);
if (!is_connected)
return -ENODEV;
if (lcid != A2_MUX_TETHERED_0) {
hdr = kmalloc(sizeof(struct bam_mux_hdr), GFP_KERNEL);
if (hdr == NULL) {
IPAERR("%s: hdr kmalloc failed. ch: %d\n",
__func__, lcid);
return -ENOMEM;
}
hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
if (a2_mux_ctx->a2_mux_apps_pc_enabled) {
hdr->cmd = BAM_MUX_HDR_CMD_OPEN;
} else {
IPAERR("%s: PC DISABLED BY A5 SW BY INTENTION\n",
__func__);
a2_mux_ctx->a2_pc_disabled = 1;
hdr->cmd = BAM_MUX_HDR_CMD_OPEN_NO_A2_PC;
}
hdr->reserved = 0;
hdr->ch_id = lcid;
hdr->pkt_len = 0;
hdr->pad_len = 0;
hdr->magic_num = htons(hdr->magic_num);
hdr->pkt_len = htons(hdr->pkt_len);
IPADBG("convert to network order magic_num=%d, pkt_len=%d\n",
hdr->magic_num, hdr->pkt_len);
rc = a2_mux_write_cmd((void *)hdr,
sizeof(struct bam_mux_hdr));
kfree(hdr);
if (rc) {
IPAERR("%s: bam_mux_write_cmd failed %d; ch: %d\n",
__func__, rc, lcid);
return rc;
}
rc = a2_mux_add_hdr(lcid);
if (rc) {
IPAERR("a2_mux_add_hdr failed %d; ch: %d\n", rc, lcid);
return rc;
}
}
open_done:
IPADBG("%s: opened ch %d\n", __func__, lcid);
return rc;
}
/**
* a2_mux_close_channel() - closes logical channel
* to A2
* @lcid: logical channel ID
*
* Returns: 0 on success, negative on failure
*/
int a2_mux_close_channel(enum a2_mux_logical_channel_id lcid)
{
struct bam_mux_hdr *hdr;
unsigned long flags;
int rc = 0;
bool is_connected;
if (lcid >= A2_MUX_NUM_CHANNELS || lcid < 0)
return -EINVAL;
IPADBG("%s: closing ch %d\n", __func__, lcid);
if (!a2_mux_ctx->a2_mux_initialized)
return -ENODEV;
read_lock(&a2_mux_ctx->ul_wakeup_lock);
is_connected = a2_mux_ctx->bam_is_connected &&
!a2_mux_ctx->bam_connect_in_progress;
read_unlock(&a2_mux_ctx->ul_wakeup_lock);
if (!is_connected && !bam_ch_is_in_reset(lcid))
return -ENODEV;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
a2_mux_ctx->bam_ch[lcid].notify_cb = NULL;
a2_mux_ctx->bam_ch[lcid].user_data = NULL;
a2_mux_ctx->bam_ch[lcid].status &= ~BAM_CH_LOCAL_OPEN;
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
if (bam_ch_is_in_reset(lcid)) {
a2_mux_ctx->bam_ch[lcid].status &= ~BAM_CH_IN_RESET;
return 0;
}
if (lcid != A2_MUX_TETHERED_0) {
hdr = kmalloc(sizeof(struct bam_mux_hdr), GFP_ATOMIC);
if (hdr == NULL) {
IPAERR("%s: hdr kmalloc failed. ch: %d\n",
__func__, lcid);
return -ENOMEM;
}
hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
hdr->cmd = BAM_MUX_HDR_CMD_CLOSE;
hdr->reserved = 0;
hdr->ch_id = lcid;
hdr->pkt_len = 0;
hdr->pad_len = 0;
hdr->magic_num = htons(hdr->magic_num);
hdr->pkt_len = htons(hdr->pkt_len);
IPADBG("convert to network order magic_num=%d, pkt_len=%d\n",
hdr->magic_num, hdr->pkt_len);
rc = a2_mux_write_cmd((void *)hdr, sizeof(struct bam_mux_hdr));
kfree(hdr);
if (rc) {
IPAERR("%s: bam_mux_write_cmd failed %d; ch: %d\n",
__func__, rc, lcid);
return rc;
}
rc = a2_mux_del_hdr(lcid);
if (rc) {
IPAERR("a2_mux_del_hdr failed %d; ch: %d\n",
rc, lcid);
return rc;
}
}
IPADBG("%s: closed ch %d\n", __func__, lcid);
return 0;
}
/**
* a2_mux_is_ch_full() - checks if channel is above predefined WM,
* used for flow control implementation
* @lcid: logical channel ID
*
* Returns: true if the channel is above predefined WM,
* false otherwise
*/
int a2_mux_is_ch_full(enum a2_mux_logical_channel_id lcid)
{
unsigned long flags;
int ret;
if (lcid >= A2_MUX_NUM_CHANNELS ||
lcid < 0)
return -EINVAL;
if (!a2_mux_ctx->a2_mux_initialized)
return -ENODEV;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
a2_mux_ctx->bam_ch[lcid].use_wm = 1;
ret = a2_mux_ctx->bam_ch[lcid].num_tx_pkts >= HIGH_WATERMARK;
IPADBG("%s: ch %d num tx pkts=%d, HWM=%d\n", __func__,
lcid, a2_mux_ctx->bam_ch[lcid].num_tx_pkts, ret);
if (!bam_ch_is_local_open(lcid)) {
ret = -ENODEV;
IPAERR("%s: port not open: %d\n", __func__,
a2_mux_ctx->bam_ch[lcid].status);
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
return ret;
}
/**
* a2_mux_is_ch_low() - checks if channel is below predefined WM,
* used for flow control implementation
* @lcid: logical channel ID
*
* Returns: true if the channel is below predefined WM,
* false otherwise
*/
int a2_mux_is_ch_low(enum a2_mux_logical_channel_id lcid)
{
unsigned long flags;
int ret;
if (lcid >= A2_MUX_NUM_CHANNELS ||
lcid < 0)
return -EINVAL;
if (!a2_mux_ctx->a2_mux_initialized)
return -ENODEV;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
a2_mux_ctx->bam_ch[lcid].use_wm = 1;
ret = a2_mux_ctx->bam_ch[lcid].num_tx_pkts <= LOW_WATERMARK;
IPADBG("%s: ch %d num tx pkts=%d, LWM=%d\n", __func__,
lcid, a2_mux_ctx->bam_ch[lcid].num_tx_pkts, ret);
if (!bam_ch_is_local_open(lcid)) {
ret = -ENODEV;
IPAERR("%s: port not open: %d\n", __func__,
a2_mux_ctx->bam_ch[lcid].status);
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
return ret;
}
/**
* a2_mux_is_ch_empty() - checks if channel is empty.
* @lcid: logical channel ID
*
* Returns: true if the channel is empty,
* false otherwise
*/
int a2_mux_is_ch_empty(enum a2_mux_logical_channel_id lcid)
{
unsigned long flags;
int ret;
if (lcid >= A2_MUX_NUM_CHANNELS ||
lcid < 0)
return -EINVAL;
if (!a2_mux_ctx->a2_mux_initialized)
return -ENODEV;
spin_lock_irqsave(&a2_mux_ctx->bam_ch[lcid].lock, flags);
a2_mux_ctx->bam_ch[lcid].use_wm = 1;
ret = a2_mux_ctx->bam_ch[lcid].num_tx_pkts == 0;
if (!bam_ch_is_local_open(lcid)) {
ret = -ENODEV;
IPAERR("%s: port not open: %d\n", __func__,
a2_mux_ctx->bam_ch[lcid].status);
}
spin_unlock_irqrestore(&a2_mux_ctx->bam_ch[lcid].lock, flags);
return ret;
}
static int a2_mux_initialize_context(int handle)
{
int i;
a2_mux_ctx->a2_mux_apps_pc_enabled = 1;
a2_mux_ctx->a2_device_handle = handle;
INIT_WORK(&a2_mux_ctx->kickoff_ul_wakeup, kickoff_ul_wakeup_func);
INIT_WORK(&a2_mux_ctx->kickoff_ul_power_down,
kickoff_ul_power_down_func);
INIT_WORK(&a2_mux_ctx->kickoff_ul_request_resource,
kickoff_ul_request_resource_func);
INIT_LIST_HEAD(&a2_mux_ctx->bam_tx_pool);
spin_lock_init(&a2_mux_ctx->bam_tx_pool_spinlock);
mutex_init(&a2_mux_ctx->wakeup_lock);
rwlock_init(&a2_mux_ctx->ul_wakeup_lock);
spin_lock_init(&a2_mux_ctx->wakelock_reference_lock);
a2_mux_ctx->disconnect_ack = 1;
mutex_init(&a2_mux_ctx->smsm_cb_lock);
for (i = 0; i < A2_MUX_NUM_CHANNELS; ++i)
spin_lock_init(&a2_mux_ctx->bam_ch[i].lock);
init_completion(&a2_mux_ctx->ul_wakeup_ack_completion);
init_completion(&a2_mux_ctx->bam_connection_completion);
init_completion(&a2_mux_ctx->request_resource_completion);
init_completion(&a2_mux_ctx->dl_wakeup_completion);
wake_lock_init(&a2_mux_ctx->bam_wakelock,
WAKE_LOCK_SUSPEND, "a2_mux_wakelock");
a2_mux_ctx->a2_mux_initialized = 1;
a2_mux_ctx->a2_mux_send_power_vote_on_init_once = 1;
a2_mux_ctx->a2_mux_tx_workqueue =
create_singlethread_workqueue("a2_mux_tx");
if (!a2_mux_ctx->a2_mux_tx_workqueue) {
IPAERR("%s: a2_mux_tx_workqueue alloc failed\n",
__func__);
return -ENOMEM;
}
a2_mux_ctx->a2_mux_rx_workqueue =
create_singlethread_workqueue("a2_mux_rx");
if (!a2_mux_ctx->a2_mux_rx_workqueue) {
IPAERR("%s: a2_mux_rx_workqueue alloc failed\n",
__func__);
return -ENOMEM;
}
return 0;
}
/**
* a2_mux_init() - initialize A2 MUX component
*
* Returns: 0 on success, negative otherwise
*/
int a2_mux_init(void)
{
int rc;
u32 h;
void *a2_virt_addr;
u32 a2_bam_mem_base;
u32 a2_bam_mem_size;
u32 a2_bam_irq;
struct sps_bam_props a2_props;
IPADBG("%s A2 MUX\n", __func__);
rc = ipa_get_a2_mux_bam_info(&a2_bam_mem_base,
&a2_bam_mem_size,
&a2_bam_irq);
if (rc) {
IPAERR("%s: ipa_get_a2_mux_bam_info failed\n", __func__);
rc = -EFAULT;
goto bail;
}
a2_virt_addr = ioremap_nocache((unsigned long)(a2_bam_mem_base),
a2_bam_mem_size);
if (!a2_virt_addr) {
IPAERR("%s: ioremap failed\n", __func__);
rc = -ENOMEM;
goto bail;
}
memset(&a2_props, 0, sizeof(a2_props));
a2_props.phys_addr = a2_bam_mem_base;
a2_props.virt_addr = a2_virt_addr;
a2_props.virt_size = a2_bam_mem_size;
a2_props.irq = a2_bam_irq;
a2_props.options = SPS_BAM_OPT_IRQ_WAKEUP;
a2_props.num_pipes = A2_NUM_PIPES;
a2_props.summing_threshold = A2_SUMMING_THRESHOLD;
a2_props.manage = SPS_BAM_MGR_DEVICE_REMOTE;
/* need to free on tear down */
rc = sps_register_bam_device(&a2_props, &h);
if (rc < 0) {
IPAERR("%s: register bam error %d\n", __func__, rc);
goto register_bam_failed;
}
a2_mux_ctx = kzalloc(sizeof(*a2_mux_ctx), GFP_KERNEL);
if (!a2_mux_ctx) {
IPAERR("%s: a2_mux_ctx alloc failed, rc: %d\n", __func__, rc);
rc = -ENOMEM;
goto register_bam_failed;
}
rc = a2_mux_initialize_context(h);
if (rc) {
IPAERR("%s: a2_mux_initialize_context failed, rc: %d\n",
__func__, rc);
goto ctx_alloc_failed;
}
rc = a2_mux_pm_initialize_rm();
if (rc) {
IPAERR("%s: a2_mux_pm_initialize_rm failed, rc: %d\n",
__func__, rc);
goto ctx_alloc_failed;
}
rc = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_A2_POWER_CONTROL,
a2_mux_smsm_cb, NULL);
if (rc) {
IPAERR("%s: smsm cb register failed, rc: %d\n", __func__, rc);
rc = -ENOMEM;
goto ctx_alloc_failed;
}
rc = smsm_state_cb_register(SMSM_MODEM_STATE,
SMSM_A2_POWER_CONTROL_ACK,
a2_mux_smsm_ack_cb, NULL);
if (rc) {
IPAERR("%s: smsm ack cb register failed, rc: %d\n",
__func__, rc);
rc = -ENOMEM;
goto smsm_ack_cb_reg_failed;
}
if (smsm_get_state(SMSM_MODEM_STATE) & SMSM_A2_POWER_CONTROL)
a2_mux_smsm_cb(NULL, 0, smsm_get_state(SMSM_MODEM_STATE));
/*
* Set remote channel open for tethered channel since there is
* no actual remote tethered channel
*/
a2_mux_ctx->bam_ch[A2_MUX_TETHERED_0].status |= BAM_CH_REMOTE_OPEN;
rc = 0;
goto bail;
smsm_ack_cb_reg_failed:
smsm_state_cb_deregister(SMSM_MODEM_STATE,
SMSM_A2_POWER_CONTROL,
a2_mux_smsm_cb, NULL);
ctx_alloc_failed:
kfree(a2_mux_ctx);
register_bam_failed:
iounmap(a2_virt_addr);
bail:
return rc;
}
/**
* a2_mux_exit() - destroy A2 MUX component
*
* Returns: 0 on success, negative otherwise
*/
int a2_mux_exit(void)
{
smsm_state_cb_deregister(SMSM_MODEM_STATE,
SMSM_A2_POWER_CONTROL_ACK,
a2_mux_smsm_ack_cb,
NULL);
smsm_state_cb_deregister(SMSM_MODEM_STATE,
SMSM_A2_POWER_CONTROL,
a2_mux_smsm_cb,
NULL);
if (a2_mux_ctx->a2_mux_tx_workqueue)
destroy_workqueue(a2_mux_ctx->a2_mux_tx_workqueue);
if (a2_mux_ctx->a2_mux_rx_workqueue)
destroy_workqueue(a2_mux_ctx->a2_mux_rx_workqueue);
return 0;
}