Skip to content

Commit

Permalink
ath9k: Add btcoex profile management support for AR9462
Browse files Browse the repository at this point in the history
AR9462 chips have the capabilities to provoide bluetooth
profile information. For non-AR9462 btcoex chips, the BT
priority traffic was identified by periodically polling
the respective registers and updated dutycycle, stomptype,
etc. As AR9462 chip offers the BT profile informations,
let us make use of that to update aggregation limit,
dutycycle, stomptype and wieghtages.

Signed-off-by: Rajkumar Manoharan <rmanohar@qca.qualcomm.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
  • Loading branch information
Rajkumar Manoharan authored and John W. Linville committed Nov 8, 2011
1 parent 38df2f0 commit 7dc181c
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 7 deletions.
1 change: 1 addition & 0 deletions drivers/net/wireless/ath/ath.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ enum ATH_DEBUG {
ATH_DBG_BTCOEX = 0x00002000,
ATH_DBG_WMI = 0x00004000,
ATH_DBG_BSTUCK = 0x00008000,
ATH_DBG_MCI = 0x00010000,
ATH_DBG_ANY = 0xffffffff
};

Expand Down
1 change: 1 addition & 0 deletions drivers/net/wireless/ath/ath9k/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ath9k-y += beacon.o \
main.o \
recv.o \
xmit.o \
mci.o \

ath9k-$(CONFIG_ATH9K_RATE_CONTROL) += rc.o
ath9k-$(CONFIG_ATH9K_PCI) += pci.o
Expand Down
3 changes: 3 additions & 0 deletions drivers/net/wireless/ath/ath9k/ath9k.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "debug.h"
#include "common.h"
#include "mci.h"

/*
* Header for the ath9k.ko driver core *only* -- hw code nor any other driver
Expand Down Expand Up @@ -443,7 +444,9 @@ struct ath_btcoex {
u32 btcoex_no_stomp; /* in usec */
u32 btcoex_period; /* in usec */
u32 btscan_no_stomp; /* in usec */
u32 duty_cycle;
struct ath_gen_timer *no_stomp_timer; /* Timer for no BT stomping */
struct ath_mci_profile mci;
};

int ath_init_btcoex_timer(struct ath_softc *sc);
Expand Down
7 changes: 4 additions & 3 deletions drivers/net/wireless/ath/ath9k/gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ static void ath_btcoex_period_timer(unsigned long data)
bool is_btscan;

ath9k_ps_wakeup(sc);
ath_detect_bt_priority(sc);

if (!(ah->caps.hw_caps & ATH9K_HW_CAP_MCI))
ath_detect_bt_priority(sc);
is_btscan = sc->sc_flags & SC_OP_BT_SCAN;

spin_lock_bh(&btcoex->btcoex_lock);
Expand All @@ -212,8 +212,9 @@ static void ath_btcoex_period_timer(unsigned long data)
}

ath9k_ps_restore(sc);
timer_period = btcoex->btcoex_period / 1000;
mod_timer(&btcoex->period_timer, jiffies +
msecs_to_jiffies(ATH_BTCOEX_DEF_BT_PERIOD));
msecs_to_jiffies(timer_period));
}

/*
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/wireless/ath/ath9k/hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -2331,7 +2331,7 @@ int ath9k_hw_fill_cap_info(struct ath_hw *ah)
ah->enabled_cals |= TX_IQ_ON_AGC_CAL;
}
if (AR_SREV_9462(ah))
pCap->hw_caps |= ATH9K_HW_CAP_RTT;
pCap->hw_caps |= ATH9K_HW_CAP_RTT | ATH9K_HW_CAP_MCI;

return 0;
}
Expand Down
11 changes: 11 additions & 0 deletions drivers/net/wireless/ath/ath9k/hw.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ enum ath9k_hw_caps {
ATH9K_HW_CAP_5GHZ = BIT(14),
ATH9K_HW_CAP_APM = BIT(15),
ATH9K_HW_CAP_RTT = BIT(16),
ATH9K_HW_CAP_MCI = BIT(17),
};

struct ath9k_hw_capabilities {
Expand Down Expand Up @@ -419,6 +420,16 @@ enum ath9k_rx_qtype {
ATH9K_RX_QUEUE_MAX,
};

enum ath_mci_gpm_coex_profile_type {
MCI_GPM_COEX_PROFILE_UNKNOWN,
MCI_GPM_COEX_PROFILE_RFCOMM,
MCI_GPM_COEX_PROFILE_A2DP,
MCI_GPM_COEX_PROFILE_HID,
MCI_GPM_COEX_PROFILE_BNEP,
MCI_GPM_COEX_PROFILE_VOICE,
MCI_GPM_COEX_PROFILE_MAX
};

struct ath9k_beacon_state {
u32 bs_nexttbtt;
u32 bs_nextdtim;
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/wireless/ath/ath9k/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ static int ath9k_init_btcoex(struct ath_softc *sc)
txq = sc->tx.txq_map[WME_AC_BE];
ath9k_hw_init_btcoex_hw(sc->sc_ah, txq->axq_qnum);
sc->btcoex.bt_stomp_type = ATH_BTCOEX_STOMP_LOW;
sc->btcoex.duty_cycle = ATH_BTCOEX_DEF_DUTY_CYCLE;
INIT_LIST_HEAD(&sc->btcoex.mci.info);
break;
default:
WARN_ON(1);
Expand Down
6 changes: 4 additions & 2 deletions drivers/net/wireless/ath/ath9k/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1133,8 +1133,9 @@ static int ath9k_start(struct ieee80211_hw *hw)

if ((ah->btcoex_hw.scheme != ATH_BTCOEX_CFG_NONE) &&
!ah->btcoex_hw.enabled) {
ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT,
AR_STOMP_LOW_WLAN_WGHT);
if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_MCI))
ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT,
AR_STOMP_LOW_WLAN_WGHT);
ath9k_hw_btcoex_enable(ah);

if (ah->btcoex_hw.scheme == ATH_BTCOEX_CFG_3WIRE)
Expand Down Expand Up @@ -1237,6 +1238,7 @@ static void ath9k_stop(struct ieee80211_hw *hw)
ath9k_hw_btcoex_disable(ah);
if (ah->btcoex_hw.scheme == ATH_BTCOEX_CFG_3WIRE)
ath9k_btcoex_timer_pause(sc);
ath_mci_flush_profile(&sc->btcoex.mci);
}

spin_lock_bh(&sc->sc_pcu_lock);
Expand Down
254 changes: 254 additions & 0 deletions drivers/net/wireless/ath/ath9k/mci.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
* Copyright (c) 2010-2011 Atheros Communications Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include "ath9k.h"
#include "mci.h"

u8 ath_mci_duty_cycle[] = { 0, 50, 60, 70, 80, 85, 90, 95, 98 };

static struct ath_mci_profile_info*
ath_mci_find_profile(struct ath_mci_profile *mci,
struct ath_mci_profile_info *info)
{
struct ath_mci_profile_info *entry;

list_for_each_entry(entry, &mci->info, list) {
if (entry->conn_handle == info->conn_handle)
break;
}
return entry;
}

static bool ath_mci_add_profile(struct ath_common *common,
struct ath_mci_profile *mci,
struct ath_mci_profile_info *info)
{
struct ath_mci_profile_info *entry;

if ((mci->num_sco == ATH_MCI_MAX_SCO_PROFILE) &&
(info->type == MCI_GPM_COEX_PROFILE_VOICE)) {
ath_dbg(common, ATH_DBG_MCI,
"Too many SCO profile, failed to add new profile\n");
return false;
}

if (((NUM_PROF(mci) - mci->num_sco) == ATH_MCI_MAX_ACL_PROFILE) &&
(info->type != MCI_GPM_COEX_PROFILE_VOICE)) {
ath_dbg(common, ATH_DBG_MCI,
"Too many ACL profile, failed to add new profile\n");
return false;
}

entry = ath_mci_find_profile(mci, info);

if (entry)
memcpy(entry, info, 10);
else {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return false;

memcpy(entry, info, 10);
INC_PROF(mci, info);
list_add_tail(&info->list, &mci->info);
}
return true;
}

static void ath_mci_del_profile(struct ath_common *common,
struct ath_mci_profile *mci,
struct ath_mci_profile_info *info)
{
struct ath_mci_profile_info *entry;

entry = ath_mci_find_profile(mci, info);

if (!entry) {
ath_dbg(common, ATH_DBG_MCI,
"Profile to be deleted not found\n");
return;
}
DEC_PROF(mci, entry);
list_del(&entry->list);
kfree(entry);
}

void ath_mci_flush_profile(struct ath_mci_profile *mci)
{
struct ath_mci_profile_info *info, *tinfo;

list_for_each_entry_safe(info, tinfo, &mci->info, list) {
list_del(&info->list);
DEC_PROF(mci, info);
kfree(info);
}
mci->aggr_limit = 0;
}

static void ath_mci_adjust_aggr_limit(struct ath_btcoex *btcoex)
{
struct ath_mci_profile *mci = &btcoex->mci;
u32 wlan_airtime = btcoex->btcoex_period *
(100 - btcoex->duty_cycle) / 100;

/*
* Scale: wlan_airtime is in ms, aggr_limit is in 0.25 ms.
* When wlan_airtime is less than 4ms, aggregation limit has to be
* adjusted half of wlan_airtime to ensure that the aggregation can fit
* without collision with BT traffic.
*/
if ((wlan_airtime <= 4) &&
(!mci->aggr_limit || (mci->aggr_limit > (2 * wlan_airtime))))
mci->aggr_limit = 2 * wlan_airtime;
}

static void ath_mci_update_scheme(struct ath_softc *sc)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_btcoex *btcoex = &sc->btcoex;
struct ath_mci_profile *mci = &btcoex->mci;
struct ath_mci_profile_info *info;
u32 num_profile = NUM_PROF(mci);

if (num_profile == 1) {
info = list_first_entry(&mci->info,
struct ath_mci_profile_info,
list);
if (mci->num_sco && info->T == 12) {
mci->aggr_limit = 8;
ath_dbg(common, ATH_DBG_MCI,
"Single SCO, aggregation limit 2 ms\n");
} else if ((info->type == MCI_GPM_COEX_PROFILE_BNEP) &&
!info->master) {
btcoex->btcoex_period = 60;
ath_dbg(common, ATH_DBG_MCI,
"Single slave PAN/FTP, bt period 60 ms\n");
} else if ((info->type == MCI_GPM_COEX_PROFILE_HID) &&
(info->T > 0 && info->T < 50) &&
(info->A > 1 || info->W > 1)) {
btcoex->duty_cycle = 30;
mci->aggr_limit = 8;
ath_dbg(common, ATH_DBG_MCI,
"Multiple attempt/timeout single HID "
"aggregation limit 2 ms dutycycle 30%%\n");
}
} else if ((num_profile == 2) && (mci->num_hid == 2)) {
btcoex->duty_cycle = 30;
mci->aggr_limit = 8;
ath_dbg(common, ATH_DBG_MCI,
"Two HIDs aggregation limit 2 ms dutycycle 30%%\n");
} else if (num_profile > 3) {
mci->aggr_limit = 6;
ath_dbg(common, ATH_DBG_MCI,
"Three or more profiles aggregation limit 1.5 ms\n");
}

if (IS_CHAN_2GHZ(sc->sc_ah->curchan)) {
if (IS_CHAN_HT(sc->sc_ah->curchan))
ath_mci_adjust_aggr_limit(btcoex);
else
btcoex->btcoex_period >>= 1;
}

ath9k_hw_btcoex_disable(sc->sc_ah);
ath9k_btcoex_timer_pause(sc);

if (IS_CHAN_5GHZ(sc->sc_ah->curchan))
return;

btcoex->duty_cycle += (mci->num_bdr ? ATH_MCI_MAX_DUTY_CYCLE : 0);
if (btcoex->duty_cycle > ATH_MCI_MAX_DUTY_CYCLE)
btcoex->duty_cycle = ATH_MCI_MAX_DUTY_CYCLE;

btcoex->btcoex_period *= 1000;
btcoex->btcoex_no_stomp = btcoex->btcoex_period *
(100 - btcoex->duty_cycle) / 100;

ath9k_hw_btcoex_enable(sc->sc_ah);
ath9k_btcoex_timer_resume(sc);
}

void ath_mci_process_profile(struct ath_softc *sc,
struct ath_mci_profile_info *info)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_btcoex *btcoex = &sc->btcoex;
struct ath_mci_profile *mci = &btcoex->mci;

if (info->start) {
if (!ath_mci_add_profile(common, mci, info))
return;
} else
ath_mci_del_profile(common, mci, info);

btcoex->btcoex_period = ATH_MCI_DEF_BT_PERIOD;
mci->aggr_limit = mci->num_sco ? 6 : 0;
if (NUM_PROF(mci)) {
btcoex->bt_stomp_type = ATH_BTCOEX_STOMP_LOW;
btcoex->duty_cycle = ath_mci_duty_cycle[NUM_PROF(mci)];
} else {
btcoex->bt_stomp_type = mci->num_mgmt ? ATH_BTCOEX_STOMP_ALL :
ATH_BTCOEX_STOMP_LOW;
btcoex->duty_cycle = ATH_BTCOEX_DEF_DUTY_CYCLE;
}

ath_mci_update_scheme(sc);
}

void ath_mci_process_status(struct ath_softc *sc,
struct ath_mci_profile_status *status)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_btcoex *btcoex = &sc->btcoex;
struct ath_mci_profile *mci = &btcoex->mci;
struct ath_mci_profile_info info;
int i = 0, old_num_mgmt = mci->num_mgmt;

/* Link status type are not handled */
if (status->is_link) {
ath_dbg(common, ATH_DBG_MCI,
"Skip link type status update\n");
return;
}

memset(&info, 0, sizeof(struct ath_mci_profile_info));

info.conn_handle = status->conn_handle;
if (ath_mci_find_profile(mci, &info)) {
ath_dbg(common, ATH_DBG_MCI,
"Skip non link state update for existing profile %d\n",
status->conn_handle);
return;
}
if (status->conn_handle >= ATH_MCI_MAX_PROFILE) {
ath_dbg(common, ATH_DBG_MCI,
"Ignore too many non-link update\n");
return;
}
if (status->is_critical)
__set_bit(status->conn_handle, mci->status);
else
__clear_bit(status->conn_handle, mci->status);

mci->num_mgmt = 0;
do {
if (test_bit(i, mci->status))
mci->num_mgmt++;
} while (++i < ATH_MCI_MAX_PROFILE);

if (old_num_mgmt != mci->num_mgmt)
ath_mci_update_scheme(sc);
}
Loading

0 comments on commit 7dc181c

Please sign in to comment.