diff --git a/Documentation/components/drivers/special/wireless.rst b/Documentation/components/drivers/special/wireless.rst index 9997ed887ff28..bdf0366c9857d 100644 --- a/Documentation/components/drivers/special/wireless.rst +++ b/Documentation/components/drivers/special/wireless.rst @@ -1,3 +1,11 @@ ================ Wireless Drivers ================ + +.. toctree:: + :glob: + :maxdepth: 1 + :titlesonly: + :caption: Contents + + ./wireless/* diff --git a/Documentation/components/drivers/special/wireless/rn2xx3.rst b/Documentation/components/drivers/special/wireless/rn2xx3.rst new file mode 100644 index 0000000000000..1844a1e040de0 --- /dev/null +++ b/Documentation/components/drivers/special/wireless/rn2xx3.rst @@ -0,0 +1,355 @@ +====== +RN2XX3 +====== + +This driver provides support for the RN2XX3 family of LoRa radio transceivers by +Microchip. This includes both the RN2903 and RN2483 modules. + +.. warning:: + This driver only contains preliminary support for a few 'radio set' commands + and raw radio transmit/receive. There is no support for the LoRaWAN stack + yet. + +Application Programming Interface +================================= + +To register the device for use, you will need to enable the standard upper half +serial drivers (``CONFIG_STANDARD_SERIAL``), since the RN2XX3 driver requires +the path to the UART interface the module is connected to. + +You will also need to ensure that the baud rate of the UART interface is set to +57600, which is the baud rate of the RN2XX3. + +At registration time, the driver will automatically determine if the device is +the RN2903 or RN2483. + +.. code-block:: c + + #include + + #ifdef CONFIG_LPWAN_RN2XX3 + + /* Register the RN2XX3 device driver */ + + ret = rn2xx3_register("/dev/rn2903", "/dev/ttyS1"); + if (ret < 0) { + syslog(LOG_ERR, "Failed to register RN2XX3 device driver: %d\n", ret); + } + #endif + +This driver uses the standard POSIX character device interface, implementing +``read()``, ``write()`` and ``ioctl()``. + +To transmit, the ``write()`` function can be used. Bytes in the provided buffer +will be transmitted as a packet. This has the following behaviour: + +* If the radio is in FSK modulation mode, packets will only contain up to 64 + bytes. A buffer of more than 64 bytes will only have 64 bytes transmitted. +* If the radio is in LoRa modulation mode, packets will only contain up to 255 + bytes. +* If the buffer contains less than the current packet size limit (64 or 255 + bytes), its contents will be transmitted as a single packet. + +.. code-block:: c + + int radio = open("/dev/rn2903", O_RDWR); + if (radio < 0) + { + fprintf(stderr, "Couldn't open radio: %d\n", errno); + return -1; + } + + char message[] = "Hello, world!"; + ssize_t b_sent = write(radio, message, sizeof(message)); + if (b_sent < 0) + { + fprintf(stderr, "Couldn't transmit: %d\n", errno); + return -1; + } + +To receive, the ``read()`` function can be used. As much of the received packet +as possible will be stored in the user buffer. This has the following behaviour: + +* If the buffer is too small to contain the full received packet, as much of the + packet as possible will be stored in the buffer. +* When the packet is fully read, ``read()`` will return ``0``. +* If only part of the packet has been read and a call to ``write()`` or + ``ioctl()`` is made, the remainder of the packet is discarded. + +.. code-block:: c + + int radio = open("/dev/rn2903", O_RDWR); + if (radio < 0) + { + fprintf(stderr, "Couldn't open radio: %d\n", errno); + return -1; + } + + char buffer[16]; + ssize_t b_read; + + do { + b_read = read(radio, buffer, sizeof(buffer)); + if (b_read < 0) + { + fprintf(stderr, "Couldn't receive: %d\n", errno); + return -1; + } + write(0, buffer, b_read); /* Print received bytes to stdout */ + } while (b_read != 0); + +Finally, the ``ioctl()`` interface provides access to some underlying module +commands. + +``WLIOC_GETSNR`` +---------------- + +Gets the signal to noise ration of the last received packet. If no packets have +been received, it will default to -128. Argument is a pointer to an ``int8_t``. + +.. code-block:: c + + int8_t snr; + err = ioctl(radio, WLIOC_GETSNR, &snr); + +``WLIOC_SETRADIOFREQ`` +---------------------- + +Sets the operating frequency of the radio module. The argument is the desired +frequency in Hz (``uint32_t``). + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETRADIOFREQ, 902400000); + +``WLIOC_GETRADIOFREQ`` +---------------------- + +Gets the current operating frequency of the radio module in Hz. The argument is +a pointer to a ``uint32_t``. + +.. code-block:: c + + uint32_t freq; + err = ioctl(radio, WLIOC_GETRADIOFREQ, &freq); + +``WLIOC_SETTXPOWERF`` +--------------------- + +Sets the transmission power of the radio. Argument is a pointer to a ``float`` +containing the desired transmission power in dBm. After setting the transmission +power successfully, this pointer will contain the new transmission power. This +value may be different from the desired value, but will be the closest available +setting that is greater than or equal to the desired value. + +.. code-block:: c + + float txpower = 12.0; + err = ioctl(radio, WLIOC_SETTXPOWERF, &txpower); + printf("Actual TX power: %.2f dBm\n", txpower); + +``WLIOC_GETTXPOWERF`` +-------------------- + +Gets the current transmission power level in dBm. The argument is a pointer to +a ``float``. + +.. code-block:: c + + float txpwr; + err = ioctl(radio, WLIOC_GETTXPOWER, &txpwr); + +``WLIOC_SETBANDWIDTH`` +---------------------- + +Sets the operating bandwidth of the radio module. The argument is the desired +bandwidth in kHz (``uint32_t``). The radio only supports exact values of 125, +250 and 500. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETBANDWIDTH, 250); + +``WLIOC_GETBANDWIDTH`` +---------------------- + +Gets the current operating bandwidth of the radio module in kHz. The argument is +a pointer to a ``uint32_t``. + +.. code-block:: c + + uint32_t bandwidth; + err = ioctl(radio, WLIOC_GETBANDWIDTH, &bandwidth); + +``WLIOC_SETSPREAD`` +---------------------- + +Sets the operating spread factor of the radio module. The argument is a +``uint8_t`` containing the desired spread factor between 7 and 12 (inclusive). + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETSPREAD, 8); + +``WLIOC_GETSPREAD`` +---------------------- + +Gets the current operating spread factor of the radio module. The argument is a +pointer to a ``uint8_t``. + +.. code-block:: c + + uint8_t spread; + err = ioctl(radio, WLIOC_GETSPREAD, &spread); + +``WLIOC_SETPRLEN`` +---------------------- + +Sets the operating preamble length of the radio module. The argument is a +``uint16_t`` containing the desired preamble length. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETPRLEN, 8); + +``WLIOC_GETPRLEN`` +---------------------- + +Gets the current operating preamble length of the radio module. The argument is +a pointer to a ``uint16_t``. + +.. code-block:: c + + uint16_t prlen; + err = ioctl(radio, WLIOC_GETPRLEN, &prlen); + +``WLIOC_SETMOD`` +---------------------- + +Sets the operating modulation of the radio module. The argument is one of the +values in ``enum rn2xx3_mod_e``. + +.. code-block:: c + + err = ioctl(radio, WLIOC_SETMOD, RN2XX3_MOD_FSK); + +``WLIOC_GETMOD`` +---------------------- + +Gets the current operating modulation of the radio module. The argument is a +pointer to an ``enum rn2xx3_mod_e``. + +.. code-block:: c + + enum rn2xx3_mod_e modulation; + err = ioctl(radio, WLIOC_GETMOD, &modulation); + if (modulation == RN2XX3_MOD_LORA) + { + printf("LoRa modulation!\n"); + } + +``WLIOC_RESET`` +--------------- + +Resets the RN2xx3 radio module. This command takes no arguments. + +.. code-block:: c + + err = ioctl(radio, WLIOC_RESET, 0); + +``WLIOC_SETSYNC`` +----------------- + +Sets the sync word parameter of the RN2xx3 module. The argument is a pointer to +a ``uint64_t``. Please note that when operating using FSK modulation, the sync +word can be a full 8 bytes (64 bits), but LoRa modulation only accepts a single +byte sync word. + +.. code-block:: c + + /* Radio in FSK mode prior to this call */ + + uint64_t syncword = 0xdeadbeefdeadbeef; + err = ioctl(radio, WLIOC_SETSYNC, &syncword); + +``WLIOC_GETSYNC`` +----------------- + +Gets the sync word parameter of the RN2xx3 module. The argument is a pointer to +a ``uint64_t``. + +.. code-block:: c + + uint64_t syncword; + err = ioctl(radio, WLIOC_GETSYNC, &syncword); + +``WLIOC_SETBITRATE`` +-------------------- + +Sets the bit rate of the RN2xx3 module. The argument is a ``uint32_t``. The +bit rate only applies to the module when it is in FSK modulation mode, and it +must be between 1 - 300000. + +.. code-block:: c + + /* Radio in FSK mode prior to this call */ + + err = ioctl(radio, WLIOC_SETBITRATE, 300000); + +``WLIOC_GETBITRATE`` +-------------------- + +Gets the configured bit rate of the RN2xx3 module. The argument is a pointer to +a ``uint32_t``. + +.. code-block:: c + + uint32_t bitrate; + err = ioctl(radio, WLIOC_GETBITRATE, &bitrate); + +``WLIOC_IQIEN`` +--------------- + +Enables the invert IQ functionality of the module. The argument is boolean of +either true (non-zero) or false (zero). + +.. code-block:: c + + /* Enables IQI */ + + err = ioctl(radio, WLIOC_IQIEN, 1); + +``WLIOC_CRCEN`` +--------------- + +Enables adding a CRC header to packets. The argument is a boolean of either true +(non-zero) or false (zero). + +.. code-block:: c + + /* Enables CRC */ + + err = ioctl(radio, WLIOC_CRCEN, 1); + +``WLIOC_SETCODERATE`` +--------------------- + +Sets the coding rate of the RN2xx3 module. The argument is one of the values in +``enum rn2xx3_cr_e``. + +.. code-block:: c + + /* Sets 4/7 coding rate */ + + err = ioctl(radio, WLIOC_SETCODERATE, RN2XX3_CR_4_7); + +``WLIOC_GETCODERATE`` +--------------------- + +Gets the currently configured coding rate of the RN2xx3 module. The argument is +a pointer to an ``enum rn2xx3_cr_e``. + +.. code-block:: c + + enum rn2xx3_cr_e coderate; + err = ioctl(radio, WLIOC_GETCODERATE, &coderate); diff --git a/drivers/wireless/lpwan/Kconfig b/drivers/wireless/lpwan/Kconfig index eb8dae6258d9a..8c2c56c50f9e7 100644 --- a/drivers/wireless/lpwan/Kconfig +++ b/drivers/wireless/lpwan/Kconfig @@ -5,6 +5,17 @@ if DRIVERS_LPWAN +config LPWAN_RN2XX3 + bool "Microchip RN2xx3 driver support" + default n + select UART # TODO what to select? + ---help--- + Enable driver support for the RN2xx3 LoRa radio transceiver. + +if LPWAN_RN2XX3 + +endif # LPWAN_RN2XX3 + config LPWAN_SX127X bool "SX127X Low Power Long Range transceiver support" default n diff --git a/drivers/wireless/lpwan/Make.defs b/drivers/wireless/lpwan/Make.defs index 023bff806f2c5..8a4f1aabe2904 100644 --- a/drivers/wireless/lpwan/Make.defs +++ b/drivers/wireless/lpwan/Make.defs @@ -25,5 +25,6 @@ ifeq ($(CONFIG_DRIVERS_LPWAN),y) include wireless/lpwan/sx127x/Make.defs +include wireless/lpwan/rn2xx3/Make.defs endif # CONFIG_DRIVERS_LPWAN diff --git a/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt b/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt new file mode 100644 index 0000000000000..008dd7069fdfe --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# drivers/wireless/lpwan/rn2xx3/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_LPWAN_RN2903) + target_sources(drivers PRIVATE rn2xx3.c) +endif() diff --git a/drivers/wireless/lpwan/rn2xx3/Make.defs b/drivers/wireless/lpwan/rn2xx3/Make.defs new file mode 100644 index 0000000000000..d1adf6b5af73f --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/Make.defs @@ -0,0 +1,31 @@ +############################################################################ +# drivers/wireless/lpwan/rn2xx3/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_LPWAN_RN2XX3),y) + +CSRCS += rn2xx3.c + +DEPPATH += --dep-path wireless$(DELIM)lpwan$(DELIM)rn2xx3 +VPATH += :wireless$(DELIM)lpwan$(DELIM)rn2xx3 +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)wireless$(DELIM)lpwan$(DELIM)rn2xx3 + +endif # CONFIG_LPWAN_RN2903 diff --git a/drivers/wireless/lpwan/rn2xx3/rn2xx3.c b/drivers/wireless/lpwan/rn2xx3/rn2xx3.c new file mode 100644 index 0000000000000..91795b1ae77ec --- /dev/null +++ b/drivers/wireless/lpwan/rn2xx3/rn2xx3.c @@ -0,0 +1,1938 @@ +/**************************************************************************** + * drivers/wireless/lpwan/rn2xx3/rn2xx3.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_LIBC_LONG_LONG +#error "CONFIG_LIBC_LONG_LONG must be enabled for this driver" +#endif + +/* Duration of maximum MAC layer pause in milliseconds */ + +#define MAC_PAUSE_DUR "4294967245" + +/* Helper to get array length */ + +#define array_len(arr) ((sizeof(arr)) / sizeof((arr)[0])) + +/**************************************************************************** + * Private Data Types + ****************************************************************************/ + +/* Radio transceiver modules */ + +enum rn2xx3_type_e +{ + RN2903 = 0, /* RN2903 transceiver */ + RN2483 = 1, /* RN2483 transceiver */ +}; + +/* Transmit power level and associated dBm power */ + +struct txlvl_s +{ + int8_t lvl; /* Transmit power level */ + float power; /* Transmit power in dBm */ +}; + +/* Radio device struct */ + +struct rn2xx3_dev_s +{ + FAR struct file uart; /* UART interface */ + bool receiving; /* Currently receiving */ + mutex_t devlock; /* Exclusive access */ + enum rn2xx3_type_e model; /* Transceiver model */ + enum rn2xx3_mod_e mod; /* Modulation mode */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + int16_t crefs; /* Number of open references */ +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int rn2xx3_open(FAR struct file *filep); +static int rn2xx3_close(FAR struct file *filep); +#endif +static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_rn2xx3fops = +{ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + .open = rn2xx3_open, + .close = rn2xx3_close, +#else + .open = NULL, + .close = NULL, +#endif + .read = rn2xx3_read, + .write = rn2xx3_write, + .ioctl = rn2xx3_ioctl, +}; + +/* Modulation types */ + +static const char *MODULATIONS[] = +{ + "lora", /* RN2XX3_MOD_LORA */ + "fsk", /* RN2XX3_MOD_FSK */ +}; + +/* Coding rates */ + +static const char *CODING_RATES[] = +{ + "4/5", /* RN2XX3_CR_4_5 */ + "4/6", /* RN2XX3_CR_4_6 */ + "4/7", /* RN2XX3_CR_4_7 */ + "4/8", /* RN2XX3_CR_4_8 */ +}; + +/* Max packet sizes for each modulation type. */ + +static const uint8_t MAX_PKTSIZE[] = +{ + 255, /* RN2XX3_MOD_LORA */ + 64, /* RN2XX3_MOD_FSK */ +}; + +/* Transmit power levels and their output powers in dBm for the RN2903 + * module, ordered from least to greatest + */ + +static const struct txlvl_s RN2903_TXLVLS[] = +{ + {.lvl = 2, .power = 3.0f}, + {.lvl = 3, .power = 4.0f}, + {.lvl = 4, .power = 5.0f}, + {.lvl = 5, .power = 6.0f}, + {.lvl = 6, .power = 7.0f}, + {.lvl = 7, .power = 8.0f}, + {.lvl = 8, .power = 9.0f}, + {.lvl = 9, .power = 10.0f}, + {.lvl = 10, .power = 11.0f}, + {.lvl = 11, .power = 12.0f}, + {.lvl = 12, .power = 13.0f}, + {.lvl = 14, .power = 14.7f}, + {.lvl = 15, .power = 15.5f}, + {.lvl = 16, .power = 16.3f}, + {.lvl = 17, .power = 17.0f}, + {.lvl = 20, .power = 18.5f}, +}; + +/* Transmit power levels and their output powers in dBm for the RN2483 + * module, ordered from least to greatest + */ + +static const struct txlvl_s RN2483_TXLVLS[] = +{ + {.lvl = -3, .power = -4.0f}, + {.lvl = -2, .power = -2.9f}, + {.lvl = -1, .power = -1.9f}, + {.lvl = 0, .power = -1.7f}, + {.lvl = 1, .power = -0.6f}, + {.lvl = 2, .power = 0.4f}, + {.lvl = 3, .power = 1.4f}, + {.lvl = 4, .power = 2.5f}, + {.lvl = 5, .power = 3.6f}, + {.lvl = 6, .power = 4.7f}, + {.lvl = 7, .power = 5.8f}, + {.lvl = 8, .power = 6.9f}, + {.lvl = 9, .power = 8.1f}, + {.lvl = 10, .power = 9.3f}, + {.lvl = 11, .power = 10.4f}, + {.lvl = 12, .power = 11.6f}, + {.lvl = 13, .power = 12.5f}, + {.lvl = 14, .power = 13.5f}, + {.lvl = 15, .power = 14.1f}, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: radio_flush + * + * Description: + * Flushes all characters in the receive buffer from the RN2xx3 radio. + * Returns the number of bytes read. + * + ****************************************************************************/ + +static int radio_flush(FAR struct rn2xx3_dev_s *priv) +{ + int err; + int to_read; + char buf[10]; + + err = file_ioctl(&priv->uart, FIONREAD, &to_read); + if (err < 0) + { + return err; + } + + while (to_read > 0) + { + err = file_read(&priv->uart, buf, sizeof(buf)); + if (err < 0) + { + return err; + } + + to_read -= err; + } + + return 0; +} + +/**************************************************************************** + * Name: read_line + * + * Description: + * Reads a whole line of response from the RN2xx3 radio. Returns the number + * of bytes read, excluding the terminating \r\n and null terminator. + * + ****************************************************************************/ + +static ssize_t read_line(FAR struct rn2xx3_dev_s *priv, FAR char *buf, + size_t nbytes) +{ + ssize_t length = 0; + ssize_t i = 0; + bool line_complete = false; + + for (; i <= nbytes; ) + { + length = file_read(&priv->uart, &buf[i], nbytes - i); + + if (length < 0) + { + return length; + } + + i += length; + + /* Check if the character we just read was '\n'. This only occurs when + * the transceiver's response is complete. Length check ensures that + * we're not checking uninitialized memory in the case that `length` is + * 0. + */ + + if (length > 0 && buf[i - 1] == '\n') + { + line_complete = true; + break; + } + } + + /* Insufficient buffer space to handle response */ + + if (!line_complete) + { + return -ENOBUFS; + } + + /* Overwrite preceding \r with null terminator */ + + buf[i - 2] = '\0'; + return i - 2; /* Number of bytes read excluding \r\n */ +} + +/**************************************************************************** + * Name: mac_pause + * + * Description: + * Pauses the MAC layer. Required before every transmission/receive. + * + ****************************************************************************/ + +static int mac_pause(FAR struct rn2xx3_dev_s *priv) +{ + ssize_t length = 0; + char response[30]; + + /* Issue pause command */ + + length = + file_write(&priv->uart, "mac pause\r\n", sizeof("mac pause\r\n") - 1); + if (length < 0) + { + return length; + } + + /* Wait for response of watchdog timer timeout */ + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + return length; + } + + /* Check for pause duration */ + + if (strstr(response, MAC_PAUSE_DUR) == NULL) + { + return -EIO; + } + + return 0; +}; + +/**************************************************************************** + * Name: get_command_err + * + * Description: + * Parses the command error response from the radio module and converts it + * into an error code. 0 for 'ok', -EINVAL for 'invalid_param' and -EBUSY + * for 'busy'. If an unknown error occurs, -EIO is returned. + * + ****************************************************************************/ + +static int get_command_err(FAR struct rn2xx3_dev_s *priv) +{ + char response[18]; + ssize_t length; + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + return length; + } + + if (strstr(response, "ok")) + { + /* Do nothing, this is good */ + + return 0; + } + else if (strstr(response, "invalid_param")) + { + wlerr("RN2xx3 invalid_param\n"); + return -EINVAL; + } + else if (strstr(response, "busy")) + { + wlerr("RN2xx3 busy"); + return -EBUSY; + } + + wlerr("Unknown error"); + return -EIO; +} + +/**************************************************************************** + * Name: rn2xx3_txpwrlevel + * + * Description: + * Get the transmission power level that is greater than or equal to the + * requested transmission power in dBm. + * The true dBm level that was selected is returned in the `dbm` pointer. + * + ****************************************************************************/ + +static int8_t rn2xx3_txpwrlevel(FAR float *dbm, + FAR const struct txlvl_s *arr, uint8_t len) +{ + /* Look until a power level is found that is equal to or exceeds the + * requested one. Starting from lowest power + */ + + for (uint8_t i = 0; i < len; i++) + { + if (arr[i].power >= *dbm) + { + *dbm = arr[i].power; + return arr[i].lvl; + } + } + + /* Requested power was higher than all options, return highest possible + * power + */ + + *dbm = arr[len - 1].power; + return arr[len - 1].lvl; +} + +/**************************************************************************** + * Name: rn2xx3_txpwrdbm + * + * Description: + * Get the transmission power in dBm that is equal to the passed tx power + * level. + * + * Return: `nan` if level doesn't exist. + * + ****************************************************************************/ + +static float rn2xx3_txpwrdbm(int8_t lvl, FAR const struct txlvl_s *arr, + uint8_t len) +{ + /* Look until a power level is found that is equal to this one. */ + + for (uint8_t i = 0; i < len; i++) + { + if (arr[i].lvl == lvl) + { + return arr[i].power; + } + } + + /* Requested level does not exist */ + + return NAN; +} + +/**************************************************************************** + * Name: rn2xx3_get_param + * + * Description: + * Send a command and read the response from the RN2XX3. + * + * Arguments: + * priv - Pointer to radio device struct + * cmd - Null terminated command string, including \r\n + * resp - Buffer to store radio response + * nbytes - Size of 'resp' buffer + * + * Returns: Number of bytes read in response. + * + ****************************************************************************/ + +static ssize_t rn2xx3_getparam(FAR struct rn2xx3_dev_s *priv, FAR char *cmd, + FAR char *resp, size_t nbytes) +{ + ssize_t length; + + length = file_write(&priv->uart, cmd, strlen(cmd)); + if (length < 0) + { + return length; + } + + length = read_line(priv, resp, nbytes); + return length; +} + +/**************************************************************************** + * Name: rn2xx3_reset + * + * Description: + * Reset the radio module. + * + ****************************************************************************/ + +static int rn2xx3_reset(FAR struct rn2xx3_dev_s *priv) +{ + ssize_t ret; + char response[50]; + int rxavail = 0; + + ret = file_write(&priv->uart, "sys reset\r\n", + sizeof("sys reset\r\n") - 1); + if (ret < 0) + { + return ret; + } + + /* Might take some time to reset, wait for something to appear in the + * receive buffer + */ + + while (rxavail <= 10) + { + ret = file_ioctl(&priv->uart, FIONREAD, &rxavail); + if (ret < 0) + { + return ret; + } + } + + /* Clear out the receive buffer, we've now reset */ + + ret = read_line(priv, response, sizeof(response)); + if (ret < 0) + { + return ret; + } + + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_getmodel + * + * Description: + * Get the transceiver model type. + * + ****************************************************************************/ + +static int rn2xx3_getmodel(FAR struct rn2xx3_dev_s *priv, + FAR enum rn2xx3_type_e *model) +{ + ssize_t length; + char response[40]; + + length = radio_flush(priv); + if (length < 0) + { + return length; + } + + length = + rn2xx3_getparam(priv, "sys get ver\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + if (strstr(response, "RN2903") != NULL) + { + *model = RN2903; + } + else if (strstr(response, "RN2483") != NULL) + { + *model = RN2483; + } + else + { + /* Something went wrong, model number should have been returned */ + + return -EIO; + } + + /* Flush any leftover radio data */ + + length = radio_flush(priv); + if (length < 0) + { + return length; + } + + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_getsnr + * + * Description: + * Get the signal to noise ratio of the last received packet. + * + ****************************************************************************/ + +static int rn2xx3_getsnr(FAR struct rn2xx3_dev_s *priv, FAR int8_t *snr) +{ + ssize_t length; + char response[10]; + + length = + rn2xx3_getparam(priv, "radio get snr\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *snr = strtol(response, NULL, 10); + return -errno; /* Set by strtol on failure */ +} + +/**************************************************************************** + * Name: rn2xx3_crc_en + * + * Description: + * Enabled/disable CRC for the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_crc_en(FAR struct rn2xx3_dev_s *priv, bool enable) +{ + char enstr[16]; + ssize_t length; + + length = file_write(&priv->uart, "radio set crc ", + sizeof("radio set crc ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(enstr, sizeof(enstr), "%s\r\n", enable ? "on" : "off"); + length = file_write(&priv->uart, enstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_iqi_en + * + * Description: + * Enabled/disable invert IQ for the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_iqi_en(FAR struct rn2xx3_dev_s *priv, bool enable) +{ + char enstr[16]; + ssize_t length; + + length = file_write(&priv->uart, "radio set iqi ", + sizeof("radio set iqi ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(enstr, sizeof(enstr), "%s\r\n", enable ? "on" : "off"); + length = file_write(&priv->uart, enstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_setfreq + * + * Description: + * Set the operating frequency of the RN2xx3 in Hz. + * + ****************************************************************************/ + +static int rn2xx3_setfreq(FAR struct rn2xx3_dev_s *priv, uint32_t freq) +{ + char freqstr[18]; + ssize_t length; + + length = file_write(&priv->uart, "radio set freq ", + sizeof("radio set freq ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(freqstr, sizeof(freqstr), "%lu\r\n", freq); + length = file_write(&priv->uart, freqstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getfreq + * + * Description: + * Get the operating frequency of the RN2xx3 in Hz. + * + ****************************************************************************/ + +static int rn2xx3_getfreq(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *freq) +{ + ssize_t length; + char response[20]; + + length = rn2xx3_getparam(priv, "radio get freq\r\n", response, + sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *freq = strtoul(response, NULL, 10); + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_settxpwr + * + * Description: + * Set the transmit power of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_settxpwr(FAR struct rn2xx3_dev_s *priv, FAR float *pwr) +{ + char powerstr[16]; + ssize_t length; + int8_t txpwr; + + if (priv->model == RN2903) + { + txpwr = rn2xx3_txpwrlevel(pwr, RN2903_TXLVLS, + array_len(RN2903_TXLVLS)); + } + else + { + txpwr = rn2xx3_txpwrlevel(pwr, RN2483_TXLVLS, + array_len(RN2483_TXLVLS)); + } + + length = file_write(&priv->uart, "radio set pwr ", + sizeof("radio set pwr ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(powerstr, sizeof(powerstr), "%d\r\n", txpwr); + length = file_write(&priv->uart, powerstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_gettxpwr + * + * Description: + * Get the transmission power of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_gettxpwr(FAR struct rn2xx3_dev_s *priv, FAR float *txpwr) +{ + ssize_t length; + char response[20]; + int8_t txlevel; + + length = + rn2xx3_getparam(priv, "radio get pwr\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + txlevel = strtol(response, NULL, 10); + if (errno) + { + return -errno; + } + + /* Convert transmission power level to dBm */ + + if (priv->model == RN2903) + { + *txpwr = rn2xx3_txpwrdbm(txlevel, RN2903_TXLVLS, + array_len(RN2903_TXLVLS)); + } + else + { + *txpwr = rn2xx3_txpwrdbm(txlevel, RN2483_TXLVLS, + array_len(RN2483_TXLVLS)); + } + + if (*txpwr == NAN) + { + /* Some transmission power that was not expected was returned */ + + return -EIO; + } + + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setbw + * + * Description: + * Set the bandwidth of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setbw(FAR struct rn2xx3_dev_s *priv, uint32_t bw) +{ + char bwstr[16]; + ssize_t length; + length = + file_write(&priv->uart, "radio set bw ", sizeof("radio set bw ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(bwstr, sizeof(bwstr), "%lu\r\n", bw); + length = file_write(&priv->uart, bwstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getbw + * + * Description: + * Get the bandwidth of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getbw(FAR struct rn2xx3_dev_s *priv, FAR uint32_t *bw) +{ + int length; + char response[10]; + + length = + rn2xx3_getparam(priv, "radio get bw\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *bw = strtoul(response, NULL, 10); + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_setsf + * + * Description: + * Set the spread factor of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setsf(FAR struct rn2xx3_dev_s *priv, uint8_t sf) +{ + char sfstr[15]; + ssize_t length; + + length = + file_write(&priv->uart, "radio set sf ", sizeof("radio set sf ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(sfstr, sizeof(sfstr), "sf%u\r\n", sf); + length = file_write(&priv->uart, sfstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getsf + * + * Description: + * Get the spread factor of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getsf(FAR struct rn2xx3_dev_s *priv, FAR uint8_t *sf) +{ + ssize_t length; + char response[20]; + + length = + rn2xx3_getparam(priv, "radio get sf\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + /* String should contain at least `sf` followed by one digit */ + + if (length < 3) + { + return -EIO; + } + + errno = 0; + *sf = strtoul(&response[2], NULL, 10); /* Skip 'sf' */ + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_setprlen + * + * Description: + * Set the preamble length of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setprlen(FAR struct rn2xx3_dev_s *priv, uint16_t prlen) +{ + char prstr[16]; + ssize_t length; + + length = file_write(&priv->uart, "radio set prlen ", + sizeof("radio set prlen ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(prstr, sizeof(prstr), "%u\r\n", prlen); + length = file_write(&priv->uart, prstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getprlen + * + * Description: + * Get the preamble length of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getprlen(FAR struct rn2xx3_dev_s *priv, + FAR uint16_t *prlen) +{ + ssize_t length; + char response[20]; + + length = rn2xx3_getparam(priv, "radio get prlen\r\n", response, + sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *prlen = strtoul(response, NULL, 10); + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_setmod + * + * Description: + * Set the modulation of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setmod(FAR struct rn2xx3_dev_s *priv, + enum rn2xx3_mod_e mod) +{ + int err; + char modstr[16]; + ssize_t length; + + DEBUGASSERT(mod == RN2XX3_MOD_LORA || mod == RN2XX3_MOD_FSK); + + length = file_write(&priv->uart, "radio set mod ", + sizeof("radio set mod ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(modstr, sizeof(modstr), "%s\r\n", MODULATIONS[mod]); + length = file_write(&priv->uart, modstr, length); + if (length < 0) + { + return length; + } + + err = get_command_err(priv); + if (err < 0) + { + return err; + } + + priv->mod = mod; + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_getmod + * + * Description: + * Get the modulation type of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getmod(FAR struct rn2xx3_dev_s *priv, + FAR enum rn2xx3_mod_e *mod) +{ + ssize_t length; + char response[12]; + + length = + rn2xx3_getparam(priv, "radio get mod\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + /* Only 'lora' and 'fsk' are valid return values from the radio */ + + if (strstr(response, "lora")) + { + *mod = RN2XX3_MOD_LORA; + } + else if (strstr(response, "fsk")) + { + *mod = RN2XX3_MOD_FSK; + } + else + { + return -EIO; + } + + return 0; +} + +/**************************************************************************** + * Name: rn2xx3_setcr + * + * Description: + * Set the coding rate of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setcr(FAR struct rn2xx3_dev_s *priv, enum rn2xx3_cr_e cr) +{ + char crstr[8]; + ssize_t length; + + length = file_write(&priv->uart, "radio set cr ", + sizeof("radio set cr ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(crstr, sizeof(crstr), "%s\r\n", CODING_RATES[cr]); + length = file_write(&priv->uart, crstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getcr + * + * Description: + * Get the coding rate of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getcr(FAR struct rn2xx3_dev_s *priv, + FAR enum rn2xx3_cr_e *cr) +{ + ssize_t length; + char response[10]; + + length = + rn2xx3_getparam(priv, "radio get cr\r\n", response, sizeof(response)); + if (length < 0) + { + return length; + } + + /* Check if the response contains any of the valid options */ + + for (int i = 0; i < array_len(CODING_RATES); i++) + { + if (strstr(response, CODING_RATES[i])) + { + *cr = i; + return 0; + } + } + + /* No valid response found */ + + return -EIO; +} + +/**************************************************************************** + * Name: rn2xx3_setsync + * + * Description: + * Set the sync word of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setsync(FAR struct rn2xx3_dev_s *priv, uint64_t sync) +{ + char syncstr[20]; + ssize_t length; + + length = file_write(&priv->uart, "radio set sync ", + sizeof("radio set sync ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(syncstr, sizeof(syncstr), "%016llX\r\n", sync); + length = file_write(&priv->uart, syncstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getsync + * + * Description: + * Get the sync word of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getsync(FAR struct rn2xx3_dev_s *priv, + FAR uint64_t *sync) +{ + ssize_t length; + char response[20]; + + length = rn2xx3_getparam(priv, "radio get sync\r\n", response, + sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *sync = strtoull(response, NULL, 16); + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_setbitrate + * + * Description: + * Set the bit rate of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_setbitrate(FAR struct rn2xx3_dev_s *priv, uint32_t bitrate) +{ + char bitstr[20]; + ssize_t length; + + length = file_write(&priv->uart, "radio set bitrate ", + sizeof("radio set bitrate ") - 1); + if (length < 0) + { + return length; + } + + /* Write actual parameter and end command */ + + length = snprintf(bitstr, sizeof(bitstr), "%lu\r\n", bitrate); + length = file_write(&priv->uart, bitstr, length); + if (length < 0) + { + return length; + } + + return get_command_err(priv); +} + +/**************************************************************************** + * Name: rn2xx3_getbitrate + * + * Description: + * Get the bit rate of the RN2xx3. + * + ****************************************************************************/ + +static int rn2xx3_getbitrate(FAR struct rn2xx3_dev_s *priv, + FAR uint32_t *bitrate) +{ + ssize_t length; + char response[20]; + + length = rn2xx3_getparam(priv, "radio get bitrate\r\n", response, + sizeof(response)); + if (length < 0) + { + return length; + } + + errno = 0; + *bitrate = strtoull(response, NULL, 16); + return -errno; +} + +/**************************************************************************** + * Name: rn2xx3_set_mode_recv + * + * Description: + * Put the RN2xx3 into indefinite receive mode. + * + ****************************************************************************/ + +static int rn2xx3_set_mode_recv(FAR struct rn2xx3_dev_s *priv) +{ + ssize_t ret; + static const char receive_cmd[] = "radio rx 0\r\n"; + + /* Pause the MAC layer */ + + ret = mac_pause(priv); + if (ret < 0) + { + return ret; + } + + /* Put the radio into indefinite receive mode */ + + ret = file_write(&priv->uart, receive_cmd, sizeof(receive_cmd) - 1); + if (ret < 0) + { + return ret; + } + + /* Check for okay receipt of command */ + + ret = get_command_err(priv); + if (ret == 0) + { + priv->receiving = true; + } + + return ret; +} + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + +/**************************************************************************** + * Name: rn2xx3_open + ****************************************************************************/ + +static int rn2xx3_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + int err; + + err = nxmutex_lock(&priv->devlock); + if (err < 0) + { + return err; + } + + /* Only one user at a time */ + + if (priv->crefs > 0) + { + err = -EBUSY; + wlerr("Too many fds"); + goto early_ret; + } + + priv->crefs++; + +early_ret: + nxmutex_unlock(&priv->devlock); + return err; +} + +#endif + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + +/**************************************************************************** + * Name: rn2xx3_close + ****************************************************************************/ + +static int rn2xx3_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + int err; + + err = nxmutex_lock(&priv->devlock); + if (err < 0) + { + return err; + } + + priv->crefs--; + nxmutex_unlock(&priv->devlock); + return err; +} + +#endif + +/**************************************************************************** + * Name: rn2xx3_read + * + * Description: + * Puts the RN2xx3 into continuous receive mode and waits for data to be + * received before returning it. + * + ****************************************************************************/ + +static ssize_t rn2xx3_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + ssize_t ret = 0; + ssize_t received = 0; + char response[30]; + char hex_byte[3] = + { + 0, 0 /* Extra space for null terminator */ + }; + + /* Exclusive access */ + + ret = nxmutex_lock(&priv->devlock); + if (ret < 0) + { + return ret; + } + + /* If the file position is non-zero and we're not receiving, return 0 to + * indicate the end of the last packet. + */ + + if (filep->f_pos > 0 && !priv->receiving) + { + filep->f_pos = 0; + return nxmutex_unlock(&priv->devlock); + } + + /* If we're still marked as receiving and the file position in non-zero, we + * can skip initiating receive mode and just continue parsing the packet. + */ + + else if (filep->f_pos > 0 && priv->receiving) + { + goto parse_packet; + } + + /* Otherwise, we're not receiving and haven't been receiving, so we + * initiate a new receive mode. + */ + + /* Flush data */ + + ret = radio_flush(priv); + if (ret < 0) + { + wlerr("Could not flush RN2xx3\n"); + goto early_ret; + } + + ret = rn2xx3_set_mode_recv(priv); + if (ret < 0) + { + wlerr("Could not enter receive mode\n"); + goto early_ret; + } + + /* Begin slowly parsing the response to check if we either received data or + * had an error (timeout) + * 9 is the size of 'radio_rx ' or 'radio_err'. + */ + + for (uint8_t i = 0; i < 10; ) + { + ret = file_read(&priv->uart, &response[i], 10 - i); + if (ret < 0) + { + goto early_ret; + } + + i += ret; + } + + response[10] = '\0'; + + /* Check for either error or received message */ + + if (strstr(response, "radio_err") != NULL) + { + ret = -EAGAIN; /* Timeout */ + wlerr("Timeout during receive\n"); + goto early_ret; + } + else if (strstr(response, "radio_rx ") == NULL) + { + /* Unknown error, we didn't expect this output */ + + wlerr("Unknown error during receive\n"); + ret = -EIO; + goto early_ret; + } + + /* Here we've received some actual data, and that data is what's currently + * pending to be read. + * We read until the end of message is signalled with \r\n. + */ + +parse_packet: + + hex_byte[2] = '\0'; /* Appease strtoul */ + + while (received < buflen) + { + ret = file_read(&priv->uart, &hex_byte, 2); + if (ret < 2) + { + goto early_ret; + } + + if (hex_byte[0] == '\r' && hex_byte[1] == '\n') + { + break; + } + + /* Convert ASCII hex byte into real bytes to store in the buffer */ + + buffer[received] = strtoul(hex_byte, NULL, 16); + received++; + } + + /* We've received everything we can possibly receive, let the user know by + * returning how much of the buffer we've filled. + * If we've seen the end of packet indicator (\r\n), we'll mark the receive + * as complete. However, if we only stopped to not exceed the buffer + * length, we will leave our status as receiving to continue on the next + * read call. + */ + + if (hex_byte[0] == '\r' && hex_byte[1] == '\n') + { + priv->receiving = false; + } + + ret = received; + filep->f_pos += received; /* Record our position in received packet */ + +early_ret: + + /* We can't continue receiving if we had an issue */ + + if (ret < 0) + { + priv->receiving = false; + } + + nxmutex_unlock(&priv->devlock); + return ret; +} + +/**************************************************************************** + * Name: rn2xx3_write + * + * Description: + * Transmits the data from the user provided buffer over radio as a + * packet. + * + ****************************************************************************/ + +static ssize_t rn2xx3_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + ssize_t length = 0; + size_t i; + char response[20]; + char hexbyte[3]; /* Two hex characters per byte, plus null terminator */ + + /* Exclusive access */ + + length = nxmutex_lock(&priv->devlock); + if (length < 0) + { + return length; + } + + /* Receives in progress are forfeited */ + + priv->receiving = false; + + /* Flush data */ + + length = radio_flush(priv); + if (length < 0) + { + wlerr("Could not flush RN2xx3\n"); + } + + /* Pause the MAC layer */ + + length = mac_pause(priv); + if (length < 0) + { + wlerr("Could not pause MAC layer\n"); + goto early_ret; + } + + /* Start the transmission with the initial 'radio tx', followed by all the + * data in the user buffer converted into hexadecimal characters one by + * one + */ + + length = file_write(&priv->uart, "radio tx ", sizeof("radio tx ") - 1); + if (length < 0) + { + goto early_ret; + } + + /* Print out every character up to the buffer size or the packet size limit + */ + + for (i = 0; i < buflen && i < MAX_PKTSIZE[priv->mod]; i++) + { + snprintf(hexbyte, sizeof(hexbyte), "%02X", buffer[i]); + length = file_write(&priv->uart, hexbyte, 2); + if (length < 0) + { + goto early_ret; + } + } + + /* Complete the packet */ + + length = file_write(&priv->uart, "\r\n", sizeof("\r\n") - 1); + if (length < 0) + { + goto early_ret; + } + + /* Check for okay message */ + + length = get_command_err(priv); + if (length < 0) + { + goto early_ret; + } + + /* Check for radio_tx_ok */ + + length = read_line(priv, response, sizeof(response)); + if (length < 0) + { + goto early_ret; + } + + if (strstr(response, "radio_tx_ok") == NULL) + { + length = -EIO; + goto early_ret; + } + + /* All transmissions were nominal, so we let the user know that `i` bytes + * were sent + */ + + length = i; + +early_ret: + nxmutex_unlock(&priv->devlock); + return length; +} + +/**************************************************************************** + * Name: rn2xx3_ioctl + * + * Description: + * Execute commands on the RN2xx3. + ****************************************************************************/ + +static int rn2xx3_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + int err; + FAR struct inode *inode = filep->f_inode; + FAR struct rn2xx3_dev_s *priv = inode->i_private; + + /* Exclusive access */ + + err = nxmutex_lock(&priv->devlock); + if (err < 0) + { + return err; + } + + /* Receives in progress are forfeited */ + + priv->receiving = false; + + /* Flush radio to clear any lingering messages */ + + err = radio_flush(priv); + if (err < 0) + { + wlerr("Couldn't flush radio: %d\n", err); + goto early_ret; + } + + switch (cmd) + { + case WLIOC_RESET: + { + err = rn2xx3_reset(priv); + break; + } + + case WLIOC_IQIEN: + { + err = rn2xx3_iqi_en(priv, arg); + break; + } + + case WLIOC_CRCEN: + { + err = rn2xx3_crc_en(priv, arg); + break; + } + + case WLIOC_GETSNR: + { + FAR int8_t *snr = (FAR int8_t *)(arg); + DEBUGASSERT(snr != NULL); + err = rn2xx3_getsnr(priv, snr); + break; + } + + case WLIOC_SETRADIOFREQ: + { + err = rn2xx3_setfreq(priv, arg); + break; + } + + case WLIOC_GETRADIOFREQ: + { + FAR uint32_t *freq = (FAR uint32_t *)(arg); + DEBUGASSERT(freq != NULL); + err = rn2xx3_getfreq(priv, freq); + break; + } + + case WLIOC_SETTXPOWERF: + { + FAR float *txpwr = (FAR float *)(arg); + DEBUGASSERT(txpwr != NULL); + err = rn2xx3_settxpwr(priv, txpwr); + break; + } + + case WLIOC_GETTXPOWERF: + { + FAR float *txpwr = (FAR float *)(arg); + DEBUGASSERT(txpwr != NULL); + err = rn2xx3_gettxpwr(priv, txpwr); + break; + } + + case WLIOC_SETBANDWIDTH: + { + err = rn2xx3_setbw(priv, arg); + break; + } + + case WLIOC_GETBANDWIDTH: + { + FAR uint32_t *bw = (FAR uint32_t *)(arg); + DEBUGASSERT(bw != NULL); + err = rn2xx3_getbw(priv, bw); + break; + } + + case WLIOC_SETSPREAD: + { + err = rn2xx3_setsf(priv, arg); + break; + } + + case WLIOC_GETSPREAD: + { + FAR uint8_t *sf = (FAR uint8_t *)(arg); + DEBUGASSERT(sf != NULL); + err = rn2xx3_getsf(priv, sf); + break; + } + + case WLIOC_SETPRLEN: + { + err = rn2xx3_setprlen(priv, arg); + break; + } + + case WLIOC_GETPRLEN: + { + FAR uint16_t *prlen = (FAR uint16_t *)(arg); + DEBUGASSERT(prlen != NULL); + err = rn2xx3_getprlen(priv, prlen); + break; + } + + case WLIOC_SETMOD: + { + err = rn2xx3_setmod(priv, arg); + break; + } + + case WLIOC_GETMOD: + { + FAR enum rn2xx3_mod_e *mod = (FAR enum rn2xx3_mod_e *)(arg); + DEBUGASSERT(mod != NULL); + err = rn2xx3_getmod(priv, mod); + break; + } + + case WLIOC_SETSYNC: + { + FAR uint64_t *syncword = (FAR uint64_t *)(arg); + DEBUGASSERT(syncword != NULL); + err = rn2xx3_setsync(priv, *syncword); + break; + } + + case WLIOC_GETSYNC: + { + FAR uint64_t *sync = (FAR uint64_t *)(arg); + DEBUGASSERT(sync != NULL); + err = rn2xx3_getsync(priv, sync); + break; + } + + case WLIOC_SETBITRATE: + { + err = rn2xx3_setbitrate(priv, arg); + break; + } + + case WLIOC_GETBITRATE: + { + FAR uint32_t *bitrate = (FAR uint32_t *)(arg); + DEBUGASSERT(bitrate != NULL); + err = rn2xx3_getbitrate(priv, bitrate); + break; + } + + case WLIOC_SETCODERATE: + { + err = rn2xx3_setcr(priv, arg); + break; + } + + case WLIOC_GETCODERATE: + { + FAR enum rn2xx3_cr_e *cr = (FAR enum rn2xx3_cr_e *)(arg); + DEBUGASSERT(cr != NULL); + err = rn2xx3_getcr(priv, cr); + break; + } + + default: + { + err = -EINVAL; + break; + } + } + +early_ret: + nxmutex_unlock(&priv->devlock); + return err; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rn2xx3_register + * + * Description: + * Register the RN2xx3 LoRa transceiver driver. + * + * Arguments: + * devpath - The device path to use for the driver + * uartpath - The path to the UART character driver connected to the + * transceiver + * + ****************************************************************************/ + +int rn2xx3_register(FAR const char *devpath, FAR const char *uartpath) +{ + FAR struct rn2xx3_dev_s *priv = NULL; + int err = 0; + int retries = 0; + + DEBUGASSERT(uartpath != NULL); + DEBUGASSERT(devpath != NULL); + + /* Initialize device structure */ + + priv = kmm_zalloc(sizeof(struct rn2xx3_dev_s)); + if (priv == NULL) + { + wlerr("Failed to allocate instance of RN2xx3 driver."); + return -ENOMEM; + } + + priv->receiving = false; /* Not receiving yet */ + priv->mod = RN2XX3_MOD_LORA; /* Starts in LoRa */ + + /* Initialize mutex */ + + err = nxmutex_init(&priv->devlock); + if (err < 0) + { + wlerr("Failed to initialize mutex for RN2xx3 device: %d\n", err); + goto free_mem; + } + + /* Open UART interface for use */ + + err = file_open(&priv->uart, uartpath, O_RDWR | O_CLOEXEC); + if (err < 0) + { + wlerr("Failed to open UART interface %s for RN2xx3 driver: %d\n", + uartpath, err); + goto destroy_mutex; + } + + /* Check model type of the transceiver. Try a few times since the + * transceiver has some boot time delay. + */ + + do + { + err = rn2xx3_getmodel(priv, &priv->model); + retries++; + usleep(10000); /* 10ms between tries */ + } + while (retries < 3 && err < 0); + + if (err < 0) + { + wlerr("Could not detect model: %d\n", err); + goto close_file; + } + + wlinfo("Detected model %s\n", priv->model == RN2903 ? "RN2903" : "RN2483"); + + /* Register driver */ + + err = register_driver(devpath, &g_rn2xx3fops, 0666, priv); + if (err < 0) + { + wlerr("Failed to register RN2xx3 driver: %d\n", err); + goto close_file; + } + + /* Cleanup items on error */ + + if (err < 0) + { + close_file: + file_close(&priv->uart); + destroy_mutex: + nxmutex_destroy(&priv->devlock); + free_mem: + kmm_free(priv); + } + + return err; +} diff --git a/include/nuttx/wireless/ioctl.h b/include/nuttx/wireless/ioctl.h index a4c2da3969c5d..1cba9817db77e 100644 --- a/include/nuttx/wireless/ioctl.h +++ b/include/nuttx/wireless/ioctl.h @@ -60,13 +60,49 @@ /* output power (in dBm) */ #define WLIOC_GETTXPOWER _WLCIOC(0x0006) /* arg: Pointer to int32_t, */ /* output power (in dBm) */ +#define WLIOC_SETBANDWIDTH _WLCIOC(0x0007) /* arg: Pointer to uint32_t, */ + /* bandwidth in Hz */ +#define WLIOC_GETBANDWIDTH _WLCIOC(0x0008) /* arg: Pointer to uint32_t, */ + /* bandwidth in Hz */ +#define WLIOC_SETSPREAD _WLCIOC(0x0009) /* arg: Pointer to uint8_t, */ + /* spread factor */ +#define WLIOC_GETSPREAD _WLCIOC(0x000a) /* arg: Pointer to uint8_t, */ + /* spread factor */ +#define WLIOC_GETSNR _WLCIOC(0x000b) /* arg: Pointer to int8_t, */ + /* signal to noise ratio */ +#define WLIOC_SETPRLEN _WLCIOC(0x000c) /* arg: uint16_t, */ + /* preamble length */ +#define WLIOC_GETPRLEN _WLCIOC(0x000d) /* arg: Pointer to uint16_t, */ + /* preamble length */ +#define WLIOC_SETMOD _WLCIOC(0x000e) /* arg: enum, */ + /* modulation type */ +#define WLIOC_GETMOD _WLCIOC(0x000f) /* arg: enum pointer, */ + /* modulation type */ +#define WLIOC_RESET _WLCIOC(0x0010) /* arg: none */ +#define WLIOC_SETSYNC _WLCIOC(0x0011) /* arg: uint64_t pointer */ + /* sync word */ +#define WLIOC_GETSYNC _WLCIOC(0x0012) /* arg: uint64_t pointer, */ + /* sync word */ +#define WLIOC_SETBITRATE _WLCIOC(0x0013) /* arg: uint32_t */ + /* sync word */ +#define WLIOC_GETBITRATE _WLCIOC(0x0014) /* arg: uint32_t pointer, */ + /* sync word */ +#define WLIOC_IQIEN _WLCIOC(0x0015) /* arg: bool, enable invert IQ */ +#define WLIOC_CRCEN _WLCIOC(0x0016) /* arg: bool, enable CRC */ +#define WLIOC_SETCODERATE _WLCIOC(0x0017) /* arg: enum, coding rate */ +#define WLIOC_GETCODERATE _WLCIOC(0x0018) /* arg: enum pointer, */ + /* coding rate */ +#define WLIOC_SETTXPOWERF _WLCIOC(0x0019) /* arg: Pointer to flaot, */ + /* output power (in dBm) */ +#define WLIOC_GETTXPOWERF _WLCIOC(0x0020) /* arg: Pointer to float, */ + /* output power (in dBm) */ /**************************************************************************** * Device-specific IOCTL commands ****************************************************************************/ #define WL_FIRST 0x0001 /* First common command */ -#define WL_NCMDS 0x0006 /* Number of common commands */ +#define WL_NCMDS 0x0020 /* Number of common commands */ /* User defined ioctl commands are also supported. These will be forwarded * by the upper-half driver to the lower-half driver via the ioctl() diff --git a/include/nuttx/wireless/lpwan/rn2xx3.h b/include/nuttx/wireless/lpwan/rn2xx3.h new file mode 100644 index 0000000000000..ebb6bf4a8fae2 --- /dev/null +++ b/include/nuttx/wireless/lpwan/rn2xx3.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * include/nuttx/wireless/lpwan/rn2xx3.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H +#define __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-Processor Declarations + ****************************************************************************/ + +/**************************************************************************** + * Public Data Types + ****************************************************************************/ + +/* Modulation types */ + +enum rn2xx3_mod_e +{ + RN2XX3_MOD_LORA = 0, /* LoRa modulation */ + RN2XX3_MOD_FSK = 1, /* FSK modulation */ +}; + +/* Coding rates */ + +enum rn2xx3_cr_e +{ + RN2XX3_CR_4_5 = 0, /* 4/5 coding rate */ + RN2XX3_CR_4_6 = 1, /* 4/6 coding rate */ + RN2XX3_CR_4_7 = 2, /* 4/7 coding rate */ + RN2XX3_CR_4_8 = 3, /* 4/8 coding rate */ +}; + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: rn2xx3_register + * + * Description: + * Register the RN2xx3 LoRa transceiver driver. + * + * Arguments: + * devpath - The device path to use for the driver + * uartpath - The path to the UART character driver connected to the + * transceiver + * + ****************************************************************************/ + +int rn2xx3_register(FAR const char *devpath, FAR const char *uartpath); + +#endif /* __INCLUDE_NUTTX_WIRELESS_LPWAN_RN2XX3_H */