Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/nrf/app_dev/device_guides/fem/fem_nrf2220.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ To use nRF2220, complete the following steps:
};
};

Additionally, you can consider setting the :kconfig:option:`CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION` Kconfig option.

#. Optionally replace the device name ``name_of_fem_node``.
#. Replace the pin numbers provided for each of the required properties:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Developing with PMICs
Developing with Front-End Modules
=================================

|no_changes_yet_note|
* Added the temperature compensation feature for the nRF2220 Front-End Module.

Developing with custom boards
=============================
Expand Down
13 changes: 13 additions & 0 deletions lib/fem_al/fem_al.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

#include <mpsl_fem_config_common.h>
#include <mpsl_fem_protocol_api.h>
#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION) && \
!defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER)
#include <protocol/mpsl_fem_nrf2220_protocol_api.h>
#endif

#include "fem_al/fem_al.h"
#include "fem_interface.h"
Expand Down Expand Up @@ -186,6 +190,15 @@ int fem_tx_configure(uint32_t ramp_up_time)

fem_activate_event.event.timer.counter_period.end = ramp_up_time;

#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION) && \
!defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER)
err = mpsl_fem_nrf2220_temperature_changed_update_now();
if (err) {
printk("mpsl_fem_nrf2220_temperature_changed_update_now failed (err %d)\n", err);
return -EFAULT;
}
#endif

mpsl_fem_enable();
err = mpsl_fem_pa_configuration_set(&fem_activate_event, &fem_deactivate_evt);
if (err) {
Expand Down
50 changes: 50 additions & 0 deletions subsys/mpsl/fem/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,56 @@ config MPSL_FEM_USE_TWI_DRV
select I2C
default y

config MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION
bool "Temperature compensation of the nRF2220 Front-End Module [EXPERIMENTAL]"
depends on MPSL_FEM_NRF2220 && MPSL_FEM_USE_TWI_DRV
select EXPERIMENTAL
help
This allows to achieve better accuracy in output power among all
temperature ranges for the nRF2220 Front-End modules.

config MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER
bool
depends on MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION
default y if MPSL && !MPSL_FEM_ONLY
help
Enable this option when using protocols relying on the MPSL scheduler.
After it turns out that for the current temperature some registers
of the nRF2220 must be written, the writes are performed within a timeslot
negotiated with the MPSL scheduler. For operation with Bluetooth, the timeslot
for the nRF2220 register update is between other Bluetooth timeslots.
For operation with the nRF 802.15.4 Radio Driver, the grant for the timeslot
for the nRF2220 register update causes short inability to transmit or receive.

choice MPSL_FEM_NRF2220_TEMPERATURE_SOURCE
prompt "Source of temperature measurement of the nRF2220"
depends on MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION
default MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_SOC

config MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_SOC
bool "Temperature of the SoC as the temperature of the nRF2220"
select SENSOR
help
The temperature of the SoC controlling the nRF2220 Front-End Module
is taken as an input for the temperature compensation.
Use this option if there is good thermal coupling between the
temperature of the SoC and the nRF2220 Front-End Module.

config MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_CUSTOM
bool "Custom nRF2220 temperature measurement provider"
help
Use this option if you have a custom temperature sensor measuring the
temperature of the nRF2220 device. Your custom code must call
the fem_temperature_change() function to notify the module handling the FEM
that the new temperature value is to be processed.

endchoice # MPSL_FEM_NRF2220_TEMPERATURE_SOURCE

config MPSL_FEM_NRF2220_TEMPERATURE_POLL_PERIOD
int "Temperature measurement poll period (in ms) for temperature compensation of the nRF2220"
depends on MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_SOC
default 5000

config MPSL_FEM_INIT_PRIORITY
int "Init priority of the Front-End Module support code"
depends on MPSL_FEM
Expand Down
50 changes: 34 additions & 16 deletions subsys/mpsl/fem/common/include/mpsl_fem_twi_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,43 @@

#include <zephyr/devicetree.h>
#include <mpsl_fem_nrf22xx_twi_config_common.h>
#include <nrfx_twim.h>

/**
* @brief Initializer of structure @c mpsl_fem_twi_if_t allowing to use TWI interface.
*
* @param node_id Devicetree node identifier for the TWI part for the FEM device.
*/
#define MPSL_FEM_TWI_DRV_IF_INITIALIZER(node_id) \
(mpsl_fem_twi_if_t){ \
.enabled = true, \
.slave_address = ((uint8_t)DT_REG_ADDR(node_id)), \
.p_instance = (void *)DEVICE_DT_GET(DT_BUS(node_id)), \
.p_xfer_read = mpsl_fem_twi_drv_impl_xfer_read, \
.p_xfer_write = mpsl_fem_twi_drv_impl_xfer_write, \
/** @brief Structure representing an I2C bus driver for a Front-End Module. */
typedef struct {
const struct device *dev;
nrfx_twim_evt_handler_t nrfx_twim_callback_saved;
void *nrfx_twim_callback_ctx_saved;
mpsl_fem_twi_async_xfer_write_cb_t fem_twi_async_xfwr_write_cb;
void *fem_twi_async_xfwr_write_cb_ctx;
} mpsl_fem_twi_drv_t;

#define MPSL_FEM_TWI_DRV_INITIALIZER(node_id) \
(mpsl_fem_twi_drv_t){ \
.dev = DEVICE_DT_GET(DT_BUS(node_id)), \
}

int32_t mpsl_fem_twi_drv_impl_xfer_read(void *p_instance, uint8_t slave_address,
uint8_t internal_address, uint8_t *p_data, uint8_t data_length);
/** @brief Prepare the FEM TWI interface structure.
*
* @note This function prepares just synchronous part of the interface.
* If Front-End Module driver requires an asynchronous part of the interface call
* the @ref mpsl_fem_twi_drv_fem_twi_if_prepare_add_async function afterwards.
*
* @param drv Pointer to the FEM TWI driver instance object initialized with
* MPSL_FEM_TWI_DRV_INITIALIZER.
* @param twi_if Pointer to the FEM TWI interface structure to fill.
* @param address The address of the FEM device on the TWI bus.
*/
void mpsl_fem_twi_drv_fem_twi_if_prepare(mpsl_fem_twi_drv_t *drv, mpsl_fem_twi_if_t *twi_if,
uint8_t address);

int32_t mpsl_fem_twi_drv_impl_xfer_write(void *p_instance, uint8_t slave_address,
uint8_t internal_address, const uint8_t *p_data, uint8_t data_length);
/** @brief Prepare the asynchronous part of the FEM TWI interface structure.
*
* @note Assumes that the @ref mpsl_fem_twi_drv_fem_twi_if_prepare function
* has already been called.
*
* @param twi_if Pointer to the interface structure to fill.
*/
void mpsl_fem_twi_drv_fem_twi_if_prepare_add_async(mpsl_fem_twi_if_t *twi_if);

#endif /* MPSL_FEM_TWI_DRV__ */
164 changes: 156 additions & 8 deletions subsys/mpsl/fem/common/mpsl_fem_twi_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,168 @@

#include <mpsl_fem_twi_drv.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c/i2c_nrfx_twim.h>
#include <../drivers/i2c/i2c_nrfx_twim_common.h>

static int32_t mpsl_fem_twi_drv_impl_xfer_read(void *p_instance, uint8_t slave_address,
uint8_t internal_address, uint8_t *p_data,
uint8_t data_length)
{
mpsl_fem_twi_drv_t *drv = (mpsl_fem_twi_drv_t *)p_instance;

return i2c_burst_read(drv->dev, slave_address, internal_address, p_data, data_length);
}

static int32_t mpsl_fem_twi_drv_impl_xfer_write(void *p_instance, uint8_t slave_address,
uint8_t internal_address, const uint8_t *p_data,
uint8_t data_length)
{
mpsl_fem_twi_drv_t *drv = (mpsl_fem_twi_drv_t *)p_instance;

return i2c_burst_write(drv->dev, slave_address, internal_address, p_data, data_length);
}

static inline void mpsl_fem_twi_drv_nrfx_twim_callback_replace(mpsl_fem_twi_drv_t *drv,
nrfx_twim_evt_handler_t callback)
{
const struct i2c_nrfx_twim_common_config *config = drv->dev->config;
nrfx_err_t err;

nrfx_twim_callback_get(&config->twim, &drv->nrfx_twim_callback_saved,
&drv->nrfx_twim_callback_ctx_saved);

err = nrfx_twim_callback_set(&config->twim, callback, drv);

__ASSERT_NO_MSG(err == NRFX_SUCCESS);
(void)err;
}

static inline void mpsl_fem_twi_drv_nrfx_twim_callback_restore(mpsl_fem_twi_drv_t *drv)
{
const struct i2c_nrfx_twim_common_config *config = drv->dev->config;
nrfx_err_t err;

err = nrfx_twim_callback_set(&config->twim, drv->nrfx_twim_callback_saved,
drv->nrfx_twim_callback_ctx_saved);

__ASSERT_NO_MSG(err == NRFX_SUCCESS);
(void)err;
}

static void mpsl_fem_twi_drv_nrfx_twim_evt_handler(nrfx_twim_evt_t const *p_event, void *p_context)
{
mpsl_fem_twi_drv_t *drv = (mpsl_fem_twi_drv_t *)p_context;
int32_t res = 0;

mpsl_fem_twi_drv_nrfx_twim_callback_restore(drv);

int32_t mpsl_fem_twi_drv_impl_xfer_read(void *p_instance, uint8_t slave_address,
uint8_t internal_address, uint8_t *p_data, uint8_t data_length)
if (p_event->type != NRFX_TWIM_EVT_DONE) {
res = -EIO;
}

/* Call the callback which was passed to the mpsl_fem_twi_drv_impl_xfer_write_async call,
* that started the transfer.
*/
drv->fem_twi_async_xfwr_write_cb(drv, res, drv->fem_twi_async_xfwr_write_cb_ctx);
}

static int32_t mpsl_fem_twi_drv_impl_xfer_write_async(void *p_instance, uint8_t slave_address,
const uint8_t *p_data, uint8_t data_length,
mpsl_fem_twi_async_xfer_write_cb_t p_callback,
void *p_context)
{
const struct device *dev = (const struct device *)p_instance;
mpsl_fem_twi_drv_t *drv = (mpsl_fem_twi_drv_t *)p_instance;
const struct i2c_nrfx_twim_common_config *config = drv->dev->config;

/* At this moment the exclusive access to the drv->dev should have been already acquired.
* No ongoing twi transfers are expected. Because of that it is safe to replace
* original event handler of the TWIM with custom one, perform twim transfer and then
* restore the original event handler.
*/

nrfx_twim_xfer_desc_t cur_xfer = {
.address = slave_address,
.type = NRFX_TWIM_XFER_TX,
.p_primary_buf = (uint8_t *)p_data,
.primary_length = data_length,
};
nrfx_err_t err;
int32_t ret = 0;

drv->fem_twi_async_xfwr_write_cb = p_callback;
drv->fem_twi_async_xfwr_write_cb_ctx = p_context;

return i2c_burst_read(dev, slave_address, internal_address, p_data, data_length);
mpsl_fem_twi_drv_nrfx_twim_callback_replace(drv, mpsl_fem_twi_drv_nrfx_twim_evt_handler);

err = nrfx_twim_xfer(&config->twim, &cur_xfer, 0);

if (err != NRFX_SUCCESS) {
mpsl_fem_twi_drv_nrfx_twim_callback_restore(drv);
if (err == NRFX_ERROR_BUSY) {
ret = -EBUSY;
} else {
ret = -EIO;
}
}

return ret;
}

int32_t mpsl_fem_twi_drv_impl_xfer_write(void *p_instance, uint8_t slave_address,
uint8_t internal_address, const uint8_t *p_data, uint8_t data_length)
static uint32_t mpsl_fem_twi_drv_frequency_hz_get(mpsl_fem_twi_drv_t *drv)
{
const struct device *dev = (const struct device *)p_instance;
const struct i2c_nrfx_twim_common_config *config = drv->dev->config;

switch (config->twim_config.frequency) {
case NRF_TWIM_FREQ_100K:
return 100000;
case NRF_TWIM_FREQ_250K:
return 250000;
case NRF_TWIM_FREQ_400K:
return 400000;
#if NRF_TWIM_HAS_1000_KHZ_FREQ
case NRF_TWIM_FREQ_1000K:
return 1000000;
#endif
default:
__ASSERT_NO_MSG(false);
return 100000;
}
}

return i2c_burst_write(dev, slave_address, internal_address, p_data, data_length);
static uint32_t mpsl_fem_twi_drv_impl_xfer_write_async_time_get(void *p_instance,
uint8_t data_length)
{
mpsl_fem_twi_drv_t *drv = (mpsl_fem_twi_drv_t *)p_instance;

static const uint32_t sw_overhead_safety_margin_time_us = 10U;
/* Note: on nRF5 devices the first bit of each data octet is delayed by one period,
* thus +1 below.
*/
static const uint32_t i2c_data_byte_sck_periods_with_ack = 1U + 8U + 1U;
static const uint32_t i2c_start_bit_sck_periods = 2U;
static const uint32_t i2c_stop_bit_sck_periods = 2U;
uint32_t i2c_sck_frequency_hz = mpsl_fem_twi_drv_frequency_hz_get(drv);

/* Total number of sck periods needed to perform a write transfer. */
uint32_t total_periods = i2c_start_bit_sck_periods
+ (data_length + 1U) * i2c_data_byte_sck_periods_with_ack
+ i2c_stop_bit_sck_periods;

return (total_periods * 1000000 / i2c_sck_frequency_hz) + sw_overhead_safety_margin_time_us;
}

void mpsl_fem_twi_drv_fem_twi_if_prepare(mpsl_fem_twi_drv_t *drv, mpsl_fem_twi_if_t *twi_if,
uint8_t address)
{
twi_if->enabled = true;
twi_if->slave_address = address;
twi_if->p_instance = (void *)drv;
twi_if->p_xfer_read = mpsl_fem_twi_drv_impl_xfer_read;
twi_if->p_xfer_write = mpsl_fem_twi_drv_impl_xfer_write;
}

void mpsl_fem_twi_drv_fem_twi_if_prepare_add_async(mpsl_fem_twi_if_t *twi_if)
{
twi_if->p_xfer_write_async = mpsl_fem_twi_drv_impl_xfer_write_async;
twi_if->p_xfer_write_async_time_get = mpsl_fem_twi_drv_impl_xfer_write_async_time_get;
}
Loading