AD9361
Recommended for New Designs
The AD9361 is a high performance, highly integrated radio
frequency (RF) Agile Transceiverâ„¢ designed for use in 3G and
4G base station applications....
Datasheet
AD9361 on Analog.com
I'm working with AntSDR E200 board, which is basically ADALM-PLUTO clone with VCTXCO and AD9361 (https://github.com/MicroPhase/antsdr-fw/blob/master/schematic/ANT-E200_Public.pdf); I'm implementing some changes in HDL, I need 61.44Msps and since AD9361 is connected in CMOS mode, I have to stay with 1Rx1Tx to ensure correct sample rate.
At the same time I want to be able to switch input to other channel, so I can use two different RF input paths / antennas. I reviewed driver code and I noticed there is devicetree setting to use channel 2 in 1Rx1Tx mode - adi,1rx-1tx-mode-use-rx-num , however this setting is static and channel is set up when driver is initialized. I was able to develop a patch, which allows me to dynamically change the channel with IIO attribute. I had also to:
- call ad9361_tracking_control after channel switch is performed
- modify part of code, which does AGC setup
I performed some tests with RX and it looks everything works just fine - I didn't take any measurements though.
Can you take a look at my patch (attached)? Is there anything else I should add to ensure the channel switch works as expected?
diff --git a/drivers/iio/adc/ad9361.c b/drivers/iio/adc/ad9361.c
index a752a6453212..34e2b231b61b 100644
--- a/drivers/iio/adc/ad9361.c
+++ b/drivers/iio/adc/ad9361.c
@@ -5034,7 +5034,7 @@ static int ad9361_setup(struct ad9361_rf_phy *phy)
clamp_t(u32, pd->rx1tx1_mode_use_rx_num, RX_1, RX_2);
ad9361_en_dis_tx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_tx_num);
- ad9361_en_dis_rx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_rx_num);
+ ad9361_en_dis_rx(phy, RX_1 | RX_2, pd->rx1tx1_mode_use_rx_num);
} else {
ad9361_en_dis_tx(phy, TX_1 | TX_2, TX_1 | TX_2);
ad9361_en_dis_rx(phy, RX_1 | RX_2, RX_1 | RX_2);
@@ -7401,6 +7401,7 @@ enum lo_ext_info {
LOEXT_SAVE,
LOEXT_EXTERNAL,
LOEXT_PD,
+ LOEXT_RXTX_NUM,
};
static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev,
@@ -7410,6 +7411,8 @@ static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev,
{
struct ad9361_rf_phy *phy = iio_priv(indio_dev);
struct ad9361_rf_phy_state *st = phy->state;
+ struct ad9361_phy_platform_data *pd = phy->pdata;
+ u32 rxtx_num;
u64 readin;
bool res;
int ret = 0;
@@ -7535,6 +7538,38 @@ static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev,
}
break;
+ case LOEXT_RXTX_NUM:
+ if (pd->rx2tx2) {
+ ret = -EINVAL;
+ break;
+ }
+ switch (chan->channel) {
+ case 0:
+ rxtx_num = clamp_t(u32, readin, RX_1, RX_2);
+ ret = ad9361_en_dis_rx(phy, RX_1 | RX_2, rxtx_num);
+ if (ret < 0)
+ break;
+
+ pd->rx1tx1_mode_use_rx_num = rxtx_num;
+ break;
+ case 1:
+ rxtx_num = clamp_t(u32, readin, TX_1, TX_2);
+ ret = ad9361_en_dis_tx(phy, TX_1 | TX_2, rxtx_num);
+ if (ret < 0)
+ break;
+
+ pd->rx1tx1_mode_use_tx_num = rxtx_num;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret < 0)
+ break;
+
+ ret = ad9361_tracking_control(phy, st->bbdc_track_en,
+ st->rfdc_track_en,
+ st->quad_track_en);
+ break;
}
mutex_unlock(&phy->lock);
@@ -7548,6 +7583,7 @@ static ssize_t ad9361_phy_lo_read(struct iio_dev *indio_dev,
{
struct ad9361_rf_phy *phy = iio_priv(indio_dev);
struct ad9361_rf_phy_state *st = phy->state;
+ struct ad9361_phy_platform_data *pd = phy->pdata;
u64 val = 0;
size_t len;
int ret = 0;
@@ -7612,6 +7648,18 @@ static ssize_t ad9361_phy_lo_read(struct iio_dev *indio_dev,
mutex_unlock(&phy->lock);
return len;
}
+ case LOEXT_RXTX_NUM:
+ switch (chan->channel) {
+ case 0:
+ val = pd->rx1tx1_mode_use_rx_num;
+ break;
+ case 1:
+ val = pd->rx1tx1_mode_use_tx_num;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
default:
ret = 0;
@@ -7647,6 +7695,7 @@ static const struct iio_chan_spec_ext_info ad9361_phy_ext_info[] = {
_AD9361_EXT_LO_INFO("fastlock_save", LOEXT_SAVE),
_AD9361_EXT_LO_INFO("external", LOEXT_EXTERNAL),
_AD9361_EXT_LO_INFO("powerdown", LOEXT_PD),
+ _AD9361_EXT_LO_INFO("rxtx_num", LOEXT_RXTX_NUM),
{ },
};
@@ -7656,12 +7705,14 @@ static int ad9361_set_agc_mode(struct iio_dev *indio_dev,
struct ad9361_rf_phy *phy = iio_priv(indio_dev);
struct ad9361_rf_phy_state *st = phy->state;
struct rf_gain_ctrl gc = {0};
+ int ant;
- if (st->agc_mode[chan->channel] == mode)
+ ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1);
+ if (st->agc_mode[ant - 1] == mode)
return 0;
- gc.ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1);
- gc.mode = st->agc_mode[chan->channel] = mode;
+ gc.ant = ant;
+ gc.mode = st->agc_mode[ant - 1] = mode;
return ad9361_set_gain_ctrl_mode(phy, &gc);
}
@@ -7671,8 +7722,10 @@ static int ad9361_get_agc_mode(struct iio_dev *indio_dev,
{
struct ad9361_rf_phy *phy = iio_priv(indio_dev);
struct ad9361_rf_phy_state *st = phy->state;
+ int ant;
- return st->agc_mode[chan->channel];
+ ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1);
+ return st->agc_mode[ant - 1];
}
static const char * const ad9361_agc_modes[] =
I performed a bit more testing and I've noticed there is one change missing in the patch. Final version attached.
diff --git a/drivers/iio/adc/ad9361.c b/drivers/iio/adc/ad9361.c index a752a6453212..21d896164a0a 100644 --- a/drivers/iio/adc/ad9361.c +++ b/drivers/iio/adc/ad9361.c @@ -5034,7 +5034,7 @@ static int ad9361_setup(struct ad9361_rf_phy *phy) clamp_t(u32, pd->rx1tx1_mode_use_rx_num, RX_1, RX_2); ad9361_en_dis_tx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_tx_num); - ad9361_en_dis_rx(phy, TX_1 | TX_2, pd->rx1tx1_mode_use_rx_num); + ad9361_en_dis_rx(phy, RX_1 | RX_2, pd->rx1tx1_mode_use_rx_num); } else { ad9361_en_dis_tx(phy, TX_1 | TX_2, TX_1 | TX_2); ad9361_en_dis_rx(phy, RX_1 | RX_2, RX_1 | RX_2); @@ -7401,6 +7401,7 @@ enum lo_ext_info { LOEXT_SAVE, LOEXT_EXTERNAL, LOEXT_PD, + LOEXT_RXTX_NUM, }; static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev, @@ -7410,6 +7411,8 @@ static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev, { struct ad9361_rf_phy *phy = iio_priv(indio_dev); struct ad9361_rf_phy_state *st = phy->state; + struct ad9361_phy_platform_data *pd = phy->pdata; + u32 rxtx_num; u64 readin; bool res; int ret = 0; @@ -7426,6 +7429,7 @@ static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev, case LOEXT_STORE: case LOEXT_RECALL: case LOEXT_SAVE: + case LOEXT_RXTX_NUM: ret = kstrtoull(buf, 10, &readin); if (ret) return ret; @@ -7535,6 +7539,38 @@ static ssize_t ad9361_phy_lo_write(struct iio_dev *indio_dev, } break; + case LOEXT_RXTX_NUM: + if (pd->rx2tx2) { + ret = -EINVAL; + break; + } + switch (chan->channel) { + case 0: + rxtx_num = clamp_t(u32, readin, RX_1, RX_2); + ret = ad9361_en_dis_rx(phy, RX_1 | RX_2, rxtx_num); + if (ret < 0) + break; + + pd->rx1tx1_mode_use_rx_num = rxtx_num; + break; + case 1: + rxtx_num = clamp_t(u32, readin, TX_1, TX_2); + ret = ad9361_en_dis_tx(phy, TX_1 | TX_2, rxtx_num); + if (ret < 0) + break; + + pd->rx1tx1_mode_use_tx_num = rxtx_num; + break; + default: + ret = -EINVAL; + } + if (ret < 0) + break; + + ret = ad9361_tracking_control(phy, st->bbdc_track_en, + st->rfdc_track_en, + st->quad_track_en); + break; } mutex_unlock(&phy->lock); @@ -7548,6 +7584,7 @@ static ssize_t ad9361_phy_lo_read(struct iio_dev *indio_dev, { struct ad9361_rf_phy *phy = iio_priv(indio_dev); struct ad9361_rf_phy_state *st = phy->state; + struct ad9361_phy_platform_data *pd = phy->pdata; u64 val = 0; size_t len; int ret = 0; @@ -7612,6 +7649,18 @@ static ssize_t ad9361_phy_lo_read(struct iio_dev *indio_dev, mutex_unlock(&phy->lock); return len; } + case LOEXT_RXTX_NUM: + switch (chan->channel) { + case 0: + val = pd->rx1tx1_mode_use_rx_num; + break; + case 1: + val = pd->rx1tx1_mode_use_tx_num; + break; + default: + ret = -EINVAL; + } + break; default: ret = 0; @@ -7647,6 +7696,7 @@ static const struct iio_chan_spec_ext_info ad9361_phy_ext_info[] = { _AD9361_EXT_LO_INFO("fastlock_save", LOEXT_SAVE), _AD9361_EXT_LO_INFO("external", LOEXT_EXTERNAL), _AD9361_EXT_LO_INFO("powerdown", LOEXT_PD), + _AD9361_EXT_LO_INFO("rxtx_num", LOEXT_RXTX_NUM), { }, }; @@ -7656,12 +7706,14 @@ static int ad9361_set_agc_mode(struct iio_dev *indio_dev, struct ad9361_rf_phy *phy = iio_priv(indio_dev); struct ad9361_rf_phy_state *st = phy->state; struct rf_gain_ctrl gc = {0}; + int ant; - if (st->agc_mode[chan->channel] == mode) + ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1); + if (st->agc_mode[ant - 1] == mode) return 0; - gc.ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1); - gc.mode = st->agc_mode[chan->channel] = mode; + gc.ant = ant; + gc.mode = st->agc_mode[ant - 1] = mode; return ad9361_set_gain_ctrl_mode(phy, &gc); } @@ -7671,8 +7723,10 @@ static int ad9361_get_agc_mode(struct iio_dev *indio_dev, { struct ad9361_rf_phy *phy = iio_priv(indio_dev); struct ad9361_rf_phy_state *st = phy->state; + int ant; - return st->agc_mode[chan->channel]; + ant = ad9361_1rx1tx_channel_map(phy, false, chan->channel + 1); + return st->agc_mode[ant - 1]; } static const char * const ad9361_agc_modes[] =