summaryrefslogtreecommitdiffstats
path: root/drivers/staging/pi433/pi433_if.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/pi433/pi433_if.c')
-rw-r--r--drivers/staging/pi433/pi433_if.c1436
1 files changed, 1436 insertions, 0 deletions
diff --git a/drivers/staging/pi433/pi433_if.c b/drivers/staging/pi433/pi433_if.c
new file mode 100644
index 000000000..b59f6a4cb
--- /dev/null
+++ b/drivers/staging/pi433/pi433_if.c
@@ -0,0 +1,1436 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * userspace interface for pi433 radio module
+ *
+ * Pi433 is a 433MHz radio module for the Raspberry Pi.
+ * It is based on the HopeRf Module RFM69CW. Therefore inside of this
+ * driver, you'll find an abstraction of the rf69 chip.
+ *
+ * If needed, this driver could be extended, to also support other
+ * devices, basing on HopeRfs rf69.
+ *
+ * The driver can also be extended, to support other modules of
+ * HopeRf with a similar interace - e. g. RFM69HCW, RFM12, RFM95, ...
+ *
+ * Copyright (C) 2016 Wolf-Entwicklungen
+ * Marcus Wolf <linux@wolf-entwicklungen.de>
+ */
+
+#undef DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/idr.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/err.h>
+#include <linux/kfifo.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/spi/spi.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include "pi433_if.h"
+#include "rf69.h"
+
+#define N_PI433_MINORS BIT(MINORBITS) /*32*/ /* ... up to 256 */
+#define MAX_MSG_SIZE 900 /* min: FIFO_SIZE! */
+#define MSG_FIFO_SIZE 65536 /* 65536 = 2^16 */
+#define NUM_DIO 2
+
+static dev_t pi433_dev;
+static DEFINE_IDR(pi433_idr);
+static DEFINE_MUTEX(minor_lock); /* Protect idr accesses */
+static struct dentry *root_dir; /* debugfs root directory for the driver */
+
+static struct class *pi433_class; /* mainly for udev to create /dev/pi433 */
+
+/*
+ * tx config is instance specific
+ * so with each open a new tx config struct is needed
+ */
+/*
+ * rx config is device specific
+ * so we have just one rx config, ebedded in device struct
+ */
+struct pi433_device {
+ /* device handling related values */
+ dev_t devt;
+ int minor;
+ struct device *dev;
+ struct cdev *cdev;
+ struct spi_device *spi;
+
+ /* irq related values */
+ struct gpio_desc *gpiod[NUM_DIO];
+ int irq_num[NUM_DIO];
+ u8 irq_state[NUM_DIO];
+
+ /* tx related values */
+ STRUCT_KFIFO_REC_1(MSG_FIFO_SIZE) tx_fifo;
+ struct mutex tx_fifo_lock; /* serialize userspace writers */
+ struct task_struct *tx_task_struct;
+ wait_queue_head_t tx_wait_queue;
+ u8 free_in_fifo;
+ char buffer[MAX_MSG_SIZE];
+
+ /* rx related values */
+ struct pi433_rx_cfg rx_cfg;
+ u8 *rx_buffer;
+ unsigned int rx_buffer_size;
+ u32 rx_bytes_to_drop;
+ u32 rx_bytes_dropped;
+ unsigned int rx_position;
+ struct mutex rx_lock; /* protects rx_* variable accesses */
+ wait_queue_head_t rx_wait_queue;
+
+ /* fifo wait queue */
+ struct task_struct *fifo_task_struct;
+ wait_queue_head_t fifo_wait_queue;
+
+ /* flags */
+ bool rx_active;
+ bool tx_active;
+ bool interrupt_rx_allowed;
+};
+
+struct pi433_instance {
+ struct pi433_device *device;
+ struct pi433_tx_cfg tx_cfg;
+
+ /* control flags */
+ bool tx_cfg_initialized;
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* GPIO interrupt handlers */
+static irqreturn_t DIO0_irq_handler(int irq, void *dev_id)
+{
+ struct pi433_device *device = dev_id;
+
+ if (device->irq_state[DIO0] == DIO_PACKET_SENT) {
+ device->free_in_fifo = FIFO_SIZE;
+ dev_dbg(device->dev, "DIO0 irq: Packet sent\n");
+ wake_up_interruptible(&device->fifo_wait_queue);
+ } else if (device->irq_state[DIO0] == DIO_RSSI_DIO0) {
+ dev_dbg(device->dev, "DIO0 irq: RSSI level over threshold\n");
+ wake_up_interruptible(&device->rx_wait_queue);
+ } else if (device->irq_state[DIO0] == DIO_PAYLOAD_READY) {
+ dev_dbg(device->dev, "DIO0 irq: Payload ready\n");
+ device->free_in_fifo = 0;
+ wake_up_interruptible(&device->fifo_wait_queue);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t DIO1_irq_handler(int irq, void *dev_id)
+{
+ struct pi433_device *device = dev_id;
+
+ if (device->irq_state[DIO1] == DIO_FIFO_NOT_EMPTY_DIO1) {
+ device->free_in_fifo = FIFO_SIZE;
+ } else if (device->irq_state[DIO1] == DIO_FIFO_LEVEL) {
+ if (device->rx_active)
+ device->free_in_fifo = FIFO_THRESHOLD - 1;
+ else
+ device->free_in_fifo = FIFO_SIZE - FIFO_THRESHOLD - 1;
+ }
+ dev_dbg(device->dev,
+ "DIO1 irq: %d bytes free in fifo\n", device->free_in_fifo);
+ wake_up_interruptible(&device->fifo_wait_queue);
+
+ return IRQ_HANDLED;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int
+rf69_set_rx_cfg(struct pi433_device *dev, struct pi433_rx_cfg *rx_cfg)
+{
+ int ret;
+ int payload_length;
+
+ /* receiver config */
+ ret = rf69_set_frequency(dev->spi, rx_cfg->frequency);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_modulation(dev->spi, rx_cfg->modulation);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_bit_rate(dev->spi, rx_cfg->bit_rate);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_antenna_impedance(dev->spi, rx_cfg->antenna_impedance);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_rssi_threshold(dev->spi, rx_cfg->rssi_threshold);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_ook_threshold_dec(dev->spi, rx_cfg->threshold_decrement);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_bandwidth(dev->spi, rx_cfg->bw_mantisse,
+ rx_cfg->bw_exponent);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_bandwidth_during_afc(dev->spi, rx_cfg->bw_mantisse,
+ rx_cfg->bw_exponent);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_dagc(dev->spi, rx_cfg->dagc);
+ if (ret < 0)
+ return ret;
+
+ dev->rx_bytes_to_drop = rx_cfg->bytes_to_drop;
+
+ /* packet config */
+ /* enable */
+ if (rx_cfg->enable_sync == OPTION_ON) {
+ ret = rf69_enable_sync(dev->spi);
+ if (ret < 0)
+ return ret;
+
+ ret = rf69_set_fifo_fill_condition(dev->spi,
+ after_sync_interrupt);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_disable_sync(dev->spi);
+ if (ret < 0)
+ return ret;
+
+ ret = rf69_set_fifo_fill_condition(dev->spi, always);
+ if (ret < 0)
+ return ret;
+ }
+ if (rx_cfg->enable_length_byte == OPTION_ON) {
+ ret = rf69_set_packet_format(dev->spi, packet_length_var);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_set_packet_format(dev->spi, packet_length_fix);
+ if (ret < 0)
+ return ret;
+ }
+ ret = rf69_set_address_filtering(dev->spi,
+ rx_cfg->enable_address_filtering);
+ if (ret < 0)
+ return ret;
+
+ if (rx_cfg->enable_crc == OPTION_ON) {
+ ret = rf69_enable_crc(dev->spi);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_disable_crc(dev->spi);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* lengths */
+ ret = rf69_set_sync_size(dev->spi, rx_cfg->sync_length);
+ if (ret < 0)
+ return ret;
+ if (rx_cfg->enable_length_byte == OPTION_ON) {
+ ret = rf69_set_payload_length(dev->spi, 0xff);
+ if (ret < 0)
+ return ret;
+ } else if (rx_cfg->fixed_message_length != 0) {
+ payload_length = rx_cfg->fixed_message_length;
+ if (rx_cfg->enable_length_byte == OPTION_ON)
+ payload_length++;
+ if (rx_cfg->enable_address_filtering != filtering_off)
+ payload_length++;
+ ret = rf69_set_payload_length(dev->spi, payload_length);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_set_payload_length(dev->spi, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* values */
+ if (rx_cfg->enable_sync == OPTION_ON) {
+ ret = rf69_set_sync_values(dev->spi, rx_cfg->sync_pattern);
+ if (ret < 0)
+ return ret;
+ }
+ if (rx_cfg->enable_address_filtering != filtering_off) {
+ ret = rf69_set_node_address(dev->spi, rx_cfg->node_address);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_broadcast_address(dev->spi,
+ rx_cfg->broadcast_address);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+rf69_set_tx_cfg(struct pi433_device *dev, struct pi433_tx_cfg *tx_cfg)
+{
+ int ret;
+
+ ret = rf69_set_frequency(dev->spi, tx_cfg->frequency);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_modulation(dev->spi, tx_cfg->modulation);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_bit_rate(dev->spi, tx_cfg->bit_rate);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_deviation(dev->spi, tx_cfg->dev_frequency);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_pa_ramp(dev->spi, tx_cfg->pa_ramp);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_modulation_shaping(dev->spi, tx_cfg->mod_shaping);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_tx_start_condition(dev->spi, tx_cfg->tx_start_condition);
+ if (ret < 0)
+ return ret;
+
+ /* packet format enable */
+ if (tx_cfg->enable_preamble == OPTION_ON) {
+ ret = rf69_set_preamble_length(dev->spi,
+ tx_cfg->preamble_length);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_set_preamble_length(dev->spi, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (tx_cfg->enable_sync == OPTION_ON) {
+ ret = rf69_set_sync_size(dev->spi, tx_cfg->sync_length);
+ if (ret < 0)
+ return ret;
+ ret = rf69_set_sync_values(dev->spi, tx_cfg->sync_pattern);
+ if (ret < 0)
+ return ret;
+ ret = rf69_enable_sync(dev->spi);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_disable_sync(dev->spi);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (tx_cfg->enable_length_byte == OPTION_ON) {
+ ret = rf69_set_packet_format(dev->spi, packet_length_var);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_set_packet_format(dev->spi, packet_length_fix);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (tx_cfg->enable_crc == OPTION_ON) {
+ ret = rf69_enable_crc(dev->spi);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = rf69_disable_crc(dev->spi);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int pi433_start_rx(struct pi433_device *dev)
+{
+ int retval;
+
+ /* return without action, if no pending read request */
+ if (!dev->rx_active)
+ return 0;
+
+ /* setup for receiving */
+ retval = rf69_set_rx_cfg(dev, &dev->rx_cfg);
+ if (retval)
+ return retval;
+
+ /* setup rssi irq */
+ retval = rf69_set_dio_mapping(dev->spi, DIO0, DIO_RSSI_DIO0);
+ if (retval < 0)
+ return retval;
+ dev->irq_state[DIO0] = DIO_RSSI_DIO0;
+ irq_set_irq_type(dev->irq_num[DIO0], IRQ_TYPE_EDGE_RISING);
+
+ /* setup fifo level interrupt */
+ retval = rf69_set_fifo_threshold(dev->spi, FIFO_SIZE - FIFO_THRESHOLD);
+ if (retval < 0)
+ return retval;
+ retval = rf69_set_dio_mapping(dev->spi, DIO1, DIO_FIFO_LEVEL);
+ if (retval < 0)
+ return retval;
+ dev->irq_state[DIO1] = DIO_FIFO_LEVEL;
+ irq_set_irq_type(dev->irq_num[DIO1], IRQ_TYPE_EDGE_RISING);
+
+ /* set module to receiving mode */
+ retval = rf69_set_mode(dev->spi, receive);
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int pi433_receive(void *data)
+{
+ struct pi433_device *dev = data;
+ struct spi_device *spi = dev->spi;
+ int bytes_to_read, bytes_total;
+ int retval;
+
+ dev->interrupt_rx_allowed = false;
+
+ /* wait for any tx to finish */
+ dev_dbg(dev->dev, "rx: going to wait for any tx to finish\n");
+ retval = wait_event_interruptible(dev->rx_wait_queue, !dev->tx_active);
+ if (retval) {
+ /* wait was interrupted */
+ dev->interrupt_rx_allowed = true;
+ wake_up_interruptible(&dev->tx_wait_queue);
+ return retval;
+ }
+
+ /* prepare status vars */
+ dev->free_in_fifo = FIFO_SIZE;
+ dev->rx_position = 0;
+ dev->rx_bytes_dropped = 0;
+
+ /* setup radio module to listen for something "in the air" */
+ retval = pi433_start_rx(dev);
+ if (retval)
+ return retval;
+
+ /* now check RSSI, if low wait for getting high (RSSI interrupt) */
+ while (!(rf69_read_reg(spi, REG_IRQFLAGS1) & MASK_IRQFLAGS1_RSSI)) {
+ /* allow tx to interrupt us while waiting for high RSSI */
+ dev->interrupt_rx_allowed = true;
+ wake_up_interruptible(&dev->tx_wait_queue);
+
+ /* wait for RSSI level to become high */
+ dev_dbg(dev->dev, "rx: going to wait for high RSSI level\n");
+ retval = wait_event_interruptible(dev->rx_wait_queue,
+ rf69_read_reg(spi, REG_IRQFLAGS1) &
+ MASK_IRQFLAGS1_RSSI);
+ if (retval) /* wait was interrupted */
+ goto abort;
+ dev->interrupt_rx_allowed = false;
+
+ /* cross check for ongoing tx */
+ if (!dev->tx_active)
+ break;
+ }
+
+ /* configure payload ready irq */
+ retval = rf69_set_dio_mapping(spi, DIO0, DIO_PAYLOAD_READY);
+ if (retval < 0)
+ goto abort;
+ dev->irq_state[DIO0] = DIO_PAYLOAD_READY;
+ irq_set_irq_type(dev->irq_num[DIO0], IRQ_TYPE_EDGE_RISING);
+
+ /* fixed or unlimited length? */
+ if (dev->rx_cfg.fixed_message_length != 0) {
+ if (dev->rx_cfg.fixed_message_length > dev->rx_buffer_size) {
+ retval = -1;
+ goto abort;
+ }
+ bytes_total = dev->rx_cfg.fixed_message_length;
+ dev_dbg(dev->dev, "rx: msg len set to %d by fixed length\n",
+ bytes_total);
+ } else {
+ bytes_total = dev->rx_buffer_size;
+ dev_dbg(dev->dev, "rx: msg len set to %d as requested by read\n",
+ bytes_total);
+ }
+
+ /* length byte enabled? */
+ if (dev->rx_cfg.enable_length_byte == OPTION_ON) {
+ retval = wait_event_interruptible(dev->fifo_wait_queue,
+ dev->free_in_fifo < FIFO_SIZE);
+ if (retval) /* wait was interrupted */
+ goto abort;
+
+ rf69_read_fifo(spi, (u8 *)&bytes_total, 1);
+ if (bytes_total > dev->rx_buffer_size) {
+ retval = -1;
+ goto abort;
+ }
+ dev->free_in_fifo++;
+ dev_dbg(dev->dev, "rx: msg len reset to %d due to length byte\n",
+ bytes_total);
+ }
+
+ /* address byte enabled? */
+ if (dev->rx_cfg.enable_address_filtering != filtering_off) {
+ u8 dummy;
+
+ bytes_total--;
+
+ retval = wait_event_interruptible(dev->fifo_wait_queue,
+ dev->free_in_fifo < FIFO_SIZE);
+ if (retval) /* wait was interrupted */
+ goto abort;
+
+ rf69_read_fifo(spi, &dummy, 1);
+ dev->free_in_fifo++;
+ dev_dbg(dev->dev, "rx: address byte stripped off\n");
+ }
+
+ /* get payload */
+ while (dev->rx_position < bytes_total) {
+ if (!(rf69_read_reg(spi, REG_IRQFLAGS2) & MASK_IRQFLAGS2_PAYLOAD_READY)) {
+ retval = wait_event_interruptible(dev->fifo_wait_queue,
+ dev->free_in_fifo < FIFO_SIZE);
+ if (retval) /* wait was interrupted */
+ goto abort;
+ }
+
+ /* need to drop bytes or acquire? */
+ if (dev->rx_bytes_to_drop > dev->rx_bytes_dropped)
+ bytes_to_read = dev->rx_bytes_to_drop -
+ dev->rx_bytes_dropped;
+ else
+ bytes_to_read = bytes_total - dev->rx_position;
+
+ /* access the fifo */
+ if (bytes_to_read > FIFO_SIZE - dev->free_in_fifo)
+ bytes_to_read = FIFO_SIZE - dev->free_in_fifo;
+ retval = rf69_read_fifo(spi,
+ &dev->rx_buffer[dev->rx_position],
+ bytes_to_read);
+ if (retval) /* read failed */
+ goto abort;
+
+ dev->free_in_fifo += bytes_to_read;
+
+ /* adjust status vars */
+ if (dev->rx_bytes_to_drop > dev->rx_bytes_dropped)
+ dev->rx_bytes_dropped += bytes_to_read;
+ else
+ dev->rx_position += bytes_to_read;
+ }
+
+ /* rx done, wait was interrupted or error occurred */
+abort:
+ dev->interrupt_rx_allowed = true;
+ if (rf69_set_mode(dev->spi, standby))
+ pr_err("rf69_set_mode(): radio module failed to go standby\n");
+ wake_up_interruptible(&dev->tx_wait_queue);
+
+ if (retval)
+ return retval;
+ else
+ return bytes_total;
+}
+
+static int pi433_tx_thread(void *data)
+{
+ struct pi433_device *device = data;
+ struct spi_device *spi = device->spi;
+ struct pi433_tx_cfg tx_cfg;
+ size_t size;
+ bool rx_interrupted = false;
+ int position, repetitions;
+ int retval;
+
+ while (1) {
+ /* wait for fifo to be populated or for request to terminate*/
+ dev_dbg(device->dev, "thread: going to wait for new messages\n");
+ wait_event_interruptible(device->tx_wait_queue,
+ (!kfifo_is_empty(&device->tx_fifo) ||
+ kthread_should_stop()));
+ if (kthread_should_stop())
+ return 0;
+
+ /*
+ * get data from fifo in the following order:
+ * - tx_cfg
+ * - size of message
+ * - message
+ */
+ retval = kfifo_out(&device->tx_fifo, &tx_cfg, sizeof(tx_cfg));
+ if (retval != sizeof(tx_cfg)) {
+ dev_dbg(device->dev,
+ "reading tx_cfg from fifo failed: got %d byte(s), expected %d\n",
+ retval, (unsigned int)sizeof(tx_cfg));
+ continue;
+ }
+
+ retval = kfifo_out(&device->tx_fifo, &size, sizeof(size_t));
+ if (retval != sizeof(size_t)) {
+ dev_dbg(device->dev,
+ "reading msg size from fifo failed: got %d, expected %d\n",
+ retval, (unsigned int)sizeof(size_t));
+ continue;
+ }
+
+ /* use fixed message length, if requested */
+ if (tx_cfg.fixed_message_length != 0)
+ size = tx_cfg.fixed_message_length;
+
+ /* increase size, if len byte is requested */
+ if (tx_cfg.enable_length_byte == OPTION_ON)
+ size++;
+
+ /* increase size, if adr byte is requested */
+ if (tx_cfg.enable_address_byte == OPTION_ON)
+ size++;
+
+ /* prime buffer */
+ memset(device->buffer, 0, size);
+ position = 0;
+
+ /* add length byte, if requested */
+ if (tx_cfg.enable_length_byte == OPTION_ON)
+ /*
+ * according to spec, length byte itself must be
+ * excluded from the length calculation
+ */
+ device->buffer[position++] = size - 1;
+
+ /* add adr byte, if requested */
+ if (tx_cfg.enable_address_byte == OPTION_ON)
+ device->buffer[position++] = tx_cfg.address_byte;
+
+ /* finally get message data from fifo */
+ retval = kfifo_out(&device->tx_fifo, &device->buffer[position],
+ sizeof(device->buffer) - position);
+ dev_dbg(device->dev,
+ "read %d message byte(s) from fifo queue.\n", retval);
+
+ /*
+ * if rx is active, we need to interrupt the waiting for
+ * incoming telegrams, to be able to send something.
+ * We are only allowed, if currently no reception takes
+ * place otherwise we need to wait for the incoming telegram
+ * to finish
+ */
+ wait_event_interruptible(device->tx_wait_queue,
+ !device->rx_active ||
+ device->interrupt_rx_allowed);
+
+ /*
+ * prevent race conditions
+ * irq will be reenabled after tx config is set
+ */
+ disable_irq(device->irq_num[DIO0]);
+ device->tx_active = true;
+
+ /* clear fifo, set fifo threshold, set payload length */
+ retval = rf69_set_mode(spi, standby); /* this clears the fifo */
+ if (retval < 0)
+ goto abort;
+
+ if (device->rx_active && !rx_interrupted) {
+ /*
+ * rx is currently waiting for a telegram;
+ * we need to set the radio module to standby
+ */
+ rx_interrupted = true;
+ }
+
+ retval = rf69_set_fifo_threshold(spi, FIFO_THRESHOLD);
+ if (retval < 0)
+ goto abort;
+ if (tx_cfg.enable_length_byte == OPTION_ON) {
+ retval = rf69_set_payload_length(spi, size * tx_cfg.repetitions);
+ if (retval < 0)
+ goto abort;
+ } else {
+ retval = rf69_set_payload_length(spi, 0);
+ if (retval < 0)
+ goto abort;
+ }
+
+ /* configure the rf chip */
+ retval = rf69_set_tx_cfg(device, &tx_cfg);
+ if (retval < 0)
+ goto abort;
+
+ /* enable fifo level interrupt */
+ retval = rf69_set_dio_mapping(spi, DIO1, DIO_FIFO_LEVEL);
+ if (retval < 0)
+ goto abort;
+ device->irq_state[DIO1] = DIO_FIFO_LEVEL;
+ irq_set_irq_type(device->irq_num[DIO1], IRQ_TYPE_EDGE_FALLING);
+
+ /* enable packet sent interrupt */
+ retval = rf69_set_dio_mapping(spi, DIO0, DIO_PACKET_SENT);
+ if (retval < 0)
+ goto abort;
+ device->irq_state[DIO0] = DIO_PACKET_SENT;
+ irq_set_irq_type(device->irq_num[DIO0], IRQ_TYPE_EDGE_RISING);
+ enable_irq(device->irq_num[DIO0]); /* was disabled by rx active check */
+
+ /* enable transmission */
+ retval = rf69_set_mode(spi, transmit);
+ if (retval < 0)
+ goto abort;
+
+ /* transfer this msg (and repetitions) to chip fifo */
+ device->free_in_fifo = FIFO_SIZE;
+ position = 0;
+ repetitions = tx_cfg.repetitions;
+ while ((repetitions > 0) && (size > position)) {
+ if ((size - position) > device->free_in_fifo) {
+ /* msg to big for fifo - take a part */
+ int write_size = device->free_in_fifo;
+
+ device->free_in_fifo = 0;
+ rf69_write_fifo(spi,
+ &device->buffer[position],
+ write_size);
+ position += write_size;
+ } else {
+ /* msg fits into fifo - take all */
+ device->free_in_fifo -= size;
+ repetitions--;
+ rf69_write_fifo(spi,
+ &device->buffer[position],
+ (size - position));
+ position = 0; /* reset for next repetition */
+ }
+
+ retval = wait_event_interruptible(device->fifo_wait_queue,
+ device->free_in_fifo > 0);
+ if (retval) {
+ dev_dbg(device->dev, "ABORT\n");
+ goto abort;
+ }
+ }
+
+ /* we are done. Wait for packet to get sent */
+ dev_dbg(device->dev,
+ "thread: wait for packet to get sent/fifo to be empty\n");
+ wait_event_interruptible(device->fifo_wait_queue,
+ device->free_in_fifo == FIFO_SIZE ||
+ kthread_should_stop());
+ if (kthread_should_stop())
+ return 0;
+
+ /* STOP_TRANSMISSION */
+ dev_dbg(device->dev, "thread: Packet sent. Set mode to stby.\n");
+ retval = rf69_set_mode(spi, standby);
+ if (retval < 0)
+ goto abort;
+
+ /* everything sent? */
+ if (kfifo_is_empty(&device->tx_fifo)) {
+abort:
+ if (rx_interrupted) {
+ rx_interrupted = false;
+ pi433_start_rx(device);
+ }
+ device->tx_active = false;
+ wake_up_interruptible(&device->rx_wait_queue);
+ }
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+
+static ssize_t
+pi433_read(struct file *filp, char __user *buf, size_t size, loff_t *f_pos)
+{
+ struct pi433_instance *instance;
+ struct pi433_device *device;
+ int bytes_received;
+ ssize_t retval;
+
+ /* check, whether internal buffer is big enough for requested size */
+ if (size > MAX_MSG_SIZE)
+ return -EMSGSIZE;
+
+ instance = filp->private_data;
+ device = instance->device;
+
+ /* just one read request at a time */
+ mutex_lock(&device->rx_lock);
+ if (device->rx_active) {
+ mutex_unlock(&device->rx_lock);
+ return -EAGAIN;
+ }
+
+ device->rx_active = true;
+ mutex_unlock(&device->rx_lock);
+
+ /* start receiving */
+ /* will block until something was received*/
+ device->rx_buffer_size = size;
+ bytes_received = pi433_receive(device);
+
+ /* release rx */
+ mutex_lock(&device->rx_lock);
+ device->rx_active = false;
+ mutex_unlock(&device->rx_lock);
+
+ /* if read was successful copy to user space*/
+ if (bytes_received > 0) {
+ retval = copy_to_user(buf, device->rx_buffer, bytes_received);
+ if (retval)
+ return -EFAULT;
+ }
+
+ return bytes_received;
+}
+
+static ssize_t
+pi433_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct pi433_instance *instance;
+ struct pi433_device *device;
+ int retval;
+ unsigned int required, available, copied;
+
+ instance = filp->private_data;
+ device = instance->device;
+
+ /*
+ * check, whether internal buffer (tx thread) is big enough
+ * for requested size
+ */
+ if (count > MAX_MSG_SIZE)
+ return -EMSGSIZE;
+
+ /*
+ * check if tx_cfg has been initialized otherwise we won't be able to
+ * config the RF trasmitter correctly due to invalid settings
+ */
+ if (!instance->tx_cfg_initialized) {
+ dev_notice_once(device->dev,
+ "write: failed due to unconfigured tx_cfg (see PI433_IOC_WR_TX_CFG)\n");
+ return -EINVAL;
+ }
+
+ /*
+ * write the following sequence into fifo:
+ * - tx_cfg
+ * - size of message
+ * - message
+ */
+ mutex_lock(&device->tx_fifo_lock);
+
+ required = sizeof(instance->tx_cfg) + sizeof(size_t) + count;
+ available = kfifo_avail(&device->tx_fifo);
+ if (required > available) {
+ dev_dbg(device->dev, "write to fifo failed: %d bytes required but %d available\n",
+ required, available);
+ mutex_unlock(&device->tx_fifo_lock);
+ return -EAGAIN;
+ }
+
+ retval = kfifo_in(&device->tx_fifo, &instance->tx_cfg,
+ sizeof(instance->tx_cfg));
+ if (retval != sizeof(instance->tx_cfg))
+ goto abort;
+
+ retval = kfifo_in(&device->tx_fifo, &count, sizeof(size_t));
+ if (retval != sizeof(size_t))
+ goto abort;
+
+ retval = kfifo_from_user(&device->tx_fifo, buf, count, &copied);
+ if (retval || copied != count)
+ goto abort;
+
+ mutex_unlock(&device->tx_fifo_lock);
+
+ /* start transfer */
+ wake_up_interruptible(&device->tx_wait_queue);
+ dev_dbg(device->dev, "write: generated new msg with %d bytes.\n", copied);
+
+ return copied;
+
+abort:
+ dev_warn(device->dev,
+ "write to fifo failed, non recoverable: 0x%x\n", retval);
+ mutex_unlock(&device->tx_fifo_lock);
+ return -EAGAIN;
+}
+
+static long pi433_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct pi433_instance *instance;
+ struct pi433_device *device;
+ struct pi433_tx_cfg tx_cfg;
+ void __user *argp = (void __user *)arg;
+
+ /* Check type and command number */
+ if (_IOC_TYPE(cmd) != PI433_IOC_MAGIC)
+ return -ENOTTY;
+
+ instance = filp->private_data;
+ device = instance->device;
+
+ if (!device)
+ return -ESHUTDOWN;
+
+ switch (cmd) {
+ case PI433_IOC_RD_TX_CFG:
+ if (copy_to_user(argp, &instance->tx_cfg,
+ sizeof(struct pi433_tx_cfg)))
+ return -EFAULT;
+ break;
+ case PI433_IOC_WR_TX_CFG:
+ if (copy_from_user(&tx_cfg, argp, sizeof(struct pi433_tx_cfg)))
+ return -EFAULT;
+ mutex_lock(&device->tx_fifo_lock);
+ memcpy(&instance->tx_cfg, &tx_cfg, sizeof(struct pi433_tx_cfg));
+ instance->tx_cfg_initialized = true;
+ mutex_unlock(&device->tx_fifo_lock);
+ break;
+ case PI433_IOC_RD_RX_CFG:
+ if (copy_to_user(argp, &device->rx_cfg,
+ sizeof(struct pi433_rx_cfg)))
+ return -EFAULT;
+ break;
+ case PI433_IOC_WR_RX_CFG:
+ mutex_lock(&device->rx_lock);
+
+ /* during pendig read request, change of config not allowed */
+ if (device->rx_active) {
+ mutex_unlock(&device->rx_lock);
+ return -EAGAIN;
+ }
+
+ if (copy_from_user(&device->rx_cfg, argp,
+ sizeof(struct pi433_rx_cfg))) {
+ mutex_unlock(&device->rx_lock);
+ return -EFAULT;
+ }
+
+ mutex_unlock(&device->rx_lock);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int pi433_open(struct inode *inode, struct file *filp)
+{
+ struct pi433_device *device;
+ struct pi433_instance *instance;
+
+ mutex_lock(&minor_lock);
+ device = idr_find(&pi433_idr, iminor(inode));
+ mutex_unlock(&minor_lock);
+ if (!device) {
+ pr_debug("device: minor %d unknown.\n", iminor(inode));
+ return -ENODEV;
+ }
+
+ instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+ if (!instance)
+ return -ENOMEM;
+
+ /* setup instance data*/
+ instance->device = device;
+
+ /* instance data as context */
+ filp->private_data = instance;
+ stream_open(inode, filp);
+
+ return 0;
+}
+
+static int pi433_release(struct inode *inode, struct file *filp)
+{
+ struct pi433_instance *instance;
+
+ instance = filp->private_data;
+ kfree(instance);
+ filp->private_data = NULL;
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int setup_gpio(struct pi433_device *device)
+{
+ char name[5];
+ int retval;
+ int i;
+ const irq_handler_t DIO_irq_handler[NUM_DIO] = {
+ DIO0_irq_handler,
+ DIO1_irq_handler
+ };
+
+ for (i = 0; i < NUM_DIO; i++) {
+ /* "construct" name and get the gpio descriptor */
+ snprintf(name, sizeof(name), "DIO%d", i);
+ device->gpiod[i] = gpiod_get(&device->spi->dev, name,
+ 0 /*GPIOD_IN*/);
+
+ if (device->gpiod[i] == ERR_PTR(-ENOENT)) {
+ dev_dbg(&device->spi->dev,
+ "Could not find entry for %s. Ignoring.\n", name);
+ continue;
+ }
+
+ if (device->gpiod[i] == ERR_PTR(-EBUSY))
+ dev_dbg(&device->spi->dev, "%s is busy.\n", name);
+
+ if (IS_ERR(device->gpiod[i])) {
+ retval = PTR_ERR(device->gpiod[i]);
+ /* release already allocated gpios */
+ for (i--; i >= 0; i--) {
+ free_irq(device->irq_num[i], device);
+ gpiod_put(device->gpiod[i]);
+ }
+ return retval;
+ }
+
+ /* configure the pin */
+ gpiod_unexport(device->gpiod[i]);
+ retval = gpiod_direction_input(device->gpiod[i]);
+ if (retval)
+ return retval;
+
+ /* configure irq */
+ device->irq_num[i] = gpiod_to_irq(device->gpiod[i]);
+ if (device->irq_num[i] < 0) {
+ device->gpiod[i] = ERR_PTR(-EINVAL);
+ return device->irq_num[i];
+ }
+ retval = request_irq(device->irq_num[i],
+ DIO_irq_handler[i],
+ 0, /* flags */
+ name,
+ device);
+
+ if (retval)
+ return retval;
+
+ dev_dbg(&device->spi->dev, "%s successfully configured\n", name);
+ }
+
+ return 0;
+}
+
+static void free_gpio(struct pi433_device *device)
+{
+ int i;
+
+ for (i = 0; i < NUM_DIO; i++) {
+ /* check if gpiod is valid */
+ if (IS_ERR(device->gpiod[i]))
+ continue;
+
+ free_irq(device->irq_num[i], device);
+ gpiod_put(device->gpiod[i]);
+ }
+}
+
+static int pi433_get_minor(struct pi433_device *device)
+{
+ int retval = -ENOMEM;
+
+ mutex_lock(&minor_lock);
+ retval = idr_alloc(&pi433_idr, device, 0, N_PI433_MINORS, GFP_KERNEL);
+ if (retval >= 0) {
+ device->minor = retval;
+ retval = 0;
+ } else if (retval == -ENOSPC) {
+ dev_err(&device->spi->dev, "too many pi433 devices\n");
+ retval = -EINVAL;
+ }
+ mutex_unlock(&minor_lock);
+ return retval;
+}
+
+static void pi433_free_minor(struct pi433_device *dev)
+{
+ mutex_lock(&minor_lock);
+ idr_remove(&pi433_idr, dev->minor);
+ mutex_unlock(&minor_lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static const struct file_operations pi433_fops = {
+ .owner = THIS_MODULE,
+ /*
+ * REVISIT switch to aio primitives, so that userspace
+ * gets more complete API coverage. It'll simplify things
+ * too, except for the locking.
+ */
+ .write = pi433_write,
+ .read = pi433_read,
+ .unlocked_ioctl = pi433_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = pi433_open,
+ .release = pi433_release,
+ .llseek = no_llseek,
+};
+
+static int pi433_debugfs_regs_show(struct seq_file *m, void *p)
+{
+ struct pi433_device *dev;
+ u8 reg_data[114];
+ int i;
+ char *fmt = "0x%02x, 0x%02x\n";
+ int ret;
+
+ dev = m->private;
+
+ mutex_lock(&dev->tx_fifo_lock);
+ mutex_lock(&dev->rx_lock);
+
+ // wait for on-going operations to finish
+ ret = wait_event_interruptible(dev->rx_wait_queue, !dev->tx_active);
+ if (ret)
+ goto out_unlock;
+
+ ret = wait_event_interruptible(dev->tx_wait_queue, !dev->rx_active);
+ if (ret)
+ goto out_unlock;
+
+ // skip FIFO register (0x0) otherwise this can affect some of uC ops
+ for (i = 1; i < 0x50; i++)
+ reg_data[i] = rf69_read_reg(dev->spi, i);
+
+ reg_data[REG_TESTLNA] = rf69_read_reg(dev->spi, REG_TESTLNA);
+ reg_data[REG_TESTPA1] = rf69_read_reg(dev->spi, REG_TESTPA1);
+ reg_data[REG_TESTPA2] = rf69_read_reg(dev->spi, REG_TESTPA2);
+ reg_data[REG_TESTDAGC] = rf69_read_reg(dev->spi, REG_TESTDAGC);
+ reg_data[REG_TESTAFC] = rf69_read_reg(dev->spi, REG_TESTAFC);
+
+ seq_puts(m, "# reg, val\n");
+
+ for (i = 1; i < 0x50; i++)
+ seq_printf(m, fmt, i, reg_data[i]);
+
+ seq_printf(m, fmt, REG_TESTLNA, reg_data[REG_TESTLNA]);
+ seq_printf(m, fmt, REG_TESTPA1, reg_data[REG_TESTPA1]);
+ seq_printf(m, fmt, REG_TESTPA2, reg_data[REG_TESTPA2]);
+ seq_printf(m, fmt, REG_TESTDAGC, reg_data[REG_TESTDAGC]);
+ seq_printf(m, fmt, REG_TESTAFC, reg_data[REG_TESTAFC]);
+
+out_unlock:
+ mutex_unlock(&dev->rx_lock);
+ mutex_unlock(&dev->tx_fifo_lock);
+
+ return ret;
+}
+DEFINE_SHOW_ATTRIBUTE(pi433_debugfs_regs);
+
+/*-------------------------------------------------------------------------*/
+
+static int pi433_probe(struct spi_device *spi)
+{
+ struct pi433_device *device;
+ int retval;
+ struct dentry *entry;
+
+ /* setup spi parameters */
+ spi->mode = 0x00;
+ spi->bits_per_word = 8;
+ /*
+ * spi->max_speed_hz = 10000000;
+ * 1MHz already set by device tree overlay
+ */
+
+ retval = spi_setup(spi);
+ if (retval) {
+ dev_dbg(&spi->dev, "configuration of SPI interface failed!\n");
+ return retval;
+ }
+
+ dev_dbg(&spi->dev,
+ "spi interface setup: mode 0x%2x, %d bits per word, %dhz max speed\n",
+ spi->mode, spi->bits_per_word, spi->max_speed_hz);
+
+ /* read chip version */
+ retval = rf69_get_version(spi);
+ if (retval < 0)
+ return retval;
+
+ switch (retval) {
+ case 0x24:
+ dev_dbg(&spi->dev, "found pi433 (ver. 0x%x)\n", retval);
+ break;
+ default:
+ dev_dbg(&spi->dev, "unknown chip version: 0x%x\n", retval);
+ return -ENODEV;
+ }
+
+ /* Allocate driver data */
+ device = kzalloc(sizeof(*device), GFP_KERNEL);
+ if (!device)
+ return -ENOMEM;
+
+ /* Initialize the driver data */
+ device->spi = spi;
+ device->rx_active = false;
+ device->tx_active = false;
+ device->interrupt_rx_allowed = false;
+
+ /* init rx buffer */
+ device->rx_buffer = kmalloc(MAX_MSG_SIZE, GFP_KERNEL);
+ if (!device->rx_buffer) {
+ retval = -ENOMEM;
+ goto RX_failed;
+ }
+
+ /* init wait queues */
+ init_waitqueue_head(&device->tx_wait_queue);
+ init_waitqueue_head(&device->rx_wait_queue);
+ init_waitqueue_head(&device->fifo_wait_queue);
+
+ /* init fifo */
+ INIT_KFIFO(device->tx_fifo);
+
+ /* init mutexes and locks */
+ mutex_init(&device->tx_fifo_lock);
+ mutex_init(&device->rx_lock);
+
+ /* setup GPIO (including irq_handler) for the different DIOs */
+ retval = setup_gpio(device);
+ if (retval) {
+ dev_dbg(&spi->dev, "setup of GPIOs failed\n");
+ goto GPIO_failed;
+ }
+
+ /* setup the radio module */
+ retval = rf69_set_mode(spi, standby);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_set_data_mode(spi, DATAMODUL_MODE_PACKET);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_enable_amplifier(spi, MASK_PALEVEL_PA0);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_disable_amplifier(spi, MASK_PALEVEL_PA1);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_disable_amplifier(spi, MASK_PALEVEL_PA2);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_set_output_power_level(spi, 13);
+ if (retval < 0)
+ goto minor_failed;
+ retval = rf69_set_antenna_impedance(spi, fifty_ohm);
+ if (retval < 0)
+ goto minor_failed;
+
+ /* determ minor number */
+ retval = pi433_get_minor(device);
+ if (retval) {
+ dev_dbg(&spi->dev, "get of minor number failed\n");
+ goto minor_failed;
+ }
+
+ /* create device */
+ device->devt = MKDEV(MAJOR(pi433_dev), device->minor);
+ device->dev = device_create(pi433_class,
+ &spi->dev,
+ device->devt,
+ device,
+ "pi433.%d",
+ device->minor);
+ if (IS_ERR(device->dev)) {
+ pr_err("pi433: device register failed\n");
+ retval = PTR_ERR(device->dev);
+ goto device_create_failed;
+ } else {
+ dev_dbg(device->dev,
+ "created device for major %d, minor %d\n",
+ MAJOR(pi433_dev),
+ device->minor);
+ }
+
+ /* start tx thread */
+ device->tx_task_struct = kthread_run(pi433_tx_thread,
+ device,
+ "pi433.%d_tx_task",
+ device->minor);
+ if (IS_ERR(device->tx_task_struct)) {
+ dev_dbg(device->dev, "start of send thread failed\n");
+ retval = PTR_ERR(device->tx_task_struct);
+ goto send_thread_failed;
+ }
+
+ /* create cdev */
+ device->cdev = cdev_alloc();
+ if (!device->cdev) {
+ dev_dbg(device->dev, "allocation of cdev failed\n");
+ retval = -ENOMEM;
+ goto cdev_failed;
+ }
+ device->cdev->owner = THIS_MODULE;
+ cdev_init(device->cdev, &pi433_fops);
+ retval = cdev_add(device->cdev, device->devt, 1);
+ if (retval) {
+ dev_dbg(device->dev, "register of cdev failed\n");
+ goto del_cdev;
+ }
+
+ /* spi setup */
+ spi_set_drvdata(spi, device);
+
+ entry = debugfs_create_dir(dev_name(device->dev), root_dir);
+ debugfs_create_file("regs", 0400, entry, device, &pi433_debugfs_regs_fops);
+
+ return 0;
+
+del_cdev:
+ cdev_del(device->cdev);
+cdev_failed:
+ kthread_stop(device->tx_task_struct);
+send_thread_failed:
+ device_destroy(pi433_class, device->devt);
+device_create_failed:
+ pi433_free_minor(device);
+minor_failed:
+ free_gpio(device);
+GPIO_failed:
+ kfree(device->rx_buffer);
+RX_failed:
+ kfree(device);
+
+ return retval;
+}
+
+static void pi433_remove(struct spi_device *spi)
+{
+ struct pi433_device *device = spi_get_drvdata(spi);
+
+ debugfs_lookup_and_remove(dev_name(device->dev), root_dir);
+
+ /* free GPIOs */
+ free_gpio(device);
+
+ /* make sure ops on existing fds can abort cleanly */
+ device->spi = NULL;
+
+ kthread_stop(device->tx_task_struct);
+
+ device_destroy(pi433_class, device->devt);
+
+ cdev_del(device->cdev);
+
+ pi433_free_minor(device);
+
+ kfree(device->rx_buffer);
+ kfree(device);
+}
+
+static const struct of_device_id pi433_dt_ids[] = {
+ { .compatible = "Smarthome-Wolf,pi433" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, pi433_dt_ids);
+
+static struct spi_driver pi433_spi_driver = {
+ .driver = {
+ .name = "pi433",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pi433_dt_ids),
+ },
+ .probe = pi433_probe,
+ .remove = pi433_remove,
+
+ /*
+ * NOTE: suspend/resume methods are not necessary here.
+ * We don't do anything except pass the requests to/from
+ * the underlying controller. The refrigerator handles
+ * most issues; the controller driver handles the rest.
+ */
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init pi433_init(void)
+{
+ int status;
+
+ /*
+ * If MAX_MSG_SIZE is smaller then FIFO_SIZE, the driver won't
+ * work stable - risk of buffer overflow
+ */
+ if (MAX_MSG_SIZE < FIFO_SIZE)
+ return -EINVAL;
+
+ /*
+ * Claim device numbers. Then register a class
+ * that will key udev/mdev to add/remove /dev nodes.
+ * Last, register the driver which manages those device numbers.
+ */
+ status = alloc_chrdev_region(&pi433_dev, 0, N_PI433_MINORS, "pi433");
+ if (status < 0)
+ return status;
+
+ pi433_class = class_create(THIS_MODULE, "pi433");
+ if (IS_ERR(pi433_class)) {
+ unregister_chrdev(MAJOR(pi433_dev),
+ pi433_spi_driver.driver.name);
+ return PTR_ERR(pi433_class);
+ }
+
+ root_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
+
+ status = spi_register_driver(&pi433_spi_driver);
+ if (status < 0) {
+ class_destroy(pi433_class);
+ unregister_chrdev(MAJOR(pi433_dev),
+ pi433_spi_driver.driver.name);
+ }
+
+ return status;
+}
+
+module_init(pi433_init);
+
+static void __exit pi433_exit(void)
+{
+ spi_unregister_driver(&pi433_spi_driver);
+ class_destroy(pi433_class);
+ unregister_chrdev(MAJOR(pi433_dev), pi433_spi_driver.driver.name);
+ debugfs_remove(root_dir);
+}
+module_exit(pi433_exit);
+
+MODULE_AUTHOR("Marcus Wolf, <linux@wolf-entwicklungen.de>");
+MODULE_DESCRIPTION("Driver for Pi433");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:pi433");