[PATCH 2/2] misc: add support for the cc1101 RF transceiver chip from TI

From: Heiko Schocher
Date: Sun Sep 22 2019 - 02:05:42 EST


This driver provides support for the Low-Power Sub-1 GHz RF
Transceiver chip from Texas Instruments. It provides a
simple message based protocol to set chip registers, send
and receive packets to/from the chip. For more details
see Documentation/misc-devices/cc1101.txt and
Documentation/devicetree/bindings/misc/cc1101.txt

Signed-off-by: Heiko Schocher <hs@xxxxxxx>
---

Documentation/misc-devices/cc1101.txt | 446 ++++++
drivers/misc/Kconfig | 11 +
drivers/misc/Makefile | 1 +
drivers/misc/cc1101.c | 2004 +++++++++++++++++++++++++
drivers/misc/cc1101.h | 89 ++
include/uapi/linux/cc1101_user.h | 255 ++++
6 files changed, 2806 insertions(+)
create mode 100644 Documentation/misc-devices/cc1101.txt
create mode 100644 drivers/misc/cc1101.c
create mode 100644 drivers/misc/cc1101.h
create mode 100644 include/uapi/linux/cc1101_user.h

diff --git a/Documentation/misc-devices/cc1101.txt b/Documentation/misc-devices/cc1101.txt
new file mode 100644
index 0000000000000..61d11baa70dad
--- /dev/null
+++ b/Documentation/misc-devices/cc1101.txt
@@ -0,0 +1,446 @@
+=============
+cc1101 driver
+=============
+
+abstract
+========
+
+This driver add support for the cc1101 Low-Power Sub-1 GHz RF Transceiver
+chip from Texas Instruments [1].
+
+The driver do not know anything about the data which is received
+or transmitted, beside the first byte is always a length byte, as
+this driver supports only variable packet length mode, see [1]
+chapter "0x08: PKTCTRL0 â Packet Automation Control"
+
+driver supports:
+
+- define different cc1101 register configurations. Each configuration
+ is referenced through a name.
+
+- define delay tables which are used for CSMA delay timeouts. Each
+ delay table is referenced through a name.
+
+- receive data from the cc1101 chip.
+
+- send data to the cc1101 chip
+
+ - optionally check if channel is free (CSMA). It is configurable
+ which delay table is used for the current packet, and how many
+ times the driver tries to get a free band.
+
+ - optionally send a preamble before the data, length of the preamble
+ is configurable through module parameter preamble_timeout in msec.
+
+- communication with userspace is message based
+
+- userspace can define a job, which contains more than one message
+ the driver executes each message.
+
+- driver can generate events (if enabled in register configuration)
+ which means the driver calculates all 100 ms the current rssi value
+ and send it to userspace.
+
+module parameter
+================
+
+preamble_timeout: timeout for sending preamble in msec
+ default: 360
+
+DTS
+===
+
+see:
+Documentation/devicetree/bindings/misc/cc1101.txt
+
+cc1101 register settings
+========================
+
+For correct working the driver needs some register settings,
+you cannot change. Without them it makes no sense to use the
+driver (for example, the GDOx pins are used as IRQ pins, and
+if they are not setup in IRQ mode you get unexpected behaviour.
+For example on GDO0 outputs a 24MHz clock ... which will stall
+your cpu when the driver enables the GDO0 pin as irq).
+
+Following the register settings the driver checks:
+
+GDO0 setting
+............
+
+IOCFG0 (0x2) mode 0x6
+Asserts when sync word has been sent / received, and de-asserts
+at the end of the packet. In RX, the pin will also deassert when
+a packet is discarded due to address or maximum length filtering
+or when the radio enters RXFIFO_OVERFLOW state. In TX the pin
+will de-assert if the TX FIFO underflows.
+
+GDO2 setting
+............
+
+IOCFG2 (0x0) mode 0xe
+Carrier sense. High if RSSI level is above threshold.
+Cleared when entering IDLE mode.
+
+set automatic rx mode
+.....................
+
+MCSM1 (0x17): RXOFF_MODE und TXOFF_MODE must be 0xf
+so the cc1101 chip go into rx mode after tx or rx finished.
+
+If this bits are not set, driver tries to set rx mode after
+successful rx or tx. But this is slow and may leads in missing
+rx packets.
+
+variable packet length mode
+...........................
+
+This driver supports variable packet length mode only,
+see [1] chapter "0x08: PKTCTRL0 â Packet Automation Control"
+
+packet format
+.............
+
+only "normal" mode (use FIFOs for RX and TX) is supported.
+
+append status
+.............
+
+This setting is optional.
+
+PKTCTRL1 (0x07) APPEND_STATUS
+-> cc1101 appends 2 bytes to each received packet
+
+
+userspace interface
+===================
+
+The driver interface is message based, which means the Userspace
+has to setup messages and send them to the driver. Also, the driver
+use messages to communicate with the Userspace process.
+
+It could be needed to send more than one message to the driver
+at once, so messages can be grouped into one job, give the job an ID
+and send it to the driver, see "send a job". The driver than go through
+the single messages in the job and executes them. For example it is
+possible to set a register configuration A, send some data, switch than
+to register configuration B and send there again some data, switch than
+back to register setting A and switch to rx mode. If the driver has
+finsihed the job, or an error occured, the driver sends back
+a message to the userspace, see "infos for a finished job".
+
+common message format
+---------------------
+
+all messages in read and write direction start with:
+
++----------+...
+| int type |
++----------+...
+
+The content of type differs between read and write direction.
+
+write to driver
+---------------
+
+The following message types are possible:
+
+- CC1101_MSG_RCV
+- CC1101_MSG_DEFINE_CONFIG
+- CC1101_MSG_DEL_CONFIG
+- CC1101_MSG_JOB
+- CC1101_MSG_DEFINE_DELAY
+- CC1101_MSG_SET_PATABLE
+- CC1101_MSG_GET_CARRIER_SENSE
+
+set receive mode
+................
+
+Use this type for setting up the cc1101 registers with
+a register configuration, defined with CC1101_MSG_DEFINE_CONFIG
+and put the cc1101 chip into rx mode.
+
+setup the struct msg_queue_user_rcv
+
++----------------+-------------------------------+
+| CC1101_MSG_RCV | config_name[CFG_NAME_MAX_LEN] |
++----------------+-------------------------------+
+
+config_name: 0 terminated string, contains the name of a register setting.
+
+define a register configuration
+...............................
+
+type: CC1101_MSG_DEFINE_CONFIG
+
+define a register setting for the cc1101 and give it a name.
+This register setting can than be used for setting a specific
+register setting. Before a register setting is applied to the
+cc1101 chip, the driver resets the cc1101 chip, so all registers
+have default values, which means, you only have to define registers
+which have different values from the default value.
+
+If you use SmartRFstudio [2] you can export a register table,
+in c friendly format which exactly contains only the differences,
+and you can use it in your userspace program.
+
+setup the struct msg_queue_user_config_data
+
++--------------------------+-------------------------------+------------+-----------+----------+ +---+---+
+| CC1101_MSG_DEFINE_CONFIG | config_name[CFG_NAME_MAX_LEN] | char flags | char addr | char val | .... | 0 | 0 |
++--------------------------+-------------------------------+------------+-----------+----------+ +---+---+
+
+flags : CC1101_FLAG_CFG_*
+config_name: 0 terminated string, contains the name of a register setting.
+followed by a list of "addr | value" elements, terminated with "0 0"
+
+Userspace example:
+
+::
+
+ static const struct config_param demo_rx[] =
+ {
+ {CCxxx0_IOCFG0, 0x06},
+ {CCxxx0_FIFOTHR, 0x47},
+ {CCxxx0_PKTCTRL0, 0x05},
+ {CCxxx0_FSCTRL1, 0x06},
+ {CCxxx0_FREQ2, 0x21},
+ {CCxxx0_FREQ1, 0x62},
+ {CCxxx0_FREQ0, 0x76},
+ {CCxxx0_MDMCFG4, 0xC8},
+ {CCxxx0_MDMCFG3, 0x93},
+ {CCxxx0_MDMCFG2, 0x13},
+ {CCxxx0_DEVIATN, 0x34},
+ {CCxxx0_MCSM0, 0x18},
+ {CCxxx0_FOCCFG, 0x16},
+ {CCxxx0_AGCCTRL2, 0x43},
+ {CCxxx0_WORCTRL, 0xFB},
+ {CCxxx0_FSCAL3, 0xE9},
+ {CCxxx0_FSCAL2, 0x2A},
+ {CCxxx0_FSCAL1, 0x00},
+ {CCxxx0_FSCAL0, 0x1F},
+ {CCxxx0_TEST2, 0x81},
+ {CCxxx0_TEST1, 0x35},
+ {CCxxx0_TEST0, 0x09},
+ {CFG_END, CFG_END},
+ };
+
+ #define CONFIG_MAX_EL 30
+ struct config_demo {
+ int type;
+ char config_name[CFG_NAME_MAX_LEN];
+ struct config_param cfg[CONFIG_MAX_EL];
+ };
+
+ struct config_demo cfg1;
+
+ memset(&cfg1, 0x0, sizeof(cfg1));
+ cfg1.type = CC1101_MSG_DEFINE_CONFIG;
+ memcpy(cfg1.cfg, demo_rx, sizeof(demo_rx));
+ sprintf(cfg1.config_name, "%s", "demo");
+ ret = write(fd, &cfg1, sizeof(cfg1));
+
+delete a register configuration
+...............................
+
+not implemented yet.
+
+type = CC1101_MSG_DEL_CONFIG
+
+define a delay timing configuration
+...................................
+
+With this message userspace can define a table of count min and max
+timeout values and give this table a name. Later when sending a
+packet with csma checking activated, this table can be selected from
+the CC1101_MSG_USER_TYPE_SEND and the timeouts from this table are
+used.
+
+type: CC1101_MSG_DEFINE_DELAY
+
+setup the struct msg_queue_user_delay_data
+
++-------------------------+------------------------------+-----------+---------------+---------------+ +---+
+| CC1101_MSG_DEFINE_DELAY | delay_name[CFG_NAME_MAX_LEN] | int count | int delay_min | int delay_max | .... | |
++-------------------------+------------------------------+-----------+---------------+---------------+ +---+
+
+config_name: 0 terminated string, contains the name of a delay table.
+count: maximal delays
+delay: list of count delays (min/max pairs) in ms
+
+example:
+
+::
+
+ static const struct delay_param delay_table_first[] =
+ {
+ {1, 4},
+ {5, 9},
+ {16, 32},
+ };
+
+ struct delay_table {
+ struct msg_queue_user msg_user;
+ struct msg_delay_data_user delay_data_user;
+ struct delay_param delays[MAX_DELAYS];
+ };
+
+ struct delay_table dt;
+ memset(&dt, 0x0, sizeof(dt));
+ dt.msg_user.type = CC1101_MSG_DEFINE_DELAY;
+ dt.delay_data_user.count = sizeof(delay_table_first) / sizeof(struct delay_param);
+ sprintf(dt.delay_data_user.delay_name, "%s", "delay_first");
+ memcpy(&dt.delays, &delay_table_first, sizeof(delay_table_first));
+ ret = write(fd, &dt, sizeof(dt));
+
+Now you can select this delay table under the name "delay_first" in the
+driver.
+
+set patable
+...........
+
+Use this type for setting up the cc1101 registers 0x3e
+(PATABLE)
+
+setup the struct msg_queue_user_rcv
+
++------------------------+------------+
+| CC1101_MSG_SET_PATABLE | char value |
++------------------------+------------+
+
+value : contains the patable value
+
+send a job
+..........
+
+use this message format:
+
++----------------+------------+ ... +
+| CC1101_MSG_JOB | int job id | n * struct msg_user |
++----------------+------------+ ... +
+
+job id:
+ int, give the job a unique id. The driver sends a CC1101_READ_TYPE_ID
+ message with this ID back to the userspace, when the job is finished.
+
+struct msg_user:
+
++----------+--------------+----------+--------------------------+
+| int type | int preamble | int csma | buf[CC1101_MAX_DATA_LEN] |
++----------+--------------+----------+--------------------------+
+
+with
+
+type : CC1101_MSG_USER_TYPE_
+preamble: 0 = short other long preamble
+csma : -1 no csma,
+ 0 send only
+ >0 try n times to get free channel
+buf : dependend on type contains different content
+
+
+possible message types:
+
+CC1101_MSG_USER_TYPE_SEND
+
+send data in buf, with preamble and csma settings
+buf[0] is the length of the data
+
+CC1101_MSG_USER_TYPE_SET_CFG
+
+set a register configuration with name in buf
+buf contains a 0 terminated string.
+preamble and csma not used, should be 0
+
+CC1101_MSG_USER_TYPE_SET_DELAY
+
+set a delay table with name in buf. This table is used
+when a CC1101_MSG_USER_TYPE_SEND message with csma > 0
+value runs later.
+
+CC1101_MSG_USER_TYPE_SET_PATABLE
+
+patable value in buf[0]
+
+get carrier sense event
+.......................
+
+message format:
+
++------------------------------+
+| CC1101_MSG_GET_CARRIER_SENSE |
++------------------------------+
+
+You get back an event message:
+
+type : CC1101_READ_TYPE_EVENT
+eventid : CC1101_READ_EVENT_ID_CARRIER
+value : value
+reason : CC1101_READ_EVENT_REASON_CALCULATED
+
+see events.
+
+Only enabled, if eventgeneration (CC1101_FLAG_CFG_TRIGGER) is set
+in configuration flags of the current register configuration.
+
+read from driver
+----------------
+
+all messages in read direction start with:
+
++----------+...
+| int type |
++----------+...
+
+types:
+
+rx data
+.......
+
++----------+--------+-----------+----------+-------------------------------+
+| int type | int id | char rssi | char len | char buf[CC1101_MAX_DATA_LEN] |
++----------+--------+-----------+----------+-------------------------------+
+
+ type: CC1101_READ_TYPE_RX
+ id : not used should be 0
+ rssi : contain rssi value
+ len : len of received bytes
+ buf : received data + 2 additional data bytes RSSI and LQI
+
+infos for a finished job
+........................
+
++----------+--------+---------------+-----------------------------------+
+| int type | int id | int errorcode | char data[CC1101_MAX_DATA_LEN -2] |
++----------+--------+---------------+-----------------------------------+
+
+ type : C1101_READ_TYPE_ID
+ id : job id
+ errorcode : CC1101_ECA_*
+
+Error Errorcode data
+all fine 0 none
+channel busy -1 string: config name
+SPI communication error -2 none
+unknown config name -3 string: name of unknown config
+error setting config -4 string: name of config
+unknown delay name -5 string: name of unknown table
+error setting patable -6 none
+
+events
+......
+
++----------+-------------+-----------+-----------+--------------------------------+
+| int type | int eventid | u8 value | u8 reason | char data[CC1101_MAX_DATA_LEN] |
++----------+-------------+-----------+-----------+--------------------------------+
+
+type : CC1101_READ_TYPE_EVENT
+eventid : CC1101_READ_EVENT_ID_*
+value : value
+reason : CC1101_READ_EVENT_REASON_*
+
+links
+=====
+
+[1] datasheet: http://www.ti.com/lit/ds/symlink/cc1101.pdf
+[2] smartRFstudio: http://www.ti.com/tool/SMARTRFTM-STUDIO
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c55b63750757d..fe2c9b8b72a63 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -466,6 +466,17 @@ config PVPANIC
a paravirtualized device provided by QEMU; it lets a virtual machine
(guest) communicate panic events to the host.

+config CC1101
+ tristate "cc1101 device support"
+ depends on SPI
+ help
+ This driver provides support for the Low-Power Sub-1 GHz RF
+ Transceiver chip from Texas Instruments. It provides a
+ simple message based protocol to set chip registers, send
+ and receive packets to/from the chip. For more details
+ see Documentation/misc-devices/cc1101.txt and
+ Documentation/devicetree/bindings/misc/cc1101.txt
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c1860d35dc7e2..ec51e73359c48 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,3 +57,4 @@ obj-y += cardreader/
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_HABANA_AI) += habanalabs/
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
+obj-$(CONFIG_CC1101) += cc1101.o
diff --git a/drivers/misc/cc1101.c b/drivers/misc/cc1101.c
new file mode 100644
index 0000000000000..432a33039ac02
--- /dev/null
+++ b/drivers/misc/cc1101.c
@@ -0,0 +1,2004 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Heiko Schocher <hs@xxxxxxx>
+ * DENX Software Engineering GmbH
+ *
+ */
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/gpio/machine.h>
+#include <linux/of_gpio.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi.h>
+#include <linux/cc1101_user.h>
+
+#include <cc1101.h>
+
+static int preamble_timeout = 360;
+module_param(preamble_timeout, int, 0644);
+MODULE_PARM_DESC(preamble_timeout,
+ "timeout for long preamble in msec");
+
+#define CC1101_TIMEOUT_WAIT_IRQ (5 * HZ)
+
+#define CLASS_NAME "rf868"
+#define DRV_NAME "cc1101"
+#define DRV_VERSION "1.00"
+#define CC1101_MAJOR 247
+
+static int opened;
+static int cc1101_major = CC1101_MAJOR;
+static struct class *cc1101_class;
+static struct device *cc1101_device;
+struct cc1101_data *g_cc1101;
+
+#define BITS_X_WORD 8
+
+#define CC1101_WRITE_SINGLE(A) ((A)&(~0xC0))
+#define CC1101_WRITE_BURST(A) (((A)&(~0xC0)) | 0x40)
+#define CC1101_READ_SINGLE(A) (((A)&(~0xC0)) | 0x80)
+#define CC1101_READ_BURST(A) (((A)&(~0xC0)) | 0xC0)
+
+#define FIFO_BYTES 0x7F
+#define FIFO_UNDER_OVERFLOW 0x80
+
+/* max time in usec, for waiting after chip reset */
+#define CC1101_WAIT_CHIP_RESET 1000000
+
+struct msg_config_data {
+ struct list_head list;
+ char config_name[CFG_NAME_MAX_LEN];
+ char flags; /* CC1101_FLAG_CFG_* */
+ struct config_param *cfg;
+} __packed;
+
+struct msg_delay_data {
+ struct list_head list;
+ struct msg_delay_data_user mddu;
+ struct delay_param *delay;
+} __packed;
+
+/* list of jobs to do */
+struct job_data {
+ struct list_head list;
+ struct job_data_user jobu;
+ struct list_head msg_list;
+} __packed;
+
+/* one message in a job */
+struct msg_data {
+ struct list_head list;
+ struct msg_user user;
+} __packed;
+
+/* list of data to user space */
+struct read_data {
+ struct list_head list;
+ struct read_data_user user;
+} __packed;
+
+struct cc1101_stat {
+ unsigned long flags;
+ int rx_pcts;
+ int rx_bytes;
+ int tx_pcts;
+ int tx_bytes;
+ int tx_miss;
+ int tx_signal;
+ int err_csma;
+ int err_dt_count;
+ int err_rx;
+ int err_rx_len;
+ int err_tx_busy;
+ int err_tx_count;
+ int err_tx_fifo_not_empty;
+ int err_tx_underflow;
+ int err_crc_drop;
+ int poll_reset;
+};
+
+#define CC1101_INIT_MEM 0x01
+#define CC1101_INIT_CHAR_DEV 0x02
+#define CC1101_INIT_CDEV 0x04
+#define CC1101_INIT_CLASS 0x08
+#define CC1101_INIT_DEVICE 0x10
+#define CC1101_INIT_WQ_IRQ 0x20
+#define CC1101_INIT_SYSFS 0x80
+
+struct cc1101_data {
+ /* structs */
+ struct cdev cdev;
+ struct spi_device *spi;
+ wait_queue_head_t poll_wait;
+ wait_queue_head_t wait_tx_irq;
+ struct workqueue_struct *wq_irq;
+ struct work_struct work;
+ struct msg_config_data cfg_list;
+ struct mutex lock_cfg_list;
+ struct msg_delay_data delay_list;
+ struct mutex lock_delay_list;
+ struct msg_delay_data *cur_delay_table;
+ struct job_data job_list;
+ struct mutex lock_write;
+ struct read_data read_list;
+ struct mutex lock_read;
+ /* vars */
+ struct cc1101_stat stat;
+ atomic_t tx_active;
+ struct msg_config_data *config_set;
+ unsigned long init;
+ int irq_ena;
+ int csma_busy;
+ int spi_transfer_error;
+ int newjob;
+ int append_status;
+ int set_rx;
+ /* carrier sense event */
+ int average;
+ u8 cur_val;
+ u8 old_val;
+ int trigger;
+ atomic_t dotimer;
+ atomic_t doirq;
+ int cancel_timer;
+ struct hrtimer event_timer;
+ ktime_t event_delay;
+ /* parameters */
+ u32 freq;
+ u32 gdo0; /* IRQ pin */
+ u32 gdo0_flags;
+ u32 gdo2; /* busy */
+ u32 gdo2_flags;
+ /* buffers */
+ char buf[CC1101_MAX_USER_BUF];
+ u8 __aligned(4) spi_rx_buf[CC1101_MAX_DATA_LEN];
+ u8 __aligned(4) spi_tx_buf[CC1101_MAX_DATA_LEN];
+};
+
+static inline int
+cc1101_get_count_msg(size_t l)
+{
+ return (l - sizeof(struct job_data_user)) / sizeof(struct msg_user);
+}
+
+static inline int
+cc1101_check_count_msg(size_t l)
+{
+ return (l - sizeof(struct job_data_user)) % sizeof(struct msg_user);
+}
+
+static inline struct job_data_user
+*cc1101_get_job_data_user(char *buf)
+{
+ return (struct job_data_user *)&buf[sizeof(struct msg_queue_user)];
+}
+
+static inline struct msg_user
+*cc1101_get_msg_data_user(char *buf)
+{
+ int off;
+
+ off = sizeof(struct job_data_user) + sizeof(struct msg_queue_user);
+ return (struct msg_user *)&buf[off];
+}
+
+/* list functions */
+static int
+cc1101_cfg_add_list(struct cc1101_data *cc1101, struct msg_config_data *new)
+{
+ int ret = 0;
+
+ mutex_lock(&cc1101->lock_cfg_list);
+ list_add_tail(&(new->list), &(cc1101->cfg_list.list));
+ mutex_unlock(&cc1101->lock_cfg_list);
+
+ return ret;
+}
+
+static int cc1101_cfg_rm_list(struct cc1101_data *cc1101, char *name)
+{
+ struct msg_config_data *found, *tmp;
+ int len = strlen(name);
+
+ mutex_lock(&cc1101->lock_cfg_list);
+ list_for_each_entry_safe(found, tmp,
+ &cc1101->cfg_list.list, list) {
+ if (strncmp(found->config_name, name, len) == 0) {
+ kfree(found->cfg);
+ list_del(&found->list);
+ kfree(found);
+ mutex_unlock(&cc1101->lock_cfg_list);
+ return 0;
+ }
+ }
+ mutex_unlock(&cc1101->lock_cfg_list);
+
+ return -EFAULT;
+}
+
+static int
+cc1101_cfg_create_new_list(struct cc1101_data *cc1101,
+ struct msg_config_data_user *user,
+ int len)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct msg_config_data *cfg_data;
+ struct config_param *cfg;
+ int count;
+ int ret;
+
+ cc1101_cfg_rm_list(cc1101, user->config_name);
+ cfg_data = kzalloc(sizeof(struct msg_config_data), GFP_ATOMIC);
+ if (!cfg_data) {
+ dev_err(&spi->dev, "could not alloc msg_config_data\n");
+ return -ENOMEM;
+ }
+
+ count = (len - CFG_NAME_MAX_LEN - 1) / sizeof(struct config_param);
+ /* plus one for end */
+ cfg = kzalloc(sizeof(struct config_param) * (count + 1), GFP_ATOMIC);
+ if (!cfg) {
+ dev_err(&spi->dev, "could not alloc config_param\n");
+ kfree(cfg_data);
+ return -ENOMEM;
+ }
+ strncpy(cfg_data->config_name, user->config_name, CFG_NAME_MAX_LEN);
+ cfg_data->flags = user->flags;
+ memcpy(cfg, &user->cfg, count * sizeof(struct config_param));
+ cfg[count].addr = CFG_END;
+ cfg[count].val = CFG_END;
+
+ cfg_data->cfg = cfg;
+
+ ret = cc1101_cfg_add_list(cc1101, cfg_data);
+
+ return ret;
+}
+
+static struct msg_config_data
+*cc1101_cfg_get_data_list(struct cc1101_data *cc1101, char *name)
+{
+ struct msg_config_data *found, *tmp;
+ int len = strlen(name);
+
+ mutex_lock(&cc1101->lock_cfg_list);
+ list_for_each_entry_safe(found, tmp,
+ &cc1101->cfg_list.list, list) {
+ if (strncmp(found->config_name, name, len) == 0) {
+ mutex_unlock(&cc1101->lock_cfg_list);
+ return found;
+ }
+ }
+
+ mutex_unlock(&cc1101->lock_cfg_list);
+
+ return NULL;
+}
+
+static struct msg_delay_data
+*cc1101_delay_get_data_list(struct cc1101_data *cc1101, char *name)
+{
+ struct msg_delay_data *found, *tmp;
+ int len = strlen(name);
+
+ mutex_lock(&cc1101->lock_delay_list);
+ list_for_each_entry_safe(found, tmp,
+ &cc1101->delay_list.list, list) {
+ if (strncmp(found->mddu.delay_name, name, len) == 0) {
+ mutex_unlock(&cc1101->lock_delay_list);
+ return found;
+ }
+ }
+
+ mutex_unlock(&cc1101->lock_delay_list);
+
+ return NULL;
+}
+
+static int cc1101_delay_rm_list(struct cc1101_data *cc1101, char *name)
+{
+ struct msg_delay_data *found;
+
+ found = cc1101_delay_get_data_list(cc1101, name);
+ if (!found)
+ return 0;
+
+ if (cc1101->cur_delay_table == found)
+ cc1101->cur_delay_table = NULL;
+
+ kfree(found->delay);
+ list_del(&found->list);
+ kfree(found);
+ return 0;
+}
+
+static int
+cc1101_delay_create_new_list(struct cc1101_data *cc1101,
+ struct msg_queue_user_delay_data *user)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct msg_delay_data_user *delay_data_user = &user->delay_data_user;
+ struct msg_delay_data *delay_data;
+ int count;
+ int ret;
+
+ cc1101_delay_rm_list(cc1101, delay_data_user->delay_name);
+ count = delay_data_user->count;
+ delay_data = kzalloc(sizeof(struct msg_delay_data), GFP_ATOMIC);
+ if (!delay_data) {
+ dev_err(&spi->dev, "out of memory\n");
+ return -ENOMEM;
+ }
+
+ delay_data->delay = kzalloc(sizeof(struct delay_param) * count,
+ GFP_ATOMIC);
+ if (!delay_data->delay) {
+ dev_err(&spi->dev, "could not alloc delay_param\n");
+ kfree(delay_data);
+ return -ENOMEM;
+ }
+
+ memcpy(&delay_data->mddu, delay_data_user,
+ sizeof(struct msg_delay_data_user));
+ memcpy(delay_data->delay, &user->delays,
+ count * sizeof(struct delay_param));
+
+ mutex_lock(&cc1101->lock_delay_list);
+ list_add_tail(&(delay_data->list), &(cc1101->delay_list.list));
+ mutex_unlock(&cc1101->lock_delay_list);
+
+ return ret;
+}
+
+static int cc1101_job_add(struct cc1101_data *cc1101, struct job_data *new)
+{
+ int ret = 0;
+
+ mutex_lock(&cc1101->lock_write);
+ list_add_tail(&(new->list), &(cc1101->job_list.list));
+ cc1101->newjob = 1;
+ mutex_unlock(&cc1101->lock_write);
+ queue_work(cc1101->wq_irq, &cc1101->work);
+
+ return ret;
+}
+
+static int cc1101_job_empty(struct cc1101_data *cc1101)
+{
+ int ret = 0;
+
+ mutex_lock(&cc1101->lock_write);
+ ret = list_empty(&cc1101->job_list.list);
+ cc1101->newjob = !ret;
+ mutex_unlock(&cc1101->lock_write);
+
+ return ret;
+}
+
+static struct job_data *cc1101_job_get(struct cc1101_data *cc1101)
+{
+ struct job_data *job;
+ int ret = 0;
+
+ mutex_lock(&cc1101->lock_write);
+ ret = list_empty(&cc1101->job_list.list);
+ cc1101->newjob = !ret;
+ if (ret) {
+ mutex_unlock(&cc1101->lock_write);
+ return NULL;
+ }
+
+ job = list_first_entry(&(cc1101->job_list.list), struct job_data,
+ list);
+ list_del(&(job->list));
+ mutex_unlock(&cc1101->lock_write);
+ kfree(job);
+
+ return job;
+}
+
+static int cc1101_read_add(struct cc1101_data *cc1101, struct read_data *new)
+{
+ int ret = 0;
+
+ mutex_lock(&cc1101->lock_read);
+ list_add_tail(&(new->list), &(cc1101->read_list.list));
+ mutex_unlock(&cc1101->lock_read);
+
+ /* wakeup poll users */
+ wake_up_interruptible(&cc1101->poll_wait);
+
+ return ret;
+}
+
+static int cc1101_read_empty(struct cc1101_data *cc1101)
+{
+ int ret;
+
+ mutex_lock(&cc1101->lock_read);
+ ret = list_empty(&cc1101->read_list.list);
+ mutex_unlock(&cc1101->lock_read);
+
+ return ret;
+}
+
+static struct read_data *cc1101_read_get(struct cc1101_data *cc1101)
+{
+ struct read_data *read;
+
+ mutex_lock(&cc1101->lock_read);
+ if (list_empty(&cc1101->read_list.list)) {
+ mutex_unlock(&cc1101->lock_read);
+ return NULL;
+ }
+
+ read = list_first_entry(&(cc1101->read_list.list), struct read_data,
+ list);
+ list_del(&(read->list));
+ mutex_unlock(&cc1101->lock_read);
+
+ return read;
+}
+
+static int
+cc1101_msg_add(struct cc1101_data *cc1101, struct job_data *job,
+ struct msg_data *new)
+{
+ int ret = 0;
+
+ list_add_tail(&(new->list), &(job->msg_list));
+ return ret;
+}
+
+static struct msg_data
+*cc1101_msg_get(struct cc1101_data *cc1101, struct job_data *job)
+{
+ struct msg_data *tmp;
+
+ if (list_empty(&job->msg_list))
+ return NULL;
+
+ tmp = list_first_entry(&(job->msg_list), struct msg_data, list);
+ return tmp;
+}
+
+static int cc1101_msg_free(struct cc1101_data *cc1101, struct msg_data *msg)
+{
+ list_del(&(msg->list));
+ kfree(msg);
+ return 0;
+}
+
+/* spi functions */
+
+/* The parameters for SPI are fixed as defined from chip */
+static void spi_cc1101_initialize(struct spi_device *spi)
+{
+ /* Configure the SPI bus */
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = BITS_X_WORD;
+ spi_setup(spi);
+}
+
+static int cc1101_spi_transfer(struct cc1101_data *cc1101, u32 len)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct spi_message m;
+ struct spi_transfer t;
+ int ret = 0;
+
+ memset(&t, 0, sizeof(t));
+ t.tx_buf = cc1101->spi_tx_buf;
+ t.rx_buf = cc1101->spi_rx_buf;
+ t.len = len;
+ t.cs_change = 0;
+ if (cc1101->freq)
+ t.speed_hz = cc1101->freq;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret)
+ dev_err(&spi->dev, "spi transfer failed\n");
+ cc1101->spi_transfer_error = ret;
+ return ret;
+}
+
+static int
+cc1101_write_reg(struct cc1101_data *cc1101, int reg, u8 data, u8 *status)
+{
+ struct spi_device *spi = cc1101->spi;
+ int len = 2;
+ int ret = 0;
+
+ cc1101->spi_tx_buf[0] = CC1101_WRITE_SINGLE(reg);
+ cc1101->spi_tx_buf[1] = data;
+ ret = cc1101_spi_transfer(cc1101, len);
+ if (ret < 0) {
+ dev_err(&spi->dev, "write reg failed: %x ret: %d\n", reg,
+ ret);
+ return ret;
+ }
+ *status = cc1101->spi_rx_buf[1];
+ return ret;
+}
+
+static int
+cc1101_write_reg_burst(struct cc1101_data *cc1101, int reg, u8 *data,
+ int count, u8 *status)
+{
+ struct spi_device *spi = cc1101->spi;
+ int len = count + 1;
+ int ret = 0;
+
+ cc1101->spi_tx_buf[0] = CC1101_WRITE_BURST(reg);
+ memcpy(&cc1101->spi_tx_buf[1], data, count);
+ ret = cc1101_spi_transfer(cc1101, len);
+ if (ret < 0) {
+ dev_err(&spi->dev, "write reg burst failed: %x ret: %d\n",
+ reg, ret);
+ return ret;
+ }
+ *status = cc1101->spi_rx_buf[1];
+ return ret;
+}
+
+static int
+cc1101_read_reg(struct cc1101_data *cc1101, int reg, u8 *status, u8 *val)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret = 0;
+
+ if ((reg >= CCxxx0_IOCFG2) && (reg <= CCxxx0_TEST0)) {
+ cc1101->spi_tx_buf[0] = CC1101_READ_SINGLE(reg);
+ } else if ((reg >= CCxxx0_PARTNUM) && (reg <= CCxxx0_RXFIFO)) {
+ cc1101->spi_tx_buf[0] = CC1101_READ_BURST(reg);
+ } else {
+ dev_err(&spi->dev, "no valid register address: %x\n", reg);
+ return -EFAULT;
+ }
+ cc1101->spi_tx_buf[1] = 0;
+ ret = cc1101_spi_transfer(cc1101, 2);
+ if (ret < 0) {
+ dev_err(&spi->dev, "read reg failed: %x ret: %d\n", reg,
+ ret);
+ return ret;
+ }
+ *status = cc1101->spi_rx_buf[0];
+ *val = cc1101->spi_rx_buf[1];
+ return ret;
+}
+
+static int
+cc1101_read_reg_burst(struct cc1101_data *cc1101, int reg, u8 *data,
+ int count, u8 *status)
+{
+ struct spi_device *spi = cc1101->spi;
+ int len = count + 1;
+ int ret = 0;
+
+ memset(cc1101->spi_tx_buf, 0, len);
+ if ((reg >= CCxxx0_PARTNUM) && (reg <= CCxxx0_RXFIFO)) {
+ cc1101->spi_tx_buf[0] = CC1101_READ_BURST(reg);
+ } else {
+ dev_err(&spi->dev, "no valid register address for burst: %x\n",
+ reg);
+ return -EFAULT;
+ }
+ ret = cc1101_spi_transfer(cc1101, len);
+ if (ret < 0) {
+ dev_err(&spi->dev, "read reg burst failed: %x ret: %d\n",
+ reg, ret);
+ return ret;
+ }
+ *status = cc1101->spi_rx_buf[0];
+ memcpy(data, &cc1101->spi_rx_buf[1], count);
+
+ return ret;
+}
+
+static int cc1101_cmd(struct cc1101_data *cc1101, int reg)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret = 0;
+ u8 status;
+
+ cc1101->spi_tx_buf[0] = CC1101_WRITE_SINGLE(reg);
+ cc1101->spi_rx_buf[0] = 0;
+ cc1101->spi_rx_buf[1] = 0;
+ ret = cc1101_spi_transfer(cc1101, 1);
+ if (ret < 0)
+ dev_err(&spi->dev, "command %x failed. ret: %d\n", reg, ret);
+
+ /* status table page 31 */
+ status = cc1101->spi_rx_buf[0];
+
+ return ret;
+}
+
+static void cc1101_sleep(int intervall)
+{
+ if (intervall > 10000) {
+ msleep(intervall / 1000);
+ return;
+ }
+ usleep_range(intervall, intervall);
+}
+
+/* chip functions */
+static int cc1101_start_rx_mode(struct cc1101_data *cc1101)
+{
+ struct spi_device *spi = cc1101->spi;
+
+ /* check, if we are not in Tx Mode */
+ if (atomic_read(&cc1101->tx_active) == 1) {
+ dev_err(&spi->dev, "we are in tx mode\n");
+ return -EBUSY;
+ }
+
+ /* switch to receive mode */
+ cc1101_cmd(cc1101, CCxxx0_SRX);
+
+ return 0;
+}
+
+static int
+cc1101_tx_packet(struct cc1101_data *cc1101, u8 *buf, u8 count, int preamble)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret;
+ u8 state;
+ u8 val;
+
+ if (preamble) {
+ atomic_set(&cc1101->tx_active, 1);
+ /* set into IDLE */
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ /* flush TX FIFO */
+ cc1101_cmd(cc1101, CCxxx0_SFTX);
+ /* enable freq synthesizer */
+ cc1101_cmd(cc1101, CCxxx0_SFSTXON);
+ /* enable TX */
+ cc1101_cmd(cc1101, CCxxx0_STX);
+
+ /*
+ * now cc1101 starts with generating the preamble
+ * wait preamble timeout
+ */
+ cc1101_sleep(preamble_timeout * 1000);
+ }
+
+ ret = cc1101_write_reg_burst(cc1101, CCxxx0_TXFIFO, buf, count,
+ &state);
+ if (ret < 0)
+ return ret;
+
+ if (!preamble) {
+ /* now start the Tx */
+ atomic_set(&cc1101->tx_active, 1);
+ cc1101_cmd(cc1101, CCxxx0_STX);
+ }
+
+ /* Now wait for Tx end IRQ */
+ ret = wait_event_interruptible_timeout(cc1101->wait_tx_irq,
+ !atomic_read(&cc1101->tx_active),
+ CC1101_TIMEOUT_WAIT_IRQ);
+ if (ret > 0) {
+ /* all fine, IRQ arrived */
+ ret = cc1101_read_reg(cc1101, CCxxx0_TXBYTES, &state, &val);
+ if ((ret != 0) || ((val & FIFO_BYTES) != 0)) {
+ dev_err(&spi->dev,
+ "%s: TX fifo underflow val: %x state: %x\n",
+ __func__, val, state);
+ cc1101->stat.err_tx_underflow++;
+ return -EIO;
+ }
+
+ cc1101->stat.tx_pcts++;
+ cc1101->stat.tx_bytes += count;
+ return 0;
+ } else if (ret == 0) {
+ /* timeout ... missing IRQ */
+ dev_err(&spi->dev, "%s IRQ missing!\n", __func__);
+ atomic_set(&cc1101->tx_active, 0);
+ cc1101->stat.tx_miss++;
+ return -EIO;
+ }
+
+ /* Interrupted through signal */
+ atomic_set(&cc1101->tx_active, 0);
+ cc1101->stat.tx_signal++;
+
+ return ret;
+}
+
+/*
+ * function should be called, after GDO0 is cleared
+ * -> end of packet received
+ */
+static int
+cc1101_rx_packet(struct cc1101_data *cc1101, u8 *buf, u8 *sz, u8 *rssi)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret;
+ u8 state;
+ u8 val;
+ u8 p_len;
+
+ ret = cc1101_read_reg(cc1101, CCxxx0_RXBYTES, &state, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & FIFO_UNDER_OVERFLOW) {
+ cc1101->stat.err_rx++;
+ dev_err(&spi->dev, "%s fifo underflow: state: %x val: %x\n",
+ __func__, state, val);
+ cc1101_read_reg(cc1101, CCxxx0_PKTSTATUS, &state, &val);
+ dev_err(&spi->dev, "%s pktstate: %x\n", __func__, val);
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ cc1101_cmd(cc1101, CCxxx0_SFRX);
+ cc1101_start_rx_mode(cc1101);
+ return -EPROTO;
+ } else if (!(val & FIFO_BYTES)) {
+ /* no packet due to filtering */
+ cc1101_read_reg(cc1101, CCxxx0_PKTSTATUS, &state, &val);
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ cc1101_cmd(cc1101, CCxxx0_SFRX);
+ cc1101_start_rx_mode(cc1101);
+ return -ENOENT;
+ }
+
+ ret = cc1101_read_reg(cc1101, CCxxx0_RXFIFO, &state, &p_len);
+ if (ret < 0)
+ return ret;
+
+ if (p_len > *sz) {
+ dev_err(&spi->dev, "%s p_len: %x > buffersize: %x\n",
+ __func__, p_len, *sz);
+ cc1101->stat.err_rx_len++;
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ cc1101_cmd(cc1101, CCxxx0_SFRX);
+ cc1101_start_rx_mode(cc1101);
+ return -ENOSPC;
+ }
+
+ /*
+ * read 2 additional state bytes if
+ * PKTCTRL1 (0x07) APPEND_STATUS is set.
+ * state[0] = RSSI
+ * state[1] = LQI
+ */
+ ret = cc1101_read_reg_burst(cc1101, CCxxx0_RXFIFO, buf,
+ p_len + cc1101->append_status, &state);
+ if (ret < 0)
+ return ret;
+
+ *sz = p_len;
+ cc1101->stat.rx_pcts++;
+ cc1101->stat.rx_bytes += p_len;
+
+ *rssi = 0;
+ if (cc1101->append_status) {
+ *rssi = buf[p_len];
+ if (!(buf[p_len + 1] & CRC_OK)) {
+ cc1101->stat.err_crc_drop++;
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+static int
+cc1101_check_register(struct cc1101_data *cc1101, int reg, u8 *val)
+{
+ struct spi_device *spi = cc1101->spi;
+
+ if (reg == CCxxx0_IOCFG2) {
+ if (*val != 0xe)
+ dev_err(&spi->dev,
+ "reg: %x val: %x not supported. Only mode 0xe allowed.\n",
+ reg, *val);
+ *val = 0xe;
+ }
+ if (reg == CCxxx0_IOCFG1) {
+ if (*val != 0x2e)
+ dev_err(&spi->dev,
+ "reg: %x val: %x not supported. Only mode 0x2e allowed.\n",
+ reg, *val);
+ *val = 0x2e;
+ }
+
+ if (reg == CCxxx0_IOCFG0) {
+ if (*val != 0x6)
+ dev_err(&spi->dev,
+ "reg: %x val: %x not supported. Only mode 0x6 allowed.\n",
+ reg, *val);
+ *val = 0x6;
+ }
+ if (reg == CCxxx0_PKTCTRL1) {
+ cc1101->append_status = 0;
+ /* check if append status bit is set */
+ if ((*val & CCxxx0_PKTCTRL1_APPEND_STATUS) ==
+ CCxxx0_PKTCTRL1_APPEND_STATUS)
+ cc1101->append_status = 2;
+ }
+ if (reg == CCxxx0_PKTCTRL0) {
+ /* check if append status bit is set */
+ if ((*val & 0x30) != 0) {
+ dev_err(&spi->dev,
+ "reg: %x val: %x pkt_format must be 0\n",
+ reg, *val);
+ }
+ if ((*val & 0x03) != 1) {
+ dev_err(&spi->dev,
+ "reg: %x val: %x length config must be 1\n",
+ reg, *val);
+ }
+ }
+
+ if (reg == CCxxx0_MCSM1) {
+ cc1101->set_rx = 0;
+ if ((*val & 0xf) != 0xf) {
+ cc1101->set_rx = 1;
+ dev_err(&spi->dev,
+ "reg: %x val: %x not supported. Try to set Rx mode.\n",
+ reg, *val);
+ }
+ }
+ return 0;
+}
+
+static int cc1101_chip_reset(struct cc1101_data *cc1101)
+{
+ struct spi_device *spi = cc1101->spi;
+ ktime_t ktstart;
+ ktime_t delta_ktime;
+ u32 delta_usecs = 0;
+ u8 status, part;
+
+ /*
+ * remove irq, as after reset default of gdo0 pin is 24MHz
+ * which would stall our cpu.
+ */
+ if (cc1101->irq_ena == 1) {
+ devm_free_irq(&spi->dev, gpio_to_irq(cc1101->gdo0), cc1101);
+ cc1101->irq_ena = 0;
+ }
+
+ /* reset */
+ cc1101_cmd(cc1101, 0);
+ cc1101_cmd(cc1101, CCxxx0_SRES);
+
+ /*
+ * after a reset, we must poll the SO line, until it goes
+ * low, see chapter 10.4 in the manual for the cc1101.
+ *
+ * Unfortunately, we cannot do this, so we read the
+ * part number and check if it is 0.
+ *
+ * This works only for part number == 0, but we do check
+ * the partnumber in this driver, so other supported
+ * partnumbers must be added here too!
+ */
+ cc1101->stat.poll_reset = 0;
+ ktstart = ktime_get();
+ cc1101_read_reg(cc1101, CCxxx0_PARTNUM, &status, &part);
+ while (part != 0) {
+ cc1101->stat.poll_reset++;
+ cc1101_read_reg(cc1101, CCxxx0_PARTNUM, &status, &part);
+ delta_ktime = ktime_sub(ktime_get(), ktstart);
+ delta_usecs = ktime_to_us(delta_ktime);
+ if (delta_usecs > CC1101_WAIT_CHIP_RESET) {
+ dev_dbg(&spi->dev,
+ "%s Timeout waiting for chip reset\n",
+ __func__);
+ return -EIO;
+ }
+ }
+
+ /* after reset we need a new config */
+ cc1101->config_set = NULL;
+ return 0;
+}
+
+static irqreturn_t cc1101_irq(int irq, void *pdata);
+
+static int
+cc1101_apply_config(struct cc1101_data *cc1101, struct msg_config_data *cfg)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct config_param *config = cfg->cfg;
+ int ret = 0;
+ int i;
+ int reg;
+ u8 val;
+
+ /* check if config is already set, so do not set it again. */
+ if (cc1101->config_set) {
+ if (cfg == cc1101->config_set) {
+ dev_dbg(&spi->dev, "config %s already set.\n",
+ cfg->config_name);
+ return -EEXIST;
+ }
+ }
+
+ /*
+ * we reset the chip, as we know the default configuration
+ * of the chip registers. So we need only to set register-
+ * differences when we want a new configuration of the
+ * chip.
+ */
+ ret = cc1101_chip_reset(cc1101);
+ if (ret)
+ return ret;
+
+ i = 0;
+ while (!((config[i].addr == CFG_END) &&
+ (config[i].val == CFG_END))) {
+ u8 status = 0;
+ u8 tmp;
+
+ reg = config[i].addr;
+ val = config[i].val;
+
+ cc1101_check_register(cc1101, reg, &val);
+ ret = cc1101_write_reg(cc1101, reg, val, &status);
+ if (ret != 0)
+ return ret;
+ ret = cc1101_read_reg(cc1101, reg, &status, &tmp);
+ if (ret != 0)
+ return ret;
+
+ if (tmp != val) {
+ dev_err(&spi->dev,
+ "config reg: %x failed: %x != %x\n", reg,
+ tmp, val);
+ return -EIO;
+ }
+ i++;
+ }
+
+ if (!cc1101->irq_ena) {
+ u32 active = (cc1101->gdo0_flags & GPIO_ACTIVE_LOW) ? 0 : 1;
+
+ /*
+ * enable irq only after config is written
+ * else we may have a wrong config for GDO0
+ * default: there is a 24MHz clock output on it
+ */
+ ret = devm_request_irq(&spi->dev, gpio_to_irq(cc1101->gdo0),
+ cc1101_irq,
+ (active ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING),
+ "cc1101", cc1101);
+ if (ret)
+ return -ENODEV;
+
+ cc1101->irq_ena = 1;
+ }
+
+ cc1101->config_set = cfg;
+ return ret;
+}
+
+static int cc1101_detect_chip(struct cc1101_data *cc1101)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret = 0;
+ u8 status, part, vers;
+
+ ret = cc1101_chip_reset(cc1101);
+ if (ret != 0)
+ return ret;
+
+ ret = cc1101_read_reg(cc1101, CCxxx0_PARTNUM, &status, &part);
+ if (ret != 0)
+ return ret;
+
+ ret = cc1101_read_reg(cc1101, CCxxx0_VERSION, &status, &vers);
+ if (ret != 0)
+ return ret;
+
+ if ((part != 0) || (vers != 0x14)) {
+ dev_err(&spi->dev, "chip not found: part: %d version: %d\n",
+ part, vers);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int
+cc1101_startup(struct cc1101_data *cc1101, struct msg_config_data *cfg)
+{
+ int rx_notset;
+
+ rx_notset = cc1101_apply_config(cc1101, cfg);
+ if (rx_notset == -EEXIST)
+ rx_notset = 0;
+
+ atomic_set(&cc1101->tx_active, 0);
+ /* start in rx mode */
+ if (rx_notset == 0)
+ cc1101_start_rx_mode(cc1101);
+
+ return rx_notset;
+}
+
+/* GPIO Interrupt Handler. */
+static irqreturn_t cc1101_irq_gdo2(int irq, void *pdata)
+{
+ struct cc1101_data *cc1101 = (struct cc1101_data *)pdata;
+
+ cc1101->csma_busy = 1;
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t cc1101_irq(int irq, void *pdata)
+{
+ struct cc1101_data *cc1101 = (struct cc1101_data *)pdata;
+
+ if (atomic_read(&cc1101->tx_active) == 1) {
+ atomic_set(&cc1101->tx_active, 0);
+ wake_up(&cc1101->wait_tx_irq);
+ } else {
+ queue_work(cc1101->wq_irq, &cc1101->work);
+ atomic_inc(&cc1101->doirq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* event specific functions */
+static int cc1101_send_event(struct cc1101_data *cc1101, u8 reason)
+{
+ struct read_data *new;
+ struct read_data_user_event *event;
+
+ new = kzalloc(sizeof(struct read_data), GFP_ATOMIC);
+ event = (struct read_data_user_event *)&new->user;
+ event->type = CC1101_READ_TYPE_EVENT;
+ event->eventid = CC1101_READ_EVENT_ID_CARRIER;
+ event->value = cc1101->cur_val;
+ event->reason = reason;
+
+ cc1101->old_val = cc1101->cur_val;
+ return cc1101_read_add(cc1101, new);
+}
+
+static int
+cc1101_check_carrier_sense_event(struct cc1101_data *cc1101, u8 reason)
+{
+ int ret = 0;
+
+ if (reason == CC1101_READ_EVENT_REASON_CALCULATED) {
+ ret = cc1101_send_event(cc1101, reason);
+ return ret;
+ }
+
+ /* if we have no running timer -> do not send event */
+ if (!hrtimer_active(&cc1101->event_timer))
+ return 0;
+
+ if (cc1101->cur_val > cc1101->old_val + 20)
+ ret = cc1101_send_event(cc1101,
+ CC1101_READ_EVENT_REASON_RISING);
+ if (cc1101->cur_val < cc1101->old_val - 20)
+ ret = cc1101_send_event(cc1101,
+ CC1101_READ_EVENT_REASON_FALLING);
+
+ return ret;
+}
+
+static int cc1101_calc_carrier_sense(struct cc1101_data *cc1101, u8 reason)
+{
+ struct spi_device *spi = cc1101->spi;
+ int val;
+ u16 carrier = 0;
+
+ if (atomic_read(&cc1101->tx_active) == 1) {
+ dev_dbg(&spi->dev, "do not calc carrier, as in tx mode\n");
+ return -EBUSY;
+ }
+
+ val = gpio_get_value(cc1101->gdo2);
+ if (val)
+ carrier = 200;
+
+ cc1101->average = cc1101->average - cc1101->cur_val + carrier;
+ cc1101->cur_val = cc1101->average >> 6;
+ cc1101_check_carrier_sense_event(cc1101, reason);
+
+ return 0;
+}
+
+static enum hrtimer_restart
+cc1101_event_timer_callback_nolock(struct hrtimer *timer)
+{
+ struct cc1101_data *cc1101 = container_of(timer, struct cc1101_data,
+ event_timer);
+
+ atomic_inc(&cc1101->dotimer);
+ queue_work(cc1101->wq_irq, &cc1101->work);
+ if (cc1101->cancel_timer)
+ return HRTIMER_NORESTART;
+
+ hrtimer_forward_now(timer, ns_to_ktime(cc1101->event_delay));
+ return HRTIMER_RESTART;
+}
+
+static void cc1101_stop_event_timer(struct cc1101_data *cc1101)
+{
+ cc1101->cancel_timer = 1;
+ hrtimer_cancel(&cc1101->event_timer);
+}
+
+static void cc1101_start_event_timer(struct cc1101_data *cc1101)
+{
+ unsigned int delay = 100;
+ ktime_t softlimit = ms_to_ktime(delay);
+ int ret;
+
+ cc1101->cancel_timer = 0;
+ ret = hrtimer_active(&cc1101->event_timer);
+ if (ret)
+ return;
+
+ cc1101->event_delay = softlimit;
+ hrtimer_start(&cc1101->event_timer, softlimit, HRTIMER_MODE_REL);
+}
+
+/*
+ * check if channel is free (CSMA)
+ *
+ * csma : -1 no csma,
+ * 0 send only
+ * >0 try n times to get free channel
+ */
+static int cc1101_check_csma(struct cc1101_data *cc1101,
+ struct msg_user *msg)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct delay_param *dp;
+ int csma = msg->csma;
+ int val;
+ int wait;
+ int i = 0;
+ int ret;
+ char random;
+
+ if (csma <= 0)
+ return 0;
+
+ /* timeouts in cur_delay_table */
+ mutex_lock(&cc1101->lock_delay_list);
+ if (csma > cc1101->cur_delay_table->mddu.count) {
+ cc1101->stat.err_dt_count++;
+ mutex_unlock(&cc1101->lock_delay_list);
+ return -EFAULT;
+ }
+ dp = cc1101->cur_delay_table->delay;
+ mutex_unlock(&cc1101->lock_delay_list);
+
+ while (i < csma) {
+ /* wait for cc1101->cur_delay_table */
+ get_random_bytes(&random, 1);
+ wait = dp->min + (dp->max - dp->min) * random / 255;
+
+ val = gpio_get_value(cc1101->gdo2);
+ if (val == 0) {
+ cc1101->csma_busy = 0;
+ /*
+ * use ggdo2 as interrupt
+ * if gdo2 goes to 1 we know, that the slot is busy!
+ */
+ ret = devm_request_irq(&spi->dev,
+ gpio_to_irq(cc1101->gdo2),
+ cc1101_irq_gdo2,
+ IRQF_TRIGGER_RISING,
+ "cc1101-gdo2", cc1101);
+ if (ret)
+ return -ENODEV;
+
+ cc1101_sleep(wait * 1000);
+ /* disable gdo2 irq */
+ devm_free_irq(&spi->dev, gpio_to_irq(cc1101->gdo2),
+ cc1101);
+ if (cc1101->csma_busy == 0)
+ return 0;
+ } else {
+ /* slot is busy -> wait */
+ cc1101_sleep(dp->max * 1000);
+ }
+ i++;
+ dp++;
+ }
+
+ cc1101->stat.err_csma++;
+ return -EBUSY;
+}
+
+static int cc1101_handle_message_send(struct cc1101_data *cc1101,
+ struct msg_user *msg)
+{
+ struct spi_device *spi = cc1101->spi;
+ int ret = 0;
+ u8 state;
+ u8 val;
+ int preamble = msg->preamble;
+
+ if ((atomic_read(&cc1101->tx_active) == 1) ||
+ (cc1101->config_set == NULL)) {
+ cc1101->stat.err_tx_busy++;
+ dev_err(&spi->dev, "%s tx not possible %d %p\n", __func__,
+ atomic_read(&cc1101->tx_active), cc1101->config_set);
+ return -EBUSY;
+ }
+
+ /* check if Tx FIFO is empty */
+ ret = cc1101_read_reg(cc1101, CCxxx0_TXBYTES, &state, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val != 0) {
+ cc1101->stat.err_tx_fifo_not_empty++;
+ dev_err(&spi->dev, "%s Tx FIFO not empty val: %x\n",
+ __func__, val);
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ cc1101_cmd(cc1101, CCxxx0_SFRX);
+ cc1101_cmd(cc1101, CCxxx0_SFTX);
+ cc1101_start_rx_mode(cc1101);
+ }
+
+ /* check CSMA */
+ ret = cc1101_check_csma(cc1101, msg);
+ if (ret) {
+ cc1101_start_rx_mode(cc1101);
+ return ret;
+ }
+
+ /* now we can send our message */
+ ret = cc1101_tx_packet(cc1101, msg->buf, msg->buf[0] + 1, preamble);
+
+ if (cc1101->set_rx)
+ /* always go back into rx mode */
+ cc1101_start_rx_mode(cc1101);
+
+ if (ret) {
+ dev_err(&spi->dev, "error sending: %d\n", ret);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int
+cc1101_handle_set_cfg(struct cc1101_data *cc1101, char *config_name)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct msg_config_data *config_data;
+ int ret = 0;
+
+ /* find config from name */
+ config_data = cc1101_cfg_get_data_list(cc1101, config_name);
+ if (!config_data) {
+ dev_err(&spi->dev, "unknown cfg: %s\n", config_name);
+ return -EINVAL;
+ }
+
+ cc1101_stop_event_timer(cc1101);
+
+ /* switch to config */
+ ret = cc1101_startup(cc1101, config_data);
+ if (ret) {
+ dev_err(&spi->dev, "error startup %d cfg: %s\n", ret,
+ config_name);
+ return -EFAULT;
+ }
+
+ /* start event timer */
+ if ((config_data->flags & CC1101_FLAG_CFG_TRIGGER) ==
+ CC1101_FLAG_CFG_TRIGGER)
+ cc1101_start_event_timer(cc1101);
+
+ return 0;
+}
+
+static int
+cc1101_handle_set_delay(struct cc1101_data *cc1101, struct msg_user *msg)
+{
+ struct spi_device *spi = cc1101->spi;
+ unsigned char *name = msg->buf;
+ struct msg_delay_data *delay_data;
+
+ /* find config from name */
+ delay_data = cc1101_delay_get_data_list(cc1101, name);
+ if (!delay_data) {
+ dev_err(&spi->dev, "unknown delay table: %s\n", name);
+ return -EINVAL;
+ }
+
+ /* set table */
+ mutex_lock(&cc1101->lock_delay_list);
+ cc1101->cur_delay_table = delay_data;
+ mutex_unlock(&cc1101->lock_delay_list);
+
+ return 0;
+}
+
+static int
+cc1101_handle_set_patable(struct cc1101_data *cc1101, struct msg_user *msg)
+{
+ struct spi_device *spi = cc1101->spi;
+ u8 state;
+ int ret;
+
+ ret = cc1101_write_reg(cc1101, CCxxx0_PATABLE, msg->buf[0], &state);
+ if (ret) {
+ dev_err(&spi->dev, "error %d set patable: %x %x\n", ret,
+ msg->buf[0], state);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+cc1101_handle_message(struct cc1101_data *cc1101, struct msg_data *msg)
+{
+ struct spi_device *spi = cc1101->spi;
+ struct msg_user *msgu = &msg->user;
+ int type = msgu->type;
+ int ret = 0;
+
+ switch (type) {
+ case CC1101_MSG_USER_TYPE_SEND:
+ ret = cc1101_handle_message_send(cc1101, msgu);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_CFG:
+ ret = cc1101_handle_set_cfg(cc1101,
+ (unsigned char *)msgu->buf);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_DELAY:
+ ret = cc1101_handle_set_delay(cc1101, msgu);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_PATABLE:
+ ret = cc1101_handle_set_patable(cc1101, msgu);
+ break;
+ default:
+ dev_err(&spi->dev, "unknown type: %d\n", type);
+ ret = -EFAULT;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * end job, as an error occurred.
+ * generate the message to userspace
+ */
+static struct read_data
+*cc1101_break_job(struct cc1101_data *cc1101, struct msg_data *msg,
+ struct job_data *job, int retval)
+{
+ struct read_data *new;
+ struct read_data_user_ack *dtu;
+ struct msg_user *msgu = &msg->user;
+ int type = msgu->type;
+
+ /* init the message for the userspace */
+ new = kzalloc(sizeof(struct read_data), GFP_ATOMIC);
+ dtu = (struct read_data_user_ack *)&new->user;
+ dtu->type = CC1101_READ_TYPE_ID;
+ dtu->id = job->jobu.id;
+
+ dtu->errorcode = retval;
+ /* detect errorcode */
+ switch (type) {
+ case CC1101_MSG_USER_TYPE_SEND:
+ dtu->errorcode = CC1101_ECA_SEND;
+ memcpy(dtu->buf, cc1101->config_set->config_name,
+ CFG_NAME_MAX_LEN);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_CFG:
+ if (retval == -EINVAL)
+ dtu->errorcode = CC1101_ECA_CFG_INVAL;
+ else
+ dtu->errorcode = CC1101_ECA_CFG;
+ memcpy(dtu->buf, msgu->buf,
+ CC1101_MAX_DATA_LEN - 2);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_DELAY:
+ dtu->errorcode = CC1101_ECA_DELAY;
+ memcpy(dtu->buf, msgu->buf,
+ CC1101_MAX_DATA_LEN - 2);
+ break;
+ case CC1101_MSG_USER_TYPE_SET_PATABLE:
+ dtu->errorcode = CC1101_ECA_PATABLE;
+ break;
+ }
+
+ /* in errorcase, delete all following messages */
+ while (msg != NULL) {
+ cc1101_msg_free(cc1101, msg);
+ msg = cc1101_msg_get(cc1101, job);
+ }
+
+ return new;
+}
+
+static int
+cc1101_handle_job(struct cc1101_data *cc1101, struct job_data *job)
+{
+ struct read_data *new = NULL;
+ struct read_data_user *dtu;
+ struct msg_data *msg;
+ int id = job->jobu.id;
+ int ret = 0;
+
+ msg = cc1101_msg_get(cc1101, job);
+ while (msg) {
+ /* handle message */
+ ret = cc1101_handle_message(cc1101, msg);
+ if (ret != 0) {
+ new = cc1101_break_job(cc1101, msg, job, ret);
+ msg = NULL;
+ } else {
+ cc1101_msg_free(cc1101, msg);
+ msg = cc1101_msg_get(cc1101, job);
+ }
+ }
+
+ if (!new) {
+ /* No error, Ack to userspace */
+ new = kzalloc(sizeof(struct read_data), GFP_ATOMIC);
+ dtu = &new->user;
+ dtu->type = CC1101_READ_TYPE_ID;
+ dtu->id = id;
+ }
+ if (cc1101->spi_transfer_error) {
+ struct read_data_user_ack *dua = (struct read_data_user_ack *)&new->user;
+
+ /* SPI communication problem */
+ dua->errorcode = CC1101_ECA_SPI_COM;
+ }
+ cc1101_read_add(cc1101, new);
+
+ return ret;
+}
+
+/* sysFS functions */
+static ssize_t
+cc1101_stat_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct cc1101_data *cc1101 = g_cc1101;
+ struct cc1101_stat *st = &cc1101->stat;
+
+ return sprintf(buf, "%d %d %d %d %d %d %d %d\n",
+ st->rx_pcts,
+ st->rx_bytes,
+ st->tx_pcts,
+ st->tx_bytes,
+ st->tx_miss,
+ st->tx_signal,
+ cc1101->append_status,
+ cc1101->set_rx);
+}
+static DEVICE_ATTR_RO(cc1101_stat);
+
+static ssize_t
+cc1101_stat_error_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct cc1101_data *cc1101 = g_cc1101;
+ struct cc1101_stat *st = &cc1101->stat;
+
+ return sprintf(buf, "%d %d %d %d %d %d %d %d %d %d\n",
+ st->err_csma,
+ st->err_dt_count,
+ st->err_rx,
+ st->err_rx_len,
+ st->err_tx_fifo_not_empty,
+ st->err_tx_count,
+ st->err_tx_busy,
+ st->err_tx_underflow,
+ st->err_crc_drop,
+ st->poll_reset);
+}
+static DEVICE_ATTR_RO(cc1101_stat_error);
+
+static int cc1101_create_sysfs(struct cc1101_data *cc1101)
+{
+ int ret = 0;
+
+ ret = device_create_file(cc1101_device, &dev_attr_cc1101_stat);
+ if (!ret)
+ cc1101->stat.flags |= 0x01;
+
+ ret = device_create_file(cc1101_device, &dev_attr_cc1101_stat_error);
+ if (!ret)
+ cc1101->stat.flags |= 0x02;
+
+ return ret;
+}
+
+static int cc1101_delete_sysfs(struct cc1101_data *cc1101)
+{
+ int ret = 0;
+
+ if ((cc1101->stat.flags & 0x01) == 0x01)
+ device_remove_file(cc1101_device, &dev_attr_cc1101_stat);
+
+ if ((cc1101->stat.flags & 0x02) == 0x02)
+ device_remove_file(cc1101_device,
+ &dev_attr_cc1101_stat_error);
+ return ret;
+}
+
+/* device functions */
+static int cc1101_open(struct inode *inodep, struct file *filep)
+{
+ struct cc1101_data *cc1101 = g_cc1101;
+ struct spi_device *spi = cc1101->spi;
+ int ret;
+
+ if (opened)
+ return -EBUSY;
+
+ atomic_set(&cc1101->tx_active, 0);
+ filep->private_data = cc1101;
+ spi_cc1101_initialize(cc1101->spi);
+ ret = cc1101_detect_chip(cc1101);
+ if (ret != 0) {
+ dev_err(&spi->dev, "%s: could not find chip\n", DRV_NAME);
+ return ret;
+ }
+
+ cc1101_cmd(cc1101, CCxxx0_SIDLE);
+ cc1101_cmd(cc1101, CCxxx0_SFRX);
+ cc1101_cmd(cc1101, CCxxx0_SFTX);
+
+ hrtimer_init(&cc1101->event_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ cc1101->event_timer.function = cc1101_event_timer_callback_nolock;
+ cc1101_stop_event_timer(cc1101);
+ opened++;
+ return 0;
+}
+
+static ssize_t
+cc1101_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
+{
+ struct cc1101_data *cc1101 = filep->private_data;
+ struct read_data *read;
+ struct read_data_user *user;
+ int ret;
+ int count = 0;
+ int pcklen = sizeof(struct read_data_user);
+
+ read = cc1101_read_get(cc1101);
+ while (read != NULL && (len >= count + pcklen)) {
+ user = &read->user;
+ ret = copy_to_user(&buffer[count], user, pcklen);
+ kfree(read);
+ if (ret != 0)
+ return -EFAULT;
+
+ count += pcklen;
+ read = cc1101_read_get(cc1101);
+ }
+
+ return count;
+}
+
+static ssize_t
+cc1101_write(struct file *filep, const char *buffer, size_t len,
+ loff_t *offset)
+{
+ struct cc1101_data *cc1101 = filep->private_data;
+ struct spi_device *spi = cc1101->spi;
+ int ret;
+ struct msg_queue_user *msg;
+ struct msg_queue_user_config_data *config_data;
+ struct msg_queue_user_delay_data *dd;
+ struct msg_queue_user_rcv *rcv;
+ struct msg_queue_user_patable_data *pa;
+ struct job_data_user *user;
+ struct job_data *job;
+ struct msg_user *msg_user;
+ struct msg_data *msg_data;
+ int count;
+ int i;
+ u8 state;
+ u8 reason;
+ size_t l;
+
+ if (len > CC1101_MAX_USER_BUF)
+ return -ENOMEM;
+
+ ret = copy_from_user(&cc1101->buf[0], buffer, len);
+ if (ret)
+ return -EFAULT;
+
+ msg = (struct msg_queue_user *)cc1101->buf;
+ switch (msg->type) {
+ case CC1101_MSG_RCV:
+ /* set config */
+ rcv = (struct msg_queue_user_rcv *)cc1101->buf;
+ ret = cc1101_handle_set_cfg(cc1101,
+ rcv->config_name);
+ break;
+ case CC1101_MSG_DEFINE_CONFIG:
+ config_data = (struct msg_queue_user_config_data *)cc1101->buf;
+ ret = cc1101_cfg_create_new_list(cc1101,
+ &config_data->config_data_user,
+ len - sizeof(int));
+ break;
+ case CC1101_MSG_DEFINE_DELAY:
+ dd = (struct msg_queue_user_delay_data *)cc1101->buf;
+ ret = cc1101_delay_create_new_list(cc1101, dd);
+ break;
+ case CC1101_MSG_GET_CARRIER_SENSE:
+ reason = CC1101_READ_EVENT_REASON_CALCULATED;
+ ret = cc1101_check_carrier_sense_event(cc1101, reason);
+ break;
+ case CC1101_MSG_SET_PATABLE:
+ /* set patable */
+ pa = (struct msg_queue_user_patable_data *)cc1101->buf;
+ ret = cc1101_write_reg(cc1101, CCxxx0_PATABLE,
+ pa->patable_data_user.val, &state);
+ if (ret)
+ dev_err(&spi->dev, "error %d set patable: %x %x\n",
+ ret, pa->patable_data_user.val, state);
+ break;
+ case CC1101_MSG_JOB:
+ l = len - sizeof(struct msg_queue_user);
+ if (cc1101_check_count_msg(l)) {
+ dev_err(&spi->dev, "len: %d l: %d %d %d %d\n",
+ len, l, sizeof(struct msg_queue_user),
+ sizeof(struct job_data_user),
+ sizeof(struct msg_user));
+ return -EINVAL;
+ }
+ count = cc1101_get_count_msg(l);
+ user = cc1101_get_job_data_user(cc1101->buf);
+ job = kzalloc(sizeof(struct job_data), GFP_ATOMIC);
+ if (!job) {
+ dev_err(&spi->dev, "error job create, no mem\n");
+ return -ENOMEM;
+ }
+ job->jobu.id = user->id;
+ INIT_LIST_HEAD(&job->msg_list);
+ /* get now messages */
+ msg_user = cc1101_get_msg_data_user(cc1101->buf);
+ for (i = 0; i < count; i++) {
+ msg_data = kzalloc(sizeof(struct msg_data),
+ GFP_ATOMIC);
+ memcpy(&msg_data->user, msg_user,
+ sizeof(struct msg_user));
+ /* add to job list */
+ cc1101_msg_add(cc1101, job, msg_data);
+ msg_user++;
+ }
+ /* add job to job list */
+ cc1101_job_add(cc1101, job);
+ break;
+ default:
+ dev_err(&spi->dev, "unknown type: %d\n", msg->type);
+ ret = -EINVAL;
+ }
+
+ if (ret == 0)
+ return len;
+
+ return ret;
+}
+
+static int cc1101_release(struct inode *inodep, struct file *filep)
+{
+ struct cc1101_data *cc1101 = filep->private_data;
+ struct spi_device *spi = cc1101->spi;
+ struct read_data *rd;
+
+ if (!opened)
+ return 0;
+
+ /* wait for finishing all jobs */
+ while (!cc1101_job_empty(cc1101))
+ msleep(10);
+
+ cc1101_stop_event_timer(cc1101);
+
+ /* delete all messages to userspace */
+ rd = cc1101_read_get(cc1101);
+ while (rd != NULL)
+ rd = cc1101_read_get(cc1101);
+
+ atomic_set(&cc1101->dotimer, 0);
+ atomic_set(&cc1101->doirq, 0);
+ devm_free_irq(&spi->dev, gpio_to_irq(cc1101->gdo0), cc1101);
+ cc1101->irq_ena = 0;
+ opened--;
+
+ return 0;
+}
+
+static unsigned int
+cc1101_poll(struct file *filep, poll_table *wait)
+{
+ struct cc1101_data *cc1101 = filep->private_data;
+ unsigned int mask = 0;
+
+ poll_wait(filep, &cc1101->poll_wait, wait);
+ if (!cc1101_read_empty(cc1101))
+ mask |= POLLIN | POLLRDNORM;
+ return mask;
+}
+
+static void cc1101_irq_work(struct cc1101_data *cc1101)
+{
+ struct read_data *new;
+ struct read_data_user *dt;
+ int ret;
+
+ /* called if rx data is there */
+ /* get data and save it in buffer */
+ new = kzalloc(sizeof(struct read_data), GFP_ATOMIC);
+ dt = &new->user;
+ dt->type = CC1101_READ_TYPE_RX;
+ dt->len = CC1101_MAX_DATA_LEN;
+ ret = cc1101_rx_packet(cc1101, dt->buf, &dt->len, &dt->rssi);
+ if (ret) {
+ kfree(new);
+ new = NULL;
+ }
+ if (new)
+ cc1101_read_add(cc1101, new);
+
+ /* we must enable rx mode again */
+ if (cc1101->set_rx)
+ cc1101_start_rx_mode(cc1101);
+}
+
+static void cc1101_work(struct work_struct *ws)
+{
+ struct cc1101_data *cc1101 = container_of(ws, struct cc1101_data,
+ work);
+ struct job_data *job;
+
+ while (atomic_read(&cc1101->dotimer) || atomic_read(&cc1101->doirq)) {
+ if (atomic_read(&cc1101->doirq)) {
+ cc1101_irq_work(cc1101);
+ atomic_dec(&cc1101->doirq);
+ }
+ if (atomic_read(&cc1101->dotimer)) {
+ cc1101_calc_carrier_sense(cc1101,
+ CC1101_READ_EVENT_REASON_CYCLE);
+ atomic_dec(&cc1101->dotimer);
+ }
+ }
+ job = cc1101_job_get(cc1101);
+ if (job)
+ cc1101_handle_job(cc1101, job);
+}
+
+static const struct file_operations cc1101_fops = {
+ .open = cc1101_open,
+ .read = cc1101_read,
+ .write = cc1101_write,
+ .release = cc1101_release,
+ .poll = cc1101_poll,
+};
+
+static void cc1101_free(struct cc1101_data *cc1101)
+{
+ if ((cc1101->init & CC1101_INIT_SYSFS) == CC1101_INIT_SYSFS)
+ cc1101_delete_sysfs(cc1101);
+ if ((cc1101->init & CC1101_INIT_WQ_IRQ) == CC1101_INIT_WQ_IRQ) {
+ flush_workqueue(cc1101->wq_irq);
+ destroy_workqueue(cc1101->wq_irq);
+ }
+ if ((cc1101->init & CC1101_INIT_DEVICE) == CC1101_INIT_DEVICE)
+ device_destroy(cc1101_class, MKDEV(cc1101_major, 0));
+ if ((cc1101->init & CC1101_INIT_CLASS) == CC1101_INIT_CLASS)
+ class_destroy(cc1101_class);
+ if ((cc1101->init & CC1101_INIT_CDEV) == CC1101_INIT_CDEV)
+ cdev_del(&cc1101->cdev);
+ if ((cc1101->init & CC1101_INIT_CHAR_DEV) == CC1101_INIT_CHAR_DEV)
+ unregister_chrdev(cc1101_major, DRV_NAME);
+
+ g_cc1101 = NULL;
+}
+
+static int cc1101_probe(struct spi_device *spi)
+{
+ dev_t dev = MKDEV(CC1101_MAJOR, 0);
+ struct cc1101_data *cc1101;
+ u32 flags;
+ int ret = 0;
+ int freq = 0;
+
+ cc1101 = devm_kzalloc(&spi->dev, sizeof(*cc1101), GFP_ATOMIC);
+ if (!cc1101)
+ return -ENOMEM;
+
+ cc1101->init |= CC1101_INIT_MEM;
+ g_cc1101 = cc1101;
+
+ cc1101->spi = spi;
+ spi_set_drvdata(spi, cc1101);
+
+ if (!spi->dev.of_node) {
+ dev_err(&spi->dev, "no DTS info\n");
+ cc1101_free(cc1101);
+ return -ENODEV;
+ }
+
+ if (!freq)
+ of_property_read_u32(spi->dev.of_node,
+ "freq", &cc1101->freq);
+
+ cc1101->gdo0 = of_get_gpio_flags(spi->dev.of_node, 0, &flags);
+ cc1101->gdo0_flags = flags;
+ if (!gpio_is_valid(cc1101->gdo0)) {
+ dev_err(&spi->dev, "irq GPIO: %d\n",
+ cc1101->gdo0);
+ cc1101_free(cc1101);
+ return -EINVAL;
+ }
+
+ cc1101->gdo2 = of_get_gpio_flags(spi->dev.of_node, 1, &flags);
+ cc1101->gdo2_flags = flags;
+ if (!gpio_is_valid(cc1101->gdo2)) {
+ dev_err(&spi->dev, "GDO2 GPIO: %d\n",
+ cc1101->gdo0);
+ cc1101_free(cc1101);
+ return -EINVAL;
+ }
+
+
+ ret = devm_gpio_request(&spi->dev, cc1101->gdo0, "cc1101-irq");
+ if (ret) {
+ dev_err(&spi->dev, "gpio %d cannot be acquired\n",
+ cc1101->gdo0);
+ cc1101_free(cc1101);
+ return -ENODEV;
+ }
+
+ ret = devm_gpio_request(&spi->dev, cc1101->gdo2, "cc1101-gdo2");
+ if (ret) {
+ dev_err(&spi->dev, "gpio %d cannot be acquired\n",
+ cc1101->gdo2);
+ cc1101_free(cc1101);
+ return -ENODEV;
+ }
+
+ /*
+ * Get the GPIO used as interrupt. The Slave raises
+ * an interrupt when receive data is there.
+ */
+ gpio_direction_input(cc1101->gdo0);
+ gpio_direction_input(cc1101->gdo2);
+
+ cc1101_major = register_chrdev(0, DRV_NAME, &cc1101_fops);
+ if (cc1101_major < 0) {
+ dev_err(&spi->dev, "Could not get major %d\n", cc1101_major);
+ cc1101_free(cc1101);
+ return cc1101_major;
+ }
+ cc1101->init |= CC1101_INIT_CHAR_DEV;
+
+ cdev_init(&cc1101->cdev, &cc1101_fops);
+ ret = cdev_add(&cc1101->cdev, dev, 1);
+ if (ret) {
+ dev_err(&spi->dev, "ccdev init %d\n", ret);
+ cc1101_free(cc1101);
+ return ret;
+ }
+ cc1101->init |= CC1101_INIT_CDEV;
+
+ /* Register the device class */
+ cc1101_class = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(cc1101_class)) {
+ cc1101_free(cc1101);
+ return PTR_ERR(cc1101_class);
+ }
+ cc1101->init |= CC1101_INIT_CLASS;
+
+ /* Register the device driver */
+ cc1101_device = device_create(cc1101_class, NULL,
+ MKDEV(cc1101_major, 0),
+ cc1101, DRV_NAME);
+ if (IS_ERR(cc1101_device)) {
+ cc1101_free(cc1101);
+ return PTR_ERR(cc1101_device);
+ }
+ cc1101->init |= CC1101_INIT_DEVICE;
+
+ atomic_set(&cc1101->dotimer, 0);
+ atomic_set(&cc1101->doirq, 0);
+ INIT_WORK(&cc1101->work, cc1101_work);
+ init_waitqueue_head(&cc1101->wait_tx_irq);
+ /* stuff from userspace */
+ init_waitqueue_head(&cc1101->poll_wait);
+ INIT_LIST_HEAD(&cc1101->cfg_list.list);
+ mutex_init(&cc1101->lock_cfg_list);
+ INIT_LIST_HEAD(&cc1101->delay_list.list);
+ mutex_init(&cc1101->lock_delay_list);
+ INIT_LIST_HEAD(&cc1101->job_list.list);
+ mutex_init(&cc1101->lock_write);
+ /* stuff to userspace */
+ INIT_LIST_HEAD(&cc1101->read_list.list);
+ mutex_init(&cc1101->lock_read);
+ cc1101->wq_irq = alloc_workqueue("cc1101_wq_irq",
+ WQ_HIGHPRI | WQ_FREEZABLE |
+ WQ_MEM_RECLAIM, 0);
+ if (!cc1101->wq_irq) {
+ cc1101_free(cc1101);
+ return -ENOMEM;
+ }
+ cc1101->init |= CC1101_INIT_WQ_IRQ;
+
+ ret = cc1101_create_sysfs(cc1101);
+ if (ret) {
+ cc1101_free(cc1101);
+ return ret;
+ }
+ cc1101->init |= CC1101_INIT_SYSFS;
+
+ dev_info(&spi->dev, "%s version %s initialized, major: %d freq: %d preamble_timeout: %d %p\n",
+ DRV_NAME, DRV_VERSION, cc1101_major, cc1101->freq,
+ preamble_timeout, g_cc1101);
+
+ return ret;
+};
+
+static int cc1101_remove(struct spi_device *spi)
+{
+ struct cc1101_data *cc1101 = spi_get_drvdata(spi);
+ int ret = 0;
+
+ cc1101_free(cc1101);
+ return ret;
+};
+
+static const struct spi_device_id cc1101_ids[] = {
+ {DRV_NAME, 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cc1101_ids);
+
+static const struct of_device_id cc1101_of_table[] = {
+ { .compatible = "ti,cc1101" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cc1101_of_table);
+
+static struct spi_driver cc1101_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = cc1101_of_table,
+ .bus = &spi_bus_type,
+ },
+ .id_table = cc1101_ids,
+ .probe = cc1101_probe,
+ .remove = cc1101_remove,
+};
+module_spi_driver(cc1101_driver);
+
+MODULE_DESCRIPTION("TI cc1101 driver");
+MODULE_AUTHOR("Heiko Schocher hs@xxxxxxx");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/cc1101.h b/drivers/misc/cc1101.h
new file mode 100644
index 0000000000000..4877a5cf836f7
--- /dev/null
+++ b/drivers/misc/cc1101.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Heiko Schocher <hs@xxxxxxx>
+ * DENX Software Engineering GmbH
+ *
+ */
+#ifndef _CC1101_H
+#define _CC1101_H
+
+/* Strobe commands */
+
+// Reset chip.
+#define CCxxx0_SRES 0x30
+/*
+ * Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1).
+ * If in RX/TX: Go to a wait state where only the synthesizer is
+ * running (for quick RX / TX turnaround).
+ */
+#define CCxxx0_SFSTXON 0x31
+// Turn off crystal oscillator.
+#define CCxxx0_SXOFF 0x32
+/*
+ * Calibrate frequency synthesizer and turn it off
+ *(enables quick start).
+ */
+#define CCxxx0_SCAL 0x33
+/*
+ * Enable RX. Perform calibration first if coming from IDLE and
+ * MCSM0.FS_AUTOCAL=1.
+ */
+#define CCxxx0_SRX 0x34
+/*
+ * In IDLE state: Enable TX. Perform calibration first if
+ * MCSM0.FS_AUTOCAL=1. If in RX state and CCA is enabled:
+ * Only go to TX if channel is clear.
+ */
+#define CCxxx0_STX 0x35
+/*
+ * Exit RX / TX, turn off frequency synthesizer and exit
+ * Wake-On-Radio mode if applicable.
+ */
+#define CCxxx0_SIDLE 0x36
+// Perform AFC adjustment of the frequency synthesizer
+#define CCxxx0_SAFC 0x37
+// Start automatic RX polling sequence (Wake-on-Radio)
+#define CCxxx0_SWOR 0x38
+// Enter power down mode when CSn goes high.
+#define CCxxx0_SPWD 0x39
+// Flush the RX FIFO buffer.
+#define CCxxx0_SFRX 0x3A
+// Flush the TX FIFO buffer.
+#define CCxxx0_SFTX 0x3B
+// Reset real time clock.
+#define CCxxx0_SWORRST 0x3C
+/*
+ * No operation. May be used to pad strobe commands to two
+ * bytes for simpler software.
+ */
+#define CCxxx0_SNOP 0x3D
+
+#define CCxxx0_PARTNUM 0x30
+#define CCxxx0_VERSION 0x31
+#define CCxxx0_FREQEST 0x32
+#define CCxxx0_LQI 0x33
+#define CCxxx0_RSSI 0x34
+#define CCxxx0_MARCSTATE 0x35
+#define CCxxx0_WORTIME1 0x36
+#define CCxxx0_WORTIME0 0x37
+#define CCxxx0_PKTSTATUS 0x38
+#define CCxxx0_VCO_VC_DAC 0x39
+#define CCxxx0_TXBYTES 0x3A
+#define CCxxx0_RXBYTES 0x3B
+#define CCxxx0_RCCTRL1_STATUS 0x3C
+#define CCxxx0_RCCTRL0_STATUS 0x3D
+
+#define CCxxx0_PATABLE 0x3E
+#define CCxxx0_TXFIFO 0x3F
+#define CCxxx0_RXFIFO 0x3F
+
+/* main state machine mode in status byte */
+#define MSMM 0x70
+#define IS_RXFIFO_OVERFLOW(status) (((status) & (0x80)) == 0x80)
+#define IS_TXFIFO_UNDERFLOW(status) (((status) & (0x80)) == 0x80)
+
+#define CRC_OK 0x80
+
+#define CCxxx0_PKTCTRL1_APPEND_STATUS 0x04
+
+#endif /* _CC1101_H */
diff --git a/include/uapi/linux/cc1101_user.h b/include/uapi/linux/cc1101_user.h
new file mode 100644
index 0000000000000..20dca5271a9ad
--- /dev/null
+++ b/include/uapi/linux/cc1101_user.h
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Heiko Schocher <hs@xxxxxxx>
+ * DENX Software Engineering GmbH
+ *
+ */
+#ifndef _CC1101_UAPI_H
+#define _CC1101_UAPI_H
+
+// GDO2 output pin configuration
+#define CCxxx0_IOCFG2 0x00
+// GDO1 output pin configuration
+#define CCxxx0_IOCFG1 0x01
+// GDO0 output pin configuration
+#define CCxxx0_IOCFG0 0x02
+// RX FIFO and TX FIFO thresholds
+#define CCxxx0_FIFOTHR 0x03
+// Sync word, high byte
+#define CCxxx0_SYNC1 0x04
+// Sync word, low byte
+#define CCxxx0_SYNC0 0x05
+// Packet length
+#define CCxxx0_PKTLEN 0x06
+// Packet automation control
+#define CCxxx0_PKTCTRL1 0x07
+// Packet automation control
+#define CCxxx0_PKTCTRL0 0x08
+// Device address
+#define CCxxx0_ADDR 0x09
+// Channel number
+#define CCxxx0_CHANNR 0x0A
+// Frequency synthesizer control
+#define CCxxx0_FSCTRL1 0x0B
+// Frequency synthesizer control
+#define CCxxx0_FSCTRL0 0x0C
+// Frequency control word, high byte
+#define CCxxx0_FREQ2 0x0D
+// Frequency control word, middle byte
+#define CCxxx0_FREQ1 0x0E
+// Frequency control word, low byte
+#define CCxxx0_FREQ0 0x0F
+// Modem configuration
+#define CCxxx0_MDMCFG4 0x10
+// Modem configuration
+#define CCxxx0_MDMCFG3 0x11
+// Modem configuration
+#define CCxxx0_MDMCFG2 0x12
+// Modem configuration
+#define CCxxx0_MDMCFG1 0x13
+// Modem configuration
+#define CCxxx0_MDMCFG0 0x14
+// Modem deviation setting
+#define CCxxx0_DEVIATN 0x15
+// Main Radio Control State Machine configuration
+#define CCxxx0_MCSM2 0x16
+// Main Radio Control State Machine configuration
+#define CCxxx0_MCSM1 0x17
+// Main Radio Control State Machine configuration
+#define CCxxx0_MCSM0 0x18
+// Frequency Offset Compensation configuration
+#define CCxxx0_FOCCFG 0x19
+// Bit Synchronization configuration
+#define CCxxx0_BSCFG 0x1A
+// AGC control
+#define CCxxx0_AGCCTRL2 0x1B
+// AGC control
+#define CCxxx0_AGCCTRL1 0x1C
+// AGC control
+#define CCxxx0_AGCCTRL0 0x1D
+// High byte Event 0 timeout
+#define CCxxx0_WOREVT1 0x1E
+// Low byte Event 0 timeout
+#define CCxxx0_WOREVT0 0x1F
+// Wake On Radio control
+#define CCxxx0_WORCTRL 0x20
+// Front end RX configuration
+#define CCxxx0_FREND1 0x21
+// Front end TX configuration
+#define CCxxx0_FREND0 0x22
+// Frequency synthesizer calibration
+#define CCxxx0_FSCAL3 0x23
+// Frequency synthesizer calibration
+#define CCxxx0_FSCAL2 0x24
+// Frequency synthesizer calibration
+#define CCxxx0_FSCAL1 0x25
+// Frequency synthesizer calibration
+#define CCxxx0_FSCAL0 0x26
+// RC oscillator configuration
+#define CCxxx0_RCCTRL1 0x27
+// RC oscillator configuration
+#define CCxxx0_RCCTRL0 0x28
+// Frequency synthesizer calibration control
+#define CCxxx0_FSTEST 0x29
+// Production test
+#define CCxxx0_PTEST 0x2A
+// AGC test
+#define CCxxx0_AGCTEST 0x2B
+// Various test settings
+#define CCxxx0_TEST2 0x2C
+// Various test settings
+#define CCxxx0_TEST1 0x2D
+// Various test settings
+#define CCxxx0_TEST0 0x2E
+// RSSI
+#define CCxxx0_RSSI 0x34
+// PA Table
+#define CCxxx0_PATABLE 0x3E
+
+#define CFG_NAME_MAX_LEN 20
+/* 1 byte length + max length 64 + 2 bytes status */
+#define CC1101_MAX_DATA_LEN (1 + 64 + 2)
+
+#define CC1101_MAX_USER_BUF 1024
+
+/* Message IDs */
+#define CC1101_MSG_RCV 2
+#define CC1101_MSG_DEFINE_CONFIG 3
+#define CC1101_MSG_DEL_CONFIG 4
+#define CC1101_MSG_JOB 5
+#define CC1101_MSG_DEFINE_DELAY 6
+#define CC1101_MSG_SET_PATABLE 7
+#define CC1101_MSG_GET_CARRIER_SENSE 8
+
+struct __attribute__ ((packed)) msg_queue_user {
+ int type; /* CC1101_MSG_SET_ */
+};
+
+/* CC1101_MSG_DEFINE_CONFIG */
+struct __attribute__ ((packed)) config_param {
+ char addr;
+ char val;
+};
+#define CFG_END 0x0
+
+#define CC1101_FLAG_CFG_TRIGGER 0x01
+
+struct __attribute__ ((packed)) msg_config_data_user {
+ char config_name[CFG_NAME_MAX_LEN];
+ char flags; /* CC1101_FLAG_CFG_* */
+ struct config_param cfg;
+};
+
+struct __attribute__ ((packed)) msg_queue_user_config_data {
+ struct msg_queue_user msg_user;
+ struct msg_config_data_user config_data_user;
+};
+
+/* CC1101_MSG_DEFINE_DELAY */
+struct __attribute__ ((packed)) delay_param {
+ int min;
+ int max;
+};
+
+struct __attribute__ ((packed)) msg_delay_data_user {
+ char delay_name[CFG_NAME_MAX_LEN];
+ int count;
+};
+
+struct __attribute__ ((packed)) msg_queue_user_delay_data {
+ struct msg_queue_user msg_user;
+ struct msg_delay_data_user delay_data_user;
+ struct delay_param delays;
+};
+
+/* CC1101_MSG_SET_PATABLE */
+struct __attribute__ ((packed)) msg_patable_data_user {
+ char val;
+};
+
+struct __attribute__ ((packed)) msg_queue_user_patable_data {
+ struct msg_queue_user msg_user;
+ struct msg_patable_data_user patable_data_user;
+};
+
+/* CC1101_MSG_RCV */
+struct __attribute__ ((packed)) msg_queue_user_rcv {
+ struct msg_queue_user msg_user;
+ char config_name[CFG_NAME_MAX_LEN];
+};
+
+/* CC1101_MSG_SEND */
+struct __attribute__ ((packed)) msg_queue_user_send {
+ struct msg_queue_user msg_user;
+ int preamble; /* 0 short, other long */
+ int csma; /* -1 = no csma,
+ * 0 = send,
+ * try csma times with delays defines from
+ * currentdelay table.
+ */
+ char buf[CC1101_MAX_DATA_LEN]; /* first byte is len */
+};
+
+#define CC1101_READ_TYPE_RX 0 /* Rx packet */
+#define CC1101_READ_TYPE_ID 1 /* ID ack tx */
+#define CC1101_READ_TYPE_EVENT 2 /* Events */
+
+struct __attribute__ ((packed)) read_data_user {
+ int type; /* CC1101_READ_TYPE_RX */
+ int id;
+ char rssi;
+ char len;
+ char buf[CC1101_MAX_DATA_LEN];
+};
+
+/* Errorcodes from driver to Userspace */
+#define CC1101_ECA_SEND -1
+#define CC1101_ECA_SPI_COM -2
+#define CC1101_ECA_CFG_INVAL -3
+#define CC1101_ECA_CFG -4
+#define CC1101_ECA_DELAY -5
+#define CC1101_ECA_PATABLE -6
+
+struct __attribute__ ((packed)) read_data_user_ack {
+ int type; /* CC1101_READ_TYPE_ID */
+ int id;
+ int errorcode;
+ char buf[CC1101_MAX_DATA_LEN - 2];
+};
+
+#define CC1101_READ_EVENT_ID_CARRIER 1
+
+#define CC1101_READ_EVENT_REASON_CYCLE 0
+#define CC1101_READ_EVENT_REASON_CALCULATED 1
+#define CC1101_READ_EVENT_REASON_FALLING 2
+#define CC1101_READ_EVENT_REASON_RISING 3
+
+struct __attribute__ ((packed)) read_data_user_event {
+ int type; /* CC1101_READ_TYPE_EVENT */
+ int eventid;/* CC1101_READ_EVENT_ID_* */
+ char value; /* current event value */
+ char reason; /* CC1101_READ_EVENT_REASON_* */
+ char buf[CC1101_MAX_DATA_LEN];
+};
+
+/* JOB struct */
+struct __attribute__ ((packed)) job_data_user {
+ int id; /* unique ID got from userspace */
+};
+
+#define CC1101_MSG_USER_TYPE_SEND 1
+#define CC1101_MSG_USER_TYPE_SET_CFG 2
+#define CC1101_MSG_USER_TYPE_SET_DELAY 3
+#define CC1101_MSG_USER_TYPE_SET_PATABLE 4
+struct __attribute__ ((packed)) msg_user {
+ int type; /* CC1101_MSG_USER_TYPE_ */
+ int preamble; /* 0 short, other long */
+ int csma; /* -1 = no csma,
+ * 0 = send,
+ * try csma times with delays defines from
+ * currentdelay table.
+ */
+ char buf[CC1101_MAX_DATA_LEN]; /* first byte is len */
+};
+
+#endif
--
2.21.0