blob: 10205522214003e22a9ad75a9e25cee9b33a488c [file] [log] [blame]
/*
* 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 "dw3000.h"
#include "dw3000_txpower_adjustment.h"
/* UWB High band 802.15.4a-2007. Only channels 5 & 9 for DW3000. */
#define DW3000_SUPPORTED_CHANNELS ((1 << 5) | (1 << 9))
/* clang-format off */
#define CHAN_PRF_PARAMS (4 * DW3000_CALIBRATION_PRF_MAX)
#define ANT_CHAN_PARAMS (CHAN_PRF_PARAMS * DW3000_CALIBRATION_CHANNEL_MAX)
#define ANT_OTHER_PARAMS (3) /* port, selector_gpio... */
#define ANTPAIR_CHAN_PARAMS (2 * DW3000_CALIBRATION_CHANNEL_MAX + 1)
#define OTHER_PARAMS (9) /* xtal_trim, temperature_reference, smart_tx_power,
spi_pid, dw3000_pid, auto_sleep_margin,
restricted_channels, alternate_pulse_shape,
phrMode */
#define MAX_CALIB_KEYS ((ANTMAX * (ANT_CHAN_PARAMS + ANT_OTHER_PARAMS)) + \
(ANTPAIR_MAX * ANTPAIR_CHAN_PARAMS) + \
(DW3000_CALIBRATION_CHANNEL_MAX) + \
OTHER_PARAMS)
#define DW_OFFSET(m) offsetof(struct dw3000, m)
#define DW_SIZE(m) sizeof_field(struct dw3000, m)
#define DW_INFO(m) { .offset = DW_OFFSET(m), .length = DW_SIZE(m) }
#define CAL_INFO(m) DW_INFO(calib_data.m)
#define OTP_INFO(m) DW_INFO(otp_data.m)
#define PRF_CAL_INFO(b,x) \
CAL_INFO(b.prf[x].ant_delay), \
CAL_INFO(b.prf[x].tx_power), \
CAL_INFO(b.prf[x].pg_count), \
CAL_INFO(b.prf[x].pg_delay)
#define ANTENNA_CAL_INFO(x) \
PRF_CAL_INFO(ant[x].ch[0], 0), \
PRF_CAL_INFO(ant[x].ch[0], 1), \
PRF_CAL_INFO(ant[x].ch[1], 0), \
PRF_CAL_INFO(ant[x].ch[1], 1), \
CAL_INFO(ant[x].port), \
CAL_INFO(ant[x].selector_gpio), \
CAL_INFO(ant[x].selector_gpio_value)
#define ANTPAIR_CAL_INFO(x,y) \
CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_offset), \
CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_lut), \
CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_offset), \
CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_lut), \
CAL_INFO(antpair[ANTPAIR_IDX(x, y)].spacing_mm_q11)
static const struct {
unsigned int offset;
unsigned int length;
} dw3000_calib_keys_info[MAX_CALIB_KEYS] = {
/* ant0.* */
ANTENNA_CAL_INFO(0),
/* ant1.* */
ANTENNA_CAL_INFO(1),
/* ant0.* */
ANTENNA_CAL_INFO(2),
/* ant1.* */
ANTENNA_CAL_INFO(3),
/* antX.antW.* */
ANTPAIR_CAL_INFO(0,1),
ANTPAIR_CAL_INFO(0,2),
ANTPAIR_CAL_INFO(0,3),
ANTPAIR_CAL_INFO(1,2),
ANTPAIR_CAL_INFO(1,3),
ANTPAIR_CAL_INFO(2,3),
/* chY.* */
CAL_INFO(ch[0].pll_locking_code),
CAL_INFO(ch[1].pll_locking_code),
/* other with direct access in struct dw3000 */
DW_INFO(txconfig.smart),
DW_INFO(auto_sleep_margin_us),
DW_INFO(spi_pid),
DW_INFO(dw3000_pid),
DW_INFO(restricted_channels),
/* country */
DW_INFO(config.alternate_pulse_shape),
DW_INFO(config.phrMode),
/* other with defaults from OTP */
OTP_INFO(xtal_trim),
OTP_INFO(tempP),
};
#define PRF_CAL_LABEL(a,c,p) \
"ant" #a ".ch" #c ".prf" #p ".ant_delay", \
"ant" #a ".ch" #c ".prf" #p ".tx_power", \
"ant" #a ".ch" #c ".prf" #p ".pg_count", \
"ant" #a ".ch" #c ".prf" #p ".pg_delay"
#define ANTENNA_CAL_LABEL(x) \
PRF_CAL_LABEL(x, 5, 16), \
PRF_CAL_LABEL(x, 5, 64), \
PRF_CAL_LABEL(x, 9, 16), \
PRF_CAL_LABEL(x, 9, 64), \
"ant" #x ".port", \
"ant" #x ".selector_gpio", \
"ant" #x ".selector_gpio_value"
#define PDOA_CAL_LABEL(a, b, c) \
"ant" #a ".ant" #b ".ch" #c ".pdoa_offset", \
"ant" #a ".ant" #b ".ch" #c ".pdoa_lut"
#define ANTPAIR_CAL_LABEL(x,y) \
PDOA_CAL_LABEL(x, y, 5), \
PDOA_CAL_LABEL(x, y, 9), \
"ant" #x ".ant" #y ".spacing_mm_q11"
/*
* calibration parameters keys table
*/
static const char *const dw3000_calib_keys[MAX_CALIB_KEYS + 1] = {
/* antX */
ANTENNA_CAL_LABEL(0),
ANTENNA_CAL_LABEL(1),
ANTENNA_CAL_LABEL(2),
ANTENNA_CAL_LABEL(3),
/* antX.antY.* */
ANTPAIR_CAL_LABEL(0,1),
ANTPAIR_CAL_LABEL(0,2),
ANTPAIR_CAL_LABEL(0,3),
ANTPAIR_CAL_LABEL(1,2),
ANTPAIR_CAL_LABEL(1,3),
ANTPAIR_CAL_LABEL(2,3),
/* chY.* */
"ch5.pll_locking_code",
"ch9.pll_locking_code",
/* other */
"smart_tx_power",
"auto_sleep_margin",
"spi_pid",
"dw3000_pid",
"restricted_channels",
/* country */
"alternate_pulse_shape",
"phr_mode",
/* other (OTP) */
"xtal_trim",
"temperature_reference",
/* NULL terminated array for caller of dw3000_calib_list_keys(). */
NULL
};
/* clang-format on */
const dw3000_pdoa_lut_t dw3000_default_lut_ch5 = {
/* clang-format off */
{ 0xe6de, 0xf36f },
{ 0xe88b, 0xf36f },
{ 0xea38, 0xf5b0 },
{ 0xebe5, 0xf747 },
{ 0xed92, 0xf869 },
{ 0xef3f, 0xf959 },
{ 0xf0ec, 0xfa2e },
{ 0xf299, 0xfaf1 },
{ 0xf445, 0xfba7 },
{ 0xf5f2, 0xfc53 },
{ 0xf79f, 0xfcf9 },
{ 0xf94c, 0xfd9a },
{ 0xfaf9, 0xfe36 },
{ 0xfca6, 0xfed0 },
{ 0xfe53, 0xff69 },
{ 0x0000, 0x0000 },
{ 0x01ad, 0x0097 },
{ 0x035a, 0x0130 },
{ 0x0507, 0x01ca },
{ 0x06b4, 0x0266 },
{ 0x0861, 0x0307 },
{ 0x0a0e, 0x03ad },
{ 0x0bbb, 0x0459 },
{ 0x0d67, 0x050f },
{ 0x0f14, 0x05d2 },
{ 0x10c1, 0x06a7 },
{ 0x126e, 0x0797 },
{ 0x141b, 0x08b9 },
{ 0x15c8, 0x0a50 },
{ 0x1775, 0x0c91 },
{ 0x1922, 0x0c91 }
/* clang-format on */
};
const dw3000_pdoa_lut_t dw3000_default_lut_ch9 = {
/* clang-format off */
{ 0xe6de, 0xf701 },
{ 0xe88b, 0xf7ff },
{ 0xea38, 0xf8d2 },
{ 0xebe5, 0xf98d },
{ 0xed92, 0xfa38 },
{ 0xef3f, 0xfad7 },
{ 0xf0ec, 0xfb6d },
{ 0xf299, 0xfbfc },
{ 0xf445, 0xfc86 },
{ 0xf5f2, 0xfd0c },
{ 0xf79f, 0xfd8f },
{ 0xf94c, 0xfe0f },
{ 0xfaf9, 0xfe8d },
{ 0xfca6, 0xff09 },
{ 0xfe53, 0xff85 },
{ 0x0000, 0x0000 },
{ 0x01ad, 0x007b },
{ 0x035a, 0x00f7 },
{ 0x0507, 0x0173 },
{ 0x06b4, 0x01f1 },
{ 0x0861, 0x0271 },
{ 0x0a0e, 0x02f4 },
{ 0x0bbb, 0x037a },
{ 0x0d67, 0x0404 },
{ 0x0f14, 0x0493 },
{ 0x10c1, 0x0529 },
{ 0x126e, 0x05c8 },
{ 0x141b, 0x0673 },
{ 0x15c8, 0x072e },
{ 0x1775, 0x0801 },
{ 0x1922, 0x08ff }
/* clang-format on */
};
int dw3000_calib_parse_key(struct dw3000 *dw, const char *key, void **param)
{
int i;
for (i = 0; dw3000_calib_keys[i]; i++) {
const char *k = dw3000_calib_keys[i];
if (strcmp(k, key) == 0) {
/* Key found, calculate parameter address */
*param = (void *)dw + dw3000_calib_keys_info[i].offset;
return dw3000_calib_keys_info[i].length;
}
}
return -ENOENT;
}
/**
* dw3000_calib_list_keys - return the @dw3000_calib_keys known key table
* @dw: the DW device
*
* Return: pointer to known keys table.
*/
const char *const *dw3000_calib_list_keys(struct dw3000 *dw)
{
return dw3000_calib_keys;
}
/**
* dw3000_calib_update_config - update running configuration
* @dw: the DW device
*
* This function update the required fields in struct dw3000_txconfig according
* the channel and PRF and the corresponding calibration values.
*
* Also update RX/TX RMARKER offset according calibrated antenna delay.
*
* Other calibration parameters aren't used yet.
*
* Return: zero on success, else a negative error code.
*/
int dw3000_calib_update_config(struct dw3000 *dw)
{
struct dw3000_config *config = &dw->config;
struct dw3000_txconfig *txconfig = &dw->txconfig;
const struct dw3000_antenna_calib *ant_calib;
const struct dw3000_antenna_calib_prf *ant_calib_prf;
const struct dw3000_antenna_pair_calib *antpair_calib;
int ant_rf1, ant_rf2, antpair;
int chanidx, prfidx;
ant_rf1 = config->ant[0];
ant_rf2 = config->ant[1];
/* At least, RF1 port must have a valid antenna */
if (ant_rf1 < 0)
/* Not configured yet, does nothing. */
return 0;
if (ant_rf1 >= ANTMAX)
return -1;
ant_calib = &dw->calib_data.ant[ant_rf1];
dw->llhw->hw->phy->supported.channels[4] = DW3000_SUPPORTED_CHANNELS &
~dw->restricted_channels;
/* Change channel if the current one is restricted. */
if ((1 << dw->llhw->hw->phy->current_channel) &
dw->restricted_channels) {
config->chan =
ffs(dw->llhw->hw->phy->supported.channels[4]) - 1;
dw->llhw->hw->phy->current_channel = config->chan;
}
/* Convert config into index of array. */
chanidx = config->chan == 9 ? DW3000_CALIBRATION_CHANNEL_9 :
DW3000_CALIBRATION_CHANNEL_5;
prfidx = config->txCode >= 9 ? DW3000_CALIBRATION_PRF_64MHZ :
DW3000_CALIBRATION_PRF_16MHZ;
/* Shortcut pointers to reduce line length. */
ant_calib_prf = &ant_calib->ch[chanidx].prf[prfidx];
/* Update TX configuration */
txconfig->power = ant_calib_prf->tx_power ? ant_calib_prf->tx_power :
0xfefefefe;
txconfig->PGdly = ant_calib_prf->pg_delay ? ant_calib_prf->pg_delay :
0x34;
txconfig->PGcount = ant_calib_prf->pg_count ? ant_calib_prf->pg_count :
0;
/* Update RMARKER offsets */
config->rmarkerOffset = ant_calib_prf->ant_delay;
/* Early exit if RF2 isn't configured yet. */
if (ant_rf2 < 0)
return 0;
if (ant_rf2 >= ANTMAX)
return -EINVAL;
/* RF2 port has a valid antenna, so antpair can be used */
antpair = ant_rf2 > ant_rf1 ? ANTPAIR_IDX(ant_rf1, ant_rf2) :
ANTPAIR_IDX(ant_rf2, ant_rf1);
antpair_calib = &dw->calib_data.antpair[antpair];
/* Update PDOA offset */
config->pdoaOffset = antpair_calib->ch[chanidx].pdoa_offset;
config->pdoaLut = &antpair_calib->ch[chanidx].pdoa_lut;
/* Update antpair spacing */
config->antpair_spacing_mm_q11 = antpair_calib->spacing_mm_q11;
/* Smart TX power */
/* When deactivated, reset register to default value (if change occurs
while already started) */
if (!txconfig->smart && dw3000_is_active(dw))
dw3000_set_tx_power_register(dw, txconfig->power);
/* Update idle_dtu in case auto_sleep_margin_us changed */
dw->llhw->idle_dtu = dw->auto_sleep_margin_us > 0 ?
US_TO_DTU(dw->auto_sleep_margin_us) :
DW3000_DTU_FREQ;
return 0;
}