162306a36Sopenharmony_ci/****************************************************************************** 262306a36Sopenharmony_ci * 362306a36Sopenharmony_ci * This file is provided under a dual BSD/GPLv2 license. When using or 462306a36Sopenharmony_ci * redistributing this file, you may do so under either license. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * GPL LICENSE SUMMARY 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 1162306a36Sopenharmony_ci * it under the terms of version 2 of the GNU General Public License as 1262306a36Sopenharmony_ci * published by the Free Software Foundation. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * This program is distributed in the hope that it will be useful, but 1562306a36Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of 1662306a36Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1762306a36Sopenharmony_ci * General Public License for more details. 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * You should have received a copy of the GNU General Public License 2062306a36Sopenharmony_ci * along with this program; if not, write to the Free Software 2162306a36Sopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, 2262306a36Sopenharmony_ci * USA 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * The full GNU General Public License is included in this distribution 2562306a36Sopenharmony_ci * in the file called LICENSE.GPL. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * Contact Information: 2862306a36Sopenharmony_ci * Intel Linux Wireless <ilw@linux.intel.com> 2962306a36Sopenharmony_ci * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * BSD LICENSE 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. 3462306a36Sopenharmony_ci * All rights reserved. 3562306a36Sopenharmony_ci * 3662306a36Sopenharmony_ci * Redistribution and use in source and binary forms, with or without 3762306a36Sopenharmony_ci * modification, are permitted provided that the following conditions 3862306a36Sopenharmony_ci * are met: 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * * Redistributions of source code must retain the above copyright 4162306a36Sopenharmony_ci * notice, this list of conditions and the following disclaimer. 4262306a36Sopenharmony_ci * * Redistributions in binary form must reproduce the above copyright 4362306a36Sopenharmony_ci * notice, this list of conditions and the following disclaimer in 4462306a36Sopenharmony_ci * the documentation and/or other materials provided with the 4562306a36Sopenharmony_ci * distribution. 4662306a36Sopenharmony_ci * * Neither the name Intel Corporation nor the names of its 4762306a36Sopenharmony_ci * contributors may be used to endorse or promote products derived 4862306a36Sopenharmony_ci * from this software without specific prior written permission. 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 5162306a36Sopenharmony_ci * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5262306a36Sopenharmony_ci * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 5362306a36Sopenharmony_ci * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 5462306a36Sopenharmony_ci * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 5562306a36Sopenharmony_ci * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 5662306a36Sopenharmony_ci * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 5762306a36Sopenharmony_ci * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 5862306a36Sopenharmony_ci * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 5962306a36Sopenharmony_ci * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 6062306a36Sopenharmony_ci * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6162306a36Sopenharmony_ci *****************************************************************************/ 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#include <linux/slab.h> 6462306a36Sopenharmony_ci#include <net/mac80211.h> 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci#include "common.h" 6762306a36Sopenharmony_ci#include "4965.h" 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/***************************************************************************** 7062306a36Sopenharmony_ci * INIT calibrations framework 7162306a36Sopenharmony_ci *****************************************************************************/ 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistruct stats_general_data { 7462306a36Sopenharmony_ci u32 beacon_silence_rssi_a; 7562306a36Sopenharmony_ci u32 beacon_silence_rssi_b; 7662306a36Sopenharmony_ci u32 beacon_silence_rssi_c; 7762306a36Sopenharmony_ci u32 beacon_energy_a; 7862306a36Sopenharmony_ci u32 beacon_energy_b; 7962306a36Sopenharmony_ci u32 beacon_energy_c; 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/***************************************************************************** 8362306a36Sopenharmony_ci * RUNTIME calibrations framework 8462306a36Sopenharmony_ci *****************************************************************************/ 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* "false alarms" are signals that our DSP tries to lock onto, 8762306a36Sopenharmony_ci * but then determines that they are either noise, or transmissions 8862306a36Sopenharmony_ci * from a distant wireless network (also "noise", really) that get 8962306a36Sopenharmony_ci * "stepped on" by stronger transmissions within our own network. 9062306a36Sopenharmony_ci * This algorithm attempts to set a sensitivity level that is high 9162306a36Sopenharmony_ci * enough to receive all of our own network traffic, but not so 9262306a36Sopenharmony_ci * high that our DSP gets too busy trying to lock onto non-network 9362306a36Sopenharmony_ci * activity/noise. */ 9462306a36Sopenharmony_cistatic int 9562306a36Sopenharmony_ciil4965_sens_energy_cck(struct il_priv *il, u32 norm_fa, u32 rx_enable_time, 9662306a36Sopenharmony_ci struct stats_general_data *rx_info) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci u32 max_nrg_cck = 0; 9962306a36Sopenharmony_ci int i = 0; 10062306a36Sopenharmony_ci u8 max_silence_rssi = 0; 10162306a36Sopenharmony_ci u32 silence_ref = 0; 10262306a36Sopenharmony_ci u8 silence_rssi_a = 0; 10362306a36Sopenharmony_ci u8 silence_rssi_b = 0; 10462306a36Sopenharmony_ci u8 silence_rssi_c = 0; 10562306a36Sopenharmony_ci u32 val; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* "false_alarms" values below are cross-multiplications to assess the 10862306a36Sopenharmony_ci * numbers of false alarms within the measured period of actual Rx 10962306a36Sopenharmony_ci * (Rx is off when we're txing), vs the min/max expected false alarms 11062306a36Sopenharmony_ci * (some should be expected if rx is sensitive enough) in a 11162306a36Sopenharmony_ci * hypothetical listening period of 200 time units (TU), 204.8 msec: 11262306a36Sopenharmony_ci * 11362306a36Sopenharmony_ci * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time 11462306a36Sopenharmony_ci * 11562306a36Sopenharmony_ci * */ 11662306a36Sopenharmony_ci u32 false_alarms = norm_fa * 200 * 1024; 11762306a36Sopenharmony_ci u32 max_false_alarms = MAX_FA_CCK * rx_enable_time; 11862306a36Sopenharmony_ci u32 min_false_alarms = MIN_FA_CCK * rx_enable_time; 11962306a36Sopenharmony_ci struct il_sensitivity_data *data = NULL; 12062306a36Sopenharmony_ci const struct il_sensitivity_ranges *ranges = il->hw_params.sens; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci data = &(il->sensitivity_data); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci data->nrg_auto_corr_silence_diff = 0; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Find max silence rssi among all 3 receivers. 12762306a36Sopenharmony_ci * This is background noise, which may include transmissions from other 12862306a36Sopenharmony_ci * networks, measured during silence before our network's beacon */ 12962306a36Sopenharmony_ci silence_rssi_a = 13062306a36Sopenharmony_ci (u8) ((rx_info->beacon_silence_rssi_a & ALL_BAND_FILTER) >> 8); 13162306a36Sopenharmony_ci silence_rssi_b = 13262306a36Sopenharmony_ci (u8) ((rx_info->beacon_silence_rssi_b & ALL_BAND_FILTER) >> 8); 13362306a36Sopenharmony_ci silence_rssi_c = 13462306a36Sopenharmony_ci (u8) ((rx_info->beacon_silence_rssi_c & ALL_BAND_FILTER) >> 8); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci val = max(silence_rssi_b, silence_rssi_c); 13762306a36Sopenharmony_ci max_silence_rssi = max(silence_rssi_a, (u8) val); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Store silence rssi in 20-beacon history table */ 14062306a36Sopenharmony_ci data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi; 14162306a36Sopenharmony_ci data->nrg_silence_idx++; 14262306a36Sopenharmony_ci if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L) 14362306a36Sopenharmony_ci data->nrg_silence_idx = 0; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci /* Find max silence rssi across 20 beacon history */ 14662306a36Sopenharmony_ci for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) { 14762306a36Sopenharmony_ci val = data->nrg_silence_rssi[i]; 14862306a36Sopenharmony_ci silence_ref = max(silence_ref, val); 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci D_CALIB("silence a %u, b %u, c %u, 20-bcn max %u\n", silence_rssi_a, 15162306a36Sopenharmony_ci silence_rssi_b, silence_rssi_c, silence_ref); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci /* Find max rx energy (min value!) among all 3 receivers, 15462306a36Sopenharmony_ci * measured during beacon frame. 15562306a36Sopenharmony_ci * Save it in 10-beacon history table. */ 15662306a36Sopenharmony_ci i = data->nrg_energy_idx; 15762306a36Sopenharmony_ci val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c); 15862306a36Sopenharmony_ci data->nrg_value[i] = min(rx_info->beacon_energy_a, val); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci data->nrg_energy_idx++; 16162306a36Sopenharmony_ci if (data->nrg_energy_idx >= 10) 16262306a36Sopenharmony_ci data->nrg_energy_idx = 0; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Find min rx energy (max value) across 10 beacon history. 16562306a36Sopenharmony_ci * This is the minimum signal level that we want to receive well. 16662306a36Sopenharmony_ci * Add backoff (margin so we don't miss slightly lower energy frames). 16762306a36Sopenharmony_ci * This establishes an upper bound (min value) for energy threshold. */ 16862306a36Sopenharmony_ci max_nrg_cck = data->nrg_value[0]; 16962306a36Sopenharmony_ci for (i = 1; i < 10; i++) 17062306a36Sopenharmony_ci max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i])); 17162306a36Sopenharmony_ci max_nrg_cck += 6; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci D_CALIB("rx energy a %u, b %u, c %u, 10-bcn max/min %u\n", 17462306a36Sopenharmony_ci rx_info->beacon_energy_a, rx_info->beacon_energy_b, 17562306a36Sopenharmony_ci rx_info->beacon_energy_c, max_nrg_cck - 6); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Count number of consecutive beacons with fewer-than-desired 17862306a36Sopenharmony_ci * false alarms. */ 17962306a36Sopenharmony_ci if (false_alarms < min_false_alarms) 18062306a36Sopenharmony_ci data->num_in_cck_no_fa++; 18162306a36Sopenharmony_ci else 18262306a36Sopenharmony_ci data->num_in_cck_no_fa = 0; 18362306a36Sopenharmony_ci D_CALIB("consecutive bcns with few false alarms = %u\n", 18462306a36Sopenharmony_ci data->num_in_cck_no_fa); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* If we got too many false alarms this time, reduce sensitivity */ 18762306a36Sopenharmony_ci if (false_alarms > max_false_alarms && 18862306a36Sopenharmony_ci data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK) { 18962306a36Sopenharmony_ci D_CALIB("norm FA %u > max FA %u\n", false_alarms, 19062306a36Sopenharmony_ci max_false_alarms); 19162306a36Sopenharmony_ci D_CALIB("... reducing sensitivity\n"); 19262306a36Sopenharmony_ci data->nrg_curr_state = IL_FA_TOO_MANY; 19362306a36Sopenharmony_ci /* Store for "fewer than desired" on later beacon */ 19462306a36Sopenharmony_ci data->nrg_silence_ref = silence_ref; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* increase energy threshold (reduce nrg value) 19762306a36Sopenharmony_ci * to decrease sensitivity */ 19862306a36Sopenharmony_ci data->nrg_th_cck = data->nrg_th_cck - NRG_STEP_CCK; 19962306a36Sopenharmony_ci /* Else if we got fewer than desired, increase sensitivity */ 20062306a36Sopenharmony_ci } else if (false_alarms < min_false_alarms) { 20162306a36Sopenharmony_ci data->nrg_curr_state = IL_FA_TOO_FEW; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* Compare silence level with silence level for most recent 20462306a36Sopenharmony_ci * healthy number or too many false alarms */ 20562306a36Sopenharmony_ci data->nrg_auto_corr_silence_diff = 20662306a36Sopenharmony_ci (s32) data->nrg_silence_ref - (s32) silence_ref; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci D_CALIB("norm FA %u < min FA %u, silence diff %d\n", 20962306a36Sopenharmony_ci false_alarms, min_false_alarms, 21062306a36Sopenharmony_ci data->nrg_auto_corr_silence_diff); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* Increase value to increase sensitivity, but only if: 21362306a36Sopenharmony_ci * 1a) previous beacon did *not* have *too many* false alarms 21462306a36Sopenharmony_ci * 1b) AND there's a significant difference in Rx levels 21562306a36Sopenharmony_ci * from a previous beacon with too many, or healthy # FAs 21662306a36Sopenharmony_ci * OR 2) We've seen a lot of beacons (100) with too few 21762306a36Sopenharmony_ci * false alarms */ 21862306a36Sopenharmony_ci if (data->nrg_prev_state != IL_FA_TOO_MANY && 21962306a36Sopenharmony_ci (data->nrg_auto_corr_silence_diff > NRG_DIFF || 22062306a36Sopenharmony_ci data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) { 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci D_CALIB("... increasing sensitivity\n"); 22362306a36Sopenharmony_ci /* Increase nrg value to increase sensitivity */ 22462306a36Sopenharmony_ci val = data->nrg_th_cck + NRG_STEP_CCK; 22562306a36Sopenharmony_ci data->nrg_th_cck = min((u32) ranges->min_nrg_cck, val); 22662306a36Sopenharmony_ci } else { 22762306a36Sopenharmony_ci D_CALIB("... but not changing sensitivity\n"); 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* Else we got a healthy number of false alarms, keep status quo */ 23162306a36Sopenharmony_ci } else { 23262306a36Sopenharmony_ci D_CALIB(" FA in safe zone\n"); 23362306a36Sopenharmony_ci data->nrg_curr_state = IL_FA_GOOD_RANGE; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* Store for use in "fewer than desired" with later beacon */ 23662306a36Sopenharmony_ci data->nrg_silence_ref = silence_ref; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* If previous beacon had too many false alarms, 23962306a36Sopenharmony_ci * give it some extra margin by reducing sensitivity again 24062306a36Sopenharmony_ci * (but don't go below measured energy of desired Rx) */ 24162306a36Sopenharmony_ci if (IL_FA_TOO_MANY == data->nrg_prev_state) { 24262306a36Sopenharmony_ci D_CALIB("... increasing margin\n"); 24362306a36Sopenharmony_ci if (data->nrg_th_cck > (max_nrg_cck + NRG_MARGIN)) 24462306a36Sopenharmony_ci data->nrg_th_cck -= NRG_MARGIN; 24562306a36Sopenharmony_ci else 24662306a36Sopenharmony_ci data->nrg_th_cck = max_nrg_cck; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci /* Make sure the energy threshold does not go above the measured 25162306a36Sopenharmony_ci * energy of the desired Rx signals (reduced by backoff margin), 25262306a36Sopenharmony_ci * or else we might start missing Rx frames. 25362306a36Sopenharmony_ci * Lower value is higher energy, so we use max()! 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_ci data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck); 25662306a36Sopenharmony_ci D_CALIB("new nrg_th_cck %u\n", data->nrg_th_cck); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci data->nrg_prev_state = data->nrg_curr_state; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Auto-correlation CCK algorithm */ 26162306a36Sopenharmony_ci if (false_alarms > min_false_alarms) { 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* increase auto_corr values to decrease sensitivity 26462306a36Sopenharmony_ci * so the DSP won't be disturbed by the noise 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_ci if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK) 26762306a36Sopenharmony_ci data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1; 26862306a36Sopenharmony_ci else { 26962306a36Sopenharmony_ci val = data->auto_corr_cck + AUTO_CORR_STEP_CCK; 27062306a36Sopenharmony_ci data->auto_corr_cck = 27162306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_cck, val); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK; 27462306a36Sopenharmony_ci data->auto_corr_cck_mrc = 27562306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_cck_mrc, val); 27662306a36Sopenharmony_ci } else if (false_alarms < min_false_alarms && 27762306a36Sopenharmony_ci (data->nrg_auto_corr_silence_diff > NRG_DIFF || 27862306a36Sopenharmony_ci data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) { 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci /* Decrease auto_corr values to increase sensitivity */ 28162306a36Sopenharmony_ci val = data->auto_corr_cck - AUTO_CORR_STEP_CCK; 28262306a36Sopenharmony_ci data->auto_corr_cck = max((u32) ranges->auto_corr_min_cck, val); 28362306a36Sopenharmony_ci val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK; 28462306a36Sopenharmony_ci data->auto_corr_cck_mrc = 28562306a36Sopenharmony_ci max((u32) ranges->auto_corr_min_cck_mrc, val); 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci return 0; 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic int 29262306a36Sopenharmony_ciil4965_sens_auto_corr_ofdm(struct il_priv *il, u32 norm_fa, u32 rx_enable_time) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci u32 val; 29562306a36Sopenharmony_ci u32 false_alarms = norm_fa * 200 * 1024; 29662306a36Sopenharmony_ci u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time; 29762306a36Sopenharmony_ci u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time; 29862306a36Sopenharmony_ci struct il_sensitivity_data *data = NULL; 29962306a36Sopenharmony_ci const struct il_sensitivity_ranges *ranges = il->hw_params.sens; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci data = &(il->sensitivity_data); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* If we got too many false alarms this time, reduce sensitivity */ 30462306a36Sopenharmony_ci if (false_alarms > max_false_alarms) { 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci D_CALIB("norm FA %u > max FA %u)\n", false_alarms, 30762306a36Sopenharmony_ci max_false_alarms); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM; 31062306a36Sopenharmony_ci data->auto_corr_ofdm = 31162306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_ofdm, val); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM; 31462306a36Sopenharmony_ci data->auto_corr_ofdm_mrc = 31562306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_ofdm_mrc, val); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM; 31862306a36Sopenharmony_ci data->auto_corr_ofdm_x1 = 31962306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_ofdm_x1, val); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM; 32262306a36Sopenharmony_ci data->auto_corr_ofdm_mrc_x1 = 32362306a36Sopenharmony_ci min((u32) ranges->auto_corr_max_ofdm_mrc_x1, val); 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci /* Else if we got fewer than desired, increase sensitivity */ 32762306a36Sopenharmony_ci else if (false_alarms < min_false_alarms) { 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci D_CALIB("norm FA %u < min FA %u\n", false_alarms, 33062306a36Sopenharmony_ci min_false_alarms); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM; 33362306a36Sopenharmony_ci data->auto_corr_ofdm = 33462306a36Sopenharmony_ci max((u32) ranges->auto_corr_min_ofdm, val); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM; 33762306a36Sopenharmony_ci data->auto_corr_ofdm_mrc = 33862306a36Sopenharmony_ci max((u32) ranges->auto_corr_min_ofdm_mrc, val); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM; 34162306a36Sopenharmony_ci data->auto_corr_ofdm_x1 = 34262306a36Sopenharmony_ci max((u32) ranges->auto_corr_min_ofdm_x1, val); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM; 34562306a36Sopenharmony_ci data->auto_corr_ofdm_mrc_x1 = 34662306a36Sopenharmony_ci max((u32) ranges->auto_corr_min_ofdm_mrc_x1, val); 34762306a36Sopenharmony_ci } else { 34862306a36Sopenharmony_ci D_CALIB("min FA %u < norm FA %u < max FA %u OK\n", 34962306a36Sopenharmony_ci min_false_alarms, false_alarms, max_false_alarms); 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci return 0; 35262306a36Sopenharmony_ci} 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_cistatic void 35562306a36Sopenharmony_ciil4965_prepare_legacy_sensitivity_tbl(struct il_priv *il, 35662306a36Sopenharmony_ci struct il_sensitivity_data *data, 35762306a36Sopenharmony_ci __le16 *tbl) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_IDX] = 36062306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_ofdm); 36162306a36Sopenharmony_ci tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_IDX] = 36262306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_ofdm_mrc); 36362306a36Sopenharmony_ci tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_IDX] = 36462306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_ofdm_x1); 36562306a36Sopenharmony_ci tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_IDX] = 36662306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_ofdm_mrc_x1); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX] = 36962306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_cck); 37062306a36Sopenharmony_ci tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX] = 37162306a36Sopenharmony_ci cpu_to_le16((u16) data->auto_corr_cck_mrc); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci tbl[HD_MIN_ENERGY_CCK_DET_IDX] = cpu_to_le16((u16) data->nrg_th_cck); 37462306a36Sopenharmony_ci tbl[HD_MIN_ENERGY_OFDM_DET_IDX] = cpu_to_le16((u16) data->nrg_th_ofdm); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci tbl[HD_BARKER_CORR_TH_ADD_MIN_IDX] = 37762306a36Sopenharmony_ci cpu_to_le16(data->barker_corr_th_min); 37862306a36Sopenharmony_ci tbl[HD_BARKER_CORR_TH_ADD_MIN_MRC_IDX] = 37962306a36Sopenharmony_ci cpu_to_le16(data->barker_corr_th_min_mrc); 38062306a36Sopenharmony_ci tbl[HD_OFDM_ENERGY_TH_IN_IDX] = cpu_to_le16(data->nrg_th_cca); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci D_CALIB("ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n", 38362306a36Sopenharmony_ci data->auto_corr_ofdm, data->auto_corr_ofdm_mrc, 38462306a36Sopenharmony_ci data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1, 38562306a36Sopenharmony_ci data->nrg_th_ofdm); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci D_CALIB("cck: ac %u mrc %u thresh %u\n", data->auto_corr_cck, 38862306a36Sopenharmony_ci data->auto_corr_cck_mrc, data->nrg_th_cck); 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci/* Prepare a C_SENSITIVITY, send to uCode if values have changed */ 39262306a36Sopenharmony_cistatic int 39362306a36Sopenharmony_ciil4965_sensitivity_write(struct il_priv *il) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct il_sensitivity_cmd cmd; 39662306a36Sopenharmony_ci struct il_sensitivity_data *data = NULL; 39762306a36Sopenharmony_ci struct il_host_cmd cmd_out = { 39862306a36Sopenharmony_ci .id = C_SENSITIVITY, 39962306a36Sopenharmony_ci .len = sizeof(struct il_sensitivity_cmd), 40062306a36Sopenharmony_ci .flags = CMD_ASYNC, 40162306a36Sopenharmony_ci .data = &cmd, 40262306a36Sopenharmony_ci }; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci data = &(il->sensitivity_data); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci memset(&cmd, 0, sizeof(cmd)); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci il4965_prepare_legacy_sensitivity_tbl(il, data, &cmd.table[0]); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci /* Update uCode's "work" table, and copy it to DSP */ 41162306a36Sopenharmony_ci cmd.control = C_SENSITIVITY_CONTROL_WORK_TBL; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci /* Don't send command to uCode if nothing has changed */ 41462306a36Sopenharmony_ci if (!memcmp 41562306a36Sopenharmony_ci (&cmd.table[0], &(il->sensitivity_tbl[0]), 41662306a36Sopenharmony_ci sizeof(u16) * HD_TBL_SIZE)) { 41762306a36Sopenharmony_ci D_CALIB("No change in C_SENSITIVITY\n"); 41862306a36Sopenharmony_ci return 0; 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci /* Copy table for comparison next time */ 42262306a36Sopenharmony_ci memcpy(&(il->sensitivity_tbl[0]), &(cmd.table[0]), 42362306a36Sopenharmony_ci sizeof(u16) * HD_TBL_SIZE); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci return il_send_cmd(il, &cmd_out); 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_civoid 42962306a36Sopenharmony_ciil4965_init_sensitivity(struct il_priv *il) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci int ret = 0; 43262306a36Sopenharmony_ci int i; 43362306a36Sopenharmony_ci struct il_sensitivity_data *data = NULL; 43462306a36Sopenharmony_ci const struct il_sensitivity_ranges *ranges = il->hw_params.sens; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (il->disable_sens_cal) 43762306a36Sopenharmony_ci return; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci D_CALIB("Start il4965_init_sensitivity\n"); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci /* Clear driver's sensitivity algo data */ 44262306a36Sopenharmony_ci data = &(il->sensitivity_data); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci if (ranges == NULL) 44562306a36Sopenharmony_ci return; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci memset(data, 0, sizeof(struct il_sensitivity_data)); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci data->num_in_cck_no_fa = 0; 45062306a36Sopenharmony_ci data->nrg_curr_state = IL_FA_TOO_MANY; 45162306a36Sopenharmony_ci data->nrg_prev_state = IL_FA_TOO_MANY; 45262306a36Sopenharmony_ci data->nrg_silence_ref = 0; 45362306a36Sopenharmony_ci data->nrg_silence_idx = 0; 45462306a36Sopenharmony_ci data->nrg_energy_idx = 0; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci for (i = 0; i < 10; i++) 45762306a36Sopenharmony_ci data->nrg_value[i] = 0; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) 46062306a36Sopenharmony_ci data->nrg_silence_rssi[i] = 0; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci data->auto_corr_ofdm = ranges->auto_corr_min_ofdm; 46362306a36Sopenharmony_ci data->auto_corr_ofdm_mrc = ranges->auto_corr_min_ofdm_mrc; 46462306a36Sopenharmony_ci data->auto_corr_ofdm_x1 = ranges->auto_corr_min_ofdm_x1; 46562306a36Sopenharmony_ci data->auto_corr_ofdm_mrc_x1 = ranges->auto_corr_min_ofdm_mrc_x1; 46662306a36Sopenharmony_ci data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF; 46762306a36Sopenharmony_ci data->auto_corr_cck_mrc = ranges->auto_corr_min_cck_mrc; 46862306a36Sopenharmony_ci data->nrg_th_cck = ranges->nrg_th_cck; 46962306a36Sopenharmony_ci data->nrg_th_ofdm = ranges->nrg_th_ofdm; 47062306a36Sopenharmony_ci data->barker_corr_th_min = ranges->barker_corr_th_min; 47162306a36Sopenharmony_ci data->barker_corr_th_min_mrc = ranges->barker_corr_th_min_mrc; 47262306a36Sopenharmony_ci data->nrg_th_cca = ranges->nrg_th_cca; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci data->last_bad_plcp_cnt_ofdm = 0; 47562306a36Sopenharmony_ci data->last_fa_cnt_ofdm = 0; 47662306a36Sopenharmony_ci data->last_bad_plcp_cnt_cck = 0; 47762306a36Sopenharmony_ci data->last_fa_cnt_cck = 0; 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci ret |= il4965_sensitivity_write(il); 48062306a36Sopenharmony_ci D_CALIB("<<return 0x%X\n", ret); 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_civoid 48462306a36Sopenharmony_ciil4965_sensitivity_calibration(struct il_priv *il, void *resp) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci u32 rx_enable_time; 48762306a36Sopenharmony_ci u32 fa_cck; 48862306a36Sopenharmony_ci u32 fa_ofdm; 48962306a36Sopenharmony_ci u32 bad_plcp_cck; 49062306a36Sopenharmony_ci u32 bad_plcp_ofdm; 49162306a36Sopenharmony_ci u32 norm_fa_ofdm; 49262306a36Sopenharmony_ci u32 norm_fa_cck; 49362306a36Sopenharmony_ci struct il_sensitivity_data *data = NULL; 49462306a36Sopenharmony_ci struct stats_rx_non_phy *rx_info; 49562306a36Sopenharmony_ci struct stats_rx_phy *ofdm, *cck; 49662306a36Sopenharmony_ci unsigned long flags; 49762306a36Sopenharmony_ci struct stats_general_data statis; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci if (il->disable_sens_cal) 50062306a36Sopenharmony_ci return; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci data = &(il->sensitivity_data); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (!il_is_any_associated(il)) { 50562306a36Sopenharmony_ci D_CALIB("<< - not associated\n"); 50662306a36Sopenharmony_ci return; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci spin_lock_irqsave(&il->lock, flags); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci rx_info = &(((struct il_notif_stats *)resp)->rx.general); 51262306a36Sopenharmony_ci ofdm = &(((struct il_notif_stats *)resp)->rx.ofdm); 51362306a36Sopenharmony_ci cck = &(((struct il_notif_stats *)resp)->rx.cck); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { 51662306a36Sopenharmony_ci D_CALIB("<< invalid data.\n"); 51762306a36Sopenharmony_ci spin_unlock_irqrestore(&il->lock, flags); 51862306a36Sopenharmony_ci return; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci /* Extract Statistics: */ 52262306a36Sopenharmony_ci rx_enable_time = le32_to_cpu(rx_info->channel_load); 52362306a36Sopenharmony_ci fa_cck = le32_to_cpu(cck->false_alarm_cnt); 52462306a36Sopenharmony_ci fa_ofdm = le32_to_cpu(ofdm->false_alarm_cnt); 52562306a36Sopenharmony_ci bad_plcp_cck = le32_to_cpu(cck->plcp_err); 52662306a36Sopenharmony_ci bad_plcp_ofdm = le32_to_cpu(ofdm->plcp_err); 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci statis.beacon_silence_rssi_a = 52962306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_a); 53062306a36Sopenharmony_ci statis.beacon_silence_rssi_b = 53162306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_b); 53262306a36Sopenharmony_ci statis.beacon_silence_rssi_c = 53362306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_c); 53462306a36Sopenharmony_ci statis.beacon_energy_a = le32_to_cpu(rx_info->beacon_energy_a); 53562306a36Sopenharmony_ci statis.beacon_energy_b = le32_to_cpu(rx_info->beacon_energy_b); 53662306a36Sopenharmony_ci statis.beacon_energy_c = le32_to_cpu(rx_info->beacon_energy_c); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci spin_unlock_irqrestore(&il->lock, flags); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci D_CALIB("rx_enable_time = %u usecs\n", rx_enable_time); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if (!rx_enable_time) { 54362306a36Sopenharmony_ci D_CALIB("<< RX Enable Time == 0!\n"); 54462306a36Sopenharmony_ci return; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci /* These stats increase monotonically, and do not reset 54862306a36Sopenharmony_ci * at each beacon. Calculate difference from last value, or just 54962306a36Sopenharmony_ci * use the new stats value if it has reset or wrapped around. */ 55062306a36Sopenharmony_ci if (data->last_bad_plcp_cnt_cck > bad_plcp_cck) 55162306a36Sopenharmony_ci data->last_bad_plcp_cnt_cck = bad_plcp_cck; 55262306a36Sopenharmony_ci else { 55362306a36Sopenharmony_ci bad_plcp_cck -= data->last_bad_plcp_cnt_cck; 55462306a36Sopenharmony_ci data->last_bad_plcp_cnt_cck += bad_plcp_cck; 55562306a36Sopenharmony_ci } 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci if (data->last_bad_plcp_cnt_ofdm > bad_plcp_ofdm) 55862306a36Sopenharmony_ci data->last_bad_plcp_cnt_ofdm = bad_plcp_ofdm; 55962306a36Sopenharmony_ci else { 56062306a36Sopenharmony_ci bad_plcp_ofdm -= data->last_bad_plcp_cnt_ofdm; 56162306a36Sopenharmony_ci data->last_bad_plcp_cnt_ofdm += bad_plcp_ofdm; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci if (data->last_fa_cnt_ofdm > fa_ofdm) 56562306a36Sopenharmony_ci data->last_fa_cnt_ofdm = fa_ofdm; 56662306a36Sopenharmony_ci else { 56762306a36Sopenharmony_ci fa_ofdm -= data->last_fa_cnt_ofdm; 56862306a36Sopenharmony_ci data->last_fa_cnt_ofdm += fa_ofdm; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci if (data->last_fa_cnt_cck > fa_cck) 57262306a36Sopenharmony_ci data->last_fa_cnt_cck = fa_cck; 57362306a36Sopenharmony_ci else { 57462306a36Sopenharmony_ci fa_cck -= data->last_fa_cnt_cck; 57562306a36Sopenharmony_ci data->last_fa_cnt_cck += fa_cck; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci /* Total aborted signal locks */ 57962306a36Sopenharmony_ci norm_fa_ofdm = fa_ofdm + bad_plcp_ofdm; 58062306a36Sopenharmony_ci norm_fa_cck = fa_cck + bad_plcp_cck; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci D_CALIB("cck: fa %u badp %u ofdm: fa %u badp %u\n", fa_cck, 58362306a36Sopenharmony_ci bad_plcp_cck, fa_ofdm, bad_plcp_ofdm); 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci il4965_sens_auto_corr_ofdm(il, norm_fa_ofdm, rx_enable_time); 58662306a36Sopenharmony_ci il4965_sens_energy_cck(il, norm_fa_cck, rx_enable_time, &statis); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci il4965_sensitivity_write(il); 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic inline u8 59262306a36Sopenharmony_ciil4965_find_first_chain(u8 mask) 59362306a36Sopenharmony_ci{ 59462306a36Sopenharmony_ci if (mask & ANT_A) 59562306a36Sopenharmony_ci return CHAIN_A; 59662306a36Sopenharmony_ci if (mask & ANT_B) 59762306a36Sopenharmony_ci return CHAIN_B; 59862306a36Sopenharmony_ci return CHAIN_C; 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci/* 60262306a36Sopenharmony_ci * Run disconnected antenna algorithm to find out which antennas are 60362306a36Sopenharmony_ci * disconnected. 60462306a36Sopenharmony_ci */ 60562306a36Sopenharmony_cistatic void 60662306a36Sopenharmony_ciil4965_find_disconn_antenna(struct il_priv *il, u32 * average_sig, 60762306a36Sopenharmony_ci struct il_chain_noise_data *data) 60862306a36Sopenharmony_ci{ 60962306a36Sopenharmony_ci u32 active_chains = 0; 61062306a36Sopenharmony_ci u32 max_average_sig; 61162306a36Sopenharmony_ci u16 max_average_sig_antenna_i; 61262306a36Sopenharmony_ci u8 num_tx_chains; 61362306a36Sopenharmony_ci u8 first_chain; 61462306a36Sopenharmony_ci u16 i = 0; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci average_sig[0] = 61762306a36Sopenharmony_ci data->chain_signal_a / 61862306a36Sopenharmony_ci il->cfg->chain_noise_num_beacons; 61962306a36Sopenharmony_ci average_sig[1] = 62062306a36Sopenharmony_ci data->chain_signal_b / 62162306a36Sopenharmony_ci il->cfg->chain_noise_num_beacons; 62262306a36Sopenharmony_ci average_sig[2] = 62362306a36Sopenharmony_ci data->chain_signal_c / 62462306a36Sopenharmony_ci il->cfg->chain_noise_num_beacons; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci if (average_sig[0] >= average_sig[1]) { 62762306a36Sopenharmony_ci max_average_sig = average_sig[0]; 62862306a36Sopenharmony_ci max_average_sig_antenna_i = 0; 62962306a36Sopenharmony_ci active_chains = (1 << max_average_sig_antenna_i); 63062306a36Sopenharmony_ci } else { 63162306a36Sopenharmony_ci max_average_sig = average_sig[1]; 63262306a36Sopenharmony_ci max_average_sig_antenna_i = 1; 63362306a36Sopenharmony_ci active_chains = (1 << max_average_sig_antenna_i); 63462306a36Sopenharmony_ci } 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci if (average_sig[2] >= max_average_sig) { 63762306a36Sopenharmony_ci max_average_sig = average_sig[2]; 63862306a36Sopenharmony_ci max_average_sig_antenna_i = 2; 63962306a36Sopenharmony_ci active_chains = (1 << max_average_sig_antenna_i); 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci D_CALIB("average_sig: a %d b %d c %d\n", average_sig[0], average_sig[1], 64362306a36Sopenharmony_ci average_sig[2]); 64462306a36Sopenharmony_ci D_CALIB("max_average_sig = %d, antenna %d\n", max_average_sig, 64562306a36Sopenharmony_ci max_average_sig_antenna_i); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci /* Compare signal strengths for all 3 receivers. */ 64862306a36Sopenharmony_ci for (i = 0; i < NUM_RX_CHAINS; i++) { 64962306a36Sopenharmony_ci if (i != max_average_sig_antenna_i) { 65062306a36Sopenharmony_ci s32 rssi_delta = (max_average_sig - average_sig[i]); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci /* If signal is very weak, compared with 65362306a36Sopenharmony_ci * strongest, mark it as disconnected. */ 65462306a36Sopenharmony_ci if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS) 65562306a36Sopenharmony_ci data->disconn_array[i] = 1; 65662306a36Sopenharmony_ci else 65762306a36Sopenharmony_ci active_chains |= (1 << i); 65862306a36Sopenharmony_ci D_CALIB("i = %d rssiDelta = %d " 65962306a36Sopenharmony_ci "disconn_array[i] = %d\n", i, rssi_delta, 66062306a36Sopenharmony_ci data->disconn_array[i]); 66162306a36Sopenharmony_ci } 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci /* 66562306a36Sopenharmony_ci * The above algorithm sometimes fails when the ucode 66662306a36Sopenharmony_ci * reports 0 for all chains. It's not clear why that 66762306a36Sopenharmony_ci * happens to start with, but it is then causing trouble 66862306a36Sopenharmony_ci * because this can make us enable more chains than the 66962306a36Sopenharmony_ci * hardware really has. 67062306a36Sopenharmony_ci * 67162306a36Sopenharmony_ci * To be safe, simply mask out any chains that we know 67262306a36Sopenharmony_ci * are not on the device. 67362306a36Sopenharmony_ci */ 67462306a36Sopenharmony_ci active_chains &= il->hw_params.valid_rx_ant; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci num_tx_chains = 0; 67762306a36Sopenharmony_ci for (i = 0; i < NUM_RX_CHAINS; i++) { 67862306a36Sopenharmony_ci /* loops on all the bits of 67962306a36Sopenharmony_ci * il->hw_setting.valid_tx_ant */ 68062306a36Sopenharmony_ci u8 ant_msk = (1 << i); 68162306a36Sopenharmony_ci if (!(il->hw_params.valid_tx_ant & ant_msk)) 68262306a36Sopenharmony_ci continue; 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci num_tx_chains++; 68562306a36Sopenharmony_ci if (data->disconn_array[i] == 0) 68662306a36Sopenharmony_ci /* there is a Tx antenna connected */ 68762306a36Sopenharmony_ci break; 68862306a36Sopenharmony_ci if (num_tx_chains == il->hw_params.tx_chains_num && 68962306a36Sopenharmony_ci data->disconn_array[i]) { 69062306a36Sopenharmony_ci /* 69162306a36Sopenharmony_ci * If all chains are disconnected 69262306a36Sopenharmony_ci * connect the first valid tx chain 69362306a36Sopenharmony_ci */ 69462306a36Sopenharmony_ci first_chain = 69562306a36Sopenharmony_ci il4965_find_first_chain(il->cfg->valid_tx_ant); 69662306a36Sopenharmony_ci data->disconn_array[first_chain] = 0; 69762306a36Sopenharmony_ci active_chains |= BIT(first_chain); 69862306a36Sopenharmony_ci D_CALIB("All Tx chains are disconnected" 69962306a36Sopenharmony_ci "- declare %d as connected\n", first_chain); 70062306a36Sopenharmony_ci break; 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci } 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci if (active_chains != il->hw_params.valid_rx_ant && 70562306a36Sopenharmony_ci active_chains != il->chain_noise_data.active_chains) 70662306a36Sopenharmony_ci D_CALIB("Detected that not all antennas are connected! " 70762306a36Sopenharmony_ci "Connected: %#x, valid: %#x.\n", active_chains, 70862306a36Sopenharmony_ci il->hw_params.valid_rx_ant); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci /* Save for use within RXON, TX, SCAN commands, etc. */ 71162306a36Sopenharmony_ci data->active_chains = active_chains; 71262306a36Sopenharmony_ci D_CALIB("active_chains (bitwise) = 0x%x\n", active_chains); 71362306a36Sopenharmony_ci} 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic void 71662306a36Sopenharmony_ciil4965_gain_computation(struct il_priv *il, u32 * average_noise, 71762306a36Sopenharmony_ci u16 min_average_noise_antenna_i, u32 min_average_noise, 71862306a36Sopenharmony_ci u8 default_chain) 71962306a36Sopenharmony_ci{ 72062306a36Sopenharmony_ci int i, ret; 72162306a36Sopenharmony_ci struct il_chain_noise_data *data = &il->chain_noise_data; 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci data->delta_gain_code[min_average_noise_antenna_i] = 0; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci for (i = default_chain; i < NUM_RX_CHAINS; i++) { 72662306a36Sopenharmony_ci s32 delta_g = 0; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci if (!data->disconn_array[i] && 72962306a36Sopenharmony_ci data->delta_gain_code[i] == 73062306a36Sopenharmony_ci CHAIN_NOISE_DELTA_GAIN_INIT_VAL) { 73162306a36Sopenharmony_ci delta_g = average_noise[i] - min_average_noise; 73262306a36Sopenharmony_ci data->delta_gain_code[i] = (u8) ((delta_g * 10) / 15); 73362306a36Sopenharmony_ci data->delta_gain_code[i] = 73462306a36Sopenharmony_ci min(data->delta_gain_code[i], 73562306a36Sopenharmony_ci (u8) CHAIN_NOISE_MAX_DELTA_GAIN_CODE); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci data->delta_gain_code[i] = 73862306a36Sopenharmony_ci (data->delta_gain_code[i] | (1 << 2)); 73962306a36Sopenharmony_ci } else { 74062306a36Sopenharmony_ci data->delta_gain_code[i] = 0; 74162306a36Sopenharmony_ci } 74262306a36Sopenharmony_ci } 74362306a36Sopenharmony_ci D_CALIB("delta_gain_codes: a %d b %d c %d\n", data->delta_gain_code[0], 74462306a36Sopenharmony_ci data->delta_gain_code[1], data->delta_gain_code[2]); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci /* Differential gain gets sent to uCode only once */ 74762306a36Sopenharmony_ci if (!data->radio_write) { 74862306a36Sopenharmony_ci struct il_calib_diff_gain_cmd cmd; 74962306a36Sopenharmony_ci data->radio_write = 1; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci memset(&cmd, 0, sizeof(cmd)); 75262306a36Sopenharmony_ci cmd.hdr.op_code = IL_PHY_CALIBRATE_DIFF_GAIN_CMD; 75362306a36Sopenharmony_ci cmd.diff_gain_a = data->delta_gain_code[0]; 75462306a36Sopenharmony_ci cmd.diff_gain_b = data->delta_gain_code[1]; 75562306a36Sopenharmony_ci cmd.diff_gain_c = data->delta_gain_code[2]; 75662306a36Sopenharmony_ci ret = il_send_cmd_pdu(il, C_PHY_CALIBRATION, sizeof(cmd), &cmd); 75762306a36Sopenharmony_ci if (ret) 75862306a36Sopenharmony_ci D_CALIB("fail sending cmd " "C_PHY_CALIBRATION\n"); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci /* TODO we might want recalculate 76162306a36Sopenharmony_ci * rx_chain in rxon cmd */ 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci /* Mark so we run this algo only once! */ 76462306a36Sopenharmony_ci data->state = IL_CHAIN_NOISE_CALIBRATED; 76562306a36Sopenharmony_ci } 76662306a36Sopenharmony_ci} 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci/* 76962306a36Sopenharmony_ci * Accumulate 16 beacons of signal and noise stats for each of 77062306a36Sopenharmony_ci * 3 receivers/antennas/rx-chains, then figure out: 77162306a36Sopenharmony_ci * 1) Which antennas are connected. 77262306a36Sopenharmony_ci * 2) Differential rx gain settings to balance the 3 receivers. 77362306a36Sopenharmony_ci */ 77462306a36Sopenharmony_civoid 77562306a36Sopenharmony_ciil4965_chain_noise_calibration(struct il_priv *il, void *stat_resp) 77662306a36Sopenharmony_ci{ 77762306a36Sopenharmony_ci struct il_chain_noise_data *data = NULL; 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci u32 chain_noise_a; 78062306a36Sopenharmony_ci u32 chain_noise_b; 78162306a36Sopenharmony_ci u32 chain_noise_c; 78262306a36Sopenharmony_ci u32 chain_sig_a; 78362306a36Sopenharmony_ci u32 chain_sig_b; 78462306a36Sopenharmony_ci u32 chain_sig_c; 78562306a36Sopenharmony_ci u32 average_sig[NUM_RX_CHAINS] = { INITIALIZATION_VALUE }; 78662306a36Sopenharmony_ci u32 average_noise[NUM_RX_CHAINS] = { INITIALIZATION_VALUE }; 78762306a36Sopenharmony_ci u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE; 78862306a36Sopenharmony_ci u16 min_average_noise_antenna_i = INITIALIZATION_VALUE; 78962306a36Sopenharmony_ci u16 i = 0; 79062306a36Sopenharmony_ci u16 rxon_chnum = INITIALIZATION_VALUE; 79162306a36Sopenharmony_ci u16 stat_chnum = INITIALIZATION_VALUE; 79262306a36Sopenharmony_ci u8 rxon_band24; 79362306a36Sopenharmony_ci u8 stat_band24; 79462306a36Sopenharmony_ci unsigned long flags; 79562306a36Sopenharmony_ci struct stats_rx_non_phy *rx_info; 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci if (il->disable_chain_noise_cal) 79862306a36Sopenharmony_ci return; 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci data = &(il->chain_noise_data); 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci /* 80362306a36Sopenharmony_ci * Accumulate just the first "chain_noise_num_beacons" after 80462306a36Sopenharmony_ci * the first association, then we're done forever. 80562306a36Sopenharmony_ci */ 80662306a36Sopenharmony_ci if (data->state != IL_CHAIN_NOISE_ACCUMULATE) { 80762306a36Sopenharmony_ci if (data->state == IL_CHAIN_NOISE_ALIVE) 80862306a36Sopenharmony_ci D_CALIB("Wait for noise calib reset\n"); 80962306a36Sopenharmony_ci return; 81062306a36Sopenharmony_ci } 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci spin_lock_irqsave(&il->lock, flags); 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci rx_info = &(((struct il_notif_stats *)stat_resp)->rx.general); 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { 81762306a36Sopenharmony_ci D_CALIB(" << Interference data unavailable\n"); 81862306a36Sopenharmony_ci spin_unlock_irqrestore(&il->lock, flags); 81962306a36Sopenharmony_ci return; 82062306a36Sopenharmony_ci } 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci rxon_band24 = !!(il->staging.flags & RXON_FLG_BAND_24G_MSK); 82362306a36Sopenharmony_ci rxon_chnum = le16_to_cpu(il->staging.channel); 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci stat_band24 = 82662306a36Sopenharmony_ci !!(((struct il_notif_stats *)stat_resp)-> 82762306a36Sopenharmony_ci flag & STATS_REPLY_FLG_BAND_24G_MSK); 82862306a36Sopenharmony_ci stat_chnum = 82962306a36Sopenharmony_ci le32_to_cpu(((struct il_notif_stats *)stat_resp)->flag) >> 16; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci /* Make sure we accumulate data for just the associated channel 83262306a36Sopenharmony_ci * (even if scanning). */ 83362306a36Sopenharmony_ci if (rxon_chnum != stat_chnum || rxon_band24 != stat_band24) { 83462306a36Sopenharmony_ci D_CALIB("Stats not from chan=%d, band24=%d\n", rxon_chnum, 83562306a36Sopenharmony_ci rxon_band24); 83662306a36Sopenharmony_ci spin_unlock_irqrestore(&il->lock, flags); 83762306a36Sopenharmony_ci return; 83862306a36Sopenharmony_ci } 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci /* 84162306a36Sopenharmony_ci * Accumulate beacon stats values across 84262306a36Sopenharmony_ci * "chain_noise_num_beacons" 84362306a36Sopenharmony_ci */ 84462306a36Sopenharmony_ci chain_noise_a = 84562306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; 84662306a36Sopenharmony_ci chain_noise_b = 84762306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; 84862306a36Sopenharmony_ci chain_noise_c = 84962306a36Sopenharmony_ci le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER; 85262306a36Sopenharmony_ci chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER; 85362306a36Sopenharmony_ci chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER; 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci spin_unlock_irqrestore(&il->lock, flags); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci data->beacon_count++; 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci data->chain_noise_a = (chain_noise_a + data->chain_noise_a); 86062306a36Sopenharmony_ci data->chain_noise_b = (chain_noise_b + data->chain_noise_b); 86162306a36Sopenharmony_ci data->chain_noise_c = (chain_noise_c + data->chain_noise_c); 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci data->chain_signal_a = (chain_sig_a + data->chain_signal_a); 86462306a36Sopenharmony_ci data->chain_signal_b = (chain_sig_b + data->chain_signal_b); 86562306a36Sopenharmony_ci data->chain_signal_c = (chain_sig_c + data->chain_signal_c); 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci D_CALIB("chan=%d, band24=%d, beacon=%d\n", rxon_chnum, rxon_band24, 86862306a36Sopenharmony_ci data->beacon_count); 86962306a36Sopenharmony_ci D_CALIB("chain_sig: a %d b %d c %d\n", chain_sig_a, chain_sig_b, 87062306a36Sopenharmony_ci chain_sig_c); 87162306a36Sopenharmony_ci D_CALIB("chain_noise: a %d b %d c %d\n", chain_noise_a, chain_noise_b, 87262306a36Sopenharmony_ci chain_noise_c); 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci /* If this is the "chain_noise_num_beacons", determine: 87562306a36Sopenharmony_ci * 1) Disconnected antennas (using signal strengths) 87662306a36Sopenharmony_ci * 2) Differential gain (using silence noise) to balance receivers */ 87762306a36Sopenharmony_ci if (data->beacon_count != il->cfg->chain_noise_num_beacons) 87862306a36Sopenharmony_ci return; 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci /* Analyze signal for disconnected antenna */ 88162306a36Sopenharmony_ci il4965_find_disconn_antenna(il, average_sig, data); 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci /* Analyze noise for rx balance */ 88462306a36Sopenharmony_ci average_noise[0] = 88562306a36Sopenharmony_ci data->chain_noise_a / il->cfg->chain_noise_num_beacons; 88662306a36Sopenharmony_ci average_noise[1] = 88762306a36Sopenharmony_ci data->chain_noise_b / il->cfg->chain_noise_num_beacons; 88862306a36Sopenharmony_ci average_noise[2] = 88962306a36Sopenharmony_ci data->chain_noise_c / il->cfg->chain_noise_num_beacons; 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci for (i = 0; i < NUM_RX_CHAINS; i++) { 89262306a36Sopenharmony_ci if (!data->disconn_array[i] && 89362306a36Sopenharmony_ci average_noise[i] <= min_average_noise) { 89462306a36Sopenharmony_ci /* This means that chain i is active and has 89562306a36Sopenharmony_ci * lower noise values so far: */ 89662306a36Sopenharmony_ci min_average_noise = average_noise[i]; 89762306a36Sopenharmony_ci min_average_noise_antenna_i = i; 89862306a36Sopenharmony_ci } 89962306a36Sopenharmony_ci } 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci D_CALIB("average_noise: a %d b %d c %d\n", average_noise[0], 90262306a36Sopenharmony_ci average_noise[1], average_noise[2]); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci D_CALIB("min_average_noise = %d, antenna %d\n", min_average_noise, 90562306a36Sopenharmony_ci min_average_noise_antenna_i); 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci il4965_gain_computation(il, average_noise, min_average_noise_antenna_i, 90862306a36Sopenharmony_ci min_average_noise, 90962306a36Sopenharmony_ci il4965_find_first_chain(il->cfg->valid_rx_ant)); 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci /* Some power changes may have been made during the calibration. 91262306a36Sopenharmony_ci * Update and commit the RXON 91362306a36Sopenharmony_ci */ 91462306a36Sopenharmony_ci if (il->ops->update_chain_flags) 91562306a36Sopenharmony_ci il->ops->update_chain_flags(il); 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci data->state = IL_CHAIN_NOISE_DONE; 91862306a36Sopenharmony_ci il_power_update_mode(il, false); 91962306a36Sopenharmony_ci} 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_civoid 92262306a36Sopenharmony_ciil4965_reset_run_time_calib(struct il_priv *il) 92362306a36Sopenharmony_ci{ 92462306a36Sopenharmony_ci int i; 92562306a36Sopenharmony_ci memset(&(il->sensitivity_data), 0, sizeof(struct il_sensitivity_data)); 92662306a36Sopenharmony_ci memset(&(il->chain_noise_data), 0, sizeof(struct il_chain_noise_data)); 92762306a36Sopenharmony_ci for (i = 0; i < NUM_RX_CHAINS; i++) 92862306a36Sopenharmony_ci il->chain_noise_data.delta_gain_code[i] = 92962306a36Sopenharmony_ci CHAIN_NOISE_DELTA_GAIN_INIT_VAL; 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci /* Ask for stats now, the uCode will send notification 93262306a36Sopenharmony_ci * periodically after association */ 93362306a36Sopenharmony_ci il_send_stats_request(il, CMD_ASYNC, true); 93462306a36Sopenharmony_ci} 935