| /* |
| * This file is part of the UWB stack for linux. |
| * |
| * Copyright (c) 2020-2021 Qorvo US, Inc. |
| * |
| * This software is provided under the GNU General Public License, version 2 |
| * (GPLv2), as well as under a Qorvo commercial license. |
| * |
| * You may choose to use this software under the terms of the GPLv2 License, |
| * version 2 ("GPLv2"), as published by the Free Software Foundation. |
| * You should have received a copy of the GPLv2 along with this program. If |
| * not, see <http://www.gnu.org/licenses/>. |
| * |
| * This program is distributed under the GPLv2 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 GPLv2 for more |
| * details. |
| * |
| * If you cannot meet the requirements of the GPLv2, you may not use this |
| * software for any purpose without first obtaining a commercial license from |
| * Qorvo. Please contact Qorvo to inquire about licensing terms. |
| */ |
| |
| #include <linux/types.h> |
| #include "dw3000_trc.h" |
| #include "dw3000_txpower_adjustment.h" |
| |
| /* Size of Tx power compensation look-up table */ |
| #define LUT_COMP_SIZE 64 |
| |
| /* Margin for tx power adjustment in 0.1dB steps. */ |
| #define TXPOWER_ADJUSTMENT_MARGIN 5 |
| |
| /* TxPower difference between coarse gain n and coarse gain n+1 in 0.1dB Step. |
| * Same for CH5 and CH9 */ |
| #define COARSE_0_TO_1 32 |
| #define COARSE_1_TO_2 13 |
| #define COARSE_2_TO_3 5 |
| #define NUM_COARSE_GAIN 3 |
| |
| /* LookUpTable : 1000us - 200us */ |
| #define LUT_1000_200_US_NUM 33 |
| #define LUT_1000_200_US_STEP 25 |
| #define LUT_1000_200_US_MIN 200 |
| #define LUT_1000_200_US_MIN_BST 0 |
| |
| /* LookUpTable : 200us - 70us */ |
| #define LUT_200_70_US_NUM 14 |
| #define LUT_200_70_US_STEP 10 |
| #define LUT_200_70_US_MIN 70 |
| #define LUT_200_70_US_MAX_BST 113 |
| |
| /* The reference duration for a frame is 1000us. Longer frame will |
| * have 0dB boost. */ |
| #define FRAME_DURATION_REF 1000 |
| |
| #define MAX_BOOST_CH5 354 |
| #define MAX_BOOST_CH9 305 |
| #define TX_POWER_COARSE_GAIN_MASK 0x03 |
| #define TX_POWER_FINE_GAIN_MASK 0x3F |
| |
| /* Reference look-up table to calculate Txpower Dial Back depending on |
| * frame duration. Each new entry in the table is relative to the previous one. |
| * Using two different tables a per logarithmic calculation: |
| * - 1000us to 200us range (index unit of 25us) |
| * - 200us to 70us (index unit of 10us) |
| * The table values are in steps 0f 0.1dB |
| * This allows having a maximum granularity of 0.5dB between two frame |
| * duration, which is in the magnitude of the DW3XXX TxOutput setting. |
| */ |
| static const u8 |
| txpower_boost_per_frame_duration_1000_200_us[LUT_1000_200_US_NUM] = { |
| 0, /* 1000us */ |
| 1, /* 975us -> 1*0.1dB boost between 975us and 1000us frames */ |
| 2, /* 950us -> 2*0.1dB boost between 950us and 1000us frames */ |
| 3, /* 925us */ |
| 4, /* 900us */ |
| 5, /* 875us */ |
| 6, /* 850us */ |
| 7, /* 825us */ |
| 8, /* 800us */ |
| 9, /* 775us */ |
| 10, /* 750us */ |
| 11, /* 725us */ |
| 13, /* 700us */ |
| 15, /* 675us */ |
| 17, /* 650us */ |
| 19, /* 625us */ |
| 21, /* 600us */ |
| 23, /* 575us */ |
| 25, /* 550us */ |
| 27, /* 525us */ |
| 29, /* 500us */ |
| 31, /* 475us */ |
| 33, /* 450us */ |
| 35, /* 425us */ |
| 38, /* 400us */ |
| 41, /* 375us */ |
| 44, /* 350us */ |
| 47, /* 325us */ |
| 50, /* 300us */ |
| 54, /* 275us */ |
| 58, /* 250us */ |
| 63, /* 225us */ |
| 68 /* 200us */ |
| }; |
| |
| static const u8 txpower_boost_per_frame_duration_200_70_us[LUT_200_70_US_NUM] = { |
| 68, /* 200us -> 68*0.1dB boost between 200us frame and 1000us frame. */ |
| 70, /* 190us -> 70*0.1dB boost between 190us and 1000us frame */ |
| 72, /* 180us */ |
| 74, /* 170us */ |
| 77, /* 160us */ |
| 80, /* 150us */ |
| 83, /* 140us */ |
| 86, /* 130us */ |
| 89, /* 120us */ |
| 93, /* 110us */ |
| 97, /* 100us */ |
| 102, /* 90us */ |
| 107, /* 80us */ |
| 113 /* 70us */ |
| }; |
| |
| /* TxPower difference between the coarse gain setting (i+1) and (i) |
| * The step is in unit of 0.1dB |
| */ |
| static const u8 lut_coarse_gain[NUM_COARSE_GAIN] = { |
| COARSE_0_TO_1, |
| COARSE_1_TO_2, |
| COARSE_2_TO_3, |
| }; |
| |
| /* TxPower difference between the fine gain setting (i+1) and (i) |
| * The step is in unit of 0.1dB |
| */ |
| static const u8 fine_gain_lut_chan5[LUT_COMP_SIZE] = { |
| 0, /* Fine gain setting ( 0 ) Minimum Value */ |
| 32, /* Fine gain setting ( 1 - 0 ) */ |
| 29, /* Fine gain setting ( 2 - 1 ) */ |
| 28, /* Fine gain setting ( 3 - 2 ) */ |
| 20, /* Fine gain setting ( 4 - 3 ) */ |
| 18, /* Fine gain setting ( 5 - 4 ) */ |
| 12, /* Fine gain setting ( 6 - 5 ) */ |
| 13, /* Fine gain setting ( 7 - 6 ) */ |
| 10, /* Fine gain setting ( 8 - 7 ) */ |
| 10, /* Fine gain setting ( 9 - 8 ) */ |
| 7, /* Fine gain setting ( 10 - 9 ) */ |
| 8, /* Fine gain setting ( 11 - 10 ) */ |
| 6, /* Fine gain setting ( 12 - 11 ) */ |
| 7, /* Fine gain setting ( 13 - 12 ) */ |
| 5, /* Fine gain setting ( 14 - 13 ) */ |
| 6, /* Fine gain setting ( 15 - 14 ) */ |
| 5, /* Fine gain setting ( 16 - 15 ) */ |
| 5, /* Fine gain setting ( 17 - 16 ) */ |
| 4, /* Fine gain setting ( 18 - 17 ) */ |
| 4, /* Fine gain setting ( 19 - 18 ) */ |
| 4, /* Fine gain setting ( 20 - 19 ) */ |
| 4, /* Fine gain setting ( 21 - 20 ) */ |
| 3, /* Fine gain setting ( 22 - 21 ) */ |
| 3, /* Fine gain setting ( 23 - 22 ) */ |
| 3, /* Fine gain setting ( 24 - 23 ) */ |
| 3, /* Fine gain setting ( 25 - 24 ) */ |
| 2, /* Fine gain setting ( 26 - 25 ) */ |
| 3, /* Fine gain setting ( 27 - 26 ) */ |
| 2, /* Fine gain setting ( 28 - 27 ) */ |
| 3, /* Fine gain setting ( 29 - 28 ) */ |
| 2, /* Fine gain setting ( 30 - 29 ) */ |
| 3, /* Fine gain setting ( 31 - 30 ) */ |
| 3, /* Fine gain setting ( 32 - 31 ) */ |
| 2, /* Fine gain setting ( 33 - 32 ) */ |
| 2, /* Fine gain setting ( 34 - 33 ) */ |
| 2, /* Fine gain setting ( 35 - 34 ) */ |
| 1, /* Fine gain setting ( 36 - 35 ) */ |
| 2, /* Fine gain setting ( 37 - 36 ) */ |
| 1, /* Fine gain setting ( 38 - 37 ) */ |
| 2, /* Fine gain setting ( 39 - 38 ) */ |
| 1, /* Fine gain setting ( 40 - 39 ) */ |
| 2, /* Fine gain setting ( 41 - 40 ) */ |
| 1, /* Fine gain setting ( 42 - 41 ) */ |
| 1, /* Fine gain setting ( 43 - 42 ) */ |
| 1, /* Fine gain setting ( 44 - 43 ) */ |
| 1, /* Fine gain setting ( 45 - 44 ) */ |
| 1, /* Fine gain setting ( 46 - 45 ) */ |
| 1, /* Fine gain setting ( 47 - 46 ) */ |
| 1, /* Fine gain setting ( 48 - 47 ) */ |
| 1, /* Fine gain setting ( 49 - 48 ) */ |
| 1, /* Fine gain setting ( 50 - 49 ) */ |
| 1, /* Fine gain setting ( 51 - 50 ) */ |
| 1, /* Fine gain setting ( 52 - 51 ) */ |
| 1, /* Fine gain setting ( 53 - 52 ) */ |
| 1, /* Fine gain setting ( 54 - 53 ) */ |
| 1, /* Fine gain setting ( 55 - 54 ) */ |
| 1, /* Fine gain setting ( 56 - 55 ) */ |
| 1, /* Fine gain setting ( 57 - 56 ) */ |
| 1, /* Fine gain setting ( 58 - 57 ) */ |
| 1, /* Fine gain setting ( 59 - 58 ) */ |
| 1, /* Fine gain setting ( 60 - 59 ) */ |
| 1, /* Fine gain setting ( 61 - 60 ) */ |
| 1, /* Fine gain setting ( 62 - 61 ) */ |
| 1 /* Fine gain setting ( 63 - 62 ) */ |
| }; |
| |
| static const u8 fine_gain_lut_chan9[LUT_COMP_SIZE] = { |
| 0, /* Fine gain setting ( 0 ) Minimum Value */ |
| 11, /* Fine gain setting ( 1 - 0 ) */ |
| 14, /* Fine gain setting ( 2 - 1 ) */ |
| 18, /* Fine gain setting ( 3 - 2 ) */ |
| 15, /* Fine gain setting ( 4 - 3 ) */ |
| 15, /* Fine gain setting ( 5 - 4 ) */ |
| 10, /* Fine gain setting ( 6 - 5 ) */ |
| 12, /* Fine gain setting ( 7 - 6 ) */ |
| 9, /* Fine gain setting ( 8 - 7 ) */ |
| 9, /* Fine gain setting ( 9 - 8 ) */ |
| 7, /* Fine gain setting ( 10 - 9 ) */ |
| 8, /* Fine gain setting ( 11 - 10 ) */ |
| 6, /* Fine gain setting ( 12 - 11 ) */ |
| 7, /* Fine gain setting ( 13 - 12 ) */ |
| 5, /* Fine gain setting ( 14 - 13 ) */ |
| 6, /* Fine gain setting ( 15 - 14 ) */ |
| 5, /* Fine gain setting ( 16 - 15 ) */ |
| 5, /* Fine gain setting ( 17 - 16 ) */ |
| 4, /* Fine gain setting ( 18 - 17 ) */ |
| 5, /* Fine gain setting ( 19 - 18 ) */ |
| 4, /* Fine gain setting ( 20 - 19 ) */ |
| 4, /* Fine gain setting ( 21 - 20 ) */ |
| 3, /* Fine gain setting ( 22 - 21 ) */ |
| 4, /* Fine gain setting ( 23 - 22 ) */ |
| 3, /* Fine gain setting ( 24 - 23 ) */ |
| 3, /* Fine gain setting ( 25 - 24 ) */ |
| 3, /* Fine gain setting ( 26 - 25 ) */ |
| 3, /* Fine gain setting ( 27 - 26 ) */ |
| 3, /* Fine gain setting ( 28 - 27 ) */ |
| 3, /* Fine gain setting ( 29 - 28 ) */ |
| 2, /* Fine gain setting ( 30 - 29 ) */ |
| 3, /* Fine gain setting ( 31 - 30 ) */ |
| 3, /* Fine gain setting ( 32 - 31 ) */ |
| 2, /* Fine gain setting ( 33 - 32 ) */ |
| 2, /* Fine gain setting ( 34 - 33 ) */ |
| 2, /* Fine gain setting ( 35 - 34 ) */ |
| 2, /* Fine gain setting ( 36 - 35 ) */ |
| 2, /* Fine gain setting ( 37 - 36 ) */ |
| 1, /* Fine gain setting ( 38 - 37 ) */ |
| 2, /* Fine gain setting ( 39 - 38 ) */ |
| 2, /* Fine gain setting ( 40 - 39 ) */ |
| 2, /* Fine gain setting ( 41 - 40 ) */ |
| 2, /* Fine gain setting ( 42 - 41 ) */ |
| 2, /* Fine gain setting ( 43 - 42 ) */ |
| 1, /* Fine gain setting ( 44 - 43 ) */ |
| 2, /* Fine gain setting ( 45 - 44 ) */ |
| 1, /* Fine gain setting ( 46 - 45 ) */ |
| 2, /* Fine gain setting ( 47 - 46 ) */ |
| 1, /* Fine gain setting ( 48 - 47 ) */ |
| 1, /* Fine gain setting ( 49 - 48 ) */ |
| 1, /* Fine gain setting ( 50 - 49 ) */ |
| 2, /* Fine gain setting ( 51 - 50 ) */ |
| 1, /* Fine gain setting ( 52 - 51 ) */ |
| 1, /* Fine gain setting ( 53 - 52 ) */ |
| 1, /* Fine gain setting ( 54 - 53 ) */ |
| 1, /* Fine gain setting ( 55 - 54 ) */ |
| 1, /* Fine gain setting ( 56 - 55 ) */ |
| 1, /* Fine gain setting ( 57 - 56 ) */ |
| 1, /* Fine gain setting ( 58 - 57 ) */ |
| 1, /* Fine gain setting ( 59 - 58 ) */ |
| 0, /* Fine gain setting ( 60 - 59 ) */ |
| 1, /* Fine gain setting ( 61 - 60 ) */ |
| 1, /* Fine gain setting ( 62 - 61 ) */ |
| 1 /* Fine gain setting ( 63 - 62 ) */ |
| }; |
| |
| /* Calculate the power_boost for a frame_duration_us relative to 1ms */ |
| static u8 calculate_power_boost(u16 frame_duration_us) |
| { |
| const u8 *lut = NULL; |
| u16 lut_i; |
| u16 lut_num; |
| u16 lut_min; |
| u16 lut_step; |
| u16 limit; |
| |
| /* Calculating the LUT index corresponding to the frameduration */ |
| if (frame_duration_us >= FRAME_DURATION_REF) { |
| return LUT_1000_200_US_MIN_BST; |
| } else if (frame_duration_us < LUT_200_70_US_MIN) { |
| return LUT_200_70_US_MAX_BST; |
| } else if (frame_duration_us > LUT_1000_200_US_MIN) { |
| lut_num = LUT_1000_200_US_NUM; |
| lut_min = LUT_1000_200_US_MIN; |
| lut_step = LUT_1000_200_US_STEP; |
| lut = txpower_boost_per_frame_duration_1000_200_us; |
| } else { |
| lut_num = LUT_200_70_US_NUM; |
| lut_min = LUT_200_70_US_MIN; |
| lut_step = LUT_200_70_US_STEP; |
| lut = txpower_boost_per_frame_duration_200_70_us; |
| } |
| |
| lut_i = (lut_num - (frame_duration_us - lut_min) / lut_step); |
| limit = (lut_num - lut_i) * lut_step + lut_min; |
| |
| /* Selecting the index that gives the closest LUT */ |
| if (abs(frame_duration_us - limit) > lut_step / 2) { |
| lut_i--; |
| } |
| |
| return lut[lut_i - 1]; |
| } |
| |
| /** |
| * adjust_tx_power() - actual TxPower setting calculation |
| * @frame_duration_us: the frame (headers, payload, FCS) duration |
| * @ref_tx_power: the power setting corresponding to a frame of 1ms (0dB) |
| * @channel: the current RF channel used for transmission of UWB frames |
| * @th_boost: pointer to store the theorical boost to be applied |
| * @applied_boost: pointer to store the calculated, actually applied boost |
| * |
| * The reference TxPower setting should correspond to a 1ms frame (or 0dB) |
| * boost. The boost to be applied should be provided in unit of 0.1dB boost. |
| * |
| * Return: the adjusted_tx_power setting that can be used to configure the |
| * chip transmission level |
| */ |
| static u32 adjust_tx_power(u16 frame_duration_us, u32 ref_tx_power, u8 channel, |
| u16 *th_boost, u16 *applied_boost) |
| { |
| u32 adjusted_tx_power; |
| u16 target_boost = 0; |
| u16 current_boost = 0; |
| u16 best_boost_abs = 0; |
| u16 best_boost = 0; |
| u16 upper_limit = 0; |
| u16 lower_limit = 0; |
| |
| const u8 *lut = NULL; |
| u8 best_index = 0; |
| u8 best_coarse_gain = 0; |
| u8 ref_coarse_gain = ((u8)ref_tx_power) & TX_POWER_COARSE_GAIN_MASK; |
| u8 ref_fine_gain = ((u8)ref_tx_power >> 2) & TX_POWER_FINE_GAIN_MASK; |
| bool within_margin_flag = false; |
| bool reached_max_fine_gain_flag = false; |
| u8 tx_power_byte = 0; |
| u8 unlock = 0; |
| u8 i = 0; |
| |
| target_boost = calculate_power_boost(frame_duration_us); |
| if (th_boost) { |
| *th_boost = target_boost; |
| } |
| switch (channel) { |
| case 5: |
| lut = fine_gain_lut_chan5; |
| if (target_boost >= MAX_BOOST_CH5) |
| target_boost = MAX_BOOST_CH5; |
| break; |
| default: |
| lut = fine_gain_lut_chan9; |
| if (target_boost >= MAX_BOOST_CH9) |
| target_boost = MAX_BOOST_CH9; |
| break; |
| } |
| |
| i = ref_fine_gain; |
| upper_limit = target_boost + TXPOWER_ADJUSTMENT_MARGIN; |
| lower_limit = (target_boost > TXPOWER_ADJUSTMENT_MARGIN ? |
| target_boost - TXPOWER_ADJUSTMENT_MARGIN : |
| 0); |
| best_boost_abs = TXPOWER_ADJUSTMENT_MARGIN; |
| |
| if (applied_boost) |
| *applied_boost = 0; |
| if (target_boost < TXPOWER_ADJUSTMENT_MARGIN && |
| target_boost < lut[i + 1] - TXPOWER_ADJUSTMENT_MARGIN) { |
| return ref_tx_power; |
| } |
| |
| /* Increase coarse setting if the required boost is greater than the |
| * TxPower gain using the increased coarse setting. |
| * NB : Coarse gain = 0x3 should not be used in CHAN9 */ |
| while (ref_coarse_gain < 0x2) { |
| if (lut_coarse_gain[ref_coarse_gain] < |
| target_boost - current_boost) { |
| current_boost += lut_coarse_gain[ref_coarse_gain]; |
| ref_coarse_gain++; |
| } else { |
| break; |
| } |
| } |
| |
| /* Increase current_boost until reaching value closest to target_boot */ |
| while (current_boost != target_boost) { |
| unlock++; |
| /* Ensure loop does not got "locked" */ |
| if (unlock > 2 * LUT_COMP_SIZE) { |
| return ref_tx_power; |
| } |
| |
| if (current_boost > lower_limit && |
| current_boost < upper_limit) { |
| if (abs((s16)(target_boost - current_boost)) <= |
| best_boost_abs) { |
| best_boost_abs = |
| abs((s16)target_boost - current_boost); |
| best_boost = current_boost; |
| best_index = i; |
| best_coarse_gain = ref_coarse_gain; |
| within_margin_flag = true; |
| } else if (within_margin_flag) { |
| i = best_index; |
| ref_coarse_gain = best_coarse_gain; |
| break; |
| } |
| } else if (within_margin_flag) { |
| current_boost -= lut[i]; |
| i = best_index; |
| break; |
| } |
| |
| /* Corner case: when fine gain setting is very low, it can happend that |
| * current boost is already larger than target_boost but not within margin. |
| * Then, just return current solution. |
| */ |
| if (current_boost >= upper_limit && |
| !reached_max_fine_gain_flag) { |
| break; |
| } |
| |
| /* Search for max fine gain value */ |
| if (i == LUT_COMP_SIZE - 1) { |
| reached_max_fine_gain_flag = true; |
| |
| /* return previously found solution */ |
| if (within_margin_flag) { |
| i = best_index; |
| ref_coarse_gain = best_coarse_gain; |
| current_boost = best_boost; |
| break; |
| } |
| |
| if ((ref_coarse_gain == 0x3) || |
| (ref_coarse_gain == 0x2 && channel == 9)) { |
| break; |
| } |
| |
| if (current_boost + lut_coarse_gain[ref_coarse_gain] <= |
| target_boost) { |
| current_boost += |
| lut_coarse_gain[ref_coarse_gain]; |
| ref_coarse_gain++; |
| break; |
| } else { |
| current_boost += |
| lut_coarse_gain[ref_coarse_gain]; |
| ref_coarse_gain++; |
| } |
| } |
| |
| /* Adjust fine gain */ |
| if (!reached_max_fine_gain_flag) { |
| i++; |
| i &= TX_POWER_FINE_GAIN_MASK; |
| current_boost += lut[i]; |
| } else { |
| current_boost -= lut[i]; |
| i--; |
| i &= TX_POWER_FINE_GAIN_MASK; |
| if (i == 0) |
| reached_max_fine_gain_flag = false; |
| } |
| } |
| |
| if (applied_boost) |
| *applied_boost = current_boost; |
| |
| tx_power_byte = (i << 2) | ref_coarse_gain; |
| adjusted_tx_power = (u32)tx_power_byte << 24 | |
| (u32)tx_power_byte << 16 | (u32)tx_power_byte << 8 | |
| (u32)tx_power_byte; |
| |
| return adjusted_tx_power; |
| } |
| |
| /** |
| * dw3000_adjust_tx_power() - calculates the adjusted TxPower |
| * @dw: the DW device |
| * @payload_bytes: payload skbuf's length in bytes |
| * |
| * Wraps actual TX power calculation. Converts frame length to duration (in µs) |
| * with respect to RF settings (channel, PRF, ...) then calls adjust_tx_power |
| * |
| * Return: the adjusted_tx_power setting than can be used to configure the chip |
| * transmission level |
| */ |
| int dw3000_adjust_tx_power(struct dw3000 *dw, int payload_bytes) |
| { |
| u16 th_boost, app_boost; |
| u8 chan = dw->config.chan; |
| u16 frm_dur = |
| DTU_TO_US(dw3000_frame_duration_dtu(dw, payload_bytes, true)); |
| u32 adjusted_tx_power = adjust_tx_power(frm_dur, dw->txconfig.power, |
| chan, &th_boost, &app_boost); |
| |
| trace_dw3000_adjust_tx_power(dw, dw->txconfig.power, adjusted_tx_power, |
| frm_dur, payload_bytes, chan, th_boost, |
| app_boost); |
| |
| return dw3000_set_tx_power_register(dw, adjusted_tx_power); |
| } |