[PATCH v3] staging: nrf24: add new driver for 2.4GHz radio transceiver

From: Marcin Ciupak
Date: Wed Oct 17 2018 - 04:55:01 EST


This patch adds driver for Nordic Semiconductor nRF24L01+ radio
transceiver.

Signed-off-by: Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
---
Changes in v2:
- add terminating newlines to all logging formats
Changes in v3:
- patch subject
- comments cleanup
- goto labels cleanup
- scnprintf bugfix
- ida_simple_remove bugfix

drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/nrf24/Kconfig | 16 +
drivers/staging/nrf24/Makefile | 3 +
drivers/staging/nrf24/TODO | 7 +
.../nrf24/devicetree/nrf24-spi0-overlay.dts | 54 ++
.../nrf24/devicetree/nrf24-spi1-overlay.dts | 54 ++
drivers/staging/nrf24/devicetree/nrf24.txt | 1 +
drivers/staging/nrf24/nRF24L01.h | 82 ++
drivers/staging/nrf24/nrf24_enums.h | 60 ++
drivers/staging/nrf24/nrf24_hal.c | 764 +++++++++++++++
drivers/staging/nrf24/nrf24_hal.h | 54 ++
drivers/staging/nrf24/nrf24_if.c | 893 ++++++++++++++++++
drivers/staging/nrf24/nrf24_if.h | 63 ++
drivers/staging/nrf24/nrf24_sysfs.c | 707 ++++++++++++++
drivers/staging/nrf24/nrf24_sysfs.h | 14 +
16 files changed, 2775 insertions(+)
create mode 100644 drivers/staging/nrf24/Kconfig
create mode 100644 drivers/staging/nrf24/Makefile
create mode 100644 drivers/staging/nrf24/TODO
create mode 100644 drivers/staging/nrf24/devicetree/nrf24-spi0-overlay.dts
create mode 100644 drivers/staging/nrf24/devicetree/nrf24-spi1-overlay.dts
create mode 100644 drivers/staging/nrf24/devicetree/nrf24.txt
create mode 100644 drivers/staging/nrf24/nRF24L01.h
create mode 100644 drivers/staging/nrf24/nrf24_enums.h
create mode 100644 drivers/staging/nrf24/nrf24_hal.c
create mode 100644 drivers/staging/nrf24/nrf24_hal.h
create mode 100644 drivers/staging/nrf24/nrf24_if.c
create mode 100644 drivers/staging/nrf24/nrf24_if.h
create mode 100644 drivers/staging/nrf24/nrf24_sysfs.c
create mode 100644 drivers/staging/nrf24/nrf24_sysfs.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 1abf76be2aa8..55d688f3112e 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -126,4 +126,6 @@ source "drivers/staging/axis-fifo/Kconfig"

source "drivers/staging/erofs/Kconfig"

+source "drivers/staging/nrf24/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index ab0cbe8815b1..c18e74df03af 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_SOC_MT7621) += mt7621-dts/
obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/
obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
obj-$(CONFIG_EROFS_FS) += erofs/
+obj-$(CONFIG_NRF24) += nrf24/
diff --git a/drivers/staging/nrf24/Kconfig b/drivers/staging/nrf24/Kconfig
new file mode 100644
index 000000000000..67ebf14dd982
--- /dev/null
+++ b/drivers/staging/nrf24/Kconfig
@@ -0,0 +1,16 @@
+config NRF24
+ tristate "nRF24L01+ 2.4GHz radio module support"
+ depends on SPI
+ help
+ This enables support for Nordic Semiconductor nRF24L01+ radio module,
+ with the following features:
+ - multiple radio module instances via nrfX
+ - dedicated /dev/nrfX.Y device per pipe per instance
+ - dynamic and static payload lengths
+ - configuration via sysfs (/sys/class/nrfX)
+ - poll mechanism
+ - 64kB RX FIFO per pipe
+ - 64kB TX FIFO
+
+ To compile this driver as a module, choose M here: the module will be
+ called nrf24.
diff --git a/drivers/staging/nrf24/Makefile b/drivers/staging/nrf24/Makefile
new file mode 100644
index 000000000000..f5222567c632
--- /dev/null
+++ b/drivers/staging/nrf24/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_NRF24) += nrf24.o
+
+nrf24-objs := nrf24_if.o nrf24_hal.o nrf24_sysfs.o
diff --git a/drivers/staging/nrf24/TODO b/drivers/staging/nrf24/TODO
new file mode 100644
index 000000000000..a089e43faac5
--- /dev/null
+++ b/drivers/staging/nrf24/TODO
@@ -0,0 +1,7 @@
+Todo:
+- opening and closing pipes via sysfs
+- improve switching in between RX and TX
+- improve handling of MAX_RT interrupt
+- find and fix bugs
+- code cleanup
+
diff --git a/drivers/staging/nrf24/devicetree/nrf24-spi0-overlay.dts b/drivers/staging/nrf24/devicetree/nrf24-spi0-overlay.dts
new file mode 100644
index 000000000000..130e6787b76d
--- /dev/null
+++ b/drivers/staging/nrf24/devicetree/nrf24-spi0-overlay.dts
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//
+// Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+//
+
+// Definitions for NRF24
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "bcm,bcm2835", "bcm,bcm2708", "bcm,bcm2709";
+
+ fragment@0 {
+ target = <&spi0>;
+ __overlay__ {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ spidev@0 {
+ status = "disabled";
+ };
+
+ nrf0: nrf0@0 {
+ compatible = "nordic,nrf24";
+ reg = <0>; /* CS0 */
+ pinctrl-names = "default";
+ pinctrl-0 = <&nrf0_pins>;
+ interrupt-parent = <&gpio>;
+ interrupts = <24 0x2>; /* falling edge */
+ irq-gpio = <&gpio 24 0>;
+ ce-gpio = <&gpio 25 0>;
+ spi-max-frequency = <5000000>;
+ status = "okay";
+ };
+ };
+ };
+
+ fragment@1 {
+ target = <&gpio>;
+ __overlay__ {
+ nrf0_pins: nrf0_pins {
+ brcm,pins = <24 25>;
+ brcm,function = <0 1>; /* in out */
+ };
+ };
+ };
+
+ __overrides__ {
+ int_pin = <&nrf0>, "interrupts:0", <&nrf0_pins>, "brcm,pins:0";
+ speed = <&nrf0>, "spi-max-frequency:0";
+ };
+};
diff --git a/drivers/staging/nrf24/devicetree/nrf24-spi1-overlay.dts b/drivers/staging/nrf24/devicetree/nrf24-spi1-overlay.dts
new file mode 100644
index 000000000000..ff07507d0a14
--- /dev/null
+++ b/drivers/staging/nrf24/devicetree/nrf24-spi1-overlay.dts
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//
+// Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+//
+
+/* Definitions for NRF24 */
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "bcm,bcm2835", "bcm,bcm2708", "bcm,bcm2709";
+
+ fragment@0 {
+ target = <&spi1>;
+ __overlay__ {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ spidev@0 {
+ status = "disabled";
+ };
+
+ nrf1: nrf1@0 {
+ compatible = "nordic,nrf24";
+ reg = <0>; /* CS0 */
+ pinctrl-names = "default";
+ pinctrl-0 = <&nrf1_pins>;
+ interrupt-parent = <&gpio>;
+ interrupts = <17 0x2>; /* falling edge */
+ irq-gpio = <&gpio 17 0>;
+ ce-gpio = <&gpio 27 0>;
+ spi-max-frequency = <5000000>;
+ status = "okay";
+ };
+ };
+ };
+
+ fragment@1 {
+ target = <&gpio>;
+ __overlay__ {
+ nrf1_pins: nrf1_pins {
+ brcm,pins = <17 27>;
+ brcm,function = <0 1>; // in out
+ };
+ };
+ };
+
+ __overrides__ {
+ int_pin = <&nrf1>, "interrupts:0", <&nrf1_pins>, "brcm,pins:0";
+ speed = <&nrf1>, "spi-max-frequency:0";
+ };
+};
diff --git a/drivers/staging/nrf24/devicetree/nrf24.txt b/drivers/staging/nrf24/devicetree/nrf24.txt
new file mode 100644
index 000000000000..0eff8bfae97f
--- /dev/null
+++ b/drivers/staging/nrf24/devicetree/nrf24.txt
@@ -0,0 +1 @@
+scripts/dtc/dtc -@ -I dts -O dtb -o nrf24-spi0.dtbo nrf24-spi0-overlay.dts
diff --git a/drivers/staging/nrf24/nRF24L01.h b/drivers/staging/nrf24/nRF24L01.h
new file mode 100644
index 000000000000..ed7fac6c9c38
--- /dev/null
+++ b/drivers/staging/nrf24/nRF24L01.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#ifndef NRF24L01_H
+#define NRF24L01_H
+
+/* nRF24L01 Register map */
+
+#define CONFIG 0x00
+#define EN_AA 0x01
+#define EN_RXADDR 0x02
+#define SETUP_AW 0x03
+#define SETUP_RETR 0x04
+#define RF_CH 0x05
+#define RF_SETUP 0x06
+#define STATUS 0x07
+#define OBSERVE_TX 0x08
+#define CD 0x09
+#define RX_ADDR_P0 0x0A
+#define RX_ADDR_P1 0x0B
+#define RX_ADDR_P2 0x0C
+#define RX_ADDR_P3 0x0D
+#define RX_ADDR_P4 0x0E
+#define RX_ADDR_P5 0x0F
+#define TX_ADDR 0x10
+#define RX_PW_P0 0x11
+#define RX_PW_P1 0x12
+#define RX_PW_P2 0x13
+#define RX_PW_P3 0x14
+#define RX_PW_P4 0x15
+#define RX_PW_P5 0x16
+#define FIFO_STATUS 0x17
+#define DYNPD 0x1C
+#define FEATURE 0x1D
+
+/* nRF24L01 Instruction Definitions */
+#define W_REGISTER 0x20
+#define R_RX_PL_WID 0x60
+#define R_RX_PAYLOAD 0x61
+#define W_TX_PAYLOAD 0xA0
+#define W_ACK_PAYLOAD 0xA8
+#define W_TX_PAYLOAD_NOACK 0xB0
+#define FLUSH_TX 0xE1
+#define FLUSH_RX 0xE2
+#define REUSE_TX_PL 0xE3
+#define LOCK_UNLOCK 0x50
+#define NOP 0xFF
+
+/* CONFIG 0x00 */
+#define MASK_RX_DR 0x40
+#define MASK_TX_DS 0x20
+#define MASK_MAX_RT 0x10
+#define EN_CRC 0x08
+#define CRCO 0x04
+#define PWR_UP 0x02
+#define PRIM_RX 0x01
+
+/* RF_SETUP 0x06 */
+#define RF_DR_LO 0x20
+#define PLL_LOCK 0x10
+#define RF_DR_HI 0x08
+#define RF_PWR1 0x04
+#define RF_PWR0 0x02
+
+/* STATUS 0x07 */
+#define RX_DR 0x40
+#define TX_DS 0x20
+#define MAX_RT 0x10
+#define TX_FULL 0x01
+
+/* FEATURE 0x1D */
+#define EN_DPL 0x04
+#define EN_ACK_PAY 0x02
+#define EN_DYN_ACK 0x01
+
+#define PLOAD_MAX 32
+
+#endif
diff --git a/drivers/staging/nrf24/nrf24_enums.h b/drivers/staging/nrf24/nrf24_enums.h
new file mode 100644
index 000000000000..89f35db370b6
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_enums.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#ifndef NRF24_ENUMS_H
+#define NRF24_ENUMS_H
+
+enum nrf24_pipe_num {
+ NRF24_PIPE0,
+ NRF24_PIPE1,
+ NRF24_PIPE2,
+ NRF24_PIPE3,
+ NRF24_PIPE4,
+ NRF24_PIPE5,
+ NRF24_TX,
+ NRF24_PIPE_ALL = 0xFF
+};
+
+enum nrf24_crc_mode {
+ NRF24_CRC_OFF,
+ NRF24_CRC_8BIT = 2,
+ NRF24_CRC_16BIT
+};
+
+enum nrf24_address_width {
+ NRF24_AW_3 = 3,
+ NRF24_AW_4,
+ NRF24_AW_5
+};
+
+enum nrf24_pload {
+ NRF24_TX_PLOAD = 7,
+ NRF24_TX_PLOAD_NOACK,
+ NRF24_RX_PLOAD,
+ NRF24_ACK_PLOAD
+};
+
+enum nrf24_datarate {
+ NRF24_DATARATE_1MBPS,
+ NRF24_DATARATE_2MBPS,
+ NRF24_DATARATE_256KBPS
+};
+
+enum nrf24_mode {
+ NRF24_MODE_TX,
+ NRF24_MODE_RX
+};
+
+enum nrf24_rf_power {
+ NRF24_POWER_18DBM,
+ NRF24_POWER_12DBM,
+ NRF24_POWER_6DBM,
+ NRF24_POWER_0DBM
+};
+
+#endif
+
diff --git a/drivers/staging/nrf24/nrf24_hal.c b/drivers/staging/nrf24/nrf24_hal.c
new file mode 100644
index 000000000000..0d2242276d40
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_hal.c
@@ -0,0 +1,764 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/spi/spi.h>
+
+#include "nrf24_hal.h"
+
+static ssize_t nrf24_read_reg(struct spi_device *spi, u8 addr)
+{
+ ssize_t ret;
+
+ ret = spi_w8r8(spi, addr);
+
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: read 0x%X FAILED\n", __func__, addr);
+
+ return ret;
+}
+
+static ssize_t nrf24_write_reg(struct spi_device *spi, u8 addr, u8 val)
+{
+ ssize_t ret;
+ u8 buffer[2];
+
+ buffer[0] = addr;
+ buffer[1] = val;
+
+ if (addr < W_REGISTER) {
+ buffer[0] = buffer[0] + W_REGISTER;
+ ret = spi_write(spi, buffer, 2);
+ } else if (addr != FLUSH_TX &&
+ addr != FLUSH_RX &&
+ addr != REUSE_TX_PL) {
+ ret = spi_write(spi, buffer, 2);
+ } else {
+ ret = spi_write(spi, buffer, 1);
+ }
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: write 0x%X to 0x%X FAILED\n",
+ __func__, val, addr);
+
+ return ret;
+}
+
+static ssize_t nrf24_write_multireg(struct spi_device *spi,
+ u8 reg,
+ u8 *buf,
+ u8 length)
+{
+ u8 buffer[PLOAD_MAX + 1];
+
+ if (!length)
+ return -EINVAL;
+
+ switch (reg) {
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ case NRF24_TX:
+ buffer[0] = W_REGISTER + RX_ADDR_P0 + reg;
+ break;
+ case NRF24_TX_PLOAD:
+ buffer[0] = W_TX_PAYLOAD;
+ break;
+ case NRF24_TX_PLOAD_NOACK:
+ buffer[0] = W_TX_PAYLOAD_NOACK;
+ break;
+ default:
+ dev_dbg(&spi->dev, "%s: invalid parameter\n", __func__);
+ return -EINVAL;
+ }
+
+ memcpy(buffer + 1, buf, length);
+
+ return spi_write(spi, buffer, length + 1);
+}
+
+static ssize_t nrf24_read_multireg(struct spi_device *spi, u8 reg, u8 *buf)
+{
+ ssize_t ret;
+ u8 reg_addr;
+ ssize_t length = 0;
+
+ switch (reg) {
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ case NRF24_TX:
+ length = nrf24_get_address_width(spi);
+ if (length < 0)
+ return length;
+ reg_addr = RX_ADDR_P0 + reg;
+ break;
+ case NRF24_RX_PLOAD:
+ ret = nrf24_get_rx_data_source(spi);
+ if (ret < 0)
+ return ret;
+
+ if (ret < NRF24_TX_PLOAD) {
+ length = nrf24_get_rx_pl_w(spi);
+ if (length < 0)
+ return length;
+ reg_addr = R_RX_PAYLOAD;
+ }
+ break;
+ default:
+ dev_dbg(&spi->dev, "%s: invalid parameter\n", __func__);
+ return -EINVAL;
+ }
+
+ if (length > 0) {
+ ret = spi_write_then_read(spi,
+ &reg_addr,
+ 1,
+ buf,
+ length);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return length;
+}
+
+ssize_t nrf24_get_dynamic_pl(struct spi_device *spi)
+{
+ ssize_t feature;
+
+ feature = nrf24_read_reg(spi, FEATURE);
+ if (feature < 0)
+ return feature;
+
+ return (feature & EN_DPL) == EN_DPL;
+}
+
+ssize_t nrf24_enable_dynamic_pl(struct spi_device *spi)
+{
+ ssize_t feature;
+
+ feature = nrf24_read_reg(spi, FEATURE);
+ if (feature < 0)
+ return feature;
+ return nrf24_write_reg(spi, FEATURE, feature | EN_DPL);
+}
+
+ssize_t nrf24_disable_dynamic_pl(struct spi_device *spi)
+{
+ ssize_t feature;
+
+ feature = nrf24_read_reg(spi, FEATURE);
+ if (feature < 0)
+ return feature;
+ return nrf24_write_reg(spi, FEATURE, feature & ~EN_DPL);
+}
+
+static ssize_t nrf24_setup_dynamic_pl(struct spi_device *spi,
+ u8 pipe,
+ bool enable)
+{
+ ssize_t dynpd;
+ ssize_t ret;
+
+ if (pipe != NRF24_PIPE0 && enable) {
+ ret = nrf24_setup_dynamic_pl(spi, NRF24_PIPE0, enable);
+ if (ret < 0)
+ return ret;
+ }
+ dynpd = nrf24_read_reg(spi, DYNPD);
+ if (dynpd < 0)
+ return dynpd;
+
+ if (enable) {
+ ret = nrf24_setup_auto_ack(spi, pipe, enable);
+ if (ret < 0)
+ return ret;
+
+ dynpd |= BIT(pipe);
+ } else {
+ dynpd &= ~BIT(pipe);
+ }
+ ret = nrf24_write_reg(spi, DYNPD, dynpd);
+ if (ret < 0)
+ return ret;
+
+ if (dynpd)
+ ret = nrf24_enable_dynamic_pl(spi);
+ else
+ ret = nrf24_disable_dynamic_pl(spi);
+
+ return ret;
+}
+
+ssize_t nrf24_setup_auto_ack(struct spi_device *spi, u8 pipe, bool enable)
+{
+ ssize_t aa;
+
+ aa = nrf24_read_reg(spi, EN_AA);
+ if (aa < 0)
+ return aa;
+ if (enable)
+ aa |= BIT(pipe);
+ else
+ aa &= ~BIT(pipe);
+
+ return nrf24_write_reg(spi, EN_AA, aa);
+}
+
+static ssize_t nrf24_enable_pipe(struct spi_device *spi, u8 pipe)
+{
+ ssize_t rxaddr;
+
+ rxaddr = nrf24_read_reg(spi, EN_RXADDR);
+ if (rxaddr < 0)
+ return rxaddr;
+ return nrf24_write_reg(spi, EN_RXADDR, rxaddr | BIT(pipe));
+}
+
+static ssize_t nrf24_disable_pipe(struct spi_device *spi, u8 pipe)
+{
+ ssize_t rxaddr;
+
+ rxaddr = nrf24_read_reg(spi, EN_RXADDR);
+ if (rxaddr < 0)
+ return rxaddr;
+ return nrf24_write_reg(spi, EN_RXADDR, rxaddr & ~BIT(pipe));
+}
+
+ssize_t nrf24_open_pipe(struct spi_device *spi, enum nrf24_pipe_num pipe)
+{
+ ssize_t ret = -EINVAL;
+
+ switch (pipe) {
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ case NRF24_PIPE2:
+ case NRF24_PIPE3:
+ case NRF24_PIPE4:
+ case NRF24_PIPE5:
+ ret = nrf24_enable_pipe(spi, pipe);
+ break;
+ case NRF24_PIPE_ALL:
+ ret = nrf24_write_reg(spi, EN_RXADDR, 0x3F);
+ break;
+ default:
+ dev_dbg(&spi->dev, "%s: invalid parameter\n", __func__);
+ }
+
+ return ret;
+}
+
+ssize_t nrf24_close_pipe(struct spi_device *spi, enum nrf24_pipe_num pipe)
+{
+ ssize_t ret = -EINVAL;
+
+ switch (pipe) {
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ case NRF24_PIPE2:
+ case NRF24_PIPE3:
+ case NRF24_PIPE4:
+ case NRF24_PIPE5:
+ ret = nrf24_disable_pipe(spi, pipe);
+ if (ret < 0)
+ break;
+ ret = nrf24_setup_auto_ack(spi, pipe, false);
+ break;
+ case NRF24_PIPE_ALL:
+ ret = nrf24_write_reg(spi, EN_RXADDR, 0x00);
+ if (ret)
+ break;
+ ret = nrf24_write_reg(spi, EN_AA, 0x00);
+ break;
+ default:
+ dev_dbg(&spi->dev, "%s: invalid parameter\n", __func__);
+ }
+
+ return ret;
+}
+
+ssize_t nrf24_set_address(struct spi_device *spi,
+ enum nrf24_pipe_num pipe,
+ u8 *addr)
+{
+ ssize_t ret = -EINVAL;
+ ssize_t length;
+
+ switch (pipe) {
+ case NRF24_TX:
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ length = nrf24_get_address_width(spi);
+ if (length < 0)
+ return length;
+ ret = nrf24_write_multireg(spi, pipe, addr, length);
+ break;
+ case NRF24_PIPE2:
+ case NRF24_PIPE3:
+ case NRF24_PIPE4:
+ case NRF24_PIPE5:
+ ret = nrf24_write_reg(spi, RX_ADDR_P0 + pipe, *addr);
+ break;
+ default:
+ dev_dbg(&spi->dev, "%s: invalid parameter\n", __func__);
+ }
+
+ return ret;
+}
+
+ssize_t nrf24_get_address(struct spi_device *spi,
+ enum nrf24_pipe_num pipe,
+ u8 *addr)
+{
+ ssize_t ret;
+ ssize_t length;
+
+ switch (pipe) {
+ case NRF24_PIPE0:
+ case NRF24_PIPE1:
+ case NRF24_TX:
+ ret = nrf24_read_multireg(spi, pipe, addr);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ length = nrf24_read_multireg(spi, NRF24_PIPE1, addr);
+ if (length < 0)
+ return length;
+ ret = nrf24_read_reg(spi, RX_ADDR_P0 + pipe);
+ if (ret < 0)
+ return ret;
+ *(addr) = ret;
+ break;
+ }
+
+ return nrf24_get_address_width(spi);
+}
+
+ssize_t nrf24_set_crc_mode(struct spi_device *spi, enum nrf24_crc_mode mode)
+{
+ ssize_t config;
+
+ config = nrf24_read_reg(spi, CONFIG);
+ if (config < 0)
+ return config;
+ config &= ~(EN_CRC | CRCO);
+ config |= (mode << 2);
+
+ config = nrf24_write_reg(spi, CONFIG, config);
+
+ return config;
+}
+
+ssize_t nrf24_get_crc_mode(struct spi_device *spi)
+{
+ ssize_t config;
+
+ config = nrf24_read_reg(spi, CONFIG);
+ if (config < 0)
+ return config;
+ config &= (EN_CRC | CRCO);
+ config >>= 2;
+
+ return config;
+}
+
+ssize_t nrf24_set_auto_retr_delay(struct spi_device *spi, u16 delay)
+{
+ ssize_t retr;
+
+ retr = nrf24_read_reg(spi, SETUP_RETR);
+ if (retr < 0)
+ return retr;
+
+ retr &= 0x0F;
+ retr |= (((delay / 250) - 1) << 4);
+
+ return nrf24_write_reg(spi, SETUP_RETR, retr);
+}
+
+ssize_t nrf24_get_auto_retr_delay(struct spi_device *spi)
+{
+ ssize_t retr;
+
+ retr = nrf24_read_reg(spi, SETUP_RETR);
+ if (retr < 0)
+ return retr;
+
+ return ((retr >> 4) + 1) * 250;
+}
+
+ssize_t nrf24_set_auto_retr_count(struct spi_device *spi, u8 count)
+{
+ ssize_t retr;
+
+ retr = nrf24_read_reg(spi, SETUP_RETR);
+ if (retr < 0)
+ return retr;
+
+ retr &= 0xF0;
+ retr |= (count & 0x0F);
+
+ return nrf24_write_reg(spi, SETUP_RETR, retr);
+}
+
+ssize_t nrf24_get_auto_retr_count(struct spi_device *spi)
+{
+ ssize_t retr;
+
+ retr = nrf24_read_reg(spi, SETUP_RETR);
+
+ if (retr < 0)
+ return retr;
+
+ return retr & 0x0F;
+}
+
+ssize_t nrf24_set_address_width(struct spi_device *spi,
+ enum nrf24_address_width aw)
+{
+ return nrf24_write_reg(spi, SETUP_AW, aw - 2);
+}
+
+ssize_t nrf24_get_address_width(struct spi_device *spi)
+{
+ return nrf24_read_reg(spi, SETUP_AW) + 2;
+}
+
+ssize_t nrf24_lock_unlock(struct spi_device *spi)
+{
+ return nrf24_write_reg(spi, LOCK_UNLOCK, 0x73);
+}
+
+ssize_t nrf24_set_datarate(struct spi_device *spi, enum nrf24_datarate datarate)
+{
+ ssize_t rf;
+
+ rf = nrf24_read_reg(spi, RF_SETUP);
+ if (rf < 0)
+ return rf;
+ if (datarate == NRF24_DATARATE_1MBPS)
+ rf &= ~RF_DR_HI;
+ else
+ rf |= RF_DR_HI;
+
+ return nrf24_write_reg(spi, RF_SETUP, rf);
+}
+
+ssize_t nrf24_get_datarate(struct spi_device *spi)
+{
+ ssize_t rf;
+ ssize_t lo;
+ ssize_t hi;
+
+ rf = nrf24_read_reg(spi, RF_SETUP);
+ if (rf < 0)
+ return rf;
+
+ lo = rf & RF_DR_LO;
+ hi = rf & RF_DR_HI;
+
+ if (lo && hi)
+ return -EINVAL;
+ if (lo)
+ return NRF24_DATARATE_256KBPS;
+ if (hi)
+ return NRF24_DATARATE_2MBPS;
+ return NRF24_DATARATE_1MBPS;
+}
+
+ssize_t nrf24_set_mode(struct spi_device *spi, enum nrf24_mode mode)
+{
+ ssize_t config;
+
+ config = nrf24_read_reg(spi, CONFIG);
+
+ if (config < 0)
+ return config;
+
+ if (mode == NRF24_MODE_RX)
+ config |= PRIM_RX;
+ else
+ config &= ~PRIM_RX;
+
+ return nrf24_write_reg(spi, CONFIG, config);
+}
+
+ssize_t nrf24_set_rf_power(struct spi_device *spi, enum nrf24_rf_power rf_pwr)
+{
+ ssize_t rf_setup;
+
+ rf_setup = nrf24_read_reg(spi, RF_SETUP);
+
+ if (rf_setup < 0)
+ return rf_setup;
+
+ rf_setup &= ~(RF_PWR1 | RF_PWR0);
+ rf_setup |= (rf_pwr << 1);
+
+ return nrf24_write_reg(spi, RF_SETUP, rf_setup);
+}
+
+ssize_t nrf24_get_rf_power(struct spi_device *spi)
+{
+ ssize_t rf;
+
+ rf = nrf24_read_reg(spi, RF_SETUP);
+
+ if (rf < 0)
+ return rf;
+
+ rf &= (RF_PWR1 | RF_PWR0);
+ rf >>= 1;
+
+ return rf;
+}
+
+//plw = 0 -> dynamic
+ssize_t nrf24_set_rx_pload_width(struct spi_device *spi, u8 pipe, u8 plw)
+{
+ ssize_t ret;
+
+ if (plw > PLOAD_MAX)
+ return -EINVAL;
+
+ ret = nrf24_write_reg(spi, RX_PW_P0 + pipe, plw);
+ if (ret < 0)
+ return ret;
+
+ return nrf24_setup_dynamic_pl(spi, pipe, plw == 0);
+}
+
+ssize_t nrf24_set_rf_channel(struct spi_device *spi, u8 channel)
+{
+ return nrf24_write_reg(spi, RF_CH, channel);
+}
+
+ssize_t nrf24_power_up(struct spi_device *spi)
+{
+ ssize_t config;
+
+ config = nrf24_read_reg(spi, CONFIG);
+
+ if (config < 0)
+ return config;
+
+ return nrf24_write_reg(spi, CONFIG, config | PWR_UP);
+}
+
+ssize_t nrf24_write_tx_pload(struct spi_device *dev, u8 *buf, u8 length)
+{
+ return nrf24_write_multireg(dev, NRF24_TX_PLOAD, buf, length);
+}
+
+ssize_t nrf24_write_tx_pload_noack(struct spi_device *dev, u8 *buf, u8 length)
+{
+ return nrf24_write_multireg(dev, NRF24_TX_PLOAD_NOACK, buf, length);
+}
+
+ssize_t nrf24_read_rx_pload(struct spi_device *spi, u8 *buf)
+{
+ return nrf24_read_multireg(spi, NRF24_RX_PLOAD, buf);
+}
+
+ssize_t nrf24_get_status(struct spi_device *spi)
+{
+ return nrf24_read_reg(spi, STATUS);
+}
+
+ssize_t nrf24_get_rx_data_source(struct spi_device *spi)
+{
+ ssize_t status;
+
+ status = nrf24_get_status(spi);
+ if (status < 0)
+ return status;
+ return (status & 0x0E) >> 1;
+}
+
+ssize_t nrf24_get_rx_pload_width(struct spi_device *spi, u8 pipe)
+{
+ return nrf24_read_reg(spi, RX_PW_P0 + pipe);
+}
+
+ssize_t nrf24_get_rx_pl_w(struct spi_device *spi)
+{
+ return nrf24_read_reg(spi, R_RX_PL_WID);
+}
+
+ssize_t nrf24_soft_reset(struct spi_device *spi)
+{
+ ssize_t ret;
+ u8 addr0[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
+ u8 addr1[5] = {0xC2, 0xC2, 0xC2, 0xC2, 0xC2};
+
+ ret = nrf24_write_reg(spi, CONFIG, 0x08);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, EN_AA, 0x3F);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, EN_RXADDR, 0x03);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, SETUP_AW, 0x03);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, SETUP_RETR, 0x03);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RF_CH, 0x02);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RF_SETUP, 0x07);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, STATUS, 0x70);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_address(spi, NRF24_PIPE0, addr0);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_address(spi, NRF24_PIPE1, addr1);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_ADDR_P2, 0xC3);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_ADDR_P3, 0xC4);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_ADDR_P4, 0xC5);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_ADDR_P5, 0xC6);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_address(spi, NRF24_TX, addr0);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P0, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P1, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P2, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P3, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P4, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, RX_PW_P5, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, DYNPD, 0x00);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_write_reg(spi, FEATURE, 0x00);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+ssize_t nrf24_clear_irq(struct spi_device *spi, u8 irq)
+{
+ return nrf24_write_reg(spi, STATUS, irq);
+}
+
+ssize_t nrf24_flush_fifo(struct spi_device *spi)
+{
+ ssize_t ret;
+
+ ret = nrf24_write_reg(spi, FLUSH_RX, 0);
+ if (ret < 0)
+ return ret;
+ return nrf24_write_reg(spi, FLUSH_TX, 0);
+}
+
+ssize_t nrf24_print_status(struct spi_device *spi)
+{
+const u8 nrf_reg[] = {
+ CONFIG,
+ EN_AA,
+ EN_RXADDR,
+ SETUP_AW,
+ SETUP_RETR,
+ RF_CH,
+ RF_SETUP,
+ STATUS,
+ OBSERVE_TX,
+ CD,
+ FIFO_STATUS,
+ DYNPD,
+ FEATURE
+};
+
+char *nrf_reg_name[] = {
+ "CONFIG",
+ "EN_AA",
+ "EN_RXADDR",
+ "SETUP_AW",
+ "SETUP_RETR",
+ "RF_CH",
+ "RF_SETUP",
+ "STATUS",
+ "OBSERVE_TX",
+ "CD",
+ "FIFO_STATUS",
+ "DYNPD",
+ "FEATURE"
+};
+
+ ssize_t loop;
+ ssize_t ret;
+
+ for (loop = 0; loop < 13; loop++) {
+ ret = spi_w8r8(spi, nrf_reg[loop]);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(&spi->dev,
+ "%s: %s = 0%02zx\n",
+ __func__,
+ nrf_reg_name[loop],
+ ret);
+ }
+
+ return 0;
+}
+
+ssize_t nrf24_get_auto_ack(struct spi_device *spi, u8 pipe)
+{
+ ssize_t aa;
+
+ aa = nrf24_read_reg(spi, EN_AA);
+ if (aa < 0)
+ return aa;
+
+ return (aa & BIT(pipe)) == BIT(pipe);
+}
+
+ssize_t nrf24_is_rx_fifo_empty(struct spi_device *spi)
+{
+ ssize_t fifo;
+
+ fifo = nrf24_read_reg(spi, FIFO_STATUS);
+ if (fifo < 0)
+ return fifo;
+
+ return fifo & 0x01;
+}
+
+ssize_t nrf24_reuse_tx_pl(struct spi_device *spi)
+{
+ return nrf24_write_reg(spi, REUSE_TX_PL, 0);
+}
+
diff --git a/drivers/staging/nrf24/nrf24_hal.h b/drivers/staging/nrf24/nrf24_hal.h
new file mode 100644
index 000000000000..ce7fc190e286
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_hal.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#ifndef NRF24_HAL_H
+#define NRF24_HAL_H
+
+#include "nRF24L01.h"
+#include "nrf24_enums.h"
+
+ssize_t nrf24_open_pipe(struct spi_device *spi, enum nrf24_pipe_num pipe);
+ssize_t nrf24_close_pipe(struct spi_device *spi, enum nrf24_pipe_num pipe);
+ssize_t nrf24_set_address(struct spi_device *spi, enum nrf24_pipe_num pipe, u8 *addr);
+ssize_t nrf24_get_address(struct spi_device *spi, enum nrf24_pipe_num pipe, u8 *addr);
+ssize_t nrf24_set_crc_mode(struct spi_device *spi, enum nrf24_crc_mode mode);
+ssize_t nrf24_get_crc_mode(struct spi_device *spi);
+ssize_t nrf24_set_auto_retr_count(struct spi_device *spi, u8 count);
+ssize_t nrf24_get_auto_retr_count(struct spi_device *spi);
+ssize_t nrf24_set_auto_retr_delay(struct spi_device *spi, u16 delay);
+ssize_t nrf24_get_auto_retr_delay(struct spi_device *spi);
+ssize_t nrf24_set_address_width(struct spi_device *spi, enum nrf24_address_width aw);
+ssize_t nrf24_get_address_width(struct spi_device *spi);
+ssize_t nrf24_lock_unlock(struct spi_device *spi);
+ssize_t nrf24_set_datarate(struct spi_device *spi, enum nrf24_datarate datarate);
+ssize_t nrf24_get_datarate(struct spi_device *spi);
+ssize_t nrf24_set_mode(struct spi_device *spi, enum nrf24_mode mode);
+ssize_t nrf24_set_rf_power(struct spi_device *spi, enum nrf24_rf_power rf_pwr);
+ssize_t nrf24_get_rf_power(struct spi_device *spi);
+ssize_t nrf24_set_rx_pload_width(struct spi_device *spi, u8 pipe_no, u8 plw);
+ssize_t nrf24_set_rf_channel(struct spi_device *spi, u8 channel);
+ssize_t nrf24_write_tx_pload(struct spi_device *dev, u8 *buf, u8 length);
+ssize_t nrf24_write_tx_pload_noack(struct spi_device *dev, u8 *buf, u8 length);
+ssize_t nrf24_read_rx_pload(struct spi_device *spi, u8 *buf);
+ssize_t nrf24_power_up(struct spi_device *spi);
+ssize_t nrf24_get_status(struct spi_device *spi);
+ssize_t nrf24_get_rx_data_source(struct spi_device *spi);
+ssize_t nrf24_get_rx_pload_width(struct spi_device *spi, u8 pipe);
+ssize_t nrf24_soft_reset(struct spi_device *spi);
+ssize_t nrf24_clear_irq(struct spi_device *spi, u8 irq);
+ssize_t nrf24_flush_fifo(struct spi_device *spi);
+ssize_t nrf24_get_rx_pl_w(struct spi_device *spi);
+ssize_t nrf24_print_status(struct spi_device *spi);
+ssize_t nrf24_get_auto_ack(struct spi_device *spi, u8 pipe);
+ssize_t nrf24_setup_auto_ack(struct spi_device *spi, u8 pipe, bool enable);
+ssize_t nrf24_is_rx_fifo_empty(struct spi_device *spi);
+ssize_t nrf24_reuse_tx_pl(struct spi_device *spi);
+ssize_t nrf24_get_dynamic_pl(struct spi_device *spi);
+ssize_t nrf24_enable_dynamic_pl(struct spi_device *spi);
+ssize_t nrf24_disable_dynamic_pl(struct spi_device *spi);
+
+#endif
diff --git a/drivers/staging/nrf24/nrf24_if.c b/drivers/staging/nrf24/nrf24_if.c
new file mode 100644
index 000000000000..e2f539c7a420
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_if.c
@@ -0,0 +1,893 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/spi/spi.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/kfifo.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/list.h>
+
+#include "nrf24_if.h"
+#include "nrf24_sysfs.h"
+#include "nrf24_hal.h"
+
+#define N_NRF24_MINORS BIT(MINORBITS)
+
+static dev_t nrf24_dev;
+static DEFINE_IDA(nrf24_ida_pipe);
+static DEFINE_IDA(nrf24_ida_dev);
+static struct class *nrf24_class;
+
+ATTRIBUTE_GROUPS(nrf24_pipe);
+ATTRIBUTE_GROUPS(nrf24);
+
+static bool nrf24_is_rx_active(struct nrf24_device *device)
+{
+ struct nrf24_pipe *pipe;
+
+ bool active = false;
+
+ list_for_each_entry(pipe, &device->pipes, list)
+ active |= pipe->rx_size > 0;
+
+ return active;
+}
+
+static void nrf24_ce_hi(struct nrf24_device *device)
+{
+ gpiod_set_value(device->ce, 1);
+}
+
+static void nrf24_ce_lo(struct nrf24_device *device)
+{
+ gpiod_set_value(device->ce, 0);
+}
+
+static struct nrf24_pipe *nrf24_find_pipe_id(struct nrf24_device *device, int id)
+{
+ struct nrf24_pipe *pipe;
+
+ list_for_each_entry(pipe, &device->pipes, list)
+ if (pipe->id == id)
+ return pipe;
+
+ return ERR_PTR(-ENODEV);
+}
+
+static int nrf24_tx_thread(void *data)
+{
+ struct nrf24_device *device = data;
+ struct nrf24_pipe *p;
+ u8 pload[PLOAD_MAX];
+ int ret;
+ ssize_t size;
+ ssize_t pload_length;
+ ssize_t sent = 0;
+ u8 *buf;
+ bool spl;
+ bool dpl = false;
+
+ while (true) {
+ dev_dbg(&device->dev,
+ "%s: waiting for new messages\n",
+ __func__);
+ wait_event_interruptible(device->tx_wait_queue,
+ kthread_should_stop() ||
+ (!nrf24_is_rx_active(device) && !kfifo_is_empty(&device->tx_fifo)));
+
+ if (kthread_should_stop())
+ return 0;
+
+ device->tx_done = false;
+
+ //fifo lock is needed as write to tx fifo may be done by 6 pipes
+ mutex_lock(&device->tx_fifo_mutex);
+
+ ret = kfifo_out(&device->tx_fifo, &p, sizeof(p));
+ if (ret != sizeof(p)) {
+ dev_dbg(&device->dev, "get pipe from fifo failed\n");
+ mutex_unlock(&device->tx_fifo_mutex);
+ continue;
+ }
+
+ ret = kfifo_out(&device->tx_fifo, &size, sizeof(size));
+ if (ret != sizeof(size)) {
+ dev_dbg(&device->dev, "get size from fifo failed\n");
+ mutex_unlock(&device->tx_fifo_mutex);
+ continue;
+ }
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf) {
+ dev_dbg(&device->dev, "buf alloc failed\n");
+ mutex_unlock(&device->tx_fifo_mutex);
+ continue;
+ }
+
+ ret = kfifo_out(&device->tx_fifo, buf, size);
+ if (ret != size) {
+ dev_dbg(&device->dev, "get buf from fifo failed\n");
+ mutex_unlock(&device->tx_fifo_mutex);
+ goto next;
+ }
+
+ mutex_unlock(&device->tx_fifo_mutex);
+
+ //enter Standby-I mode
+ nrf24_ce_lo(device);
+
+ ret = nrf24_set_mode(device->spi, NRF24_MODE_TX);
+ if (ret < 0)
+ goto next;
+
+ //set PIPE0 address in order to receive ACK
+ ret = nrf24_set_address(device->spi,
+ NRF24_PIPE0,
+ (u8 *)&p->cfg.address);
+ if (ret < 0) {
+ dev_dbg(&device->dev, "set PIPE0 address failed (%d)\n", ret);
+ goto next;
+ }
+
+ ret = nrf24_set_address(device->spi,
+ NRF24_TX,
+ (u8 *)&p->cfg.address);
+ if (ret < 0) {
+ dev_dbg(&device->dev, "set TX address failed (%d)\n", ret);
+ goto next;
+ }
+
+ //check if pipe uses static payload length
+ spl = p->cfg.plw != 0;
+
+ //check if dynamic payload length is enabled
+ dpl = nrf24_get_dynamic_pl(device->spi);
+
+ if (spl && dpl) {
+ //disable dynamic payload if pipe
+ //does not use dynamic payload
+ //and dynamic paload is enabled
+ ret = nrf24_disable_dynamic_pl(device->spi);
+ if (ret < 0)
+ goto next;
+ }
+
+ memset(pload, 0, PLOAD_MAX);
+ memcpy(pload, &size, sizeof(size));
+
+ //calculate payload length
+ pload_length = spl ? p->cfg.plw : sizeof(size);
+
+ //send size
+ nrf24_write_tx_pload(device->spi, pload, pload_length);
+ if (ret < 0) {
+ dev_dbg(&device->dev, "write TX PLOAD failed (%d)\n", ret);
+ goto next;
+ }
+
+ //enter TX MODE and start transmission
+ nrf24_ce_hi(device);
+
+ //wait for ACK
+ wait_event_interruptible(device->tx_done_wait_queue,
+ (device->tx_done ||
+ kthread_should_stop()));
+
+ if (kthread_should_stop())
+ goto abort;
+
+ sent = 0;
+
+ while (size > 0) {
+ pload_length = spl ? p->cfg.plw : min_t(ssize_t, size, PLOAD_MAX);
+
+ dev_dbg(&device->dev, "tx %zd bytes\n", pload_length);
+
+ memset(pload, 0, PLOAD_MAX);
+ memcpy(pload, buf + sent, pload_length);
+
+ ret = nrf24_write_tx_pload(device->spi, pload, pload_length);
+
+ if (ret < 0) {
+ dev_dbg(&device->dev,
+ "write TX PLOAD failed (%d)\n",
+ ret);
+ goto next;
+ }
+
+ sent += pload_length;
+ size -= pload_length;
+
+ device->tx_done = false;
+
+ //wait for ACK
+ wait_event_interruptible(device->tx_done_wait_queue,
+ (device->tx_done ||
+ kthread_should_stop()));
+
+ if (kthread_should_stop())
+ goto abort;
+ }
+next:
+ kfree(buf);
+
+ //restore dynamic payload feature
+ if (dpl)
+ nrf24_enable_dynamic_pl(device->spi);
+
+ //if all sent enter RX MODE and start receiving
+ if (kfifo_is_empty(&device->tx_fifo)) {
+ dev_dbg(&device->dev, "%s: NRF24_MODE_RX\n", __func__);
+
+ //enter Standby-I
+ nrf24_ce_lo(device);
+
+ p = nrf24_find_pipe_id(device, NRF24_PIPE0);
+ if (!IS_ERR(p)) {
+ //restore PIPE0 address as it was corrupted
+ nrf24_set_address(device->spi,
+ p->id,
+ (u8 *)&p->cfg.address);
+ }
+
+ nrf24_set_mode(device->spi, NRF24_MODE_RX);
+ nrf24_ce_hi(device);
+ }
+ }
+abort:
+ kfree(buf);
+
+ return 0;
+}
+
+static int nrf24_rx_thread(void *data)
+{
+ struct nrf24_device *device = data;
+ ssize_t pipe;
+ ssize_t length;
+ u8 pload[PLOAD_MAX];
+ struct nrf24_pipe *p;
+
+ while (true) {
+ wait_event_interruptible(device->rx_wait_queue,
+ (!nrf24_is_rx_fifo_empty(device->spi) ||
+ kthread_should_stop()));
+ if (kthread_should_stop())
+ return 0;
+
+ pipe = nrf24_get_rx_data_source(device->spi);
+ if (pipe < 0) {
+ dev_dbg(&device->dev,
+ "%s: get pipe failed (err: %zd)\n",
+ __func__,
+ pipe);
+ continue;
+ }
+
+ if (pipe > NRF24_PIPE5) {
+ dev_dbg(&device->dev,
+ "%s: RX FIFO is empty!\n",
+ __func__);
+ continue;
+ }
+
+ p = nrf24_find_pipe_id(device, pipe);
+ if (IS_ERR(p))
+ continue;
+
+ memset(pload, 0, PLOAD_MAX);
+ length = nrf24_read_rx_pload(device->spi, pload);
+ if (length < 0) {
+ dev_dbg(&device->dev,
+ "%s: could not read pload (err = %zd)\n",
+ __func__,
+ length);
+ continue;
+ }
+
+ dev_dbg(p->dev, "rx %zd bytes\n", length);
+ if (p->rx_size <= 0) {
+ memcpy(&p->rx_size, pload, sizeof(p->rx_size));
+ dev_dbg(p->dev, "RX active\n");
+ } else {
+ length = p->rx_size < p->cfg.plw ? p->rx_size : length;
+
+ p->rx_size -= kfifo_in(&p->rx_fifo, &pload, length);
+
+ if (p->rx_size <= 0) {
+ dev_dbg(p->dev, "RX done\n");
+ wake_up_interruptible(&p->poll_wait_queue);
+ }
+ }
+
+ //start tx if all rx done and tx requested during active rx
+ if (!nrf24_is_rx_active(device) && !kfifo_is_empty(&device->tx_fifo)) {
+ dev_dbg(&device->dev, "wake up TX...\n");
+ wake_up_interruptible(&device->tx_wait_queue);
+ }
+ }
+}
+
+static void nrf24_isr_work_handler(struct work_struct *work)
+{
+ struct nrf24_device *device;
+ ssize_t status;
+
+ device = container_of(work, struct nrf24_device, isr_work);
+
+ status = nrf24_get_status(device->spi);
+ if (status < 0)
+ return;
+
+ if (status & RX_DR) {
+ dev_dbg(&device->dev, "%s: RX_DR\n", __func__);
+ nrf24_clear_irq(device->spi, RX_DR);
+ wake_up_interruptible(&device->rx_wait_queue);
+ }
+
+ if (status & TX_DS) {
+ dev_dbg(&device->dev, "%s: TX_DS\n", __func__);
+ nrf24_clear_irq(device->spi, TX_DS);
+ device->tx_done = true;
+ wake_up_interruptible(&device->tx_done_wait_queue);
+ }
+
+ if (status & MAX_RT) {
+ nrf24_ce_lo(device);
+ dev_dbg_ratelimited(&device->dev, "%s: MAX_RT\n", __func__);
+ nrf24_clear_irq(device->spi, MAX_RT);
+ nrf24_reuse_tx_pl(device->spi);
+ nrf24_ce_hi(device);
+ }
+}
+
+static irqreturn_t nrf24_isr(int irq, void *dev_id)
+{
+ unsigned long flags;
+ struct nrf24_device *device = dev_id;
+
+ spin_lock_irqsave(&device->lock, flags);
+
+ schedule_work(&device->isr_work);
+
+ spin_unlock_irqrestore(&device->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t nrf24_read(struct file *filp,
+ char __user *buf,
+ size_t size,
+ loff_t *f_pos)
+{
+ struct nrf24_pipe *p;
+ unsigned int copied;
+ ssize_t n;
+
+ p = filp->private_data;
+
+ if (kfifo_is_empty(&p->rx_fifo) && (filp->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ n = kfifo_to_user(&p->rx_fifo, buf, size, &copied);
+ if (n)
+ return n;
+ return copied;
+}
+
+static ssize_t nrf24_write(struct file *filp,
+ const char __user *buf,
+ size_t size,
+ loff_t *f_pos)
+{
+ struct nrf24_device *device;
+ struct nrf24_pipe *p;
+ ssize_t n;
+ unsigned int copied;
+
+ p = filp->private_data;
+ device = to_nrf24_device(p->dev->parent);
+
+ dev_dbg(p->dev, "write (%zd)\n", size);
+
+ mutex_lock(&device->tx_fifo_mutex);
+
+ n = kfifo_in(&device->tx_fifo, &p, sizeof(p));
+ if (n != sizeof(p))
+ goto err_kfifo_reset;
+
+ n = kfifo_in(&device->tx_fifo, &size, sizeof(size));
+ if (n != sizeof(size))
+ goto err_kfifo_reset;
+
+ n = kfifo_from_user(&device->tx_fifo,
+ buf,
+ size,
+ &copied);
+ if (n || size != copied)
+ goto err_kfifo_reset;
+
+ mutex_unlock(&device->tx_fifo_mutex);
+
+ wake_up_interruptible(&device->tx_wait_queue);
+
+ return copied;
+err_kfifo_reset:
+ kfifo_reset(&device->tx_fifo);
+ mutex_unlock(&device->tx_fifo_mutex);
+ return -EAGAIN;
+}
+
+static int nrf24_open(struct inode *inode, struct file *filp)
+{
+ struct nrf24_pipe *pipe;
+
+ pipe = container_of(inode->i_cdev, struct nrf24_pipe, cdev);
+
+ if (!pipe) {
+ pr_err("device: minor %d unknown.\n", iminor(inode));
+ return -ENODEV;
+ }
+
+ filp->private_data = pipe;
+ nonseekable_open(inode, filp);
+
+ return 0;
+}
+
+static int nrf24_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+
+ return 0;
+}
+
+static unsigned int nrf24_poll(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct nrf24_device *device;
+ struct nrf24_pipe *p;
+
+ p = filp->private_data;
+ device = to_nrf24_device(p->dev->parent);
+
+ dev_dbg(p->dev, "%s: waiting...\n", __func__);
+ poll_wait(filp, &p->poll_wait_queue, wait);
+ if (!kfifo_is_empty(&p->rx_fifo)) {
+ dev_dbg(p->dev, "%s: got data!\n", __func__);
+ return POLLIN | POLLRDNORM;
+ }
+ dev_dbg(p->dev, "%s: no data!\n", __func__);
+ return 0;
+}
+
+static void nrf24_destroy_devices(struct nrf24_device *device)
+{
+ struct nrf24_pipe *pipe, *temp;
+
+ list_for_each_entry_safe(pipe, temp, &device->pipes, list) {
+ cdev_del(&pipe->cdev);
+ device_destroy(nrf24_class, pipe->devt);
+ ida_simple_remove(&nrf24_ida_pipe, MINOR(pipe->devt));
+ list_del(&pipe->list);
+ kfree(pipe);
+ }
+}
+
+static const struct file_operations nrf24_fops = {
+ .owner = THIS_MODULE,
+ .open = nrf24_open,
+ .release = nrf24_release,
+ .read = nrf24_read,
+ .write = nrf24_write,
+ .llseek = no_llseek,
+ .poll = nrf24_poll,
+};
+
+static struct nrf24_pipe *nrf24_create_pipe(struct nrf24_device *device, int id)
+{
+ int ret;
+ struct nrf24_pipe *p;
+
+ //sets flags to false as well
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p) {
+ ret = -ENOMEM;
+ goto err_return;
+ }
+
+ ret = ida_simple_get(&nrf24_ida_pipe, 0, 0, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&device->dev, "%s: get_minor failed\n", __func__);
+ goto err_free_mem;
+ }
+
+ p->devt = MKDEV(MAJOR(nrf24_dev), ret);
+ p->id = id;
+
+ INIT_KFIFO(p->rx_fifo);
+ init_waitqueue_head(&p->poll_wait_queue);
+
+ p->dev = device_create_with_groups(nrf24_class,
+ &device->dev,
+ p->devt,
+ p,
+ nrf24_pipe_groups,
+ "%s.%d",
+ dev_name(&device->dev),
+ id);
+
+ if (IS_ERR(p->dev)) {
+ dev_err(&device->dev,
+ "%s: device_create of '%s' failed\n",
+ __func__,
+ dev_name(p->dev));
+ ret = PTR_ERR(p->dev);
+ goto err_ida_remove;
+ }
+
+ cdev_init(&p->cdev, &nrf24_fops);
+ p->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&p->cdev, p->devt, 1);
+ if (ret < 0) {
+ dev_err(&device->dev, "%s: cdev failed\n", __func__);
+ goto err_dev_destroy;
+ }
+
+ dev_dbg(&device->dev,
+ "%s: device created: major(%d), minor(%d)\n",
+ __func__,
+ MAJOR(p->devt),
+ MINOR(p->devt));
+
+ return p;
+
+err_dev_destroy:
+ device_destroy(nrf24_class, p->devt);
+err_ida_remove:
+ ida_simple_remove(&nrf24_ida_pipe, MINOR(p->devt));
+err_free_mem:
+ kfree(p);
+err_return:
+ return ERR_PTR(ret);
+}
+
+static void nrf24_gpio_free(struct nrf24_device *device)
+{
+ if (!IS_ERR(device->ce))
+ gpiod_put(device->ce);
+
+ free_irq(device->spi->irq, device);
+}
+
+static int nrf24_gpio_setup(struct nrf24_device *device)
+{
+ int ret;
+
+ device->ce = gpiod_get(&device->spi->dev, "ce", 0);
+
+ if (device->ce == ERR_PTR(-ENOENT))
+ dev_dbg(&device->dev, "%s: no entry for CE\n", __func__);
+ else if (device->ce == ERR_PTR(-EBUSY))
+ dev_dbg(&device->dev, "%s: CE is busy\n", __func__);
+
+ if (IS_ERR(device->ce)) {
+ ret = PTR_ERR(device->ce);
+ dev_err(&device->dev, "%s: CE gpio setup error\n", __func__);
+ return ret;
+ }
+
+ nrf24_ce_lo(device);
+
+ ret = request_irq(device->spi->irq,
+ nrf24_isr,
+ 0,
+ dev_name(&device->dev),
+ device);
+ if (ret < 0) {
+ gpiod_put(device->ce);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void nrf24_dev_release(struct device *dev)
+{
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ida_simple_remove(&nrf24_ida_dev, device->id);
+ kfree(device);
+}
+
+static struct device_type nrf24_dev_type = {
+ .name = "nrf24_device",
+ .release = nrf24_dev_release,
+};
+
+static struct nrf24_device *nrf24_dev_init(struct spi_device *spi)
+{
+ int ret;
+ struct nrf24_device *device;
+ int id;
+
+ id = ida_simple_get(&nrf24_ida_dev, 0, 0, GFP_KERNEL);
+ if (id < 0)
+ return ERR_PTR(id);
+
+ //sets flags to false as well
+ device = kzalloc(sizeof(*device), GFP_KERNEL);
+ if (!device) {
+ ida_simple_remove(&nrf24_ida_dev, id);
+ return ERR_PTR(-ENOMEM);
+ }
+ device->spi = spi;
+
+ dev_set_name(&device->dev, "nrf%d", id);
+ device->id = id;
+ device->dev.parent = &spi->dev;
+ device->dev.class = nrf24_class;
+ device->dev.type = &nrf24_dev_type;
+ device->dev.groups = nrf24_groups;
+ ret = device_register(&device->dev);
+ if (ret < 0) {
+ put_device(&device->dev);
+ ida_simple_remove(&nrf24_ida_dev, id);
+
+ return ERR_PTR(ret);
+ }
+
+ init_waitqueue_head(&device->tx_wait_queue);
+ init_waitqueue_head(&device->tx_done_wait_queue);
+ init_waitqueue_head(&device->rx_wait_queue);
+
+ INIT_WORK(&device->isr_work, nrf24_isr_work_handler);
+ INIT_KFIFO(device->tx_fifo);
+ spin_lock_init(&device->lock);
+ mutex_init(&device->tx_fifo_mutex);
+
+ INIT_LIST_HEAD(&device->pipes);
+
+ return device;
+}
+
+static int nrf24_hal_init(struct nrf24_device *device)
+{
+ int ret;
+ struct spi_device *spi = device->spi;
+ struct nrf24_pipe *pipe;
+
+ ret = nrf24_soft_reset(spi);
+ if (ret < 0)
+ return ret;
+
+ list_for_each_entry(pipe, &device->pipes, list) {
+ ret = nrf24_get_address(spi,
+ pipe->id,
+ (u8 *)&pipe->cfg.address);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_get_auto_ack(spi, pipe->id);
+ if (ret < 0)
+ return ret;
+ pipe->cfg.ack = ret;
+
+ //0 -> dynamic pload
+ pipe->cfg.plw = 0;
+ ret = nrf24_set_rx_pload_width(spi, pipe->id, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = nrf24_flush_fifo(spi);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_open_pipe(spi, NRF24_PIPE_ALL);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_lock_unlock(spi);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_mode(spi, NRF24_MODE_RX);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_crc_mode(spi, NRF24_CRC_16BIT);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_auto_retr_count(spi, 15);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_auto_retr_delay(spi, 4000);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_rf_power(spi, NRF24_POWER_0DBM);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_set_datarate(spi, NRF24_DATARATE_2MBPS);
+ if (ret < 0)
+ return ret;
+ ret = nrf24_power_up(spi);
+ if (ret < 0)
+ return ret;
+
+ nrf24_ce_hi(device);
+
+ return ret;
+}
+
+static int nrf24_probe(struct spi_device *spi)
+{
+ int ret;
+ struct nrf24_device *device;
+ struct nrf24_pipe *pipe;
+ int i;
+
+ spi->mode = SPI_MODE_0;
+ spi->bits_per_word = 8;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "%s: spi_setup failed\n", __func__);
+ return ret;
+ }
+
+ device = nrf24_dev_init(spi);
+ if (IS_ERR(device)) {
+ dev_err(&device->spi->dev, "%s: dev_init failed\n", __func__);
+ return PTR_ERR(device);
+ }
+
+ ret = nrf24_gpio_setup(device);
+ if (ret < 0) {
+ dev_err(&device->dev, "%s: gpio_setup failed\n", __func__);
+ goto err_dev_unregister;
+ }
+
+ for (i = 0; i <= NRF24_PIPE5; i++) {
+ pipe = nrf24_create_pipe(device, i);
+ if (IS_ERR(pipe)) {
+ ret = PTR_ERR(pipe);
+ goto err_devs_destroy;
+ }
+ list_add(&pipe->list, &device->pipes);
+ }
+
+ ret = nrf24_hal_init(device);
+ if (ret < 0)
+ goto err_devs_destroy;
+
+ device->rx_task_struct = kthread_run(nrf24_rx_thread,
+ device,
+ "nrf%d_rx_thread",
+ device->id);
+ if (IS_ERR(device->rx_task_struct)) {
+ dev_err(&device->dev, "start of tx thread failed\n");
+ goto err_devs_destroy;
+ }
+
+ device->tx_task_struct = kthread_run(nrf24_tx_thread,
+ device,
+ "nrf%d_tx_thread",
+ device->id);
+ if (IS_ERR(device->tx_task_struct)) {
+ dev_err(&device->dev, "start of tx thread failed\n");
+ goto err_kthread_stop;
+ }
+
+ spi_set_drvdata(spi, device);
+
+ return 0;
+
+err_kthread_stop:
+ kthread_stop(device->rx_task_struct);
+err_devs_destroy:
+ nrf24_destroy_devices(device);
+ nrf24_gpio_free(device);
+err_dev_unregister:
+ device_unregister(&device->dev);
+ return ret;
+}
+
+static int nrf24_remove(struct spi_device *spi)
+{
+ struct nrf24_device *device = spi_get_drvdata(spi);
+
+ nrf24_gpio_free(device);
+
+ kthread_stop(device->tx_task_struct);
+ kthread_stop(device->rx_task_struct);
+
+ nrf24_destroy_devices(device);
+
+ device_unregister(&device->dev);
+
+ return 0;
+}
+
+static const struct of_device_id nrf24_dt_ids[] = {
+ { .compatible = "nordic,nrf24" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, nrf24_dt_ids);
+
+static struct spi_driver nrf24_spi_driver = {
+ .driver = {
+ .name = "nrf24",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(nrf24_dt_ids),
+ },
+ .probe = nrf24_probe,
+ .remove = nrf24_remove,
+};
+
+static int __init nrf24_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&nrf24_dev, 0, N_NRF24_MINORS,
+ nrf24_spi_driver.driver.name);
+ if (ret < 0) {
+ pr_err("Unable to alloc chrdev region\n");
+ goto err_ida_destroy;
+ }
+
+ nrf24_class = class_create(THIS_MODULE, nrf24_spi_driver.driver.name);
+ if (IS_ERR(nrf24_class)) {
+ pr_err("Unable to create class\n");
+ ret = PTR_ERR(nrf24_class);
+ goto err_unreg_chrdev;
+ }
+
+ ret = spi_register_driver(&nrf24_spi_driver);
+ if (ret < 0) {
+ pr_err("Unable to register spi driver\n");
+ goto err_class_destroy;
+ }
+
+ return 0;
+
+err_class_destroy:
+ class_destroy(nrf24_class);
+err_unreg_chrdev:
+ unregister_chrdev(MAJOR(nrf24_dev), nrf24_spi_driver.driver.name);
+err_ida_destroy:
+ ida_destroy(&nrf24_ida_dev);
+ ida_destroy(&nrf24_ida_pipe);
+
+ return ret;
+}
+module_init(nrf24_init);
+
+static void __exit nrf24_exit(void)
+{
+ spi_unregister_driver(&nrf24_spi_driver);
+ class_destroy(nrf24_class);
+ unregister_chrdev(MAJOR(nrf24_dev), nrf24_spi_driver.driver.name);
+ ida_destroy(&nrf24_ida_dev);
+ ida_destroy(&nrf24_ida_pipe);
+}
+module_exit(nrf24_exit);
+
+MODULE_AUTHOR("Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>");
+MODULE_DESCRIPTION("Driver for NRF24L01+");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:nrf24");
+
diff --git a/drivers/staging/nrf24/nrf24_if.h b/drivers/staging/nrf24/nrf24_if.h
new file mode 100644
index 000000000000..2d9b0a8eaedc
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_if.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#ifndef NRF24_IF_H
+#define NRF24_IF_H
+
+#define FIFO_SIZE 65536
+
+struct nrf24_pipe_cfg {
+ u64 address;
+ u8 ack;
+ ssize_t plw;
+};
+
+struct nrf24_pipe {
+ dev_t devt;
+ struct device *dev;
+ struct cdev cdev;
+ int id;
+ struct nrf24_pipe_cfg cfg;
+
+ STRUCT_KFIFO_REC_1(FIFO_SIZE) rx_fifo;
+ wait_queue_head_t poll_wait_queue;
+ ssize_t rx_size;
+
+ struct list_head list;
+};
+
+struct nrf24_device {
+ u32 id;
+ struct device dev;
+ struct spi_device *spi;
+ struct list_head pipes;
+
+ struct gpio_desc *ce;
+
+ /* for irqsave */
+ spinlock_t lock;
+
+ struct work_struct isr_work;
+
+ /* tx */
+ STRUCT_KFIFO_REC_2(FIFO_SIZE) tx_fifo;
+
+ /* tx fifo lock */
+ struct mutex tx_fifo_mutex;
+ struct task_struct *tx_task_struct;
+ wait_queue_head_t tx_wait_queue;
+ wait_queue_head_t tx_done_wait_queue;
+
+ struct task_struct *rx_task_struct;
+ wait_queue_head_t rx_wait_queue;
+
+ u8 tx_done;
+};
+
+#define to_nrf24_device(device) container_of(device, struct nrf24_device, dev)
+
+#endif /* NRF24_IF_H */
diff --git a/drivers/staging/nrf24/nrf24_sysfs.c b/drivers/staging/nrf24/nrf24_sysfs.c
new file mode 100644
index 000000000000..8449bc0afced
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_sysfs.c
@@ -0,0 +1,707 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/err.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+
+#include "nrf24_if.h"
+#include "nrf24_hal.h"
+#include "nrf24_enums.h"
+
+static struct nrf24_pipe *nrf24_find_pipe_ptr(struct device *dev)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ struct nrf24_pipe *pipe;
+
+ list_for_each_entry(pipe, &device->pipes, list)
+ if (pipe->dev == dev)
+ return pipe;
+
+ return ERR_PTR(-ENODEV);
+}
+
+static ssize_t ack_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ int ret;
+ struct nrf24_pipe *pipe;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = nrf24_get_auto_ack(device->spi, pipe->id);
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t ack_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ int ret;
+ u8 new;
+ struct nrf24_pipe *pipe;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = kstrtou8(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+ if (new < 0 || new > 1)
+ return -EINVAL;
+
+ ret = nrf24_setup_auto_ack(device->spi, pipe->id, new);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t plw_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ int ret;
+ struct nrf24_pipe *pipe;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = nrf24_get_rx_pload_width(device->spi, pipe->id);
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t plw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ int ret;
+ u8 new;
+ u8 old;
+ struct nrf24_pipe *pipe;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = kstrtou8(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+
+ if (new < 0 || new > PLOAD_MAX)
+ return -EINVAL;
+ old = nrf24_get_rx_pload_width(device->spi, pipe->id);
+ if (old < 0)
+ return old;
+
+ if (old != new) {
+ ret = nrf24_set_rx_pload_width(device->spi, pipe->id, new);
+ if (ret < 0)
+ return ret;
+ pipe->cfg.plw = new;
+ }
+
+ return count;
+}
+
+static ssize_t address_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ u8 addr[16];
+ int ret;
+ int count;
+ int i;
+ struct nrf24_pipe *pipe;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = nrf24_get_address(device->spi, pipe->id, addr);
+ if (ret < 0)
+ return ret;
+
+ count = scnprintf(buf, PAGE_SIZE, "0x");
+ for (i = --ret; i >= 0; i--)
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%02X", addr[i]);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ return count;
+}
+
+static ssize_t address_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct nrf24_device *device = to_nrf24_device(dev->parent);
+ int ret;
+ u64 address;
+ int len;
+ struct nrf24_pipe *pipe;
+
+ ret = kstrtoull(buf, 16, &address);
+ if (ret < 0)
+ return ret;
+
+ len = nrf24_get_address_width(device->spi);
+ if (len < 0)
+ return len;
+
+ if (address >= BIT_ULL(len * BITS_PER_BYTE))
+ return -EINVAL;
+
+ pipe = nrf24_find_pipe_ptr(dev);
+ if (IS_ERR(pipe))
+ return PTR_ERR(pipe);
+
+ ret = nrf24_set_address(device->spi, pipe->id, (u8 *)&address);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(ack);
+static DEVICE_ATTR_RW(plw);
+static DEVICE_ATTR_RW(address);
+
+struct attribute *nrf24_pipe_attrs[] = {
+ &dev_attr_ack.attr,
+ &dev_attr_plw.attr,
+ &dev_attr_address.attr,
+ NULL,
+};
+
+static ssize_t tx_address_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nrf24_device *device = to_nrf24_device(dev);
+ u8 addr[16];
+ int ret;
+ int count;
+ int i;
+
+ ret = nrf24_get_address(device->spi, NRF24_TX, addr);
+ if (ret < 0)
+ return ret;
+
+ count = scnprintf(buf, PAGE_SIZE, "0x");
+ for (i = --ret; i >= 0; i--)
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%02X", addr[i]);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ return count;
+}
+
+static ssize_t tx_address_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct nrf24_device *device = to_nrf24_device(dev);
+ int ret;
+ u64 address;
+ int len;
+
+ ret = kstrtoull(buf, 16, &address);
+ if (ret < 0)
+ return ret;
+
+ len = nrf24_get_address_width(device->spi);
+ if (len < 0)
+ return len;
+
+ if (address >= BIT_ULL(len * BITS_PER_BYTE))
+ return -EINVAL;
+
+ ret = nrf24_set_address(device->spi, NRF24_TX, (u8 *)&address);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ nrf24_print_status(device->spi);
+ ret = nrf24_get_status(device->spi);
+ if (ret < 0)
+ return ret;
+ return scnprintf(buf, PAGE_SIZE, "STATUS = 0x%02X\n", ret);
+}
+
+static ssize_t available_crc_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "0 8 16\n");
+}
+
+static ssize_t crc_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_crc_mode(device->spi);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case NRF24_CRC_OFF:
+ ret = scnprintf(buf, PAGE_SIZE, "0\n");
+ break;
+ case NRF24_CRC_8BIT:
+ ret = scnprintf(buf, PAGE_SIZE, "8\n");
+ break;
+ case NRF24_CRC_16BIT:
+ ret = scnprintf(buf, PAGE_SIZE, "16\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static ssize_t crc_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtou8(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+
+ switch (new) {
+ case 0:
+ new = NRF24_CRC_OFF;
+ break;
+ case 8:
+ new = NRF24_CRC_8BIT;
+ break;
+ case 16:
+ new = NRF24_CRC_16BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = nrf24_get_crc_mode(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_crc_mode(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new crc mode = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static ssize_t available_address_width_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "3 4 5\n");
+}
+
+static ssize_t address_width_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_address_width(device->spi);
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t address_width_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtou8(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+
+ if (new != NRF24_AW_3 &&
+ new != NRF24_AW_4 &&
+ new != NRF24_AW_5)
+ return -EINVAL;
+
+ ret = nrf24_get_address_width(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_address_width(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new address width = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static ssize_t available_output_power_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "0 -6 -12 -18\n");
+}
+
+static ssize_t rf_power_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_rf_power(device->spi);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case NRF24_POWER_0DBM:
+ ret = scnprintf(buf, PAGE_SIZE, "0\n");
+ break;
+ case NRF24_POWER_6DBM:
+ ret = scnprintf(buf, PAGE_SIZE, "-6\n");
+ break;
+ case NRF24_POWER_12DBM:
+ ret = scnprintf(buf, PAGE_SIZE, "-12\n");
+ break;
+ case NRF24_POWER_18DBM:
+ ret = scnprintf(buf, PAGE_SIZE, "-18\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static ssize_t rf_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new;
+ s8 tmp;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtos8(buf, 10, &tmp);
+ if (ret < 0)
+ return ret;
+
+ switch (abs(tmp)) {
+ case 0:
+ new = NRF24_POWER_0DBM;
+ break;
+ case 6:
+ new = NRF24_POWER_6DBM;
+ break;
+ case 12:
+ new = NRF24_POWER_12DBM;
+ break;
+ case 18:
+ new = NRF24_POWER_18DBM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = nrf24_get_rf_power(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_rf_power(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new rf power level = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static ssize_t available_data_rate_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "256 1024 2048\n");
+}
+
+static ssize_t data_rate_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_datarate(device->spi);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case NRF24_DATARATE_256KBPS:
+ ret = scnprintf(buf, PAGE_SIZE, "256\n");
+ break;
+ case NRF24_DATARATE_1MBPS:
+ ret = scnprintf(buf, PAGE_SIZE, "1024\n");
+ break;
+ case NRF24_DATARATE_2MBPS:
+ ret = scnprintf(buf, PAGE_SIZE, "2048\n");
+ break;
+ }
+
+ return ret;
+}
+
+static ssize_t data_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new;
+ u16 tmp;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtou16(buf, 10, &tmp);
+ if (ret < 0)
+ return ret;
+
+ switch (tmp) {
+ case 256:
+ new = NRF24_DATARATE_256KBPS;
+ break;
+ case 1024:
+ new = NRF24_DATARATE_1MBPS;
+ break;
+ case 2048:
+ new = NRF24_DATARATE_2MBPS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = nrf24_get_datarate(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_datarate(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new datarate = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static ssize_t available_retr_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ int count = 0;
+
+ for (i = 1; i <= 16; i++)
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%d ", i * 250);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ return count;
+}
+
+static ssize_t retr_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_auto_retr_delay(device->spi);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t retr_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u16 new;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtou16(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+
+ if (new < 250 || new > 4000 || new % 250)
+ return -EINVAL;
+
+ ret = nrf24_get_auto_retr_delay(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_auto_retr_delay(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new autr retr delay = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static ssize_t available_retr_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ int count = 0;
+
+ for (i = 0; i < 16; i++)
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%d ", i);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ return count;
+}
+
+static ssize_t retr_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct nrf24_device *device = to_nrf24_device(dev);
+
+ ret = nrf24_get_auto_retr_count(device->spi);
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t retr_count_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u16 new;
+ struct nrf24_device *device;
+
+ device = to_nrf24_device(dev);
+
+ ret = kstrtou16(buf, 10, &new);
+ if (ret < 0)
+ return ret;
+
+ if (new < 0 || new > 15)
+ return -EINVAL;
+
+ ret = nrf24_get_auto_retr_count(device->spi);
+ if (ret < 0)
+ return ret;
+
+ if (new != ret) {
+ ret = nrf24_set_auto_retr_count(device->spi, new);
+ if (ret < 0)
+ return ret;
+ dev_dbg(dev, "%s: new autr retr count = %d\n", __func__, new);
+ }
+ return count;
+}
+
+static DEVICE_ATTR_RW(tx_address);
+static DEVICE_ATTR_RO(status);
+static DEVICE_ATTR_RO(available_crc);
+static DEVICE_ATTR_RW(crc);
+static DEVICE_ATTR_RO(available_address_width);
+static DEVICE_ATTR_RW(address_width);
+static DEVICE_ATTR_RO(available_output_power);
+static DEVICE_ATTR_RW(rf_power);
+static DEVICE_ATTR_RO(available_data_rate);
+static DEVICE_ATTR_RW(data_rate);
+static DEVICE_ATTR_RO(available_retr_delay);
+static DEVICE_ATTR_RW(retr_delay);
+static DEVICE_ATTR_RO(available_retr_count);
+static DEVICE_ATTR_RW(retr_count);
+
+struct attribute *nrf24_attrs[] = {
+ &dev_attr_tx_address.attr,
+ &dev_attr_status.attr,
+ &dev_attr_crc.attr,
+ &dev_attr_available_crc.attr,
+ &dev_attr_address_width.attr,
+ &dev_attr_available_address_width.attr,
+ &dev_attr_rf_power.attr,
+ &dev_attr_available_output_power.attr,
+ &dev_attr_data_rate.attr,
+ &dev_attr_available_data_rate.attr,
+ &dev_attr_retr_delay.attr,
+ &dev_attr_available_retr_delay.attr,
+ &dev_attr_retr_count.attr,
+ &dev_attr_available_retr_count.attr,
+ NULL,
+};
+
diff --git a/drivers/staging/nrf24/nrf24_sysfs.h b/drivers/staging/nrf24/nrf24_sysfs.h
new file mode 100644
index 000000000000..ae6575b13ffe
--- /dev/null
+++ b/drivers/staging/nrf24/nrf24_sysfs.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2017 Marcin Ciupak <marcin.s.ciupak@xxxxxxxxx>
+ *
+ */
+
+#ifndef NRF24_SYSFS_H
+#define NRF24_SYSFS_H
+
+extern struct attribute *nrf24_pipe_attrs[];
+extern struct attribute *nrf24_attrs[];
+
+#endif /* NRF24_SYSFS_H */
--
2.19.1