Rajkumar Manoharan | 7dc181c | 2011-10-24 18:19:49 +0530 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2010-2011 Atheros Communications Inc. |
| 3 | * |
| 4 | * Permission to use, copy, modify, and/or distribute this software for any |
| 5 | * purpose with or without fee is hereby granted, provided that the above |
| 6 | * copyright notice and this permission notice appear in all copies. |
| 7 | * |
| 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 15 | */ |
| 16 | |
Mohammed Shafi Shajakhan | 9e25365 | 2011-11-30 10:41:23 +0530 | [diff] [blame^] | 17 | #include <linux/dma-mapping.h> |
| 18 | #include <linux/slab.h> |
| 19 | |
Rajkumar Manoharan | 7dc181c | 2011-10-24 18:19:49 +0530 | [diff] [blame] | 20 | #include "ath9k.h" |
| 21 | #include "mci.h" |
| 22 | |
| 23 | u8 ath_mci_duty_cycle[] = { 0, 50, 60, 70, 80, 85, 90, 95, 98 }; |
| 24 | |
| 25 | static struct ath_mci_profile_info* |
| 26 | ath_mci_find_profile(struct ath_mci_profile *mci, |
| 27 | struct ath_mci_profile_info *info) |
| 28 | { |
| 29 | struct ath_mci_profile_info *entry; |
| 30 | |
| 31 | list_for_each_entry(entry, &mci->info, list) { |
| 32 | if (entry->conn_handle == info->conn_handle) |
| 33 | break; |
| 34 | } |
| 35 | return entry; |
| 36 | } |
| 37 | |
| 38 | static bool ath_mci_add_profile(struct ath_common *common, |
| 39 | struct ath_mci_profile *mci, |
| 40 | struct ath_mci_profile_info *info) |
| 41 | { |
| 42 | struct ath_mci_profile_info *entry; |
| 43 | |
| 44 | if ((mci->num_sco == ATH_MCI_MAX_SCO_PROFILE) && |
| 45 | (info->type == MCI_GPM_COEX_PROFILE_VOICE)) { |
| 46 | ath_dbg(common, ATH_DBG_MCI, |
| 47 | "Too many SCO profile, failed to add new profile\n"); |
| 48 | return false; |
| 49 | } |
| 50 | |
| 51 | if (((NUM_PROF(mci) - mci->num_sco) == ATH_MCI_MAX_ACL_PROFILE) && |
| 52 | (info->type != MCI_GPM_COEX_PROFILE_VOICE)) { |
| 53 | ath_dbg(common, ATH_DBG_MCI, |
| 54 | "Too many ACL profile, failed to add new profile\n"); |
| 55 | return false; |
| 56 | } |
| 57 | |
| 58 | entry = ath_mci_find_profile(mci, info); |
| 59 | |
| 60 | if (entry) |
| 61 | memcpy(entry, info, 10); |
| 62 | else { |
| 63 | entry = kzalloc(sizeof(*entry), GFP_KERNEL); |
| 64 | if (!entry) |
| 65 | return false; |
| 66 | |
| 67 | memcpy(entry, info, 10); |
| 68 | INC_PROF(mci, info); |
| 69 | list_add_tail(&info->list, &mci->info); |
| 70 | } |
| 71 | return true; |
| 72 | } |
| 73 | |
| 74 | static void ath_mci_del_profile(struct ath_common *common, |
| 75 | struct ath_mci_profile *mci, |
| 76 | struct ath_mci_profile_info *info) |
| 77 | { |
| 78 | struct ath_mci_profile_info *entry; |
| 79 | |
| 80 | entry = ath_mci_find_profile(mci, info); |
| 81 | |
| 82 | if (!entry) { |
| 83 | ath_dbg(common, ATH_DBG_MCI, |
| 84 | "Profile to be deleted not found\n"); |
| 85 | return; |
| 86 | } |
| 87 | DEC_PROF(mci, entry); |
| 88 | list_del(&entry->list); |
| 89 | kfree(entry); |
| 90 | } |
| 91 | |
| 92 | void ath_mci_flush_profile(struct ath_mci_profile *mci) |
| 93 | { |
| 94 | struct ath_mci_profile_info *info, *tinfo; |
| 95 | |
| 96 | list_for_each_entry_safe(info, tinfo, &mci->info, list) { |
| 97 | list_del(&info->list); |
| 98 | DEC_PROF(mci, info); |
| 99 | kfree(info); |
| 100 | } |
| 101 | mci->aggr_limit = 0; |
| 102 | } |
| 103 | |
| 104 | static void ath_mci_adjust_aggr_limit(struct ath_btcoex *btcoex) |
| 105 | { |
| 106 | struct ath_mci_profile *mci = &btcoex->mci; |
| 107 | u32 wlan_airtime = btcoex->btcoex_period * |
| 108 | (100 - btcoex->duty_cycle) / 100; |
| 109 | |
| 110 | /* |
| 111 | * Scale: wlan_airtime is in ms, aggr_limit is in 0.25 ms. |
| 112 | * When wlan_airtime is less than 4ms, aggregation limit has to be |
| 113 | * adjusted half of wlan_airtime to ensure that the aggregation can fit |
| 114 | * without collision with BT traffic. |
| 115 | */ |
| 116 | if ((wlan_airtime <= 4) && |
| 117 | (!mci->aggr_limit || (mci->aggr_limit > (2 * wlan_airtime)))) |
| 118 | mci->aggr_limit = 2 * wlan_airtime; |
| 119 | } |
| 120 | |
| 121 | static void ath_mci_update_scheme(struct ath_softc *sc) |
| 122 | { |
| 123 | struct ath_common *common = ath9k_hw_common(sc->sc_ah); |
| 124 | struct ath_btcoex *btcoex = &sc->btcoex; |
| 125 | struct ath_mci_profile *mci = &btcoex->mci; |
| 126 | struct ath_mci_profile_info *info; |
| 127 | u32 num_profile = NUM_PROF(mci); |
| 128 | |
| 129 | if (num_profile == 1) { |
| 130 | info = list_first_entry(&mci->info, |
| 131 | struct ath_mci_profile_info, |
| 132 | list); |
| 133 | if (mci->num_sco && info->T == 12) { |
| 134 | mci->aggr_limit = 8; |
| 135 | ath_dbg(common, ATH_DBG_MCI, |
| 136 | "Single SCO, aggregation limit 2 ms\n"); |
| 137 | } else if ((info->type == MCI_GPM_COEX_PROFILE_BNEP) && |
| 138 | !info->master) { |
| 139 | btcoex->btcoex_period = 60; |
| 140 | ath_dbg(common, ATH_DBG_MCI, |
| 141 | "Single slave PAN/FTP, bt period 60 ms\n"); |
| 142 | } else if ((info->type == MCI_GPM_COEX_PROFILE_HID) && |
| 143 | (info->T > 0 && info->T < 50) && |
| 144 | (info->A > 1 || info->W > 1)) { |
| 145 | btcoex->duty_cycle = 30; |
| 146 | mci->aggr_limit = 8; |
| 147 | ath_dbg(common, ATH_DBG_MCI, |
| 148 | "Multiple attempt/timeout single HID " |
| 149 | "aggregation limit 2 ms dutycycle 30%%\n"); |
| 150 | } |
| 151 | } else if ((num_profile == 2) && (mci->num_hid == 2)) { |
| 152 | btcoex->duty_cycle = 30; |
| 153 | mci->aggr_limit = 8; |
| 154 | ath_dbg(common, ATH_DBG_MCI, |
| 155 | "Two HIDs aggregation limit 2 ms dutycycle 30%%\n"); |
| 156 | } else if (num_profile > 3) { |
| 157 | mci->aggr_limit = 6; |
| 158 | ath_dbg(common, ATH_DBG_MCI, |
| 159 | "Three or more profiles aggregation limit 1.5 ms\n"); |
| 160 | } |
| 161 | |
| 162 | if (IS_CHAN_2GHZ(sc->sc_ah->curchan)) { |
| 163 | if (IS_CHAN_HT(sc->sc_ah->curchan)) |
| 164 | ath_mci_adjust_aggr_limit(btcoex); |
| 165 | else |
| 166 | btcoex->btcoex_period >>= 1; |
| 167 | } |
| 168 | |
| 169 | ath9k_hw_btcoex_disable(sc->sc_ah); |
| 170 | ath9k_btcoex_timer_pause(sc); |
| 171 | |
| 172 | if (IS_CHAN_5GHZ(sc->sc_ah->curchan)) |
| 173 | return; |
| 174 | |
| 175 | btcoex->duty_cycle += (mci->num_bdr ? ATH_MCI_MAX_DUTY_CYCLE : 0); |
| 176 | if (btcoex->duty_cycle > ATH_MCI_MAX_DUTY_CYCLE) |
| 177 | btcoex->duty_cycle = ATH_MCI_MAX_DUTY_CYCLE; |
| 178 | |
| 179 | btcoex->btcoex_period *= 1000; |
| 180 | btcoex->btcoex_no_stomp = btcoex->btcoex_period * |
| 181 | (100 - btcoex->duty_cycle) / 100; |
| 182 | |
| 183 | ath9k_hw_btcoex_enable(sc->sc_ah); |
| 184 | ath9k_btcoex_timer_resume(sc); |
| 185 | } |
| 186 | |
| 187 | void ath_mci_process_profile(struct ath_softc *sc, |
| 188 | struct ath_mci_profile_info *info) |
| 189 | { |
| 190 | struct ath_common *common = ath9k_hw_common(sc->sc_ah); |
| 191 | struct ath_btcoex *btcoex = &sc->btcoex; |
| 192 | struct ath_mci_profile *mci = &btcoex->mci; |
| 193 | |
| 194 | if (info->start) { |
| 195 | if (!ath_mci_add_profile(common, mci, info)) |
| 196 | return; |
| 197 | } else |
| 198 | ath_mci_del_profile(common, mci, info); |
| 199 | |
| 200 | btcoex->btcoex_period = ATH_MCI_DEF_BT_PERIOD; |
| 201 | mci->aggr_limit = mci->num_sco ? 6 : 0; |
| 202 | if (NUM_PROF(mci)) { |
| 203 | btcoex->bt_stomp_type = ATH_BTCOEX_STOMP_LOW; |
| 204 | btcoex->duty_cycle = ath_mci_duty_cycle[NUM_PROF(mci)]; |
| 205 | } else { |
| 206 | btcoex->bt_stomp_type = mci->num_mgmt ? ATH_BTCOEX_STOMP_ALL : |
| 207 | ATH_BTCOEX_STOMP_LOW; |
| 208 | btcoex->duty_cycle = ATH_BTCOEX_DEF_DUTY_CYCLE; |
| 209 | } |
| 210 | |
| 211 | ath_mci_update_scheme(sc); |
| 212 | } |
| 213 | |
| 214 | void ath_mci_process_status(struct ath_softc *sc, |
| 215 | struct ath_mci_profile_status *status) |
| 216 | { |
| 217 | struct ath_common *common = ath9k_hw_common(sc->sc_ah); |
| 218 | struct ath_btcoex *btcoex = &sc->btcoex; |
| 219 | struct ath_mci_profile *mci = &btcoex->mci; |
| 220 | struct ath_mci_profile_info info; |
| 221 | int i = 0, old_num_mgmt = mci->num_mgmt; |
| 222 | |
| 223 | /* Link status type are not handled */ |
| 224 | if (status->is_link) { |
| 225 | ath_dbg(common, ATH_DBG_MCI, |
| 226 | "Skip link type status update\n"); |
| 227 | return; |
| 228 | } |
| 229 | |
| 230 | memset(&info, 0, sizeof(struct ath_mci_profile_info)); |
| 231 | |
| 232 | info.conn_handle = status->conn_handle; |
| 233 | if (ath_mci_find_profile(mci, &info)) { |
| 234 | ath_dbg(common, ATH_DBG_MCI, |
| 235 | "Skip non link state update for existing profile %d\n", |
| 236 | status->conn_handle); |
| 237 | return; |
| 238 | } |
| 239 | if (status->conn_handle >= ATH_MCI_MAX_PROFILE) { |
| 240 | ath_dbg(common, ATH_DBG_MCI, |
| 241 | "Ignore too many non-link update\n"); |
| 242 | return; |
| 243 | } |
| 244 | if (status->is_critical) |
| 245 | __set_bit(status->conn_handle, mci->status); |
| 246 | else |
| 247 | __clear_bit(status->conn_handle, mci->status); |
| 248 | |
| 249 | mci->num_mgmt = 0; |
| 250 | do { |
| 251 | if (test_bit(i, mci->status)) |
| 252 | mci->num_mgmt++; |
| 253 | } while (++i < ATH_MCI_MAX_PROFILE); |
| 254 | |
| 255 | if (old_num_mgmt != mci->num_mgmt) |
| 256 | ath_mci_update_scheme(sc); |
| 257 | } |
Mohammed Shafi Shajakhan | 9e25365 | 2011-11-30 10:41:23 +0530 | [diff] [blame^] | 258 | |
| 259 | |
| 260 | static int ath_mci_buf_alloc(struct ath_softc *sc, struct ath_mci_buf *buf) |
| 261 | { |
| 262 | int error = 0; |
| 263 | |
| 264 | buf->bf_addr = dma_alloc_coherent(sc->dev, buf->bf_len, |
| 265 | &buf->bf_paddr, GFP_KERNEL); |
| 266 | |
| 267 | if (buf->bf_addr == NULL) { |
| 268 | error = -ENOMEM; |
| 269 | goto fail; |
| 270 | } |
| 271 | |
| 272 | return 0; |
| 273 | |
| 274 | fail: |
| 275 | memset(buf, 0, sizeof(*buf)); |
| 276 | return error; |
| 277 | } |
| 278 | |
| 279 | static void ath_mci_buf_free(struct ath_softc *sc, struct ath_mci_buf *buf) |
| 280 | { |
| 281 | if (buf->bf_addr) { |
| 282 | dma_free_coherent(sc->dev, buf->bf_len, buf->bf_addr, |
| 283 | buf->bf_paddr); |
| 284 | memset(buf, 0, sizeof(*buf)); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | int ath_mci_setup(struct ath_softc *sc) |
| 289 | { |
| 290 | struct ath_common *common = ath9k_hw_common(sc->sc_ah); |
| 291 | struct ath_mci_coex *mci = &sc->mci_coex; |
| 292 | int error = 0; |
| 293 | |
| 294 | mci->sched_buf.bf_len = ATH_MCI_SCHED_BUF_SIZE + ATH_MCI_GPM_BUF_SIZE; |
| 295 | |
| 296 | if (ath_mci_buf_alloc(sc, &mci->sched_buf)) { |
| 297 | ath_dbg(common, ATH_DBG_FATAL, "MCI buffer alloc failed\n"); |
| 298 | error = -ENOMEM; |
| 299 | goto fail; |
| 300 | } |
| 301 | |
| 302 | mci->sched_buf.bf_len = ATH_MCI_SCHED_BUF_SIZE; |
| 303 | |
| 304 | memset(mci->sched_buf.bf_addr, MCI_GPM_RSVD_PATTERN, |
| 305 | mci->sched_buf.bf_len); |
| 306 | |
| 307 | mci->gpm_buf.bf_len = ATH_MCI_GPM_BUF_SIZE; |
| 308 | mci->gpm_buf.bf_addr = (u8 *)mci->sched_buf.bf_addr + |
| 309 | mci->sched_buf.bf_len; |
| 310 | mci->gpm_buf.bf_paddr = mci->sched_buf.bf_paddr + mci->sched_buf.bf_len; |
| 311 | |
| 312 | /* initialize the buffer */ |
| 313 | memset(mci->gpm_buf.bf_addr, MCI_GPM_RSVD_PATTERN, mci->gpm_buf.bf_len); |
| 314 | |
| 315 | ar9003_mci_setup(sc->sc_ah, mci->gpm_buf.bf_paddr, |
| 316 | mci->gpm_buf.bf_addr, (mci->gpm_buf.bf_len >> 4), |
| 317 | mci->sched_buf.bf_paddr); |
| 318 | fail: |
| 319 | return error; |
| 320 | } |
| 321 | |
| 322 | void ath_mci_cleanup(struct ath_softc *sc) |
| 323 | { |
| 324 | struct ath_hw *ah = sc->sc_ah; |
| 325 | struct ath_mci_coex *mci = &sc->mci_coex; |
| 326 | |
| 327 | /* |
| 328 | * both schedule and gpm buffers will be released |
| 329 | */ |
| 330 | ath_mci_buf_free(sc, &mci->sched_buf); |
| 331 | ar9003_mci_cleanup(ah); |
| 332 | } |