mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
687 lines
22 KiB
C
687 lines
22 KiB
C
/*
|
|
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_cc110x
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Implementation of RIOT's netdev_driver API for the CC1100/CC1101
|
|
* transceiver
|
|
*
|
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
|
* @}
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "assert.h"
|
|
#include "iolist.h"
|
|
#include "irq.h"
|
|
#include "mutex.h"
|
|
#include "net/eui64.h"
|
|
#include "net/netdev.h"
|
|
#include "xtimer.h"
|
|
|
|
#include "cc110x.h"
|
|
#include "cc110x_internal.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
static int cc110x_init(netdev_t *netdev);
|
|
static int cc110x_recv(netdev_t *netdev, void *buf, size_t len, void *info);
|
|
static int cc110x_send(netdev_t *netdev, const iolist_t *iolist);
|
|
static int cc110x_get(netdev_t *netdev, netopt_t opt,
|
|
void *val, size_t max_len);
|
|
static int cc110x_set(netdev_t *netdev, netopt_t opt,
|
|
const void *val, size_t len);
|
|
|
|
/**
|
|
* @brief A lookup table to convert from dBm value to the best matching
|
|
* @ref cc110x_tx_power_t value
|
|
*/
|
|
static const int8_t tx_power_from_dbm[] = {
|
|
[CC110X_TX_POWER_MINUS_30_DBM] = -25,
|
|
[CC110X_TX_POWER_MINUS_20_DBM] = -17,
|
|
[CC110X_TX_POWER_MINUS_15_DBM] = -12,
|
|
[CC110X_TX_POWER_MINUS_10_DBM] = -5,
|
|
[CC110X_TX_POWER_0_DBM] = 3,
|
|
[CC110X_TX_POWER_PLUS_5_DBM] = 6,
|
|
[CC110X_TX_POWER_PLUS_7_DBM] = 9,
|
|
};
|
|
|
|
/**
|
|
* @brief A lookup table to convert an @ref cc110x_tx_power_t value to dBm
|
|
*/
|
|
static const int8_t dbm_from_tx_power[] = {
|
|
[CC110X_TX_POWER_MINUS_30_DBM] = -30,
|
|
[CC110X_TX_POWER_MINUS_20_DBM] = -20,
|
|
[CC110X_TX_POWER_MINUS_15_DBM] = -15,
|
|
[CC110X_TX_POWER_MINUS_10_DBM] = -10,
|
|
[CC110X_TX_POWER_0_DBM] = 0,
|
|
[CC110X_TX_POWER_PLUS_5_DBM] = 5,
|
|
[CC110X_TX_POWER_PLUS_7_DBM] = 7,
|
|
[CC110X_TX_POWER_PLUS_10_DBM] = 10,
|
|
};
|
|
|
|
const netdev_driver_t cc110x_driver = {
|
|
.init = cc110x_init,
|
|
.recv = cc110x_recv,
|
|
.send = cc110x_send,
|
|
.isr = cc110x_isr,
|
|
.get = cc110x_get,
|
|
.set = cc110x_set,
|
|
};
|
|
|
|
void cc110x_on_gdo(void *_dev)
|
|
{
|
|
cc110x_t *dev = _dev;
|
|
if ((dev->state & 0x07) == CC110X_STATE_TX_MODE) {
|
|
/* Unlock mutex to unblock netdev thread */
|
|
mutex_unlock(&dev->isr_signal);
|
|
}
|
|
else {
|
|
netdev_trigger_event_isr(&dev->netdev);
|
|
}
|
|
}
|
|
|
|
static int identify_device(cc110x_t *dev)
|
|
{
|
|
uint8_t partnum, version, status;
|
|
int is_ready;
|
|
cc110x_state_t state;
|
|
|
|
cc110x_read(dev, CC110X_REG_VERSION, &version);
|
|
/* Retrieving the status is reliable for non-transient status */
|
|
status = cc110x_read(dev, CC110X_REG_PARTNUM, &partnum);
|
|
state = cc110x_state_from_status(status);
|
|
/* Most significant bit should be zero, otherwise chip is not ready */
|
|
is_ready = cc110x_is_ready_from_status(status);
|
|
|
|
DEBUG("[cc110x] PARTNUM = %i, VERSION = %i, STATUS = 0x%02x, READY = %i\n",
|
|
(int)partnum, (int)version, (int)status, is_ready);
|
|
|
|
if ((state != CC110X_STATE_IDLE) || (!is_ready)) {
|
|
DEBUG("[cc110x] IC not ready or in invalid state\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Source: https://e2e.ti.com/support/wireless-connectivity/other-wireless/f/667/t/370643 */
|
|
if (partnum != 0) {
|
|
DEBUG("[cc110x] Device not a CC110x transceiver\n");
|
|
return -1;
|
|
}
|
|
|
|
switch (version) {
|
|
case 3:
|
|
DEBUG("[cc110x] Detected CC1100 transceiver\n");
|
|
/* RSSI offset is 78dBm @ 868MHz & 250kBaud.
|
|
* Depends on the symbol rate and base band and ranges from
|
|
* 74dBm to 79dBm.
|
|
*/
|
|
dev->rssi_offset = 78;
|
|
return 0;
|
|
case 5:
|
|
DEBUG("[cc110x] Detected CC1100E transceiver\n");
|
|
/* RSSI offset is 79 dBm @ 250kbps & 250 kbps.
|
|
* Depends on base band and symbol rate and ranges from
|
|
* 75dBm to 79dBm
|
|
*/
|
|
dev->rssi_offset = 79;
|
|
return 0;
|
|
case 4:
|
|
/* falls through */
|
|
case 14:
|
|
/* falls through */
|
|
case 20:
|
|
/* RSSI offset for the CC1101 is independent of symbol rate and
|
|
* base 74 dBm
|
|
*/
|
|
dev->rssi_offset = 74;
|
|
DEBUG("[cc110x] Detected CC1101 transceiver\n");
|
|
return 0;
|
|
default:
|
|
DEBUG("[cc110x] Device not a CC110x transceiver\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int check_config(cc110x_t *dev)
|
|
{
|
|
char buf[CC110X_CONF_SIZE];
|
|
|
|
/* Verify content of main config registers */
|
|
cc110x_burst_read(dev, CC110X_CONF_START, buf, sizeof(buf));
|
|
if (memcmp(buf, cc110x_conf, sizeof(buf))) {
|
|
DEBUG("[cc110x] ERROR: Verification of main config registers failed\n"
|
|
" Check SPI wiring or reduce SPI clock\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Verify content of "magic number" config registers */
|
|
cc110x_burst_read(dev, CC110X_REG_TEST2, buf,
|
|
sizeof(cc110x_magic_registers));
|
|
if (memcmp(buf, cc110x_magic_registers, sizeof(cc110x_magic_registers))) {
|
|
DEBUG("[cc110x] ERROR: Verification of \"magic\" registers failed\n"
|
|
" Check SPI wiring or reduce SPI clock\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Verify content of PA_TABLE */
|
|
cc110x_burst_read(dev, CC110X_MULTIREG_PATABLE, buf, CC110X_PATABLE_LEN);
|
|
if (memcmp(buf, dev->params.patable->data, CC110X_PATABLE_LEN)) {
|
|
DEBUG("[cc110x] ERROR: Verification of PA_TABLE failed\n"
|
|
" Check SPI wiring or reduce SPI clock\n");
|
|
return -1;
|
|
}
|
|
|
|
DEBUG("[cc110x] Content of configuration registers verified\n");
|
|
return 0;
|
|
}
|
|
|
|
static int check_gdo_pins(cc110x_t *dev)
|
|
{
|
|
/* GPIOs connected to GDOs are not yet configured, so we do this now.
|
|
* This configuration is just temporarily for testing, as gpio_init_int()
|
|
* will reconfigure them later again. We do not want to set up the
|
|
* interrupts before validating the transceiver, so we effectively configure
|
|
* the GPIOs twice.
|
|
*/
|
|
if (gpio_init(dev->params.gdo2, GPIO_IN)) {
|
|
DEBUG("[cc110x] Configuring GDO2 failed");
|
|
return -1;
|
|
}
|
|
|
|
if (gpio_init(dev->params.gdo0, GPIO_IN)) {
|
|
DEBUG("[cc110x] Configuring GDO0 failed");
|
|
return -1;
|
|
}
|
|
|
|
/* Validate that GDO2 responds to configuration updates */
|
|
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_HIGH);
|
|
if (!gpio_read(dev->params.gdo2)) {
|
|
DEBUG("[cc110x] GDO2 does not respond (check wiring!)\n");
|
|
return -1;
|
|
}
|
|
|
|
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_LOW);
|
|
if (gpio_read(dev->params.gdo2)) {
|
|
DEBUG("[cc110x] GDO2 does not respond (check wiring!)\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Validate that GDO0 responds to configuration updates */
|
|
cc110x_write(dev, CC110X_REG_IOCFG0, CC110X_GDO_CONSTANT_HIGH);
|
|
if (!gpio_read(dev->params.gdo0)) {
|
|
DEBUG("[cc110x] GDO0 does not respond (check wiring!)\n");
|
|
return -1;
|
|
}
|
|
|
|
cc110x_write(dev, CC110X_REG_IOCFG0, CC110X_GDO_CONSTANT_LOW);
|
|
if (gpio_read(dev->params.gdo0)) {
|
|
DEBUG("[cc110x] GDO0 does not respond (check wiring!)\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Restore default GDO2 & GDO0 config */
|
|
cc110x_write(dev, CC110X_REG_IOCFG2, cc110x_conf[CC110X_REG_IOCFG2]);
|
|
cc110x_write(dev, CC110X_REG_IOCFG0, cc110x_conf[CC110X_REG_IOCFG0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cc110x_init(netdev_t *netdev)
|
|
{
|
|
cc110x_t *dev = (cc110x_t *)netdev;
|
|
/* Use locked mutex to block thread on TX and un-block from ISR */
|
|
mutex_init(&dev->isr_signal);
|
|
mutex_lock(&dev->isr_signal);
|
|
|
|
/* Make sure the crystal is stable and the chip ready. This is needed as
|
|
* the reset is done via an SPI command, but the SPI interface must not be
|
|
* used unless the chip is ready according to the data sheet. After the
|
|
* reset, a second call to cc110x_power_on_and_acquire() is needed to finally have
|
|
* the transceiver in a known state and ready for SPI communication.
|
|
*/
|
|
if (cc110x_power_on_and_acquire(dev)) {
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Failed to pull CS pin low\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Performing a reset of the transceiver to get it in a known state */
|
|
cc110x_cmd(dev, CC110X_STROBE_RESET);
|
|
cc110x_release(dev);
|
|
|
|
/* Again, make sure the crystal is stable and the chip ready */
|
|
if (cc110x_power_on_and_acquire(dev)) {
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Failed to pull CS pin low "
|
|
"after reset\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (identify_device(dev)) {
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Device identification failed\n");
|
|
cc110x_release(dev);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Upload the main configuration */
|
|
cc110x_burst_write(dev, CC110X_CONF_START, cc110x_conf, CC110X_CONF_SIZE);
|
|
/* Set TX power to match uploaded configuration */
|
|
dev->tx_power = CC110X_TX_POWER_0_DBM;
|
|
|
|
/* Upload the poorly documented magic numbers obtained via SmartRF Studio */
|
|
cc110x_burst_write(dev, CC110X_REG_TEST2, cc110x_magic_registers,
|
|
sizeof(cc110x_magic_registers));
|
|
|
|
/* Setup the selected PA_TABLE */
|
|
cc110x_burst_write(dev, CC110X_MULTIREG_PATABLE,
|
|
dev->params.patable->data, CC110X_PATABLE_LEN);
|
|
|
|
/* Verify main config, magic numbers and PA_TABLE correctly uploaded */
|
|
if (check_config(dev)) {
|
|
cc110x_release(dev);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Verify that pins GDO2 and GDO0 are correctly connected */
|
|
if (check_gdo_pins(dev)) {
|
|
cc110x_release(dev);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Setup the layer 2 address, but do not accept CC1XXX_BCAST_ADDR (which
|
|
* has the value 0x00 and is used for broadcast)
|
|
*/
|
|
cc1xxx_eui_get(&dev->netdev, &dev->addr);
|
|
assert(dev->addr != CC1XXX_BCAST_ADDR);
|
|
cc110x_write(dev, CC110X_REG_ADDR, dev->addr);
|
|
|
|
/* Setup interrupt on GDO0 */
|
|
if (gpio_init_int(dev->params.gdo0, GPIO_IN, GPIO_BOTH,
|
|
cc110x_on_gdo, dev)) {
|
|
cc110x_release(dev);
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup interrupt on "
|
|
"GDO0 pin\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Setup interrupt on GDO2 */
|
|
if (gpio_init_int(dev->params.gdo2, GPIO_IN, GPIO_BOTH,
|
|
cc110x_on_gdo, dev)) {
|
|
gpio_irq_disable(dev->params.gdo0);
|
|
cc110x_release(dev);
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup interrupt on "
|
|
"GDO2 pin\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Update the state of the driver/transceiver */
|
|
dev->state = CC110X_STATE_IDLE;
|
|
cc110x_release(dev);
|
|
|
|
int retval; /*< Store return value to be able to pass through error code */
|
|
/* Apply configuration (if non-NULL) and channel map, which also calls
|
|
* cc110x_full_calibration
|
|
*/
|
|
retval = cc110x_apply_config(dev, dev->params.config, dev->params.channels,
|
|
CONFIG_CC110X_DEFAULT_CHANNEL);
|
|
if (retval) {
|
|
gpio_irq_disable(dev->params.gdo0);
|
|
gpio_irq_disable(dev->params.gdo2);
|
|
DEBUG("[cc110x] netdev_driver_t::init(): cc110x_apply_config() "
|
|
"failed\n");
|
|
/* Pass through received error code */
|
|
return retval;
|
|
}
|
|
|
|
DEBUG("[cc110x] netdev_driver_t::init(): Success\n");
|
|
|
|
/* signal link UP */
|
|
netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cc110x_recv(netdev_t *netdev, void *buf, size_t len, void *info)
|
|
{
|
|
cc110x_t *dev = (cc110x_t *)netdev;
|
|
|
|
/* Call to cc110x_enter_rx_mode() will clear dev->buf.len, so back up it first */
|
|
int size = dev->buf.len;
|
|
|
|
cc110x_acquire(dev);
|
|
|
|
/* Copy RX info on last frame (if requested) */
|
|
if (info != NULL) {
|
|
*((cc1xxx_rx_info_t *)info) = dev->rx_info;
|
|
}
|
|
|
|
if (!buf) {
|
|
/* Get the size of the frame; if len > 0 then also drop the frame */
|
|
if (len > 0) {
|
|
/* Drop frame requested */
|
|
cc110x_enter_rx_mode(dev);
|
|
}
|
|
cc110x_release(dev);
|
|
return size;
|
|
}
|
|
|
|
if (len < (size_t)size) {
|
|
/* Drop frame and return -ENOBUFS */
|
|
cc110x_enter_rx_mode(dev);
|
|
cc110x_release(dev);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
memcpy(buf, dev->buf.data, (size_t)size);
|
|
|
|
cc110x_enter_rx_mode(dev);
|
|
cc110x_release(dev);
|
|
return size;
|
|
}
|
|
|
|
static int cc110x_send(netdev_t *netdev, const iolist_t *iolist)
|
|
{
|
|
cc110x_t *dev = (cc110x_t *)netdev;
|
|
|
|
/* assert that cc110x_send was called with valid parameters */
|
|
assert(netdev && iolist && (iolist->iol_len == sizeof(cc1xxx_l2hdr_t)));
|
|
|
|
cc110x_acquire(dev);
|
|
|
|
switch (dev->state) {
|
|
case CC110X_STATE_FSTXON:
|
|
/* falls through */
|
|
case CC110X_STATE_RX_MODE:
|
|
break;
|
|
case CC110X_STATE_RECEIVING:
|
|
cc110x_release(dev);
|
|
DEBUG("[cc110x] netdev_driver_t::send(): Refusing to send while "
|
|
"receiving a frame\n");
|
|
return -EBUSY;
|
|
case CC110X_STATE_OFF:
|
|
cc110x_release(dev);
|
|
return -ENOTSUP;
|
|
default:
|
|
cc110x_release(dev);
|
|
DEBUG("[cc110x] netdev_driver_t::send(): Driver state %i prevents "
|
|
"sending\n", (int)dev->state);
|
|
return -1;
|
|
}
|
|
|
|
/* Copy data to send into frame buffer */
|
|
size_t size = sizeof(cc1xxx_l2hdr_t);
|
|
memcpy(dev->buf.data, iolist->iol_base, sizeof(cc1xxx_l2hdr_t));
|
|
|
|
for (const iolist_t *iol = iolist->iol_next; iol; iol = iol->iol_next) {
|
|
if (iol->iol_len) {
|
|
if (size + iol->iol_len > CC110X_MAX_FRAME_SIZE) {
|
|
cc110x_release(dev);
|
|
DEBUG("[cc110x] netdev_driver_t::send(): Frame size of %uB "
|
|
"exceeds maximum supported size of %uB\n",
|
|
(unsigned)(size + iol->iol_len),
|
|
(unsigned)CC110X_MAX_FRAME_SIZE);
|
|
return -1;
|
|
}
|
|
memcpy(dev->buf.data + size, iol->iol_base, iol->iol_len);
|
|
size += iol->iol_len;
|
|
}
|
|
}
|
|
|
|
dev->buf.len = (uint8_t)size;
|
|
|
|
/* Disable IRQs, as GDO configuration will be changed now */
|
|
gpio_irq_disable(dev->params.gdo0);
|
|
gpio_irq_disable(dev->params.gdo2);
|
|
|
|
/* Fill the TX FIFO: First write the length, then the frame */
|
|
dev->buf.pos = (size > CC110X_FIFO_SIZE - 1) ? CC110X_FIFO_SIZE - 1 : size;
|
|
/* cc110x_framebuf_t has the same memory layout as the device expects */
|
|
cc110x_burst_write(dev, CC110X_MULTIREG_FIFO,
|
|
&dev->buf, dev->buf.pos + 1);
|
|
|
|
/* Go to TX */
|
|
cc110x_cmd(dev, CC110X_STROBE_TX);
|
|
|
|
/* Configure GDO2 and update state */
|
|
if (dev->buf.pos < dev->buf.len) {
|
|
/* We need to keep feeding TX FIFO */
|
|
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_ON_TX_DATA);
|
|
dev->state = CC110X_STATE_TX_MODE;
|
|
}
|
|
else {
|
|
/* All data in TX FIFO, just waiting for transceiver to finish */
|
|
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_LOW);
|
|
dev->state = CC110X_STATE_TX_COMPLETING;
|
|
}
|
|
|
|
cc110x_release(dev);
|
|
|
|
/* Restore IRQs */
|
|
gpio_irq_enable(dev->params.gdo0);
|
|
gpio_irq_enable(dev->params.gdo2);
|
|
|
|
while ((dev->state & 0x07) == CC110X_STATE_TX_MODE) {
|
|
uint64_t timeout = (dev->state != CC110X_STATE_TX_COMPLETING) ?
|
|
2048 : 1024;
|
|
/* Block until mutex is unlocked from ISR, or a timeout occurs. The
|
|
* timeout prevents deadlocks when IRQs are lost. If the TX FIFO
|
|
* still needs to be filled, the timeout is 1024 µs - or after 32 Byte
|
|
* (= 50%) of the FIFO have been transmitted at 250 kbps. If
|
|
* no additional data needs to be fed into the FIFO, a timeout of
|
|
* 2048 µs is used instead to allow the frame to be completely drained
|
|
* before the timeout triggers. The ISR handler is prepared to be
|
|
* called prematurely, so we don't need to worry about extra calls
|
|
* to cc110x_isr() introduced by accident.
|
|
*/
|
|
xtimer_mutex_lock_timeout(&dev->isr_signal, timeout);
|
|
cc110x_isr(&dev->netdev);
|
|
}
|
|
|
|
return (int)size;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the CC110x's address filter is disabled
|
|
* @param dev Transceiver to check if in promiscuous mode
|
|
* @param dest Store the result here
|
|
*
|
|
* @return Returns the size of @ref netopt_enable_t to confirm with the API
|
|
* in @ref netdev_driver_t::get
|
|
*/
|
|
static int cc110x_get_promiscuous_mode(cc110x_t *dev, netopt_enable_t *dest)
|
|
{
|
|
cc110x_acquire(dev);
|
|
|
|
uint8_t pktctrl1;
|
|
cc110x_read(dev, CC110X_REG_PKTCTRL1, &pktctrl1);
|
|
*dest = ((pktctrl1 & CC110X_PKTCTRL1_GET_ADDR_MODE) == CC110X_PKTCTRL1_ADDR_ALL);
|
|
cc110x_release(dev);
|
|
return sizeof(netopt_enable_t);
|
|
}
|
|
|
|
static int cc110x_get(netdev_t *netdev, netopt_t opt,
|
|
void *val, size_t max_len)
|
|
{
|
|
cc110x_t *dev = (cc110x_t *)netdev;
|
|
|
|
(void)max_len; /* only used in assert() */
|
|
switch (opt) {
|
|
case NETOPT_DEVICE_TYPE:
|
|
assert(max_len == sizeof(uint16_t));
|
|
*((uint16_t *)val) = NETDEV_TYPE_CC110X;
|
|
return sizeof(uint16_t);
|
|
case NETOPT_PROTO:
|
|
assert(max_len == sizeof(gnrc_nettype_t));
|
|
*((gnrc_nettype_t *)val) = CC110X_DEFAULT_PROTOCOL;
|
|
return sizeof(gnrc_nettype_t);
|
|
case NETOPT_MAX_PDU_SIZE:
|
|
assert(max_len == sizeof(uint16_t));
|
|
*((uint16_t *)val) = CC110X_MAX_FRAME_SIZE - sizeof(cc1xxx_l2hdr_t);
|
|
return sizeof(uint16_t);
|
|
case NETOPT_ADDR_LEN:
|
|
/* falls through */
|
|
case NETOPT_SRC_LEN:
|
|
assert(max_len == sizeof(uint16_t));
|
|
*((uint16_t *)val) = CC1XXX_ADDR_SIZE;
|
|
return sizeof(uint16_t);
|
|
case NETOPT_ADDRESS:
|
|
assert(max_len >= CC1XXX_ADDR_SIZE);
|
|
*((uint8_t *)val) = dev->addr;
|
|
return CC1XXX_ADDR_SIZE;
|
|
case NETOPT_CHANNEL:
|
|
assert(max_len == sizeof(uint16_t));
|
|
*((uint16_t *)val) = dev->channel;
|
|
return sizeof(uint16_t);
|
|
case NETOPT_TX_POWER:
|
|
assert(max_len == sizeof(uint16_t));
|
|
*((uint16_t *)val) = dbm_from_tx_power[dev->tx_power];
|
|
return sizeof(uint16_t);
|
|
case NETOPT_PROMISCUOUSMODE:
|
|
assert(max_len == sizeof(netopt_enable_t));
|
|
return cc110x_get_promiscuous_mode(dev, val);
|
|
case NETOPT_STATE:
|
|
assert(max_len == sizeof(netopt_state_t));
|
|
{
|
|
netopt_state_t *state = val;
|
|
switch (dev->state) {
|
|
case CC110X_STATE_RECEIVING:
|
|
case CC110X_STATE_FRAME_READY:
|
|
case CC110X_STATE_RXFIFO_OVERFLOW:
|
|
*state = NETOPT_STATE_RX;
|
|
break;
|
|
case CC110X_STATE_IDLE:
|
|
*state = NETOPT_STATE_STANDBY;
|
|
break;
|
|
case CC110X_STATE_OFF:
|
|
*state = NETOPT_STATE_SLEEP;
|
|
break;
|
|
case CC110X_STATE_TX_MODE:
|
|
case CC110X_STATE_TX_COMPLETING:
|
|
case CC110X_STATE_TXFIFO_UNDERFLOW:
|
|
*state = NETOPT_STATE_TX;
|
|
break;
|
|
case CC110X_STATE_RX_MODE:
|
|
*state = NETOPT_STATE_IDLE;
|
|
break;
|
|
default:
|
|
*state = NETOPT_STATE_RESET;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the given address as the device's layer 2 address
|
|
*
|
|
* @param dev Device descriptor of the transceiver
|
|
* @param addr Address to set
|
|
*/
|
|
static int cc110x_set_addr(cc110x_t *dev, uint8_t addr)
|
|
{
|
|
cc110x_acquire(dev);
|
|
|
|
dev->addr = addr;
|
|
cc110x_write(dev, CC110X_REG_ADDR, addr);
|
|
cc110x_release(dev);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @brief Enables/disables the CC110x's address filter
|
|
* @param dev Transceiver to turn promiscuous mode on/off
|
|
* @param enable Whether to enable or disable promiscuous mode
|
|
*
|
|
* @return Returns the size of @ref netopt_enable_t to confirm with the API
|
|
* in @ref netdev_driver_t::set
|
|
*/
|
|
static int cc110x_set_promiscuous_mode(cc110x_t *dev, netopt_enable_t enable)
|
|
{
|
|
cc110x_acquire(dev);
|
|
|
|
uint8_t pktctrl1 = CC110X_PKTCTRL1_VALUE;
|
|
if (enable == NETOPT_ENABLE) {
|
|
pktctrl1 |= CC110X_PKTCTRL1_ADDR_ALL;
|
|
}
|
|
else {
|
|
pktctrl1 |= CC110X_PKTCTRL1_ADDR_MATCH;
|
|
}
|
|
cc110x_write(dev, CC110X_REG_PKTCTRL1, pktctrl1);
|
|
cc110x_release(dev);
|
|
return sizeof(netopt_enable_t);
|
|
}
|
|
|
|
static int cc110x_set(netdev_t *netdev, netopt_t opt,
|
|
const void *val, size_t len)
|
|
{
|
|
(void)len;
|
|
cc110x_t *dev = (cc110x_t *)netdev;
|
|
|
|
switch (opt) {
|
|
case NETOPT_ADDRESS:
|
|
assert(len == CC1XXX_ADDR_SIZE);
|
|
return cc110x_set_addr(dev, *((uint8_t *)val));
|
|
case NETOPT_CHANNEL:
|
|
{
|
|
assert(len == sizeof(uint16_t));
|
|
int retval;
|
|
uint16_t channel = *((uint16_t *)val);
|
|
if (channel >= CC110X_MAX_CHANNELS) {
|
|
return -EINVAL;
|
|
}
|
|
if ((retval = cc110x_set_channel(dev, (uint8_t)channel))) {
|
|
return retval;
|
|
}
|
|
}
|
|
return sizeof(uint16_t);
|
|
case NETOPT_TX_POWER:
|
|
{
|
|
assert(len == sizeof(int16_t));
|
|
int16_t dbm = *((int16_t *)val);
|
|
cc110x_tx_power_t power = CC110X_TX_POWER_MINUS_30_DBM;
|
|
for ( ; power < CC110X_TX_POWER_PLUS_10_DBM; power++) {
|
|
if ((int16_t)tx_power_from_dbm[power] >= dbm) {
|
|
break;
|
|
}
|
|
}
|
|
if (cc110x_set_tx_power(dev, power)) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return sizeof(uint16_t);
|
|
case NETOPT_PROMISCUOUSMODE:
|
|
assert(len == sizeof(netopt_enable_t));
|
|
return cc110x_set_promiscuous_mode(dev, *((const netopt_enable_t *)val));
|
|
case NETOPT_STATE:
|
|
assert(len == sizeof(netopt_state_t));
|
|
switch (*((netopt_state_t *)val)) {
|
|
case NETOPT_STATE_RESET:
|
|
case NETOPT_STATE_IDLE:
|
|
cc110x_wakeup(dev);
|
|
return sizeof(netopt_state_t);
|
|
case NETOPT_STATE_OFF:
|
|
case NETOPT_STATE_SLEEP:
|
|
cc110x_sleep(dev);
|
|
return sizeof(netopt_state_t);
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|