The shell could only reach the LoRa modem, so the GFSK one had no way of
being exercised by hand. Give it a command of its own, matching the two
calls the driver API offers, so neither has to say which of its
parameters apply.
Both configurations carry the direction, so an operation names it in the
one it is about to apply, and whichever was named last is the one
applied, the way the driver acts on the configuration that reached it
last.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
Set the radio up for GFSK: modulation and packet parameters of its own,
the sync word written to the register the modem matches on, and the CRC
and whitening seeds LoRaWAN's FSK datarate asks for. A bandwidth request
takes the narrowest receive filter at least as wide.
All of that lives in sx126x_gfsk.c, built only when the modem is wanted,
so the shared file carries neither it nor the guards it would need. The
handful of helpers it calls lose their static.
The whitening seed is a nine-bit value and only the low bit of its high
byte belongs to it. What the rest of that register holds is the radio's,
so the byte is read back and only the one bit replaced.
Bit rate, deviation and their combined occupied bandwidth are held to the
ranges the datasheet gives, which also keeps the values inside the
registers they are written to. A fixed-length frame is clocked out at the
configured length, so any other length is refused rather than putting
whatever the buffer still held on air, and airtime reports that length.
Frequency and transmit power are reached the same way whichever modem
follows, so they move into a helper the two configurations share, as does
taking and releasing the radio. Selecting the modem resets part of the
radio, so it now comes before that rather than after.
The packet status bytes mean different things under the two modems, so
the read follows the one in use. The four operating paths each rebuilt
the LoRa packet parameters by hand to set a payload length; they call one
function now, which builds them for the modem the radio is on.
The packet type was selected once, while bringing the radio up. Adding a
path that selects the other one left nothing to put it back, so from the
first GFSK setup onwards LoRa parameters went to the GFSK modem and
nothing left the antenna. It is stated on every configuration now.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
Set the radio up for GFSK, where bandwidth is named in Hz rather than
picked from the steps the LoRa modem offers. The setup lives in
lbm_gfsk.c, built only when the modem is wanted, so the shared file
carries neither it nor the guards it would need.
The send and receive paths used the LoRa packet parameters and packet
status whatever the radio was set up for. Both follow the modem in use
now, as does airtime. GFSK measures no signal-to-noise ratio, so a
received packet carries zero rather than a reading never made.
Whether a transmission had anything to go on was decided by looking for a
spreading factor, which GFSK has none of. Ask instead whether the stored
parameters still describe the radio, and record the modem only once it
has taken them, so a rejected configuration cannot leave the radio marked
as a modem it was never put into.
A fixed-length frame keeps the length it was configured with rather than
taking the one being sent, and any other length is refused.
The CRC-16-CCITT seed and polynomial LoRaWAN asks for are what these
parts reset to. The whitening seed is not: they come up at 0x0100 and
LoRaWAN wants the register full of ones.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
The library takes the modem as the first argument of every radio call and
the backend named the LoRa one everywhere, so a GFSK configuration had
nowhere to go.
Store the modem the last configuration selected and name it in the four
calls that act on the running one: the three that set a maximum payload
length, and the RSSI read. Airtime reads the GFSK settings when that is
the modem, and reports the configured length for a fixed-length frame,
which is what the radio clocks out whatever it is handed.
The library states FSK bandwidth across one sideband, so the value is
halved on the way in. Under FSK its datarate argument carries the bit
rate and the preamble length counts bytes.
Bit rate and deviation are held to the ranges the datasheet gives, which
also keeps the registers the library writes from taking a truncated
value, and a transmission that does not match a fixed length is refused.
Sync word, pulse shape and whitening are fixed by the library, so a
configuration asking for anything else is refused rather than quietly
ignored. What it writes is what RP002 asks of an end-device.
Only the library's SX126x radio reaches the FSK modem this way. Its
SX127x one leaves the sync word and whitening at reset values, so a frame
it sent would match nothing, and that radio goes on answering -ENOSYS.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
These parts carry a GFSK modem alongside the LoRa one with no way to
reach it, which leaves out the 50 kbps FSK that LoRaWAN defines as EU868
DR7 and the plain FSK links the radios are equally built for.
Add struct lora_modem_config_gfsk and lora_config_gfsk() rather than a
modulation field in the LoRa configuration. Almost nothing carries over:
of the two fields that share a name, bandwidth is a filter width in Hz
rather than one of the LoRa steps, and the sync word is up to eight bytes
rather than one.
Bandwidth is stated across both sidebands, the way radio datasheets state
their channel filters, so a single-sideband figure from a regulation is
halved by the caller who read it.
The preamble counts bytes where a radio counts bits in sixteen of them,
so LORA_GFSK_PREAMBLE_MAX caps it. A fixed-length frame carries no length
byte, so payload_len is what the radio clocks out and a transmission of
any other length is refused.
Declarations stay unconditional so a driver can fork on the modem with
IS_ENABLED() rather than the preprocessor. CONFIG_LORA_GFSK keeps the
implementation out of drivers that have no use for it, and one without
the operation answers -ENOSYS.
Nothing implements it yet.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
The native backend only has drivers for SX1261/SX1262, the STM32WL
sub-GHz radio and LR1121, but unlike the LoRa Basics Modem entry its
choice entry has no dependency on them, so selecting it on a board with
another radio builds with no driver at all. Depend on those radios.
Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
Assisted-by: Claude:opus-5.5
Update the type definition of `modem_chat_script_callback` to take a
pointer to a context variable. This context variable holds additional
internal information about the script that the callback might be
interested in.
The goal is to remove any need for the callback itself to query internal
state of the `modem_chat`, because anything it needs it has already
been given. Eventually, this will lead to callbacks being able to queue
a new script directly from the callback of the previous script.
Breaking API change.
Signed-off-by: Jordan Yates <jordan@embeint.com>
Use controller/peripheral and SDO/SDI terminology in the remaining
SPI-using drivers (audio, auxdisplay, bluetooth HCI, dac, eeprom,
ethernet, flash, fpga, gpio, haptics, hdlc_rcp_if, ieee802154,
input, led, led_strip, lora, mfd, misc, mux, rtc, sdhc, stepper,
video, watchdog, wifi) and the ec_host_cmd SPI STM32 backend,
following coding guideline rule A.2: switch to
SPI_OP_MODE_CONTROLLER/PERIPHERAL and SPI_SDO_OVERRUN_DT, rename
Zephyr-owned identifiers (the HCI SPI header buffers, the winc1500
bus wrapper transfer parameters), and update comments, log messages
and Kconfig help prose.
Vendor and out-of-scope identifiers keep their names: STM32
LL_SPI_MODE_SLAVE in the ec_host_cmd backend, the ATWINC1500 SDK I2C
SLAVE_ADDRESS definition, and the I2S codec clock-role variable in
tlv320aic26.
Assisted-by: Claude:fable-5
Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
Both commands enter receive mode first and leave it afterwards, since
the RSSI only means anything while the radio is receiving.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
Add lora_energy_detect(), which reports whether a channel carries energy
above a threshold. Unlike lora_cad() it reacts to any transmitter, not
only to a LoRa preamble.
No radio has a command for this, so it is built in common code on top of
lora_recv_async() and lora_rssi() rather than added as a driver
operation. Variants such as tracking the peak level can then be written
without touching any backend.
The receiver needs time to settle before the first sample is valid, so
the wait before sampling and the interval between samples are Kconfig
options. The settling time is measured per backend, so it defaults to
0.5 ms on loramac-node and the native driver and to 10 ms otherwise.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
Wire lora_rssi() to the primitive each backend already exposes:
Radio.Rssi() in loramac-node, ral_get_rssi_inst() in LoRa Basics Modem
and the GET_RSSI_INST command in the native SX126x driver.
The native LR11xx driver and the RYLRxxx modem are left out. LR11xx has
the command but no hardware was on hand to test it, and RYLRxxx is an AT
command module with no equivalent.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
One SX126x bus access is three steps: wait for the chip to be ready,
run the transfer, then wait for it to go idle again. The idle wait
sleeps, so a second thread can enter the sequence in the middle and
leave the handshake out of step.
No caller has hit this so far. Reading the RSSI of a receive already in
progress is the first case where the calling thread and the DIO1 work
handler are on the bus at the same time, so take a binary semaphore
around the sequence. Nothing inside the sequence re-enters it, so the
lock never needs to be recursive.
SX127x needs no equivalent: one access there is a single transfer,
which the SPI driver already serialises.
Signed-off-by: JaeHwan Jin <jaehwan.jin@rakwireless.com>
Replace direct NVIC_SetPendingIRQ()/NVIC_ClearPendingIRQ() calls with
k_irq_set_pending()/k_irq_clear_pending().
Assisted-by: Claude:claude-opus-5
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
parse_freq() only rejected a frequency when strtoul() returned exactly
ULONG_MAX, which does not detect overflow reliably and, on 64-bit hosts
such as native_sim/native/64, lets any value above UINT32_MAX through to
be silently truncated by the cast to uint32_t. 0x100000000 was accepted
and configured the modem at 0 Hz.
Check errno for ERANGE and reject anything that does not fit in the
uint32_t the API takes.
Signed-off-by: Ben Shalem <benshalem93@gmail.com>
enum lora_signal_bandwidth and enum lora_datarate were renumbered to
carry their kHz and spreading factor values directly, but the shell was
not updated:
- bw_table was indexed by the enum value, so a table intended to hold 3
entries became a sparse 501 entry array (~2 kB of rodata) whose values
are identical to their index.
- Only 125, 250 and 500 kHz could be selected, leaving 12 of the 15
bandwidths supported by the API unreachable.
- The spreading factor was limited to SF6..SF12, leaving SF5 unreachable.
Drop the table, print the bandwidth directly, validate the requested
value against the enum and widen the spreading factor range.
Signed-off-by: Ben Shalem <benshalem93@gmail.com>
Stopping reception with lora_recv_async(dev, NULL, NULL) from inside the
async callback races with the DIO1 interrupt. modem_release() sets the
mode to MODE_SLEEP and puts the chip to sleep, but the DIO1 interrupt is
still enabled, so a packet arriving in that window schedules a work item
that then runs with MODE_SLEEP already set and logs "Unexpected modem
mode (0)".
On parts that re-assert DIO1 when woken from sleep with a pending IRQ
(observed on an LR11xx-based board) this becomes an infinite loop:
clearing the IRQ wakes the chip, DIO1 re-asserts, another work item is
scheduled, and so on.
Fix this in three parts:
- Disable DIO1 in modem_release() before the chip goes to sleep, and
re-enable it (with a stale-IRQ clear) when starting the next RX/TX.
The dio1 gpio_dt_spec is moved into lbm_lora_config_common so the
common layer owns the operational interrupt gating directly. Parts
that do not route their IRQ through a single DIO1 line (SX127x drives
several DIO lines and manages them itself) leave the pin unset, so the
common layer accesses it through a NULL-guarded helper.
- When the work handler sees MODE_SLEEP it returns instead of touching
the hardware, which is what previously drove the loop.
- When the async callback stops reception, the handler detects that the
modem was already released (mode is now MODE_SLEEP) and returns before
re-reading the IRQ status, leaving the chip asleep instead of waking
it again.
Fixes#111484
Signed-off-by: Jakub Rzeszutko <jakub.rzeszutko@verkada.com>
Per the SX1261/2 datasheet (13.1.12) 0x000000 selects Rx Single mode
and 0xFFFFFF Rx Continuous mode; the two defines had their values
swapped.
No functional change: SX126X_RX_TIMEOUT_SINGLE (0xFFFFFF) was never
referenced, and the continuous receive paths (lora_recv_async, and
lora_recv with K_FOREVER) re-arm RX after every packet in
sx126x_rx_restart(), so they kept receiving even with the chip wrongly
in single mode. The blocking recv path needs only one packet and
sleeps the radio in the RxDone handler regardless. After the swap
those paths use true Rx Continuous mode, with identical observable
behavior.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add a synchronous counterpart to lora_recv_duty_cycle_async that
blocks until a packet is received or the timeout expires, then
stops the duty cycle and returns the data. This mirrors the
lora_recv / lora_recv_async pairing.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
When using the LoRa Basics Modem or Native backends, the SYNC word is
user-configurable. Allow users to set it to a specific (non-zero) value.
Signed-off-by: Jordan Yates <jordan@embeint.com>
For the STM32WL Low Power PA at max_power=15 dBm and frequencies above
400 MHz, the SX1261 datasheet specifies paDutyCycle=0x06. The drivers were
setting 0x07 instead, which is outside the documented range and can lead to
undefined behavior on the PA stage.
Fix the value to 0x06 in both the loramac-node and native SX126x
STM32WL HAL implementations.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
The configuration passed to lora_config() is not supposed to be modified by
the driver. Make it const to save on RAM.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Apply three documented SX1261/62 silicon errata from DS_SX1261-2_V1.2:
- IQ Polarity (chapter 15.4): after every SetPacketParams command,
manually fix bit 2 of REG_IQ_POLARITY to match the requested IQ
inversion setting. Without this, inverted-IQ packets (used by
LoRaWAN downlinks) may be silently corrupted.
- TX Modulation Quality at 500 kHz (chapter 15.1): clear bit 2 of
REG_TX_MODULATION when using 500 kHz bandwidth, set it otherwise.
Without this, 500 kHz transmissions have degraded modulation quality.
- Register Retention: add REG_RX_GAIN and REG_TX_MODULATION to the
chip's internal retention list so their values survive warm-start
sleep cycles, matching the upstream Semtech reference driver.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
The SX126x requires a minimum preamble length of 12 symbols when using
spreading factors 5 or 6 (DS.SX1261-2.W.APP, Rev 2.2, section 6.1.1).
Shorter preambles at these spreading factors can cause failed detection
on the receiver side.
Clamp the stored preamble length after configuration so that all TX, RX,
and airtime paths use the corrected value.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
test_cw() only checked that the radio was idle before starting a
continuous-wave transmission. It never moved the driver out of the rest
state, so another TX or RX operation could start while the CW test was
still active.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
The native SX126x driver accepted unsupported LoRa bandwidth enums and
silently mapped them to 125 kHz. That could leave the modem running with
a different configuration than the caller requested, and it also skewed
airtime calculations.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
During RX duty cycle mode the SX126x keeps BUSY asserted even in
the sleep phase. The standby command cannot be sent because the HAL
waits for BUSY LOW first.
Fix by calling sx126x_hal_wakeup() before set_standby when
cancelling duty cycle reception. The wakeup function drives NSS low
directly (bypassing the BUSY check), which forces the radio out of
duty cycle and into STDBY_RC.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Implement the recv_duty_cycle API using the SX126x hardware
SetRxDutyCycle command. The radio autonomously alternates between
short RX windows and sleep, waking the MCU only on packet reception.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Utilize a code spell-checking tool to scan for and correct spelling errors
in all files within:
- `drivers/interrupt_controller`
- `drivers/led_strip`
- `drivers/lora`
- `drivers/memc`
- `drivers/mfd`
Signed-off-by: Pisit Sawangvonganan <pisit@ndrsolution.com>
Register a pm_action callback that disconnects RF GPIO pins
(antenna enable, TX/RX FE_CTRL) on suspend and reconfigures
them on resume.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
When CONFIG_LORA_SX126X_NATIVE_SLEEP=n, sx126x_ensure_ready() returned
immediately without re-enabling the DIO1 interrupt. On the STM32WL the
radio ISR disables itself on entry (irq_disable), so the interrupt must be
explicitly re-armed before each TX/RX operation.
This caused every send after the first to time out because the TX_DONE
interrupt was never delivered.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add CONFIG_LORA_SX126X_NATIVE_SLEEP (default y) to allow disabling
sleep mode power management. When disabled, the radio stays in
standby for faster response times at the cost of higher idle current.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Two fixes for the async RX path:
1. Only invoke the user callback for valid packets.
2. Re-check rx_cb != NULL before restarting RX. The user's callback may
call lora_recv_async(dev, NULL) to stop reception.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Fix three issues in the spi_transfer() helper:
1. pass NULL instead of the data pointer so the SPI driver sends dummy
bytes during reads rather than the caller's buffer contents (usually
rubbish)
2. unify TX and RX buf_set count. The old logic used different counts for
TX and RX, which caused a length mismatch when data_len > 0 on reads.
3. pass NULL for the rx buf_set on write operations. The chip's MISO output
during command writes is undefined, so a full-duplex transceive is only
needed for reads.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add support for the STM32WL integrated sub-GHz radio to the native
SX126x driver. The STM32WL has an SX126x-compatible radio core but
uses internal peripherals instead of external GPIOs.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Extract common HAL functions shared between different SX126x
implementations into sx126x_hal_common.c, this prepares the driver for
supporting additional platforms like the STM32WL integrated radio.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add a native Zephyr driver for Semtech SX1261/SX1262 LoRa transceivers that
does not depend on external modules (loramac-node or lora-basics-modem).
The driver is experimental and must be explicitly enabled by selecting
CONFIG_LORA_MODULE_BACKEND_NONE.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Commit 6b2f1c19ac changed the lora_signal_bandwidth enum values from
sequential indices (0, 1, 2) to actual kHz values (125, 250, 500).
With the new enum values (125, 250, 500), this causes on loramac-node an
array out-of-bounds access, resulting in undefined behavior and crashes
(USAGE FAULT: undefined instruction).
Fix this by adding a translation function that converts the Zephyr
bandwidth enum values to the array indices expected by loramac-node.
Fixes: 6b2f1c19ac
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
A set of preliminary (but harmless) changes to prepare for adding the HAL
implementation.
- Rename CMake variables for clarity and consistency, adding an 'LBM_LIB_'
prefix when the path is a library / module path.
- Change zephyr_library_include_directories to zephyr_include_directories
for the RAL/RALF includes, since these headers may be needed outside the
library itself.
- Make lbm_common.h available to code outside the driver directory.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Rename the lora_basics_modem backend directory to lora-basics-modem
for consistency with the module naming convention.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Rename drivers/lora/loramac_node to drivers/lora/loramac-node to align
with the module naming convention (modules/loramac-node).
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Split device initialization into two phases across all LBM drivers:
1. Boot-time init: Minimal device initialization
2. First config: Radio hardware initialization
This deferred initialization approach provides full control over the
radio hardware, allowing applications to perform any necessary setup
before the radio is initialized. The radio init is automatically
triggered on the first call to lora_config().
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add functions to register and unregister user GPIO callbacks for DIO1
interrupts on LBM lora radio devices. This allows external code (e.g., the
LoRa Basics Modem HAL) to receive notifications when DIO1 fires.
The driver configures the pin mask automatically based on the DIO1 pin
from devicetree.
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Add a simple function that exposes the airtime of a packet of a given
length.
Use the new internal implementations of the airtime function when
calculating TX durations inside the send functions to reduce code
duplication on a complex function.
Signed-off-by: Jordan Yates <jordan@embeint.com>