From 8967257aa5daebfa23a050ad090a0f43748bba52 Mon Sep 17 00:00:00 2001 From: Andrzej Kuros Date: Mon, 17 Mar 2025 08:29:39 +0100 Subject: [PATCH 1/3] manifest: zephyr update to bring in i2c_nrfx_twim extension This commit brings in zephyr with extended i2c_nrfx_twim to enable nrf2220 temperature compensation. Signed-off-by: Andrzej Kuros --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index 48798a747f46..32259e1e5c44 100644 --- a/west.yml +++ b/west.yml @@ -66,7 +66,7 @@ manifest: # https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/modules.html - name: zephyr repo-path: sdk-zephyr - revision: 4ba36f2e6f4e9ba5384c664b398d5186c9a1252b + revision: pull/2624/head import: # In addition to the zephyr repository itself, NCS also # imports the contents of zephyr/west.yml at the above From 939bf1a61c0b01c0de94198d7b48cafea216a7e0 Mon Sep 17 00:00:00 2001 From: Andrzej Kuros Date: Wed, 5 Mar 2025 17:13:35 +0100 Subject: [PATCH 2/3] mpsl: fem: nrf2220: add temperature compensation For the nRF2220 the temperature compensation feature is added. It allows to achieve better output power accuracy among the whole supported temperature range. The feature is enabled by the MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION Kconfig option. It relies on functions provided by MPSL in the sdk-nrfxlib. The temperature source can be taken from the SoC or can be set by custom implementation. Signed-off-by: Andrzej Kuros --- .../app_dev/device_guides/fem/fem_nrf2220.rst | 2 + subsys/mpsl/fem/Kconfig | 50 ++++++ .../fem/common/include/mpsl_fem_twi_drv.h | 53 +++++- subsys/mpsl/fem/common/mpsl_fem_twi_drv.c | 39 ++++- subsys/mpsl/fem/nrf2220/mpsl_fem_nrf2220.c | 156 +++++++++++++++++- 5 files changed, 291 insertions(+), 9 deletions(-) diff --git a/doc/nrf/app_dev/device_guides/fem/fem_nrf2220.rst b/doc/nrf/app_dev/device_guides/fem/fem_nrf2220.rst index 9dcffd6488a2..9127330380e5 100644 --- a/doc/nrf/app_dev/device_guides/fem/fem_nrf2220.rst +++ b/doc/nrf/app_dev/device_guides/fem/fem_nrf2220.rst @@ -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: diff --git a/subsys/mpsl/fem/Kconfig b/subsys/mpsl/fem/Kconfig index 7791a6ab18fa..ba736f3bfae8 100644 --- a/subsys/mpsl/fem/Kconfig +++ b/subsys/mpsl/fem/Kconfig @@ -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 diff --git a/subsys/mpsl/fem/common/include/mpsl_fem_twi_drv.h b/subsys/mpsl/fem/common/include/mpsl_fem_twi_drv.h index 125ed87ffd71..b602afbb2eb0 100644 --- a/subsys/mpsl/fem/common/include/mpsl_fem_twi_drv.h +++ b/subsys/mpsl/fem/common/include/mpsl_fem_twi_drv.h @@ -10,19 +10,52 @@ #include #include +#define MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_BEGIN(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)), + +#define MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_XFERS \ + .p_xfer_read = mpsl_fem_twi_drv_impl_xfer_read, \ + .p_xfer_write = mpsl_fem_twi_drv_impl_xfer_write, + +#define MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_XFERS_ASYNC \ + .p_xfer_write_async = mpsl_fem_twi_drv_impl_xfer_write_async, \ + .p_xfer_write_async_time_get = mpsl_fem_twi_drv_impl_xfer_write_async_time_get, + +#define MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_END \ + } + /** * @brief Initializer of structure @c mpsl_fem_twi_if_t allowing to use TWI interface. * + * @note Initializes only the part related to the synchonous interface for boot-time + * initialization of the FEM. + * + * @sa @ref @ref MPSL_FEM_TWI_DRV_IF_INITIALIZER_WASYNC + * * @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, \ - } + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_BEGIN(node_id) \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_XFERS \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_END + +/** + * @brief Initializer of structure @c mpsl_fem_twi_if_t allowing to use TWI interface + * with asynchronous extension. + * + * @sa @ref @ref MPSL_FEM_TWI_DRV_IF_INITIALIZER + * + * @param node_id Devicetree node identifier for the TWI part for the FEM device. + */ +#define MPSL_FEM_TWI_DRV_IF_INITIALIZER_WASYNC(node_id) \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_BEGIN(node_id) \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_XFERS \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_XFERS_ASYNC \ + MPSL_FEM_TWI_DRV_IF_PARTIAL_INITIALIZER_END + 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); @@ -30,4 +63,10 @@ int32_t mpsl_fem_twi_drv_impl_xfer_read(void *p_instance, uint8_t slave_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); +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); + +uint32_t mpsl_fem_twi_drv_impl_xfer_write_async_time_get(void *p_instance, uint8_t data_length); + #endif /* MPSL_FEM_TWI_DRV__ */ diff --git a/subsys/mpsl/fem/common/mpsl_fem_twi_drv.c b/subsys/mpsl/fem/common/mpsl_fem_twi_drv.c index 4e2e5cd619c3..76d2009aaa0f 100644 --- a/subsys/mpsl/fem/common/mpsl_fem_twi_drv.c +++ b/subsys/mpsl/fem/common/mpsl_fem_twi_drv.c @@ -6,7 +6,7 @@ #include #include - +#include 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) @@ -23,3 +23,40 @@ int32_t mpsl_fem_twi_drv_impl_xfer_write(void *p_instance, uint8_t slave_address return i2c_burst_write(dev, slave_address, internal_address, p_data, data_length); } + +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; + + struct i2c_msg msg = { + .buf = (uint8_t *)p_data, + .len = data_length, + .flags = I2C_MSG_WRITE | I2C_MSG_STOP + }; + + return i2c_nrfx_twim_async_transfer_begin(dev, &msg, slave_address, + (i2c_nrfx_twim_async_transfer_handler_t)p_callback, p_context); +} + +uint32_t mpsl_fem_twi_drv_impl_xfer_write_async_time_get(void *p_instance, uint8_t data_length) +{ + (void)p_instance; + + static const uint32_t sw_overhead_safety_margin_time_us = 10U; + /* Note: on nRF devices the first bit of each data octet is delayed by one period, + * thus +1 below. + */ + static const uint32_t i2c_data_byte_periods_with_ack = 1U + (8U + 1U); + static const uint32_t i2c_start_bit_periods = 2U; + static const uint32_t i2c_stop_bit_periods = 2U; + static const uint32_t i2c_speed_hz = 100000; + + /* Total number of sck periods needed to perform a write transfer. */ + uint32_t total_periods = i2c_start_bit_periods + + (data_length + 1U) * i2c_data_byte_periods_with_ack + + i2c_stop_bit_periods; + + return (total_periods * 1000000 / i2c_speed_hz) + sw_overhead_safety_margin_time_us; +} diff --git a/subsys/mpsl/fem/nrf2220/mpsl_fem_nrf2220.c b/subsys/mpsl/fem/nrf2220/mpsl_fem_nrf2220.c index a376ce0fcd2e..731df72ff671 100644 --- a/subsys/mpsl/fem/nrf2220/mpsl_fem_nrf2220.c +++ b/subsys/mpsl/fem/nrf2220/mpsl_fem_nrf2220.c @@ -30,6 +30,11 @@ #include #endif +#if IS_ENABLED(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION) +#include +#include +#endif + #if !defined(CONFIG_PINCTRL) #error "The nRF2220 driver must be used with CONFIG_PINCTRL! Set CONFIG_PINCTRL=y" #endif @@ -66,7 +71,15 @@ static void fem_nrf2220_twi_init_regs_configure(mpsl_fem_nrf2220_interface_confi static void fem_nrf2220_twi_configure(mpsl_fem_nrf2220_interface_config_t *cfg) { - cfg->twi_if = MPSL_FEM_TWI_DRV_IF_INITIALIZER(MPSL_FEM_TWI_IF); + if (IS_ENABLED(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER)) { + /* The nRF2220 temperature compensation with the MPSL scheduler requires + * asynchronous interface. + * See mpsl_fem_nrf2220_temperature_changed_update_request function. + */ + cfg->twi_if = MPSL_FEM_TWI_DRV_IF_INITIALIZER_WASYNC(MPSL_FEM_TWI_IF); + } else { + cfg->twi_if = MPSL_FEM_TWI_DRV_IF_INITIALIZER(MPSL_FEM_TWI_IF); + } } #endif /* DT_NODE_HAS_PROP(DT_NODELABEL(nrf_radio_fem), twi_if) */ @@ -193,4 +206,145 @@ BUILD_ASSERT(CONFIG_MPSL_FEM_INIT_PRIORITY > CONFIG_I2C_INIT_PRIORITY, SYS_INIT(mpsl_fem_init, POST_KERNEL, CONFIG_MPSL_FEM_INIT_PRIORITY); +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION) + +static K_SEM_DEFINE(fem_temperature_new_value_sem, 0, 1); + +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER) +static int32_t fem_temperature_update_result; +static K_SEM_DEFINE(fem_temperature_updated_cb_sem, 0, 1); + +static void fem_temperature_update_cb(int32_t res) +{ + fem_temperature_update_result = res; + k_sem_give(&fem_temperature_updated_cb_sem); +} + +static int32_t fem_temperature_changed_update_now(void) +{ + const struct device *i2c_bus_dev = DEVICE_DT_GET(DT_BUS(MPSL_FEM_TWI_IF)); + + /* Let's have initially "taken" semaphore that will inform the operation + * on the nrf2220 is finished. + */ + k_sem_take(&fem_temperature_updated_cb_sem, K_NO_WAIT); + + (void)i2c_nrfx_twim_exclusive_access_acquire(i2c_bus_dev, K_FOREVER); + + /* Temporary raise i2c_bus_dev IRQ priority, as required by the + * mpsl_fem_nrf2220_temperature_changed_update_request function. + * The IRQs from i2c may not be delayed while performing operations + * in a timeslot granted by the MPSL scheduler. + */ + z_arm_irq_priority_set(DT_IRQN(DT_BUS(MPSL_FEM_TWI_IF)), 0, IRQ_ZERO_LATENCY); + + mpsl_fem_nrf2220_temperature_changed_update_request(fem_temperature_update_cb); + + /* Let's wait until the operation is finished */ + k_sem_take(&fem_temperature_updated_cb_sem, K_FOREVER); + + /* Restore original i2c_bus_dev IRQ priority. */ + z_arm_irq_priority_set(DT_IRQN(DT_BUS(MPSL_FEM_TWI_IF)), + DT_IRQ(DT_BUS(MPSL_FEM_TWI_IF), priority), 0); + + i2c_nrfx_twim_exclusive_access_release(i2c_bus_dev); + + return fem_temperature_update_result; +} +#endif /* CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_MPSL_SCHEDULER */ + +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_SOC) + +#include +#define FEM_TEMPERATURE_NEW_VALUE_SEM_TIMEOUT \ + (K_MSEC(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_POLL_PERIOD)) + +static int8_t fem_temperature_sensor_value_get(void) +{ + const struct device *dev = DEVICE_DT_GET_ONE(nordic_nrf_temp); + + if (!device_is_ready(dev)) { + __ASSERT(false, "Temperature sensor not ready"); + } + + struct sensor_value v; + int ret = sensor_sample_fetch(dev); + + __ASSERT(ret == 0, "Can't fetch temperature sensor sample"); + + ret = sensor_channel_get(dev, SENSOR_CHAN_DIE_TEMP, &v); + __ASSERT(ret == 0, "Can't get temperature of the die"); + + (void)ret; + + return v.val1; +} +#endif /* CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_SOC */ + +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_CUSTOM) + +#define FEM_TEMPERATURE_NEW_VALUE_SEM_TIMEOUT K_FOREVER + +#define NRF2220_TEMPERATURE_DEFAULT 25 + +static int8_t fem_temperature_value = NRF2220_TEMPERATURE_DEFAULT; + +void fem_temperature_change(int8_t temperature) +{ + fem_temperature_value = temperature; + k_sem_give(&fem_temperature_new_value_sem); +} + +static int8_t fem_temperature_sensor_value_get(void) +{ + return fem_temperature_value; +} +#endif /* CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_SOURCE_CUSTOM */ + +static void fem_temperature_compensation_thread(void *dummy1, void *dummy2, void *dummy3) +{ + ARG_UNUSED(dummy1); + ARG_UNUSED(dummy2); + ARG_UNUSED(dummy3); + + while (true) { + (void)k_sem_take(&fem_temperature_new_value_sem, + FEM_TEMPERATURE_NEW_VALUE_SEM_TIMEOUT); + + int8_t new_temperature = fem_temperature_sensor_value_get(); + + if (mpsl_fem_nrf2220_temperature_changed(new_temperature)) { +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER) + int32_t res = fem_temperature_changed_update_now(); + + __ASSERT(res == 0, "FEM update on temperature change failed"); + (void)res; +#endif /* CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER */ + } + } +} + +#define FEM_TEMPERATURE_COMPENSATION_THREAD_STACK_SIZE 512 + +static K_THREAD_STACK_DEFINE(fem_temperature_compensation_thread_stack, + FEM_TEMPERATURE_COMPENSATION_THREAD_STACK_SIZE); +static struct k_thread fem_temperature_compensation_thread_data; + +static int fem_nrf2220_temperature_compensation_init(void) +{ + (void)k_thread_create(&fem_temperature_compensation_thread_data, + fem_temperature_compensation_thread_stack, + K_THREAD_STACK_SIZEOF(fem_temperature_compensation_thread_stack), + fem_temperature_compensation_thread, + NULL, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, 0, K_NO_WAIT); + + return 0; +} + +SYS_INIT(fem_nrf2220_temperature_compensation_init, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); + +#endif /* CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION */ + #endif /* defined(CONFIG_MPSL_FEM_NRF2220) */ From 7306a64322a9daf45c338fe77e042c6d25835485 Mon Sep 17 00:00:00 2001 From: Andrzej Kuros Date: Wed, 5 Mar 2025 17:21:40 +0100 Subject: [PATCH 3/3] fem_al: use nRF2220 temperature compensation if enabled The nRF2220 temperature compensation is recently added. The protocols relying on the fem_al do not use the MPSL scheduler. In this case the function `mpsl_fem_nrf2220_temperature_changed_update_now` must be called by a protocol driver which for this case is the fem_al. Signed-off-by: Andrzej Kuros --- lib/fem_al/fem_al.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/fem_al/fem_al.c b/lib/fem_al/fem_al.c index 4ffdd5d3a300..c0756b52ac08 100644 --- a/lib/fem_al/fem_al.c +++ b/lib/fem_al/fem_al.c @@ -18,6 +18,10 @@ #include #include +#if defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION) && \ + !defined(CONFIG_MPSL_FEM_NRF2220_TEMPERATURE_COMPENSATION_WITH_MPSL_SCHEDULER) +#include +#endif #include "fem_al/fem_al.h" #include "fem_interface.h" @@ -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) {