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
18 changes: 12 additions & 6 deletions firmware-nrf54lm20a/src/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ static void capture_thread_fn(void *a, void *b, void *c)
ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
for (;;) {
k_sem_take(&audio_capture_start, K_FOREVER);
/* link_claim() transferred the session gate to this thread. Keep
* regulator settling and PDM setup out of the GATT write callback. */
if (power_sensor_rail_enable() != 0 || mic_stream_start() != 0) {
(void)power_sensor_rail_disable();
set_state(AUDIO_ARMED, AUDIO_ERR_MIC);
link_release(LINK_OWNER_AUDIO);
continue;
}
set_state(AUDIO_STREAMING, 0);
k_sem_give(&audio_tx_start);
while (!atomic_get(&stopping) && link_conn() != NULL &&
k_uptime_get() < deadline_ms) {
void *block; uint32_t size;
Expand Down Expand Up @@ -336,18 +346,14 @@ static ssize_t ctrl_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
ring_head = ring_tail = ring_count = ring_offset = 0;
k_sem_reset(&audio_credits);
for (int i = 0; i < AUDIO_CREDITS; i++) k_sem_give(&audio_credits);
if (power_sensor_rail_enable() != 0 || mic_stream_start() != 0) {
(void)power_sensor_rail_disable(); link_release(LINK_OWNER_AUDIO);
set_state(AUDIO_ARMED, AUDIO_ERR_MIC); return len;
}
struct bt_le_conn_param cp = { .interval_min = HIVE_AUDIO_CONN_INTERVAL_UNITS,
.interval_max = HIVE_AUDIO_CONN_INTERVAL_UNITS, .latency = 0,
.timeout = 200 };
(void)bt_conn_le_param_update(conn, &cp);
(void)bt_conn_le_data_len_update(conn, BT_LE_DATA_LEN_PARAM_MAX);
(void)bt_conn_le_phy_update(conn, BT_CONN_LE_PHY_PARAM_2M);
set_state(AUDIO_STREAMING, 0);
k_sem_give(&audio_capture_start); k_sem_give(&audio_tx_start);
set_state(AUDIO_ARMED, 0);
k_sem_give(&audio_capture_start);
return len;
}

Expand Down
2 changes: 1 addition & 1 deletion firmware-nrf54lm20a/src/hive_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
*/
#define HIVEINSIDE_FW_VERSION_MAJOR 0
#define HIVEINSIDE_FW_VERSION_MINOR 6
#define HIVEINSIDE_FW_VERSION_PATCH 1
#define HIVEINSIDE_FW_VERSION_PATCH 2

#define HIVEINSIDE_STRINGIFY_(value) #value
#define HIVEINSIDE_STRINGIFY(value) HIVEINSIDE_STRINGIFY_(value)
Expand Down
52 changes: 51 additions & 1 deletion firmware-nrf54lm20a/src/link.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "beacon.h"
#include "hive_config.h"
#include "ota.h"
#include "watchdog.h"

#include <zephyr/bluetooth/hci.h>
#include <zephyr/kernel.h>
Expand All @@ -15,6 +16,7 @@
static struct bt_conn *active_conn;
static enum link_owner owner;
static struct k_spinlock state_lock;
K_SEM_DEFINE(session_gate, 1, 1);

static void arm_timeout_handler(struct k_work *work);
K_WORK_DELAYABLE_DEFINE(arm_timeout_work, arm_timeout_handler);
Expand All @@ -27,6 +29,12 @@ struct bt_conn *link_conn(void)
bool link_claim(enum link_owner requested)
{
bool claimed = false;

/* GATT callbacks must never wait for a sensor capture. Taking the gate
* without waiting makes START/BEGIN report busy while a cycle is in flight. */
if (k_sem_take(&session_gate, K_NO_WAIT) != 0) {
return false;
}
k_spinlock_key_t key = k_spin_lock(&state_lock);

if (active_conn != NULL && owner == LINK_OWNER_NONE &&
Expand All @@ -35,6 +43,9 @@ bool link_claim(enum link_owner requested)
claimed = true;
}
k_spin_unlock(&state_lock, key);
if (!claimed) {
k_sem_give(&session_gate);
}
if (claimed) {
(void)k_work_cancel_delayable(&arm_timeout_work);
}
Expand All @@ -44,12 +55,18 @@ bool link_claim(enum link_owner requested)
void link_release(enum link_owner released)
{
bool arm = false;
bool released_gate = false;
k_spinlock_key_t key = k_spin_lock(&state_lock);
if (owner == released) {
owner = LINK_OWNER_NONE;
arm = active_conn != NULL;
released_gate = true;
}
k_spin_unlock(&state_lock, key);
/* Only the successful owner transition above owns this semaphore. */
if (released_gate) {
k_sem_give(&session_gate);
}
/* Releasing a completed service does not make an otherwise idle
* connection safe to retain forever. Give the central a fresh arm window
* in which to begin another bounded session. */
Expand All @@ -59,6 +76,28 @@ void link_release(enum link_owner released)
}
}

bool link_measurement_begin(void)
{
/* A session can last minutes, so keep the watchdog alive rather than
* blocking the main thread indefinitely. */
while (k_sem_take(&session_gate,
K_MSEC(HIVE_WDT_FEED_INTERVAL_MS)) != 0) {
hive_watchdog_feed();
}

/* A connection may have arrived while main was waiting for the gate. */
if (link_is_busy()) {
k_sem_give(&session_gate);
return false;
}
return true;
}

void link_measurement_end(void)
{
k_sem_give(&session_gate);
}

bool link_is_owner(enum link_owner queried)
{
k_spinlock_key_t key = k_spin_lock(&state_lock);
Expand Down Expand Up @@ -110,11 +149,22 @@ static void connected(struct bt_conn *conn, uint8_t err)

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
bool release_gate = false;
k_spinlock_key_t key;

ARG_UNUSED(conn);
(void)k_work_cancel_delayable(&arm_timeout_work);
ota_link_disconnected();
audio_link_disconnected();
owner = LINK_OWNER_NONE;
key = k_spin_lock(&state_lock);
if (owner != LINK_OWNER_NONE) {
owner = LINK_OWNER_NONE;
release_gate = true;
}
k_spin_unlock(&state_lock, key);
if (release_gate) {
k_sem_give(&session_gate);
}
if (active_conn != NULL) {
bt_conn_unref(active_conn);
active_conn = NULL;
Expand Down
5 changes: 4 additions & 1 deletion firmware-nrf54lm20a/src/link.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ bool link_claim(enum link_owner owner);
void link_release(enum link_owner owner);
bool link_is_owner(enum link_owner owner);
bool link_is_busy(void);

/* Hold around the complete powered sensor cycle. This waits in watchdog-sized
* slices, while link_claim() remains non-blocking for GATT callers. */
bool link_measurement_begin(void);
void link_measurement_end(void);
7 changes: 7 additions & 0 deletions firmware-nrf54lm20a/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ int main(void)
* than reset-looping, and MCUboot's own rollback deadline below is the
* safety net for an image that cannot get this far. */
hive_watchdog_init();
/* power_init() owns the boot-time rail reference; normal users take their
* own references below. */
(void)power_sensor_rail_disable();
confirmation_pending = !boot_is_img_confirmed();
/* Do not confirm a test image merely because main() was reached. Wait until
* one complete sensor cycle has run and Bluetooth has successfully published
Expand Down Expand Up @@ -214,6 +217,9 @@ int main(void)
k_msleep(100);
continue;
}
if (!link_measurement_begin()) {
continue;
}
struct measurement m = { 0 };

/* LDO1 is normally off between cycles. Wake the IMU and microphone
Expand All @@ -224,6 +230,7 @@ int main(void)
accel_read(&m);
mic_read(&m);
(void)power_sensor_rail_disable();
link_measurement_end();
battery_read(&m);

print_readout(&m);
Expand Down
5 changes: 2 additions & 3 deletions firmware-nrf54lm20a/src/mic.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,8 @@ void mic_read(struct measurement *m)
{
m->mic_ok = false;

/* link_is_busy() keeps the periodic sensor path out while audio owns the
* single PDM controller, so a second lock here would only obscure that
* system-level ownership rule. */
/* link's session gate is held for the complete measurement cycle, keeping
* this one-shot capture exclusive with audio's use of the PDM controller. */
int err = mic_stream_start();
if (err != 0) {
printk("[MIC] start failed (%d)\n", err);
Expand Down
3 changes: 3 additions & 0 deletions firmware-nrf54lm20a/src/mic.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ int mic_stream_read(void **buf, uint32_t *size, int32_t timeout_ms);
void mic_stream_release(void *buf);
void mic_stream_stop(void);

/* The streaming and one-shot APIs share one PDM controller and must not
* overlap. link's session gate serialises an entire measurement cycle with
* every BLE session before either path enters this module. */
/* Capture ~0.5 s of PCM from the PDM microphone and reduce it to RMS/peak dBFS
* plus the five shared acoustic bands (sets m->mic_ok). A no-op that leaves
* mic_ok false when the devicetree has no enabled nRF PDM node. */
Expand Down
24 changes: 23 additions & 1 deletion firmware-nrf54lm20a/src/power.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

static const struct device *const ldo1 = DEVICE_DT_GET(LDO1_NODE);
static bool sensor_rail_enabled;
static uint32_t sensor_rail_users;
K_MUTEX_DEFINE(sensor_rail_lock);

/* P1.12 gates the complete Sense-board sensor supply. LDO1 being enabled is
* not sufficient when this upstream fixed regulator is off. Seeed's DMIC
Expand Down Expand Up @@ -132,6 +134,7 @@ void power_init(void)
return;
}
sensor_rail_enabled = true;
sensor_rail_users = 1;
/* The IMU and microphone share this rail. Give both parts time to
* leave reset before their first I2C/PDM transaction. */
k_msleep(20);
Expand All @@ -141,8 +144,11 @@ void power_init(void)
int power_sensor_rail_enable(void)
{
int err;
k_mutex_lock(&sensor_rail_lock, K_FOREVER);

if (sensor_rail_enabled) {
sensor_rail_users++;
k_mutex_unlock(&sensor_rail_lock);
return 0;
}

Expand All @@ -151,30 +157,44 @@ int power_sensor_rail_enable(void)
#if DT_NODE_EXISTS(DT_NODELABEL(power_en))
err = enable_regulator(power_en, "sensor power gate");
if (err != 0) {
k_mutex_unlock(&sensor_rail_lock);
return err;
}
#endif

err = enable_regulator(ldo1, "LDO1");
if (err != 0) {
k_mutex_unlock(&sensor_rail_lock);
return err;
}
sensor_rail_enabled = true;
sensor_rail_users = 1;
/* The IMU and microphone need time to leave reset before use. */
k_msleep(20);
k_mutex_unlock(&sensor_rail_lock);
return 0;
}

int power_sensor_rail_disable(void)
{
int err;
k_mutex_lock(&sensor_rail_lock, K_FOREVER);

if (!sensor_rail_enabled) {
/* Every successful enable owns one reference. Only its matching disable
* may drop that reference, and hardware is switched off at zero. */
if (!sensor_rail_enabled || sensor_rail_users == 0U) {
k_mutex_unlock(&sensor_rail_lock);
return 0;
}
if (--sensor_rail_users != 0U) {
k_mutex_unlock(&sensor_rail_lock);
return 0;
}

err = disable_regulator(ldo1, "LDO1");
if (err != 0) {
sensor_rail_users = 1;
k_mutex_unlock(&sensor_rail_lock);
return err;
}
sensor_rail_enabled = false;
Expand All @@ -185,10 +205,12 @@ int power_sensor_rail_disable(void)
* measurement. Reverse order of the enable above. */
err = disable_regulator(power_en, "sensor power gate");
if (err != 0) {
k_mutex_unlock(&sensor_rail_lock);
return err;
}
#endif

k_mutex_unlock(&sensor_rail_lock);
return 0;
}

Expand Down
Loading