summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Liu <victorliu@google.com>2024-01-23 11:40:42 -0800
committerVictor Liu <victorliu@google.com>2024-01-30 00:31:42 +0000
commitab669fdc037be92576600c4014e711dab6a53862 (patch)
tree323dcdcf21e3a7eb8984da5beabedb01a4bd63af
parentcfd86ed41b8dd014dd7527c871a9844edac94000 (diff)
downloaddw3000-ab669fdc037be92576600c4014e711dab6a53862.tar.gz
Bug: 316022384 Signed-off-by: Victor Liu <victorliu@google.com> Change-Id: Id69437d5844a0be1a8a53549c8e3e7403bae4bd3 (cherry picked from commit 7bcafea394ef769256485c53695aea343a32a7ad)
-rw-r--r--.gitlab-ci.yml14
-rw-r--r--COPYING19
-rw-r--r--kernel/.gitignore11
-rw-r--r--kernel/BUILD.bazel23
-rw-r--r--kernel/Kbuild14
-rw-r--r--kernel/Makefile66
-rw-r--r--kernel/drivers/net/ieee802154/.gitignore31
-rw-r--r--kernel/drivers/net/ieee802154/Kbuild40
-rw-r--r--kernel/drivers/net/ieee802154/dw3000-overlay.dts78
-rw-r--r--kernel/drivers/net/ieee802154/dw3000.h769
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_calib.c373
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_calib.h225
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip.h175
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_c0.c419
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_c0.h48
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_d0.c331
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_d0.h48
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_e0.c711
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_chip_e0.h112
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_cir.h83
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_coex.h159
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_compat_reg.h205
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_core.c8404
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_core.h743
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_core_reg.h2219
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_core_tests.c516
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_debugfs.c775
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_debugfs.h74
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_mcps.c1517
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_mcps.h31
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h188
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c201
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h38
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c398
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h41
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c243
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h32
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c330
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h96
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_pctt.h36
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c66
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h34
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_perf.h144
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_pm.h58
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_power_stats.h105
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_spi.c296
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_stm.c406
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_stm.h75
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_testmode.c436
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_testmode.h20
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_testmode_nl.h128
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_trc.c29
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_trc.h1535
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c565
-rw-r--r--kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h36
-rw-r--r--kernel/drivers/net/ieee802154/mcps802154_fake.c488
l---------kernel/include/net/fira_region_nl.h1
l---------kernel/include/net/fira_region_params.h1
l---------kernel/include/net/idle_region_nl.h1
l---------kernel/include/net/mcps802154.h1
l---------kernel/include/net/mcps802154_frame.h1
l---------kernel/include/net/mcps802154_nl.h1
l---------kernel/include/net/mcps802154_schedule.h1
l---------kernel/include/net/mcps_skb_frag.h1
l---------kernel/include/net/nfcc_coex_region_nl.h1
l---------kernel/include/net/pctt_region_nl.h1
l---------kernel/include/net/pctt_region_params.h1
l---------kernel/include/net/vendor_cmd.h1
-rw-r--r--kernel/net/mcps802154/Kbuild71
l---------kernel/net/mcps802154/backport_nl.h1
l---------kernel/net/mcps802154/ca.c1
l---------kernel/net/mcps802154/ca.h1
l---------kernel/net/mcps802154/default_region.c1
l---------kernel/net/mcps802154/default_region.h1
l---------kernel/net/mcps802154/endless_scheduler.c1
l---------kernel/net/mcps802154/endless_scheduler.h1
l---------kernel/net/mcps802154/fira_access.c1
l---------kernel/net/mcps802154/fira_access.h1
l---------kernel/net/mcps802154/fira_crypto.c1
l---------kernel/net/mcps802154/fira_crypto.h1
l---------kernel/net/mcps802154/fira_frame.c1
l---------kernel/net/mcps802154/fira_frame.h1
l---------kernel/net/mcps802154/fira_region.c1
l---------kernel/net/mcps802154/fira_region.h1
l---------kernel/net/mcps802154/fira_region_call.c1
l---------kernel/net/mcps802154/fira_region_call.h1
-rw-r--r--kernel/net/mcps802154/fira_round_hopping_crypto.c94
l---------kernel/net/mcps802154/fira_round_hopping_crypto.h1
-rw-r--r--kernel/net/mcps802154/fira_round_hopping_crypto_impl.h36
l---------kernel/net/mcps802154/fira_round_hopping_sequence.c1
l---------kernel/net/mcps802154/fira_round_hopping_sequence.h1
l---------kernel/net/mcps802154/fira_session.c1
l---------kernel/net/mcps802154/fira_session.h1
l---------kernel/net/mcps802154/fira_session_fsm.c1
l---------kernel/net/mcps802154/fira_session_fsm.h1
l---------kernel/net/mcps802154/fira_session_fsm_active.c1
l---------kernel/net/mcps802154/fira_session_fsm_active.h1
l---------kernel/net/mcps802154/fira_session_fsm_idle.c1
l---------kernel/net/mcps802154/fira_session_fsm_idle.h1
l---------kernel/net/mcps802154/fira_session_fsm_init.c1
l---------kernel/net/mcps802154/fira_session_fsm_init.h1
l---------kernel/net/mcps802154/fira_sts.c1
l---------kernel/net/mcps802154/fira_sts.h1
-rw-r--r--kernel/net/mcps802154/fira_trace.c25
l---------kernel/net/mcps802154/fira_trace.h1
l---------kernel/net/mcps802154/fproc.c1
l---------kernel/net/mcps802154/fproc.h1
l---------kernel/net/mcps802154/fproc_broken.c1
l---------kernel/net/mcps802154/fproc_idle.c1
l---------kernel/net/mcps802154/fproc_multi.c1
l---------kernel/net/mcps802154/fproc_nothing.c1
l---------kernel/net/mcps802154/fproc_rx.c1
l---------kernel/net/mcps802154/fproc_stopped.c1
l---------kernel/net/mcps802154/fproc_tx.c1
l---------kernel/net/mcps802154/fproc_vendor.c1
l---------kernel/net/mcps802154/frame.c1
l---------kernel/net/mcps802154/idle_region.c1
l---------kernel/net/mcps802154/idle_region.h1
l---------kernel/net/mcps802154/ie.c1
l---------kernel/net/mcps802154/llhw-ops.h1
l---------kernel/net/mcps802154/mcps802154_fproc.h1
l---------kernel/net/mcps802154/mcps802154_i.h1
l---------kernel/net/mcps802154/mcps802154_qorvo.h1
-rw-r--r--kernel/net/mcps802154/mcps_crypto.c321
-rw-r--r--kernel/net/mcps802154/mcps_crypto.h197
l---------kernel/net/mcps802154/mcps_main.c1
l---------kernel/net/mcps802154/mcps_skb_frag.c1
l---------kernel/net/mcps802154/nfcc_coex_access.c1
l---------kernel/net/mcps802154/nfcc_coex_access.h1
l---------kernel/net/mcps802154/nfcc_coex_region.c1
l---------kernel/net/mcps802154/nfcc_coex_region.h1
l---------kernel/net/mcps802154/nfcc_coex_region_call.c1
l---------kernel/net/mcps802154/nfcc_coex_region_call.h1
l---------kernel/net/mcps802154/nfcc_coex_session.c1
l---------kernel/net/mcps802154/nfcc_coex_session.h1
-rw-r--r--kernel/net/mcps802154/nfcc_coex_trace.c25
l---------kernel/net/mcps802154/nfcc_coex_trace.h1
-rw-r--r--kernel/net/mcps802154/nl.c1244
-rw-r--r--kernel/net/mcps802154/nl.h30
l---------kernel/net/mcps802154/on_demand_scheduler.c1
l---------kernel/net/mcps802154/on_demand_scheduler.h1
l---------kernel/net/mcps802154/ops.c1
l---------kernel/net/mcps802154/pctt_access.c1
l---------kernel/net/mcps802154/pctt_access.h1
l---------kernel/net/mcps802154/pctt_region.c1
l---------kernel/net/mcps802154/pctt_region.h1
l---------kernel/net/mcps802154/pctt_region_call.c1
l---------kernel/net/mcps802154/pctt_region_call.h1
l---------kernel/net/mcps802154/pctt_session.c1
l---------kernel/net/mcps802154/pctt_session.h1
-rw-r--r--kernel/net/mcps802154/pctt_trace.c25
l---------kernel/net/mcps802154/pctt_trace.h1
l---------kernel/net/mcps802154/regions.c1
l---------kernel/net/mcps802154/schedule.c1
l---------kernel/net/mcps802154/schedule.h1
l---------kernel/net/mcps802154/schedulers.c1
l---------kernel/net/mcps802154/schedulers.h1
-rw-r--r--kernel/net/mcps802154/trace.c25
l---------kernel/net/mcps802154/trace.h1
l---------kernel/net/mcps802154/warn_return.h1
-rw-r--r--mac/.gitignore1
-rw-r--r--mac/Recipes39
-rw-r--r--mac/backport_nl.h46
-rw-r--r--mac/ca.c497
-rw-r--r--mac/ca.h279
-rw-r--r--mac/default_region.c175
-rw-r--r--mac/default_region.h75
-rw-r--r--mac/endless_scheduler.c139
-rw-r--r--mac/endless_scheduler.h33
-rw-r--r--mac/fira_access.c1205
-rw-r--r--mac/fira_access.h64
-rwxr-xr-xmac/fira_crypto.c1419
-rw-r--r--mac/fira_crypto.h260
-rw-r--r--mac/fira_frame.c965
-rw-r--r--mac/fira_frame.h268
-rw-r--r--mac/fira_region.c482
-rw-r--r--mac/fira_region.h473
-rw-r--r--mac/fira_region_call.c1235
-rw-r--r--mac/fira_region_call.h59
-rw-r--r--mac/fira_round_hopping_crypto.h60
-rw-r--r--mac/fira_round_hopping_sequence.c73
-rw-r--r--mac/fira_round_hopping_sequence.h53
-rw-r--r--mac/fira_session.c1217
-rw-r--r--mac/fira_session.h683
-rw-r--r--mac/fira_session_fsm.c156
-rw-r--r--mac/fira_session_fsm.h241
-rw-r--r--mac/fira_session_fsm_active.c988
-rw-r--r--mac/fira_session_fsm_active.h31
-rw-r--r--mac/fira_session_fsm_idle.c169
-rw-r--r--mac/fira_session_fsm_idle.h31
-rw-r--r--mac/fira_session_fsm_init.c77
-rw-r--r--mac/fira_session_fsm_init.h31
-rw-r--r--mac/fira_sts.c505
-rw-r--r--mac/fira_sts.h193
-rw-r--r--mac/fira_trace.h748
-rw-r--r--mac/fproc.c311
-rw-r--r--mac/fproc.h213
-rw-r--r--mac/fproc_broken.c77
-rw-r--r--mac/fproc_idle.c68
-rw-r--r--mac/fproc_multi.c415
-rw-r--r--mac/fproc_nothing.c52
-rw-r--r--mac/fproc_rx.c131
-rw-r--r--mac/fproc_stopped.c94
-rw-r--r--mac/fproc_tx.c171
-rw-r--r--mac/fproc_vendor.c162
-rw-r--r--mac/frame.c50
-rw-r--r--mac/idle_region.c166
-rw-r--r--mac/idle_region.h42
-rw-r--r--mac/ie.c312
-rw-r--r--mac/include/Android.bp20
-rw-r--r--mac/include/net/fira_region_nl.h902
-rw-r--r--mac/include/net/fira_region_params.h417
-rw-r--r--mac/include/net/idle_region_nl.h49
-rw-r--r--mac/include/net/mcps802154.h1487
-rw-r--r--mac/include/net/mcps802154_frame.h334
-rw-r--r--mac/include/net/mcps802154_nl.h254
-rw-r--r--mac/include/net/mcps802154_schedule.h859
-rw-r--r--mac/include/net/mcps_skb_frag.h34
-rw-r--r--mac/include/net/nfcc_coex_region_nl.h96
-rw-r--r--mac/include/net/pctt_region_nl.h241
-rw-r--r--mac/include/net/pctt_region_params.h220
-rw-r--r--mac/include/net/vendor_cmd.h243
-rw-r--r--mac/llhw-ops.h359
-rw-r--r--mac/mcps802154_fproc.h32
-rw-r--r--mac/mcps802154_i.h161
-rw-r--r--mac/mcps802154_qorvo.h32
-rw-r--r--mac/mcps_main.c324
-rw-r--r--mac/mcps_skb_frag.c33
-rw-r--r--mac/nfcc_coex_access.c179
-rw-r--r--mac/nfcc_coex_access.h43
-rw-r--r--mac/nfcc_coex_region.c197
-rw-r--r--mac/nfcc_coex_region.h79
-rw-r--r--mac/nfcc_coex_region_call.c221
-rw-r--r--mac/nfcc_coex_region_call.h41
-rw-r--r--mac/nfcc_coex_session.c52
-rw-r--r--mac/nfcc_coex_session.h117
-rw-r--r--mac/nfcc_coex_trace.h208
-rw-r--r--mac/on_demand_scheduler.c287
-rw-r--r--mac/on_demand_scheduler.h32
-rw-r--r--mac/ops.c215
-rw-r--r--mac/pctt_access.c786
-rw-r--r--mac/pctt_access.h43
-rw-r--r--mac/pctt_region.c310
-rw-r--r--mac/pctt_region.h343
-rw-r--r--mac/pctt_region_call.c339
-rw-r--r--mac/pctt_region_call.h57
-rw-r--r--mac/pctt_session.c194
-rw-r--r--mac/pctt_session.h166
-rw-r--r--mac/pctt_trace.h519
-rw-r--r--mac/regions.c229
-rw-r--r--mac/schedule.c252
-rw-r--r--mac/schedule.h122
-rw-r--r--mac/schedulers.c155
-rw-r--r--mac/schedulers.h84
-rw-r--r--mac/trace.h1108
-rw-r--r--mac/warn_return.h47
-rwxr-xr-xtools/calibrations/pdoa_lut_generation203
257 files changed, 54402 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..e2d7a93
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,14 @@
+image: alpine
+
+pre-integration:
+ stage: deploy
+ variables:
+ CHILD_PROJECT_PATH: $CI_PROJECT_PATH
+ MR_BRANCH: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
+ CHILDJOB: "1"
+ trigger:
+ project: qorvo/uwb-mobile/uwb-stack/release/ci
+ branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+ strategy: depend
+ only:
+ - merge_requests
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..64ff778
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2020 Qorvo US, Inc.
+
+This software is provided under the GNU General Public License, version 2
+(GPLv2), as well as under a Qorvo commercial license.
+
+You may choose to use this software under the terms of the GPLv2 License,
+version 2 (“GPLv2”), as published by the Free Software Foundation.
+You should have received a copy of the GPLv2 along with this program. If
+not, see <http://www.gnu.org/licenses/>.
+
+This program is distributed under the GPLv2 in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+details.
+
+If you cannot meet the requirements of the GPLv2, you may not use this
+software for any purpose without first obtaining a commercial license from
+Qorvo.
+Please contact Qorvo to inquire about licensing terms.
diff --git a/kernel/.gitignore b/kernel/.gitignore
new file mode 100644
index 0000000..3017489
--- /dev/null
+++ b/kernel/.gitignore
@@ -0,0 +1,11 @@
+*.ko
+*.mod.c
+*.mod
+*.o
+*.a
+.*.cmd
+.*.d
+.tmp_versions/
+Module.symvers
+modules.order
+tags
diff --git a/kernel/BUILD.bazel b/kernel/BUILD.bazel
new file mode 100644
index 0000000..327e497
--- /dev/null
+++ b/kernel/BUILD.bazel
@@ -0,0 +1,23 @@
+# NOTE: THIS FILE IS EXPERIMENTAL FOR THE BAZEL MIGRATION AND NOT USED FOR
+# YOUR BUILDS CURRENTLY.
+#
+# It is not yet the source of truth for your build. If you're looking to modify
+# the build file, modify the Android.bp file instead. Do *not* modify this file
+# unless you have coordinated with the team managing the Soong to Bazel
+# migration.
+
+load("//build/kleaf:kernel.bzl", "kernel_module")
+
+kernel_module(
+ name = "kernel.cloudripper",
+ outs = [
+ "drivers/net/ieee802154/dw3000.ko",
+ "net/mcps802154/mcps802154.ko",
+ "net/mcps802154/mcps802154_region_fira.ko",
+ "net/mcps802154/mcps802154_region_nfcc_coex.ko",
+ ],
+ kernel_build = "//private/gs-google:cloudripper",
+ visibility = [
+ "//private/gs-google:__pkg__",
+ ],
+)
diff --git a/kernel/Kbuild b/kernel/Kbuild
new file mode 100644
index 0000000..653885a
--- /dev/null
+++ b/kernel/Kbuild
@@ -0,0 +1,14 @@
+obj-m := net/mcps802154/ drivers/net/ieee802154/
+
+subdir-ccflags-y := -I$(src) -I$(src)/include \
+ -I$(srctree)/$(src) -I$(srctree)/$(src)/include -Werror -Wall $(DW3000_DEFS)
+
+ifeq ($(CONFIG_BACKPORT_LINUX),y)
+NOSTDINC_FLAGS := \
+ -I$(srctree)/backports/backport-include/ \
+ -I$(srctree)/backports/backport-include/uapi \
+ -I$(srctree)/backports/include/ \
+ -I$(srctree)/backports/include/uapi \
+ -include $(srctree)/backports/backport-include/backport/backport.h \
+ $(CFLAGS)
+endif
diff --git a/kernel/Makefile b/kernel/Makefile
new file mode 100644
index 0000000..a1cba71
--- /dev/null
+++ b/kernel/Makefile
@@ -0,0 +1,66 @@
+KDIR ?= /lib/modules/$(shell uname -r)/build
+PROJECTS_BUILD := $(abspath $(CURDIR)/../projects/linux-build)
+
+ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..))
+
+# Build in projects/linux-build, use a separated build tree.
+
+O := $(abspath $(KDIR))
+M := ../../../kernel
+B := $(abspath $O/$M)
+
+$(info *** Building in $B)
+
+default all: modules
+
+modules modules_install:
+ mkdir -p $B
+ ln -srTf include $B/include
+ ln -srTf Kbuild $B/Kbuild
+ $(MAKE) -C $(KDIR) O=$O M=$M $@
+
+clean help kernelrelease:
+ $(MAKE) -C $(KDIR) O=$O M=$M $@
+
+bindeb-pkg:
+ ./builddeb -C $(KDIR) O=$O M=$M
+
+else # ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..))
+
+# Regular build, build here.
+ifneq ($(KERNEL_SRC),)
+# Compile Android external module in OUT directory
+# KERNEL_SRC = Kernel path
+$(info *** Building Android in $O/$M )
+
+KDIR := $(KERNEL_SRC)
+
+all:
+ $(MAKE) -C $(KDIR) M=$(M) modules $(KBUILD_OPTIONS)
+
+else # ifneq ($(KERNEL_SRC),)
+M := $(CURDIR)
+endif # ifneq ($(KERNEL_SRC),)
+
+B := $(abspath $O/$M)
+
+default:
+ $(MAKE) -C $(KDIR) M=$(M)
+modules modules_install clean help kernelrelease:
+ $(MAKE) -C $(KDIR) M=$(M) $@
+
+endif # ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..))
+
+# Install generated dw3000.dtbo
+dtbo_install: modules_install
+ d=`echo $(INSTALL_MOD_PATH)/lib/modules/* | sed 's#lib/modules#boot/dtbs#'` && \
+ mkdir -p $$d/overlays && \
+ find $B -name '*.dtbo' -print -exec cp -v {} $$d/overlays \;
+
+# Run kunit tests
+tests:
+ ../tools/kunit/run $(KDIR)
+
+# Run coverage
+cov:
+ ../tools/kunit/run $(KDIR)
diff --git a/kernel/drivers/net/ieee802154/.gitignore b/kernel/drivers/net/ieee802154/.gitignore
new file mode 100644
index 0000000..8dec1e9
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/.gitignore
@@ -0,0 +1,31 @@
+# Kernel module compilation files
+*.cmd
+*.mod
+*.mod.c
+*.o
+*.ko
+modules.order
+Module.symvers
+
+# Notes
+*.md
+
+# Ctags's file
+tags
+
+# Debugging scripts
+*.sh
+
+# IDE files
+.editorconfig
+.clang-format
+
+# Symlinks
+linux
+decadriver
+decawave
+uwb-hal
+dw1000
+
+# Temporary
+rootfs
diff --git a/kernel/drivers/net/ieee802154/Kbuild b/kernel/drivers/net/ieee802154/Kbuild
new file mode 100644
index 0000000..b4c0511
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/Kbuild
@@ -0,0 +1,40 @@
+obj-$(CONFIG_MCPS_FAKE) += mcps802154_fake.o
+
+ccflags-$(CONFIG_DW3000_DEBUG) := -DDEBUG -g
+ccflags-$(CONFIG_MCPS802154_TESTMODE) += -DCONFIG_MCPS802154_TESTMODE
+
+dw3000-y := \
+ dw3000_nfcc_coex_core.o \
+ dw3000_nfcc_coex_buffer.o \
+ dw3000_nfcc_coex_msg.o \
+ dw3000_nfcc_coex_mcps.o \
+ dw3000_pctt_mcps.o \
+ dw3000_calib.o \
+ dw3000_chip_c0.o \
+ dw3000_chip_d0.o \
+ dw3000_chip_e0.o \
+ dw3000_core.o \
+ dw3000_mcps.o \
+ dw3000_spi.o \
+ dw3000_stm.o \
+ dw3000_debugfs.o \
+ dw3000_trc.o \
+ dw3000_txpower_adjustment.o
+
+dw3000-$(CONFIG_MCPS802154_TESTMODE) += dw3000_testmode.o
+
+dw3000-core-tests-$(CONFIG_KUNIT) += dw3000_core_tests.o
+
+CFLAGS_dw3000_trc.o := -I$(src) -I$(srctree)/$(src)
+ifneq ($(GITVERSION),)
+CFLAGS_dw3000_spi.o := -DGITVERSION='"$(GITVERSION)"'
+endif
+
+ifdef CONFIG_SPI_MASTER
+# Build dw3000.ko only if SPI supported on target kernel.
+obj-m += dw3000.o
+endif
+ifdef CONFIG_KUNIT
+# Build test suites if kunit enabled in target kernel config.
+obj-m += dw3000-core-tests.o
+endif
diff --git a/kernel/drivers/net/ieee802154/dw3000-overlay.dts b/kernel/drivers/net/ieee802154/dw3000-overlay.dts
new file mode 100644
index 0000000..775913a
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000-overlay.dts
@@ -0,0 +1,78 @@
+// Device Tree overlay for dw3000 for Raspberry Pi 4
+// Using a DW3700 shield connected using an Waveshare ARPI600 HAT.
+
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "brcm,brcm2711";
+
+ /* Ensure spi0 is enabled */
+
+ fragment@0 {
+ target = <&spi0>;
+ __overlay__ {
+ status = "okay";
+ };
+ };
+
+ /* Disable spi-dev for spi0.0 & spi0.1 */
+
+ fragment@1 {
+ target = <&spidev0>;
+ __overlay__ {
+ status = "disabled";
+ };
+ };
+
+ fragment@2 {
+ target = <&spidev1>;
+ __overlay__ {
+ status = "disabled";
+ };
+ };
+
+ /* Configure additional GPIOs */
+ /* Check if wherre we wire it in driver */
+
+ fragment@3 {
+ target = <&gpio>;
+ __overlay__ {
+ dw3000_pins: dw3000_pins {
+ /* Wakeup, IRQ, RST on GPIO4, GPIO25 and GPIO24 */
+ brcm,pins = <4 25 24>;
+ brcm,function = <0x0 0x0 0x0>;
+ /* Pull up/down config: 0: disabled, 1: down, 2: up */
+ brcm,pull = <0 1 2>;
+ };
+ };
+ };
+
+ /* Add DW3000 device on spi0.0 */
+
+ fragment@4 {
+ target = <&spi0>;
+ __overlay__ {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ dw3000: dw3000@0 {
+ compatible = "decawave,dw3000";
+ reg = <0>; /* CE0 */
+ pinctrl-names = "default";
+ pinctrl-0 = <&dw3000_pins>;
+ interrupt-parent = <&gpio>;
+ interrupts = <25 4>; /*IRQ_TYPE_LEVEL_HIGH*/
+ spi-max-frequency = <20000000>;
+ reset-gpio = <&gpio 24 4>; /* OPEN_DRAIN */
+ decawave,eui64 = /bits/ 64 <0>;
+ decawave,panid = /bits/ 16 <0>;
+ status = "okay";
+ };
+ };
+ };
+
+ __overrides__ {
+ eui64 = <&dw3000>,"decawave,eui64#0";
+ panid = <&dw3000>,"decawave,panid;0";
+ };
+};
diff --git a/kernel/drivers/net/ieee802154/dw3000.h b/kernel/drivers/net/ieee802154/dw3000.h
new file mode 100644
index 0000000..6fb5d5e
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000.h
@@ -0,0 +1,769 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_H
+#define __DW3000_H
+
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <net/mcps802154.h>
+#include <linux/pm_qos.h>
+#include <linux/regulator/consumer.h>
+#include "dw3000_chip.h"
+#include "dw3000_stm.h"
+#include "dw3000_calib.h"
+#include "dw3000_testmode_nl.h"
+#include "dw3000_nfcc_coex.h"
+#include "dw3000_pctt.h"
+#include "dw3000_debugfs.h"
+
+#undef BIT_MASK
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+#define LOG(format, arg...) \
+ do { \
+ pr_info("dw3000: %s(): " format "\n", __func__, ##arg); \
+ } while (0)
+
+extern unsigned dw3000_regulator_delay_us;
+
+/* Defined constants when SPI CRC mode is used */
+enum dw3000_spi_crc_mode {
+ DW3000_SPI_CRC_MODE_NO = 0, /* No CRC */
+ DW3000_SPI_CRC_MODE_WR, /* This is used to enable SPI CRC check (the SPI
+ CRC check will be enabled on DW3000 and CRC-8
+ added for SPI write transactions) */
+ DW3000_SPI_CRC_MODE_WRRD /* This is used to optionally enable additional
+ CRC check on the SPI read operations, while
+ the CRC check on the SPI write operations is
+ also enabled */
+};
+
+/* ISR data */
+struct dw3000_isr_data {
+ u64 status; /* initial value of register as ISR is entered */
+ u16 datalength; /* length of frame */
+ u64 ts_rctu; /* frame timestamp in RCTU unit */
+ u8 dss_stat; /* value of the dual-SPI semaphore events */
+ u8 rx_flags; /* RX frame flags, see dw3000_rx_flags */
+};
+
+/* Time units and conversion factor */
+#define DW3000_DTU_PER_SYS_POWER 4
+
+#define DW3000_CHIP_FREQ 499200000
+#define DW3000_CHIP_PER_SYS 2
+#define DW3000_CHIP_PER_DTU \
+ (DW3000_CHIP_PER_SYS * (1 << DW3000_DTU_PER_SYS_POWER))
+#define DW3000_CHIP_PER_DLY 512
+#define DW3000_RCTU_PER_CHIP 128
+#define DW3000_RCTU_PER_DTU (DW3000_RCTU_PER_CHIP * DW3000_CHIP_PER_DTU)
+#define DW3000_RCTU_PER_SYS (DW3000_RCTU_PER_CHIP * DW3000_CHIP_PER_SYS)
+#define DW3000_RCTU_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_RCTU_PER_CHIP)
+
+#define DW3000_DTU_FREQ (DW3000_CHIP_FREQ / DW3000_CHIP_PER_DTU)
+
+/* 6.9.1.5 in 4z, for HRP UWB PHY:
+ 416 chips = 416 / (499.2 * 10^6) ~= 833.33 ns */
+#define DW3000_DTU_PER_RSTU (416 / DW3000_CHIP_PER_DTU)
+#define DW3000_DTU_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_CHIP_PER_DTU)
+#define DW3000_SYS_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_CHIP_PER_SYS)
+
+#define DW3000_ANTICIP_DTU (16 * (DW3000_DTU_FREQ / 1000))
+
+#define DTU_TO_US(x) (int)((s64)(x)*1000000 / DW3000_DTU_FREQ)
+#define US_TO_DTU(x) (u32)((s64)(x)*DW3000_DTU_FREQ / 1000000)
+#define NS_TO_DTU(x) (u32)((s64)(x) * (DW3000_DTU_FREQ / 100000) / 10000)
+
+#define DW3000_RX_ENABLE_STARTUP_DLY 16
+#define DW3000_RX_ENABLE_STARTUP_DTU \
+ (DW3000_RX_ENABLE_STARTUP_DLY * DW3000_CHIP_PER_DLY / \
+ DW3000_CHIP_PER_DTU)
+
+/* UWB High band 802.15.4a-2007. Only channels 5 & 9 for DW3000. */
+#define DW3000_SUPPORTED_CHANNELS ((1 << 5) | (1 << 9))
+
+/* Number of symbols of accumulation to wait before checking
+ for an SFD pattern, when Ipatov len > 64 */
+#define DW3000_RX_SFD_HLDOFF 0x20
+/* Default number of symbols of accumulation to wait before checking
+ for an SFD pattern */
+#define DW3000_RX_SFD_HLDOFF_DEF 0x14
+
+/**
+ * typedef dw3000_wakeup_done_cb - Wake up done handler.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+typedef int (*dw3000_wakeup_done_cb)(struct dw3000 *dw);
+
+/**
+ * typedef dw3000_idle_timeout_cb - Idle timeout handler.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+typedef int (*dw3000_idle_timeout_cb)(struct dw3000 *dw);
+
+/**
+ * struct dw3000_otp_data - data read from OTP memory of DW3000 device
+ * @partID: device part ID
+ * @lotID: device lot ID
+ * @ldo_tune_lo: tuned value used by chip specific prog_ldo_and_bias_tune
+ * @ldo_tune_hi: tuned value used by chip specific prog_ldo_and_bias_tune
+ * @bias_tune: tuned value used by chip specific prog_ldo_and_bias_tune
+ * @dgc_addr: dgc_addr value used by chip specific prog_ldo_and_bias_tune
+ * @xtal_trim: tuned value used by dw3000_prog_xtrim
+ * @vBatP: vBatP
+ * @tempP: tempP
+ * @rev: OTP revision
+ * @mode: Saved last OTP read mode to avoid multiple read
+ * @pll_coarse_code: PLL coarse code use by chip->prog_pll_coarse_code
+ */
+struct dw3000_otp_data {
+ u32 partID;
+ u32 lotID;
+ u32 ldo_tune_lo;
+ u32 ldo_tune_hi;
+ u32 bias_tune;
+ u32 dgc_addr;
+ u8 xtal_trim;
+ u8 vBatP;
+ u8 tempP;
+ u8 rev;
+ int mode;
+ u32 pll_coarse_code;
+};
+
+/**
+ * enum dw3000_ciagdiag_reg_select - CIA diagnostic register selector config.
+ * @DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS: without STS
+ * @DW3000_CIA_DIAG_REG_SELECT_WITH_STS: with STS
+ * @DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3: PDOA mode 3
+ *
+ * According to DW3000's configuration, we must read some values
+ * (e.g: channel impulse response power, preamble accumulation count)
+ * in different registers in the CIA interface.
+ */
+enum dw3000_ciagdiag_reg_select {
+ DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS = 0,
+ DW3000_CIA_DIAG_REG_SELECT_WITH_STS = 1,
+ DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3 = 2,
+};
+
+/**
+ * struct dw3000_local_data - Local data and register cache
+ * @spicrc: current SPI crc mode
+ * @ciadiag_reg_select: CIA diagnostic register selector according to DW3000's
+ * config
+ * @ciadiag_enabled: CIA diagnostic on/off
+ * @dgc_otp_set: true if DGC info is programmed into OTP
+ * @check_cfo: true when CFO checking is required for next RX frame
+ * @ack_time: Auto ack turnaroud time
+ * @dblbuffon: double buffering mode
+ * @xtal_bias: XTAL trimming adjustment value
+ * @max_frames_len: current configured max frame length
+ * @sleep_mode: bitfield for work to be done on wakeup
+ * @ststhreshold: current computed STS threshold
+ * @tx_fctrl: Transmit frame control
+ * @rx_timeout_pac: Preamble detection timeout period in units of PAC size
+ * symbols
+ * @rx_frame_timeout_dly: reception frame timeout period in units of dly.
+ * @w4r_time: Wait-for-response time (RX after TX delay)
+ * @sts_key: STS Key
+ * @sts_iv: STS IV
+ */
+struct dw3000_local_data {
+ enum dw3000_spi_crc_mode spicrc;
+ enum dw3000_ciagdiag_reg_select ciadiag_reg_select;
+ bool ciadiag_enabled : 1;
+ bool dgc_otp_set : 1;
+ bool check_cfo : 1;
+ u8 dblbuffon;
+ u8 ack_time;
+ s8 xtal_bias;
+ u16 sleep_mode;
+ u16 max_frames_len;
+ s16 ststhreshold;
+ u16 rx_timeout_pac;
+ u32 rx_frame_timeout_dly;
+ u32 tx_fctrl;
+ u32 w4r_time;
+ u8 sts_key[AES_KEYSIZE_128];
+ u8 sts_iv[AES_BLOCK_SIZE];
+};
+
+/* Statistics items */
+enum dw3000_stats_items {
+ DW3000_STATS_RX_GOOD,
+ DW3000_STATS_RX_ERROR,
+ DW3000_STATS_RX_TO,
+ __DW3000_STATS_COUNT
+};
+
+/**
+ * struct dw3000_stats - DW3000 statistics
+ * @count: Count per-items
+ * @rssi: Last RSSI data per type
+ * @enabled: Stats enabled flag
+ * @indexes: Free-running index per-items
+ */
+struct dw3000_stats {
+ /* Total stats */
+ u16 count[__DW3000_STATS_COUNT];
+ /* Required data array for calculation of the RSSI average */
+ struct dw3000_rssi rssi[DW3000_RSSI_REPORTS_MAX];
+ /* Stats on/off */
+ bool enabled;
+ u16 indexes[__DW3000_STATS_COUNT];
+};
+
+/* Maximum skb length
+ *
+ * Maximum supported frame size minus the checksum.
+ */
+#define DW3000_MAX_SKB_LEN (IEEE802154_MAX_SIFS_FRAME_SIZE - IEEE802154_FCS_LEN)
+
+/* Additional information on rx. */
+enum dw3000_rx_flags {
+ /* Set if an automatic ack is send. */
+ DW3000_RX_FLAG_AACK = BIT(0),
+ /* Set if no data. */
+ DW3000_RX_FLAG_ND = BIT(1),
+ /* Set if timestamp known. */
+ DW3000_RX_FLAG_TS = BIT(2),
+ /* Ranging bit */
+ DW3000_RX_FLAG_RNG = BIT(3),
+ /* CIA done */
+ DW3000_RX_FLAG_CIA = BIT(4),
+ /* CIA error */
+ DW3000_RX_FLAG_CER = BIT(5),
+ /* STS error */
+ DW3000_RX_FLAG_CPER = BIT(6)
+};
+
+/* Receive descriptor */
+struct dw3000_rx {
+ /* Receive lock */
+ spinlock_t lock;
+ /* Socket buffer */
+ struct sk_buff *skb;
+ /* Frame timestamp */
+ u64 ts_rctu;
+ /* Additional information on rx. See dw3000_rx_flags. */
+ u8 flags;
+ /* Busy flag */
+ u8 busy;
+};
+
+/* DW3000 STS length field of the CP_CFG register (unit of 8 symbols bloc) */
+enum dw3000_sts_lengths {
+ DW3000_STS_LEN_8 = 0,
+ DW3000_STS_LEN_16 = 1,
+ DW3000_STS_LEN_32 = 2,
+ DW3000_STS_LEN_64 = 3,
+ DW3000_STS_LEN_128 = 4,
+ DW3000_STS_LEN_256 = 5,
+ DW3000_STS_LEN_512 = 6,
+ DW3000_STS_LEN_1024 = 7,
+ DW3000_STS_LEN_2048 = 8
+};
+
+/* DW3000 power supply structure */
+struct dw3000_power_control {
+ /* Power supply generic */
+ struct regulator *regulator_vdd;
+ /* Power supply 1.8V */
+ struct regulator *regulator_1p8;
+ /* Power supply 2.5V */
+ struct regulator *regulator_2p5;
+};
+
+/**
+ * struct dw3000_config - Structure holding current device configuration
+ * @chan: Channel number (5 or 9)
+ * @txPreambLength: DW3000_PLEN_64..DW3000_PLEN_4096
+ * @txCode: TX preamble code (the code configures the PRF, e.g. 9 -> PRF of 64 MHz)
+ * @rxCode: RX preamble code (the code configures the PRF, e.g. 9 -> PRF of 64 MHz)
+ * @sfdType: SFD type (0 for short IEEE 8b standard, 1 for DW 8b, 2 for DW 16b, 2 for 4z BPRF)
+ * @dataRate: Data rate {DW3000_BR_850K or DW3000_BR_6M8}
+ * @phrMode: PHR mode {0x0 - standard DW3000_PHRMODE_STD, 0x3 - extended frames DW3000_PHRMODE_EXT}
+ * @phrRate: PHR rate {0x0 - standard DW3000_PHRRATE_STD, 0x1 - at datarate DW3000_PHRRATE_DTA}
+ * @sfdTO: SFD timeout value (in symbols)
+ * @stsMode: STS mode (no STS, STS before PHR or STS after data)
+ * @stsLength: STS length (see enum dw3000_sts_lengths)
+ * @pdoaMode: PDOA mode
+ * @ant: Antennas currently connected to RF1 & RF2 ports respectively
+ * @pdoaOffset: Calibrated PDOA offset
+ * @pdoaLut: Pointer to calibrated PDOA to AoA look-up table
+ * @rmarkerOffset: Calibrated rmarker offset
+ * @promisc: Promiscuous mode enabled?
+ * @alternate_pulse_shape: set alternate pulse shape
+ * @hw_addr_filt: HW filter configuration
+ */
+struct dw3000_config {
+ u8 chan;
+ u8 txPreambLength;
+ u8 txCode;
+ u8 rxCode;
+ u8 sfdType;
+ u8 dataRate;
+ u8 phrMode;
+ u8 phrRate;
+ u16 sfdTO;
+ u8 stsMode;
+ enum dw3000_sts_lengths stsLength;
+ u8 pdoaMode;
+ s8 ant[2];
+ s16 pdoaOffset;
+ const dw3000_pdoa_lut_t *pdoaLut;
+ u32 rmarkerOffset;
+ bool promisc;
+ bool alternate_pulse_shape;
+ struct ieee802154_hw_addr_filt hw_addr_filt;
+};
+
+/**
+ * struct dw3000_txconfig - Structure holding current TX configuration
+ * @PGdly: PG delay
+ * @PGcount: PG count
+ * @power: TX power for 1ms frame
+ * 31:24 TX_CP_PWR
+ * 23:16 TX_SHR_PWR
+ * 15:8 TX_PHR_PWR
+ * 7:0 TX_DATA_PWR
+ * @smart: TX smart power enabled flag
+ * @testmode_enabled: Normal or test mode
+ */
+struct dw3000_txconfig {
+ u8 PGdly;
+ u8 PGcount;
+ u32 power;
+ bool smart;
+ bool testmode_enabled;
+};
+
+enum operational_state {
+ DW3000_OP_STATE_OFF = 0,
+ DW3000_OP_STATE_DEEP_SLEEP,
+ DW3000_OP_STATE_SLEEP,
+ DW3000_OP_STATE_WAKE_UP,
+ DW3000_OP_STATE_INIT_RC,
+ DW3000_OP_STATE_IDLE_RC,
+ DW3000_OP_STATE_IDLE_PLL,
+ DW3000_OP_STATE_TX_WAIT,
+ DW3000_OP_STATE_TX,
+ DW3000_OP_STATE_RX_WAIT,
+ DW3000_OP_STATE_RX,
+ DW3000_OP_STATE_MAX,
+};
+
+/**
+ * struct sysfs_power_stats - DW3000 device power related data
+ * @dur: accumulated duration in selected state in ns except for RX/TX where
+ * duration is in DTU
+ * @count: number of time this state was active
+ */
+struct sysfs_power_stats {
+ u64 dur;
+ u64 count;
+};
+
+/**
+ * enum power_state - DW3000 device current power state
+ * @DW3000_PWR_OFF: DW3000 is OFF (unpowered or in reset)
+ * @DW3000_PWR_DEEPSLEEP: DW3000 is ACTIVE (started) but in DEEP SLEEP
+ * @DW3000_PWR_RUN: DW3000 is ACTIVE (include RX/TX state below)
+ * @DW3000_PWR_IDLE: DW3000 is ACTIVE but IDLE (only count is used for it)
+ * @DW3000_PWR_RX: DW3000 is currently RECEIVING
+ * @DW3000_PWR_TX: DW3000 is currently TRANSMITTING
+ * @DW3000_PWR_MAX: Number of power states stored in struct dw3000_power
+ */
+enum power_state {
+ DW3000_PWR_OFF = 0,
+ DW3000_PWR_DEEPSLEEP,
+ DW3000_PWR_RUN,
+ DW3000_PWR_IDLE,
+ DW3000_PWR_RX,
+ DW3000_PWR_TX,
+ DW3000_PWR_MAX,
+};
+
+/**
+ * enum config_changed_flags - values for configuration changed bitfield
+ * @DW3000_AFILT_SADDR_CHANGED: same as IEEE802154_AFILT_SADDR_CHANGED
+ * @DW3000_AFILT_IEEEADDR_CHANGED: same as IEEE802154_AFILT_IEEEADDR_CHANGED
+ * @DW3000_AFILT_PANID_CHANGED: same as IEEE802154_AFILT_PANID_CHANGED
+ * @DW3000_AFILT_PANC_CHANGED: same as IEEE802154_AFILT_PANC_CHANGED
+ * @DW3000_CHANNEL_CHANGED: channel configuration changed
+ * @DW3000_PCODE_CHANGED: preamble code configuration changed
+ * @DW3000_PREAMBLE_LENGTH_CHANGED: length of preamble changed
+ * @DW3000_SFD_CHANGED: Start Frame Delimiter changed
+ * @DW3000_PHR_RATE_CHANGED: Physical HeadeR rate changed
+ * @DW3000_DATA_RATE_CHANGED: Data rate changed
+ */
+enum config_changed_flags {
+ DW3000_AFILT_SADDR_CHANGED = IEEE802154_AFILT_SADDR_CHANGED,
+ DW3000_AFILT_IEEEADDR_CHANGED = IEEE802154_AFILT_IEEEADDR_CHANGED,
+ DW3000_AFILT_PANID_CHANGED = IEEE802154_AFILT_PANID_CHANGED,
+ DW3000_AFILT_PANC_CHANGED = IEEE802154_AFILT_PANC_CHANGED,
+ DW3000_CHANNEL_CHANGED = BIT(4),
+ DW3000_PCODE_CHANGED = BIT(5),
+ DW3000_PREAMBLE_LENGTH_CHANGED = BIT(6),
+ DW3000_SFD_CHANGED = BIT(7),
+ DW3000_PHR_RATE_CHANGED = BIT(8),
+ DW3000_DATA_RATE_CHANGED = BIT(9),
+};
+
+/**
+ * struct dw3000_power - DW3000 device power related data
+ * @stats: accumulated statistics defined by struct sysfs_power_stats
+ * @start_time: timestamp of current state start
+ * @cur_state: current state defined by enum power_state
+ * @tx_adjust: TX time adjustment based on frame length
+ * @rx_start: RX start date in DTU for RX time adjustment
+ * @interrupts: Hardware interrupts count on the device.
+ */
+struct dw3000_power {
+ struct sysfs_power_stats stats[DW3000_PWR_MAX];
+ u64 start_time;
+ int cur_state;
+ int tx_adjust;
+ u32 rx_start;
+ atomic64_t interrupts;
+};
+
+/**
+ * struct dw3000_deep_sleep_state - Useful data to restore on wake up
+ * @next_operational_state: operational state to enter after DEEP SLEEP mode
+ * @config_changed: bitfield of configuration changed during DEEP-SLEEP
+ * @frame_idx: saved frame index to use for deferred TX/RX
+ * @tx_skb: saved frame to transmit for deferred TX
+ * @tx_config: saved config to use for deferred TX
+ * @rx_config: saved parameter for deferred RX
+ * @regbackup: registers backup to detect diff
+ * @compare_work: deferred registers backup compare work
+ */
+struct dw3000_deep_sleep_state {
+ enum operational_state next_operational_state;
+ unsigned long config_changed;
+ int frame_idx;
+ struct sk_buff *tx_skb;
+ union {
+ struct mcps802154_tx_frame_config tx_config;
+ struct mcps802154_rx_frame_config rx_config;
+ };
+#ifdef CONFIG_DW3000_DEBUG
+ void *regbackup;
+ struct work_struct compare_work;
+#endif
+};
+
+/** enum dw3000_rctu_conv_state - DTU to RCTU conversion state
+ * @UNALIGNED: need to redo all
+ * @ALIGNED: aligned to DTU but not synced yet with RCTU
+ * @ALIGNED_SYNCED: all done
+ *
+ */
+enum dw3000_rctu_conv_state { UNALIGNED = 0, ALIGNED, ALIGNED_SYNCED };
+
+/**
+ * struct dw3000_rctu_conv - DTU to RCTU conversion data
+ * @state: current state of converter
+ * @alignment_rmarker_dtu: alignment DTU value
+ * @synced_rmarker_rctu: rmarker RCTU value
+ */
+struct dw3000_rctu_conv {
+ enum dw3000_rctu_conv_state state;
+ u32 alignment_rmarker_dtu;
+ u64 synced_rmarker_rctu;
+};
+
+struct dw3000_cir_data;
+
+#define DW3000_MAX_QUEUED_SPI_XFER 32
+#define DW3000_QUEUED_SPI_BUFFER_SZ 2048
+
+/* Number of samples to average. */
+#define DW3000_NB_AVERAGE 1
+
+/**
+ * struct dw3000_rx_ctx - Custom rx context use with rx_init/rx_get_measurement.
+ * @pdoa_rad_q11: Array of PDoA measurements.
+ * @aoa_rad_q11: Array of AoA measurements.
+ * @index: Index for pdoa_rad_q11 and aoa_rad_q11 arrays.
+ * @sample_valid_nb: Number of valid measurements in array, used for average.
+ */
+struct dw3000_rx_ctx {
+ int pdoa_rad_q11[DW3000_NB_AVERAGE];
+ int aoa_rad_q11[DW3000_NB_AVERAGE];
+ int index;
+ int sample_valid_nb;
+};
+
+/**
+ * struct dw3000 - main DW3000 device structure
+ * @spi: pointer to corresponding spi device
+ * @dev: pointer to generic device holding sysfs attributes
+ * @pm_qos_req: CPU latency request object
+ * @sysfs_power_dir: kobject holding sysfs power directory
+ * @chip_ops: version specific chip operations
+ * @llhw: pointer to associated struct mcps802154_llhw
+ * @config: current running chip configuration
+ * @txconfig: current running TX configuration
+ * @data: local data and register cache
+ * @otp_data: OTP data cache
+ * @calib_data: calibration data
+ * @stats: statistics
+ * @power: power related statistics and states
+ * @rctu_conv: RCTU converter
+ * @time_zero_ns: initial time in ns to convert ktime to/from DTU
+ * @dtu_sync: synchro DTU immediately after wakeup
+ * @sys_time_sync: device SYS_TIME immediately after wakeup
+ * @sleep_enter_dtu: DTU when entered sleep
+ * @deep_sleep_state: state related to the deep sleep
+ * @idle_timeout: true when idle_timeout_dtu is a valid date.
+ * @idle_timeout_dtu: timestamp requested to leave idle mode.
+ * @idle_timer: timer to exiting after an idle call.
+ * @timer_expired_work: call mcps802154_timer_expired outside driver kthread.
+ * @wakeup_done_cb: callback called on wakeup done.
+ * @idle_timeout_cb: callback when idle timer expired
+ * @auto_sleep_margin_us: configurable automatic deep sleep margin
+ * @need_ranging_clock: true if next operation need ranging clock
+ * and deep sleep cannot be used
+ * @nfcc_coex: NFCC coexistence specific context
+ * @pctt: PCTT specific context
+ * @chip_dev_id: identified chip device ID
+ * @chip_idx: index of current chip in supported devices array
+ * @of_max_speed_hz: saved SPI max speed from device tree
+ * @iface_is_started: interface status
+ * @has_lock_pm: power management locked status
+ * @reset_gpio: GPIO to use for hard reset
+ * @regulators: structure that holds regulators to toggle
+ * @is_powered: save current regulators state
+ * @chips_per_pac: chips per PAC unit
+ * @pre_timeout_pac: preamble timeout in PAC unit
+ * @coex_delay_us: WiFi coexistence GPIO delay in us
+ * @coex_margin_us: WiFi coexistence GPIO margin in us
+ * @coex_interval_us: Minimum interval between two operations in us
+ * under which WiFi coexistence GPIO is kept active
+ * @coex_gpio: WiFi coexistence GPIO, >= 0 if activated
+ * @coex_enabled: WiFi coexistence activation
+ * @coex_status: WiFi coexistence GPIO status, 1 if activated
+ * @lna_pa_mode: LNA/PA configuration to use
+ * @autoack: auto-ack status, true if activated
+ * @pgf_cal_running: true if pgf calibration is running
+ * @stm: High-priority thread state machine
+ * @rx: received skbuff and associated spinlock
+ * @current_operational_state: internal operational state of the chip
+ * @operational_state_wq: Wait queue for operational state
+ * @debugfs: debugfs informations
+ * @spi_pid: PID of the SPI controller pump messages
+ * @dw3000_pid: PID the dw3000 state machine thread
+ * @restricted_channels: bit field of restricted channels
+ * @tx_rf2: parameter to enable the tx on rf2 port
+ * @rx_rf2: parameter to enable the rx on rf2 port
+ * @cir_data_changed: true if buffer data have been reallocated
+ * @full_cia_read: CIA registers fully loaded into cir_data struct
+ * @cir_data: allocated CIR exploitation data
+ * @msg_queue: SPI message holding transfer queue
+ * @msg_queue_xfer: next transfer available
+ * @msg_queue_xfer_count: number of queued transfers
+ * @msg_queue_buf: buffer for queued transfers
+ * @msg_queue_buf_pos: current position in buffer
+ * @msg_mutex: mutex protecting @msg_readwrite_fdx
+ * @msg_readwrite_fdx: pre-computed generic register read/write SPI message
+ * @msg_fast_command: pre-computed fast command SPI message
+ * @msg_read_cir_pwr: pre-computed SPI message
+ * @msg_read_pacc_cnt: pre-computed SPI message
+ * @msg_read_rdb_status: pre-computed SPI message
+ * @msg_read_rx_timestamp: pre-computed SPI message
+ * @msg_read_rx_timestamp_a: pre-computed SPI message
+ * @msg_read_rx_timestamp_b: pre-computed SPI message
+ * @msg_read_sys_status: pre-computed SPI message
+ * @msg_read_all_sys_status: pre-computed SPI message
+ * @msg_read_sys_time: pre-computed SPI message
+ * @msg_write_sys_status: pre-computed SPI message
+ * @msg_write_all_sys_status: pre-computed SPI message
+ * @msg_read_dss_status: pre-computed SPI message
+ * @msg_write_dss_status: pre-computed SPI message
+ * @msg_write_spi_collision_status: pre-computed SPI message
+ */
+struct dw3000 {
+ /* SPI device */
+ struct spi_device *spi;
+ /* Generic device */
+ struct device *dev;
+ /* PM QoS object */
+ struct pm_qos_request pm_qos_req;
+ /* Kernel object holding sysfs power sub-directory */
+ struct kobject sysfs_power_dir;
+ /* Chip version specific operations */
+ const struct dw3000_chip_ops *chip_ops;
+ /* MCPS 802.15.4 device */
+ struct mcps802154_llhw *llhw;
+ /* Configuration */
+ struct dw3000_config config;
+ struct dw3000_txconfig txconfig;
+ /* Data */
+ struct dw3000_local_data data;
+ struct dw3000_otp_data otp_data;
+ struct dw3000_calibration_data calib_data;
+ /* Statistics */
+ struct dw3000_stats stats;
+ struct dw3000_power power;
+ /* Time conversion */
+ struct dw3000_rctu_conv rctu_conv;
+ s64 time_zero_ns;
+ u32 dtu_sync;
+ u32 sys_time_sync;
+ u32 sleep_enter_dtu;
+ /* Deep Sleep & MCPS Idle management */
+ struct dw3000_deep_sleep_state deep_sleep_state;
+ bool idle_timeout;
+ u32 idle_timeout_dtu;
+ struct hrtimer idle_timer;
+ struct work_struct timer_expired_work;
+ dw3000_wakeup_done_cb wakeup_done_cb;
+ dw3000_idle_timeout_cb idle_timeout_cb;
+ bool need_ranging_clock;
+ int auto_sleep_margin_us;
+ /* NFCC coexistence specific context. */
+ struct dw3000_nfcc_coex nfcc_coex;
+ /* PCTT specific context. */
+ struct dw3000_pctt pctt;
+ /* Chip type management */
+ u32 chip_dev_id;
+ u32 chip_idx;
+ /* Saved SPI max speed from device tree */
+ u32 of_max_speed_hz;
+ /* True when MCPS start() operation had been called */
+ atomic_t iface_is_started;
+ /* SPI controller power-management */
+ bool has_lock_pm;
+ /* Control GPIOs */
+ int reset_gpio;
+ /* Regulators handler */
+ struct dw3000_power_control regulators;
+ bool is_powered;
+ /* Chips per PAC unit. */
+ int chips_per_pac;
+ /* Preamble timeout in PAC unit. */
+ int pre_timeout_pac;
+ /* WiFi coexistence GPIO and delay */
+ unsigned coex_delay_us;
+ unsigned coex_margin_us;
+ unsigned coex_interval_us;
+ s8 coex_gpio;
+ bool coex_enabled;
+ int coex_status;
+ /* LNA/PA mode */
+ s8 lna_pa_mode;
+ /* Is auto-ack activated? */
+ bool autoack;
+ /* pgf calibration running */
+ bool pgf_cal_running;
+ /* State machine */
+ struct dw3000_state stm;
+ /* Receive descriptor */
+ struct dw3000_rx rx;
+ /* Internal operational state of the chip */
+ enum operational_state current_operational_state;
+ /* Wait queue for operational state */
+ wait_queue_head_t operational_state_wq;
+ /* Debugfs settings */
+ struct dw3000_debugfs debugfs;
+ /* PID of the SPI controller pump messages */
+ pid_t spi_pid;
+ /* PID the dw3000 state machine thread */
+ pid_t dw3000_pid;
+ /* Restricted channels */
+ u16 restricted_channels;
+ /* enable tx on RF2 port */
+ u8 tx_rf2;
+ /* enable rx on RF2 port */
+ u8 rx_rf2;
+ /* Channel impulse response data */
+ bool cir_data_changed;
+ bool full_cia_read;
+ struct dw3000_cir_data *cir_data;
+ /* SPI message holding transfers queue */
+ struct spi_message *msg_queue;
+ struct spi_transfer *msg_queue_xfer;
+ unsigned msg_queue_xfer_count;
+ /* Buffer for queued transfers */
+ char *msg_queue_buf;
+ char *msg_queue_buf_pos;
+ /* dw3000 thread clamp value */
+ int min_clamp_value;
+ /* Insert new fields before this line */
+
+ /* Shared message protected by a mutex */
+ struct mutex msg_mutex;
+ /* Precomputed spi_messages */
+ struct spi_message *msg_readwrite_fdx;
+ struct spi_message *msg_fast_command;
+ struct spi_message *msg_read_rdb_status;
+ struct spi_message *msg_read_rx_timestamp;
+ struct spi_message *msg_read_rx_timestamp_a;
+ struct spi_message *msg_read_rx_timestamp_b;
+ struct spi_message *msg_read_sys_status;
+ struct spi_message *msg_read_all_sys_status;
+ struct spi_message *msg_read_sys_time;
+ struct spi_message *msg_write_sys_status;
+ struct spi_message *msg_write_all_sys_status;
+ struct spi_message *msg_read_dss_status;
+ struct spi_message *msg_write_dss_status;
+ struct spi_message *msg_write_spi_collision_status;
+};
+
+/**
+ * dw3000_is_active() - Return if DW is in active state (UP and running)
+ * @dw: The DW device.
+ *
+ * Allow to know if DW is in active state (dw3000_enable() called successfully)
+ * Used to avoid modification of registers while DW is in use.
+ * The chip can be in DEEP_SLEEP state and interface still up & running
+ *
+ * Return: true is DW is in active state
+ */
+static inline bool dw3000_is_active(struct dw3000 *dw)
+{
+ return atomic_read(&dw->iface_is_started);
+}
+
+/**
+ * nfcc_coex_to_dw() - Help function to retrieve dw3000 context from nfcc_coex.
+ * @nfcc_coex: NFCC coexistence context.
+ *
+ * Returns: DW3000 device context.
+ */
+static inline struct dw3000 *nfcc_coex_to_dw(struct dw3000_nfcc_coex *nfcc_coex)
+{
+ return container_of(nfcc_coex, struct dw3000, nfcc_coex);
+}
+
+#endif /* __DW3000_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_calib.c b/kernel/drivers/net/ieee802154/dw3000_calib.c
new file mode 100644
index 0000000..2527486
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_calib.c
@@ -0,0 +1,373 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000.h"
+#include "dw3000_txpower_adjustment.h"
+
+/* clang-format off */
+#define CHAN_PRF_PARAMS (4 * DW3000_CALIBRATION_PRF_MAX)
+#define ANT_CHAN_PARAMS (CHAN_PRF_PARAMS * DW3000_CALIBRATION_CHANNEL_MAX)
+#define ANT_OTHER_PARAMS (4) /* port, selector_gpio... */
+#define ANTPAIR_CHAN_PARAMS (2 * DW3000_CALIBRATION_CHANNEL_MAX + 1)
+
+#define OTHER_PARAMS (13) /* xtal_trim, temperature_reference,
+ smart_tx_power, spi_pid, dw3000_pid,
+ auto_sleep_margin, restricted_channels
+ phrMode, alternate_pulse_shape,
+ wificoex_gpio, wificoex_delay_us,
+ wificoex_interval_us, wificoex_margin_us */
+
+#define MAX_CALIB_KEYS ((ANTMAX * (ANT_CHAN_PARAMS + ANT_OTHER_PARAMS)) + \
+ (ANTPAIR_MAX * ANTPAIR_CHAN_PARAMS) + \
+ (DW3000_CALIBRATION_CHANNEL_MAX * 2) + \
+ OTHER_PARAMS)
+
+#define DW_OFFSET(m) offsetof(struct dw3000, m)
+#define DW_SIZE(m) sizeof_field(struct dw3000, m)
+#define DW_INFO(m) { .offset = DW_OFFSET(m), .length = DW_SIZE(m) }
+
+#define CAL_INFO(m) DW_INFO(calib_data.m)
+#define OTP_INFO(m) DW_INFO(otp_data.m)
+
+#define PRF_CAL_INFO(b,x) \
+ CAL_INFO(b.prf[x].ant_delay), \
+ CAL_INFO(b.prf[x].tx_power), \
+ CAL_INFO(b.prf[x].pg_count), \
+ CAL_INFO(b.prf[x].pg_delay)
+
+#define ANTENNA_CAL_INFO(x) \
+ PRF_CAL_INFO(ant[x].ch[0], 0), \
+ PRF_CAL_INFO(ant[x].ch[0], 1), \
+ PRF_CAL_INFO(ant[x].ch[1], 0), \
+ PRF_CAL_INFO(ant[x].ch[1], 1), \
+ CAL_INFO(ant[x].port), \
+ CAL_INFO(ant[x].selector_gpio), \
+ CAL_INFO(ant[x].selector_gpio_value), \
+ CAL_INFO(ant[x].caps)
+
+#define ANTPAIR_CAL_INFO(x,y) \
+ CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_offset), \
+ CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_lut), \
+ CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_offset), \
+ CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_lut)
+
+static const struct {
+ unsigned int offset;
+ unsigned int length;
+} dw3000_calib_keys_info[MAX_CALIB_KEYS] = {
+ /* ant0.* */
+ ANTENNA_CAL_INFO(0),
+ /* ant1.* */
+ ANTENNA_CAL_INFO(1),
+ /* ant0.* */
+ ANTENNA_CAL_INFO(2),
+ /* ant1.* */
+ ANTENNA_CAL_INFO(3),
+ /* antX.antW.* */
+ ANTPAIR_CAL_INFO(0,1),
+ ANTPAIR_CAL_INFO(0,2),
+ ANTPAIR_CAL_INFO(0,3),
+ ANTPAIR_CAL_INFO(1,2),
+ ANTPAIR_CAL_INFO(1,3),
+ ANTPAIR_CAL_INFO(2,3),
+ /* chY.* */
+ CAL_INFO(ch[0].pll_locking_code),
+ CAL_INFO(ch[0].wifi_coex_enabled),
+ CAL_INFO(ch[1].pll_locking_code),
+ CAL_INFO(ch[1].wifi_coex_enabled),
+ /* other with direct access in struct dw3000 */
+ DW_INFO(txconfig.smart),
+ DW_INFO(auto_sleep_margin_us),
+ DW_INFO(spi_pid),
+ DW_INFO(dw3000_pid),
+ DW_INFO(restricted_channels),
+ DW_INFO(config.phrMode),
+ DW_INFO(coex_gpio),
+ DW_INFO(coex_delay_us),
+ DW_INFO(coex_margin_us),
+ DW_INFO(coex_interval_us),
+ /* country */
+ DW_INFO(config.alternate_pulse_shape),
+ /* other with defaults from OTP */
+ OTP_INFO(xtal_trim),
+ OTP_INFO(tempP),
+};
+
+#define PRF_CAL_LABEL(a,c,p) \
+ "ant" #a ".ch" #c ".prf" #p ".ant_delay", \
+ "ant" #a ".ch" #c ".prf" #p ".tx_power", \
+ "ant" #a ".ch" #c ".prf" #p ".pg_count", \
+ "ant" #a ".ch" #c ".prf" #p ".pg_delay"
+
+#define ANTENNA_CAL_LABEL(x) \
+ PRF_CAL_LABEL(x, 5, 16), \
+ PRF_CAL_LABEL(x, 5, 64), \
+ PRF_CAL_LABEL(x, 9, 16), \
+ PRF_CAL_LABEL(x, 9, 64), \
+ "ant" #x ".port", \
+ "ant" #x ".selector_gpio", \
+ "ant" #x ".selector_gpio_value", \
+ "ant" #x ".caps"
+
+#define PDOA_CAL_LABEL(a, b, c) \
+ "ant" #a ".ant" #b ".ch" #c ".pdoa_offset", \
+ "ant" #a ".ant" #b ".ch" #c ".pdoa_lut"
+
+#define ANTPAIR_CAL_LABEL(x,y) \
+ PDOA_CAL_LABEL(x, y, 5), \
+ PDOA_CAL_LABEL(x, y, 9)
+
+/*
+ * calibration parameters keys table
+ */
+static const char *const dw3000_calib_keys[MAX_CALIB_KEYS + 1] = {
+ /* antX */
+ ANTENNA_CAL_LABEL(0),
+ ANTENNA_CAL_LABEL(1),
+ ANTENNA_CAL_LABEL(2),
+ ANTENNA_CAL_LABEL(3),
+ /* antX.antY.* */
+ ANTPAIR_CAL_LABEL(0,1),
+ ANTPAIR_CAL_LABEL(0,2),
+ ANTPAIR_CAL_LABEL(0,3),
+ ANTPAIR_CAL_LABEL(1,2),
+ ANTPAIR_CAL_LABEL(1,3),
+ ANTPAIR_CAL_LABEL(2,3),
+ /* chY.* */
+ "ch5.pll_locking_code",
+ "ch5.wifi_coex-enabled",
+ "ch9.pll_locking_code",
+ "ch9.wifi_coex-enabled",
+ /* other */
+ "smart_tx_power",
+ "auto_sleep_margin",
+ "spi_pid",
+ "dw3000_pid",
+ "restricted_channels",
+ "phr_mode",
+ "coex_gpio",
+ "coex_delay_us",
+ "coex_margin_us",
+ "coex_interval_us",
+ /* country */
+ "alternate_pulse_shape",
+ /* other (OTP) */
+ "xtal_trim",
+ "temperature_reference",
+ /* NULL terminated array for caller of dw3000_calib_list_keys(). */
+ NULL
+};
+/* clang-format on */
+
+const dw3000_pdoa_lut_t dw3000_default_lut_ch5 = {
+ /* clang-format off */
+ { 0xe6de, 0xf36f },
+ { 0xe88b, 0xf36f },
+ { 0xea38, 0xf5b0 },
+ { 0xebe5, 0xf747 },
+ { 0xed92, 0xf869 },
+ { 0xef3f, 0xf959 },
+ { 0xf0ec, 0xfa2e },
+ { 0xf299, 0xfaf1 },
+ { 0xf445, 0xfba7 },
+ { 0xf5f2, 0xfc53 },
+ { 0xf79f, 0xfcf9 },
+ { 0xf94c, 0xfd9a },
+ { 0xfaf9, 0xfe36 },
+ { 0xfca6, 0xfed0 },
+ { 0xfe53, 0xff69 },
+ { 0x0000, 0x0000 },
+ { 0x01ad, 0x0097 },
+ { 0x035a, 0x0130 },
+ { 0x0507, 0x01ca },
+ { 0x06b4, 0x0266 },
+ { 0x0861, 0x0307 },
+ { 0x0a0e, 0x03ad },
+ { 0x0bbb, 0x0459 },
+ { 0x0d67, 0x050f },
+ { 0x0f14, 0x05d2 },
+ { 0x10c1, 0x06a7 },
+ { 0x126e, 0x0797 },
+ { 0x141b, 0x08b9 },
+ { 0x15c8, 0x0a50 },
+ { 0x1775, 0x0c91 },
+ { 0x1922, 0x0c91 }
+ /* clang-format on */
+};
+
+const dw3000_pdoa_lut_t dw3000_default_lut_ch9 = {
+ /* clang-format off */
+ { 0xe6de, 0xf701 },
+ { 0xe88b, 0xf7ff },
+ { 0xea38, 0xf8d2 },
+ { 0xebe5, 0xf98d },
+ { 0xed92, 0xfa38 },
+ { 0xef3f, 0xfad7 },
+ { 0xf0ec, 0xfb6d },
+ { 0xf299, 0xfbfc },
+ { 0xf445, 0xfc86 },
+ { 0xf5f2, 0xfd0c },
+ { 0xf79f, 0xfd8f },
+ { 0xf94c, 0xfe0f },
+ { 0xfaf9, 0xfe8d },
+ { 0xfca6, 0xff09 },
+ { 0xfe53, 0xff85 },
+ { 0x0000, 0x0000 },
+ { 0x01ad, 0x007b },
+ { 0x035a, 0x00f7 },
+ { 0x0507, 0x0173 },
+ { 0x06b4, 0x01f1 },
+ { 0x0861, 0x0271 },
+ { 0x0a0e, 0x02f4 },
+ { 0x0bbb, 0x037a },
+ { 0x0d67, 0x0404 },
+ { 0x0f14, 0x0493 },
+ { 0x10c1, 0x0529 },
+ { 0x126e, 0x05c8 },
+ { 0x141b, 0x0673 },
+ { 0x15c8, 0x072e },
+ { 0x1775, 0x0801 },
+ { 0x1922, 0x08ff }
+ /* clang-format on */
+};
+
+int dw3000_calib_parse_key(struct dw3000 *dw, const char *key, void **param)
+{
+ int i;
+
+ for (i = 0; dw3000_calib_keys[i]; i++) {
+ const char *k = dw3000_calib_keys[i];
+
+ if (strcmp(k, key) == 0) {
+ /* Key found, calculate parameter address */
+ *param = (void *)dw + dw3000_calib_keys_info[i].offset;
+ return dw3000_calib_keys_info[i].length;
+ }
+ }
+ return -ENOENT;
+}
+
+/**
+ * dw3000_calib_list_keys - return the @dw3000_calib_keys known key table
+ * @dw: the DW device
+ *
+ * Return: pointer to known keys table.
+ */
+const char *const *dw3000_calib_list_keys(struct dw3000 *dw)
+{
+ return dw3000_calib_keys;
+}
+
+/**
+ * dw3000_calib_update_config - update running configuration
+ * @dw: the DW device
+ *
+ * This function update the required fields in struct dw3000_txconfig according
+ * the channel and PRF and the corresponding calibration values.
+ *
+ * Also update RX/TX RMARKER offset according calibrated antenna delay.
+ *
+ * Other calibration parameters aren't used yet.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_calib_update_config(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_txconfig *txconfig = &dw->txconfig;
+ const struct dw3000_antenna_calib *ant_calib;
+ const struct dw3000_antenna_calib_prf *ant_calib_prf;
+ const struct dw3000_antenna_pair_calib *antpair_calib;
+ int ant_rf1, ant_rf2, antpair;
+ int chanidx, prfidx;
+
+ dw->llhw->hw->phy->supported.channels[4] = DW3000_SUPPORTED_CHANNELS &
+ ~dw->restricted_channels;
+ /* Change channel if the current one is restricted. */
+ if ((1 << dw->llhw->hw->phy->current_channel) &
+ dw->restricted_channels) {
+ config->chan =
+ ffs(dw->llhw->hw->phy->supported.channels[4]) - 1;
+ dw->llhw->hw->phy->current_channel = config->chan;
+ }
+
+ ant_rf1 = config->ant[0];
+ ant_rf2 = config->ant[1];
+ if (ant_rf1 < 0 && ant_rf2 < 0) {
+ /* Not configured yet, does nothing. */
+ return 0;
+ }
+ /* Since both calibs can be used at this time, both needs to be safe. */
+ if (ant_rf1 >= ANTMAX || ant_rf2 >= ANTMAX)
+ return -1;
+ if (ant_rf1 >= 0)
+ ant_calib = &dw->calib_data.ant[ant_rf1];
+ else
+ ant_calib = &dw->calib_data.ant[ant_rf2];
+
+ /* Convert config into index of array. */
+ chanidx = config->chan == 9 ? DW3000_CALIBRATION_CHANNEL_9 :
+ DW3000_CALIBRATION_CHANNEL_5;
+ prfidx = config->txCode >= 9 ? DW3000_CALIBRATION_PRF_64MHZ :
+ DW3000_CALIBRATION_PRF_16MHZ;
+
+ /* Shortcut pointers to reduce line length. */
+ ant_calib_prf = &ant_calib->ch[chanidx].prf[prfidx];
+
+ /* WiFi coexistence according to current channel */
+ dw->coex_enabled = dw->calib_data.ch[chanidx].wifi_coex_enabled;
+
+ /* Update TX configuration */
+ txconfig->power = ant_calib_prf->tx_power ? ant_calib_prf->tx_power :
+ 0xfefefefe;
+ txconfig->PGdly = ant_calib_prf->pg_delay ? ant_calib_prf->pg_delay :
+ 0x34;
+ txconfig->PGcount = ant_calib_prf->pg_count ? ant_calib_prf->pg_count :
+ 0;
+ /* Update RMARKER offsets */
+ config->rmarkerOffset = ant_calib_prf->ant_delay;
+
+ /* Early exit if RF2 isn't configured yet. */
+ if (ant_rf2 < 0)
+ return 0;
+ if (ant_rf1 >= 0 && ant_rf2 >= 0) {
+ /* RF2 and RF1 port has a valid antenna, so antpair can be used */
+ antpair = ant_rf2 > ant_rf1 ? ANTPAIR_IDX(ant_rf1, ant_rf2) :
+ ANTPAIR_IDX(ant_rf2, ant_rf1);
+
+ antpair_calib = &dw->calib_data.antpair[antpair];
+ /* Update PDOA offset */
+ config->pdoaOffset = antpair_calib->ch[chanidx].pdoa_offset;
+ config->pdoaLut = &antpair_calib->ch[chanidx].pdoa_lut;
+ }
+ /* Smart TX power */
+ /* When deactivated, reset register to default value (if change occurs
+ while already started) */
+ if (!txconfig->smart && dw3000_is_active(dw))
+ dw3000_set_tx_power_register(dw, txconfig->power);
+
+ /* Update idle_dtu in case auto_sleep_margin_us changed */
+ dw->llhw->idle_dtu = dw->auto_sleep_margin_us > 0 ?
+ US_TO_DTU(dw->auto_sleep_margin_us) :
+ DW3000_DTU_FREQ;
+ return 0;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_calib.h b/kernel/drivers/net/ieee802154/dw3000_calib.h
new file mode 100644
index 0000000..b136b4c
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_calib.h
@@ -0,0 +1,225 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CALIB_H
+#define __DW3000_CALIB_H
+
+/**
+ * DW3000_CALIBRATION_ANTENNA_MAX - number of antenna
+ */
+#define DW3000_CALIBRATION_ANTENNA_MAX 4
+
+/**
+ * enum dw3000_calibration_channels - calibration channel number.
+ * @DW3000_CALIBRATION_CHANNEL_5: index in array for channel 5
+ * @DW3000_CALIBRATION_CHANNEL_9: index in array for channel 9
+ * @DW3000_CALIBRATION_CHANNEL_MAX: channel array size
+ */
+enum dw3000_calibration_channels {
+ DW3000_CALIBRATION_CHANNEL_5,
+ DW3000_CALIBRATION_CHANNEL_9,
+
+ DW3000_CALIBRATION_CHANNEL_MAX
+};
+
+/**
+ * enum dw3000_calibration_prfs - calibration Pulse Repetition Frequency.
+ * @DW3000_CALIBRATION_PRF_16MHZ: index in array for prf 16
+ * @DW3000_CALIBRATION_PRF_64MHZ: index in array for prf 64
+ * @DW3000_CALIBRATION_PRF_MAX: prf array size
+ */
+enum dw3000_calibration_prfs {
+ DW3000_CALIBRATION_PRF_16MHZ,
+ DW3000_CALIBRATION_PRF_64MHZ,
+
+ DW3000_CALIBRATION_PRF_MAX
+};
+
+/**
+ * DW3000_CALIBRATION_PDOA_LUT_MAX - number of value in PDOA LUT table
+ */
+#define DW3000_CALIBRATION_PDOA_LUT_MAX 31
+
+/* Intermediate types to fix following error:
+ * [kernel-doc ERROR] : can't parse typedef!
+ */
+typedef s16 pdoa_lut_entry_t[2];
+typedef pdoa_lut_entry_t pdoa_lut_table_t[DW3000_CALIBRATION_PDOA_LUT_MAX];
+
+/**
+ * typedef dw3000_pdoa_lut_t - PDoA LUT array type
+ */
+typedef pdoa_lut_table_t dw3000_pdoa_lut_t;
+
+/* Default LUTs, theorical values for Monalisa antenna (20.8mm) */
+extern const dw3000_pdoa_lut_t dw3000_default_lut_ch5;
+extern const dw3000_pdoa_lut_t dw3000_default_lut_ch9;
+
+/**
+ * DW3000_DEFAULT_ANT_DELAY - antenna delay default value
+ */
+#define DW3000_DEFAULT_ANT_DELAY 16450
+
+/**
+ * struct dw3000_channel_calib - per-channel dependent calibration parameters
+ * @pll_locking_code: PLL locking code
+ * @wifi_coex_enabled: WiFi coexistence activation
+ */
+struct dw3000_channel_calib {
+ /* chY.pll_locking_code */
+ u32 pll_locking_code;
+ bool wifi_coex_enabled;
+};
+
+/**
+ * struct dw3000_antenna_calib_prf - antenna calibration parameters
+ * @ant_delay: antenna delay
+ * @tx_power: tx power
+ * @pg_count: PG count
+ * @pg_delay: PG delay
+ */
+struct dw3000_antenna_calib_prf {
+ u32 ant_delay;
+ u32 tx_power;
+ u8 pg_count;
+ u8 pg_delay;
+};
+
+/**
+ * struct dw3000_antenna_calib - per-antenna dependent calibration parameters
+ * @ch: table of channels dependent calibration values
+ * @ch.prf: table of PRF dependent calibration values
+ * @port: port value this antenna belong to (0 for RF1, 1 for RF2)
+ * @selector_gpio: GPIO number to select this antenna
+ * @selector_gpio_value: GPIO value to select this antenna
+ * @caps: antenna capabilities
+ */
+struct dw3000_antenna_calib {
+ /* antX.chY.prfZ.* */
+ struct {
+ struct dw3000_antenna_calib_prf prf[DW3000_CALIBRATION_PRF_MAX];
+ } ch[DW3000_CALIBRATION_CHANNEL_MAX];
+ /* antX.* */
+ u8 port, selector_gpio, selector_gpio_value, caps;
+};
+
+/**
+ * struct dw3000_antenna_pair_calib_chan - per-channel antennas pair calibration
+ * parameters
+ * @pdoa_offset: PDOA offset
+ * @pdoa_lut: PDOA LUT
+ */
+struct dw3000_antenna_pair_calib_chan {
+ s16 pdoa_offset;
+ dw3000_pdoa_lut_t pdoa_lut;
+};
+
+/**
+ * struct dw3000_antenna_pair_calib - antenna pair dependent calibration values
+ * @ch: table of channels dependent calibration values
+ */
+struct dw3000_antenna_pair_calib {
+ /* antX.antW.chY.* */
+ struct dw3000_antenna_pair_calib_chan ch[DW3000_CALIBRATION_CHANNEL_MAX];
+};
+
+/* Just to ease reading of the following formulas. */
+#define ANTMAX DW3000_CALIBRATION_ANTENNA_MAX
+
+/**
+ * ANTPAIR_MAX - calculated antpair table size
+ */
+#define ANTPAIR_MAX ((ANTMAX * (ANTMAX - 1)) / 2)
+
+/**
+ * ANTPAIR_OFFSET - calculate antpair table indexes row offset
+ * @x: first antenna index
+ *
+ * Return: An index for the first element in antpair table for the given value.
+ */
+#define ANTPAIR_OFFSET(x) ((((2 * (ANTMAX - 1)) + 1 - (x)) * (x)) / 2)
+
+/**
+ * ANTPAIR_IDX - calculate antpair table indexes
+ * @x: first antenna index
+ * @w: second antenna index, must be > @x
+ *
+ * Return: An index for the antpair table in [0;ANTPAIR_MAX-1] interval.
+ */
+#define ANTPAIR_IDX(x, w) (ANTPAIR_OFFSET(x) + ((w) - (x)-1))
+
+/**
+ * ANTSET_ID_MAX - calculated antenna set id table size
+ */
+#define ANTSET_ID_MAX (ANTPAIR_MAX + ANTMAX)
+
+/**
+ * dw3000_calib_ant_set_id_to_ant - convert antenna set id pair to corresponding antennas
+ * @ant_set_id: antenna set id
+ * @ant_idx1: first antenna
+ * @ant_idx2: second antenna
+ */
+static inline void dw3000_calib_ant_set_id_to_ant(int ant_set_id, s8 *ant_idx1,
+ s8 *ant_idx2)
+{
+ static const s8 set_id_to_ant[ANTSET_ID_MAX][2] = {
+ { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 },
+ { 2, 3 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { 3, -1 }
+ };
+
+ *ant_idx1 = set_id_to_ant[ant_set_id][0];
+ *ant_idx2 = set_id_to_ant[ant_set_id][1];
+}
+
+/**
+ * struct dw3000_calibration_data - all per-antenna and per-channel calibration
+ * parameters
+ * @ant: table of antenna dependent calibration values
+ * @antpair: table of antenna pair dependent calibration values
+ * @ch: table of channel dependent calibration values
+ */
+struct dw3000_calibration_data {
+ struct dw3000_antenna_calib ant[ANTMAX];
+ struct dw3000_antenna_pair_calib antpair[ANTPAIR_MAX];
+ struct dw3000_channel_calib ch[DW3000_CALIBRATION_CHANNEL_MAX];
+};
+
+struct dw3000;
+
+/**
+ * dw3000_calib_parse_key - parse key and find corresponding param
+ * @dw: the DW device
+ * @key: pointer to NUL terminated string to retrieve param address and len
+ * @param: pointer where to store the corresponding parameter address
+ *
+ * This function lookup the NULL terminated table @dw3000_calib_keys and
+ * if specified key is found, store the corresponding address in @param and
+ *
+ * Return: length of corresponding parameter if found, else a -ENOENT error.
+ */
+int dw3000_calib_parse_key(struct dw3000 *dw, const char *key, void **param);
+
+const char *const *dw3000_calib_list_keys(struct dw3000 *dw);
+
+int dw3000_calib_update_config(struct dw3000 *dw);
+
+#endif /* __DW3000_CALIB_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip.h b/kernel/drivers/net/ieee802154/dw3000_chip.h
new file mode 100644
index 0000000..5871ea0
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip.h
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CHIP_H
+#define __DW3000_CHIP_H
+
+/* Forward declaration */
+struct dw3000;
+struct dw3000_rssi;
+
+/**
+ * enum dw3000_chip_register_flags - flags for the register declaration
+ * @DW3000_CHIPREG_NONE: no special flag defined
+ * @DW3000_CHIPREG_DUMP: register is dump only, address is fileid number only
+ * @DW3000_CHIPREG_RO: register is always read-only
+ * @DW3000_CHIPREG_WP: register is write-protected. write refused if device is
+ * already active.
+ * @DW3000_CHIPREG_PERM: register have a permanent access: chip off or on.
+ * This will be usefull for some virtual registers wired on callbacks
+ * @DW3000_CHIPREG_OPENONCE: allow only one file instance at a time
+ */
+enum dw3000_chip_register_flags {
+ DW3000_CHIPREG_NONE = 0,
+ DW3000_CHIPREG_DUMP = 1,
+ DW3000_CHIPREG_RO = 2,
+ DW3000_CHIPREG_WP = 4,
+ DW3000_CHIPREG_PERM = 8,
+ DW3000_CHIPREG_OPENONCE = 16,
+};
+
+/**
+ * typedef dw3000_chip_register_cb - virtual register callback function
+ * @filp: the debugfs file structure pointer
+ * @write: true when called for a write operation
+ * @buffer: input or output buffer, depending on write parameter
+ * @size: size of input buffer or available space of output buffer
+ * @ppos: the callback handle the ppos to be blocking or not
+ *
+ * All functions of this type will parse input buffer and do the relevant
+ * action according given parameters when write is true.
+ * When write is false, relevant information is written to buffer.
+ *
+ * The filp parameter is used by the function to retrieve required private
+ * data given when the file was created. See struct dw3000_chip_register_priv.
+ *
+ * Returns: length read from buffer or written to buffer or negative error
+ */
+typedef int (*dw3000_chip_register_cb)(struct file *filp, bool write,
+ void *buffer, size_t size, loff_t *ppos);
+
+/**
+ * struct dw3000_chip_register - version dependent register declaration
+ * @name: register name
+ * @address: register address
+ * @size: register size (in bits if mask defined)
+ * @mask: register mask (unused if 0)
+ * @flags: or'ed value of enum dw3000_chip_register_flags
+ * @callback: processing callback for a virtual register
+ */
+struct dw3000_chip_register {
+ const char *const name;
+ unsigned address;
+ size_t size;
+ unsigned mask;
+ unsigned flags;
+ dw3000_chip_register_cb callback;
+};
+
+/**
+ * struct dw3000_chip_register_priv - private data for debugfs file
+ * @dw: backpointer to DW3000 device instance
+ * @reg: pointer to first struct dw3000_chip_register this file belong to
+ * @count: number of struct dw3000_chip_register this file handle
+ *
+ * If a specific debugfs file need more data, it can derive this structure.
+ */
+struct dw3000_chip_register_priv {
+ struct dw3000 *dw;
+ const struct dw3000_chip_register *reg;
+ size_t count;
+};
+
+/**
+ * struct dw3000_chip_ops - version dependent chip operations
+ * @softreset: soft-reset
+ * @init: initialisation
+ * @coex_init: initialise WiFi coexistence GPIO
+ * @coex_gpio: change state of WiFi coexistence GPIO
+ * @check_tx_ok: Check device has correctly entered tx state
+ * @prog_ldo_and_bias_tune: programs the device's LDO and BIAS tuning
+ * @get_config_mrxlut_chan: Lookup table default values for channel provided or NULL
+ * @get_dgc_dec: Read DGC_DBG register
+ * @pre_read_sys_time: Workaround before the SYS_TIME register reads
+ * @adc_offset_calibration: Workaround to calibrate ADC offset
+ * @pll_calibration_from_scratch: Workaround to calibrate the PLL from scratch
+ * @pll_coarse_code: Workaround to set PLL coarse code
+ * @prog_pll_coarse_code: Program PLL coarse code from OTP
+ * @set_mrxlut: configure mrxlut
+ * @kick_ops_table_on_wakeup: kick the desired operating parameter set table
+ * @kick_dgc_on_wakeup: kick the DGC upon wakeup from sleep
+ * @get_registers: Return known registers table and it's size
+ * @compute_rssi: Uses the parameters to compute RSSI of current frame
+ */
+struct dw3000_chip_ops {
+ int (*softreset)(struct dw3000 *dw);
+ int (*init)(struct dw3000 *dw);
+ int (*coex_init)(struct dw3000 *dw);
+ int (*coex_gpio)(struct dw3000 *dw, bool state, int delay_us);
+ int (*check_tx_ok)(struct dw3000 *dw);
+ int (*prog_ldo_and_bias_tune)(struct dw3000 *dw);
+ const u32 *(*get_config_mrxlut_chan)(struct dw3000 *dw, u8 channel);
+ int (*get_dgc_dec)(struct dw3000 *dw, u8 *value);
+ int (*pre_read_sys_time)(struct dw3000 *dw);
+ int (*adc_offset_calibration)(struct dw3000 *dw);
+ int (*pll_calibration_from_scratch)(struct dw3000 *dw);
+ int (*pll_coarse_code)(struct dw3000 *dw);
+ int (*prog_pll_coarse_code)(struct dw3000 *dw);
+ int (*set_mrxlut)(struct dw3000 *dw, const u32 *lut);
+ int (*kick_ops_table_on_wakeup)(struct dw3000 *dw);
+ int (*kick_dgc_on_wakeup)(struct dw3000 *dw);
+ const struct dw3000_chip_register *(*get_registers)(struct dw3000 *dw,
+ size_t *count);
+ u32 (*compute_rssi)(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ bool rx_tune, u8 sts);
+};
+
+/**
+ * struct dw3000_chip_version - supported chip version definition
+ * @id: device model ID
+ * @ver: device registers version, saved to __dw3000_chip_version
+ * @ops: associated version specific operations
+ * @name: short version name of current device
+ */
+struct dw3000_chip_version {
+ unsigned id;
+ int ver;
+ const struct dw3000_chip_ops *ops;
+ const char *name;
+};
+
+/* DW3000 device model IDs (with or non PDOA) */
+#define DW3000_C0_DEV_ID 0xdeca0302
+#define DW3000_C0_PDOA_DEV_ID 0xdeca0312
+#define DW3000_C0_VERSION 0
+#define DW3000_D0_DEV_ID 0xdeca0303
+#define DW3000_D0_PDOA_DEV_ID 0xdeca0313
+#define DW3000_D0_VERSION 1
+#define DW3000_E0_PDOA_DEV_ID 0xdeca0314
+#define DW3000_E0_VERSION 2
+
+/* Declaration of version specific chip operations */
+extern const struct dw3000_chip_ops dw3000_chip_c0_ops;
+extern const struct dw3000_chip_ops dw3000_chip_d0_ops;
+extern const struct dw3000_chip_ops dw3000_chip_e0_ops;
+
+#endif /* __DW3000_CHIP_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_c0.c b/kernel/drivers/net/ieee802154/dw3000_chip_c0.c
new file mode 100644
index 0000000..2401504
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_c0.c
@@ -0,0 +1,419 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000.h"
+#include "dw3000_core.h"
+#include "dw3000_core_reg.h"
+#include "dw3000_chip_c0.h"
+
+#define DW3000_C0_DGC_DBG_ID 0x30060
+
+static const struct dw3000_chip_register c0_registers[] = {
+ /* virtual registers for fileID dump */
+ { "GEN_CFG0", 0x00, 0x79, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "GEN_CFG1", 0x01, 0x64, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "STS_CFG", 0x02, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL },
+ /* No fileID 0x03 documented in DW3000 user manual v0.7. */
+ { "EXT_SYNC", 0x04, 0x04, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "GPIO_CTRL", 0x05, 0x2e, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "DRX_CONF", 0x06, 0x10, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RF_CONF", 0x07, 0x4c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "TX_CAL", 0x08, 0x1e, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "FS_CTRL", 0x09, 0x15, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "AON", 0x0a, 0x15, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "OTP_IF", 0x0b, 0x18, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_1", 0x0c, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_2", 0x0d, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_3", 0x0e, 0x20, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "DIG_DIAG", 0x0f, 0x51, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "PMSC", 0x11, 0x4a, 0, DW3000_CHIPREG_DUMP, NULL },
+ /* TODO: RX/TX buffers limited to first 128bytes only.
+ Shall we dump the whole 1024 bytes? */
+ { "RX_BUFFER", 0x12, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RX_BUFFER1", 0x13, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ /* No TX_BUFFER as read isn't supported */
+ /* CIR memory require 2 bits configured elsewhere, so removed. */
+ { "SCRATCH_RAM", 0x16, 0x7f, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "AES RAM", 0x17, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "SET_X", 0x18, 0x1d0, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "IN_PTR_CFG", 0x1f, 0x12, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "euid", 0x000004, 0x08, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "frame_filter", 0x000010, 0x01, 0x01, DW3000_CHIPREG_WP, NULL },
+ { "rx_phrmode", 0x000010, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "tx_phrrate", 0x000010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL },
+ { "rx_sts_mode", 0x000011, 0x01, 0x8, DW3000_CHIPREG_WP, NULL },
+ { "pdoa_mode", 0x000012, 0x01, 0x3, DW3000_CHIPREG_WP, NULL },
+ { "role", 0x000015, 0x01, 0x1, DW3000_CHIPREG_WP, NULL },
+ { "frame_filter_cfg", 0x000014, 0x02, 0xfeff, DW3000_CHIPREG_WP, NULL },
+ { "datarate", 0x000021, 0x01, 0x4, DW3000_CHIPREG_WP, NULL },
+ { "tx_pream_len", 0x000021, 0x01, 0xf0, DW3000_CHIPREG_WP, NULL },
+ { "tx_antdly", 0x00007c, 0x02, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "txrf_pwrfin", 0x010004, 0x01, 0xfc, DW3000_CHIPREG_WP, NULL },
+ { "tx_pwr", 0x010004, 0x04, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "rx_sfdtype", 0x010008, 0x01, 0x6, DW3000_CHIPREG_WP, NULL },
+ { "channel", 0x010008, 0x01, 0x01, DW3000_CHIPREG_WP, NULL },
+ { "tx_pream_ch", 0x010008, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL },
+ { "prf", 0x010008, 0x01, 0x1f, DW3000_CHIPREG_WP, NULL },
+ { "rx_phr_rate", 0x010010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL },
+ { "rx_sts_len", 0x020000, 0x01, 0xff, DW3000_CHIPREG_WP, NULL },
+ { "ext_clkdly", 0x040000, 0x02, 0x7f8, DW3000_CHIPREG_WP, NULL },
+ { "ext_clkdly_en", 0x040001, 0x01, 0x8, DW3000_CHIPREG_WP, NULL },
+ { "gpiodir", 0x050008, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL },
+ { "gpioout", 0x05000c, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL },
+ { "rx_paclen", 0x060000, 0x01, 0x03, DW3000_CHIPREG_WP, NULL },
+ { "rx_sfd_to", 0x060002, 0x02, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "chan_pg_delay", 0x07001c, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL },
+ { "chan_pll_cfg", 0x090001, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "xtal_trim", 0x090014, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL },
+ { "rx_antdly", 0x0e0000, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL },
+ { "rx_diag", 0x0e0002, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "digi_diag_en", 0x0f0000, 0x01, 0x1, DW3000_CHIPREG_WP, NULL },
+ { "digi_diag_clr", 0x0f0000, 0x01, 0x2, DW3000_CHIPREG_WP, NULL },
+};
+
+const struct dw3000_chip_register *dw3000_c0_get_registers(struct dw3000 *dw,
+ size_t *count)
+{
+ *count = ARRAY_SIZE(c0_registers);
+ return c0_registers;
+}
+
+const u32 *dw3000_c0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel)
+{
+ /* Lookup table default values for channel 5 */
+ static const u32 dw3000_c0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x1c0fd, 0x1c43e, 0x1c6be, 0x1c77e, 0x1cf36, 0x1cfb5, 0x1cff5
+ };
+
+ /* Lookup table default values for channel 9 */
+ static const u32 dw3000_c0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x2a8fe, 0x2ac36, 0x2a5fe, 0x2af3e, 0x2af7d, 0x2afb5, 0x2afb5
+ };
+
+ switch (channel) {
+ case 5:
+ return dw3000_c0_configmrxlut_ch5;
+ case 9:
+ return dw3000_c0_configmrxlut_ch9;
+ default:
+ return NULL;
+ }
+}
+
+static int dw3000_c0_softreset(struct dw3000 *dw)
+{
+ /* Reset HIF, TX, RX and PMSC */
+ return dw3000_reg_write8(dw, DW3000_SOFT_RST_ID, 0, DW3000_RESET_ALL);
+}
+
+static int dw3000_c0_init(struct dw3000 *dw)
+{
+ /* TODO */
+ return 0;
+}
+
+static int dw3000_c0_coex_init(struct dw3000 *dw)
+{
+ /* TODO */
+ return 0;
+}
+
+static int dw3000_c0_coex_gpio(struct dw3000 *dw, bool state, int delay_us)
+{
+ /* TODO */
+ return 0;
+}
+
+static int dw3000_c0_check_tx_ok(struct dw3000 *dw)
+{
+ u32 state;
+ int rc = dw3000_reg_read32(dw, DW3000_SYS_STATE_LO_ID, 0, &state);
+ if (rc || state == DW3000_SYS_STATE_TXERR) {
+ dw3000_forcetrxoff(dw);
+ return -ETIME;
+ }
+ return 0;
+}
+
+/**
+ * dw3000_c0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_c0_prog_ldo_and_bias_tune(struct dw3000 *dw)
+{
+ const u16 bias_mask = DW3000_BIAS_CTRL_DIG_BIAS_DAC_ULV_BIT_MASK;
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_otp_data *otp = &dw->otp_data;
+ int rc;
+ u16 bias_tune = (otp->bias_tune >> 16) & bias_mask;
+ if (otp->ldo_tune_lo && otp->ldo_tune_hi && bias_tune) {
+ rc = dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0,
+ DW3000_LDO_BIAS_KICK);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_modify16(dw, DW3000_BIAS_CTRL_ID, 0, ~bias_mask,
+ bias_tune);
+ if (rc)
+ return rc;
+ }
+ local->dgc_otp_set = false;
+ return 0;
+}
+
+/**
+ * dw3000_c0_pre_read_sys_time() - Ensure SYS_TIME register is cleared
+ * @dw: The DW device.
+ *
+ * On C0 chips, the SYS_TIME register value is latched and any subsequent read
+ * will return the same value. To clear the current value in the register an SPI
+ * write transaction is necessary, the following read of the SYS_TIME register will
+ * return a new value.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_c0_pre_read_sys_time(struct dw3000 *dw)
+{
+ /* The SPI_COLLISION register is choose to make this SPI write
+ * transaction because it is unused and it is a small 8 bits register.
+ */
+ return dw3000_clear_spi_collision_status(
+ dw, DW3000_SPI_COLLISION_STATUS_BIT_MASK);
+}
+
+/**
+ * dw3000_c0_get_dgc_dec() - Read DGC_DBG register content
+ * @dw: The DW device.
+ * @value: Pointer to store DGC DECISION value.
+ *
+ * Return: zero on succes, else a negative error code.
+ */
+int dw3000_c0_get_dgc_dec(struct dw3000 *dw, u8 *value)
+{
+ int rc;
+ u32 dgc_dbg;
+
+ rc = dw3000_reg_read32(dw, DW3000_C0_DGC_DBG_ID, 0, &dgc_dbg);
+ if (unlikely(rc))
+ return rc;
+
+ /* DGC_DECISION is on bits 28 to 30 of DGC_CFG, cf 8.2.4.2 of DW3700
+ * User Manual, store it to right the rssi stats entry */
+ *value = (u8)((dgc_dbg & 0x70000000) >> 0x1c);
+ return 0;
+}
+
+/**
+ * dw3000_c0_pll_calibration_from_scratch() - Calibrate the PLL from scratch
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_c0_pll_calibration_from_scratch(struct dw3000 *dw)
+{
+ int rc = 0;
+
+ /* Run the PLL calibration from scratch.
+ * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start
+ * its calculation. This is just in order to fasten the process.
+ */
+ rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0,
+ DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK |
+ DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Wait for the PLL calibration (needed before read the calibration status register) */
+ usleep_range(DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US,
+ DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10);
+ return rc;
+}
+
+/**
+ * dw3000_c0_prog_pll_coarse_code() - Programs the device's coarse code
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw)
+{
+ struct dw3000_otp_data *otp = &dw->otp_data;
+ int rc = 0;
+
+ if (otp->pll_coarse_code) {
+ /* set the coarse code value as read from OTP */
+ rc = dw3000_reg_write8(dw, DW3000_PLL_COARSE_CODE_ID, 0,
+ otp->pll_coarse_code);
+ }
+ return rc;
+}
+
+/**
+ * dw3000_c0_set_mrxlut() - Configure mrxlut
+ * @dw: The DW device.
+ * @lut: Pointer to LUT to write to DGC_LUT_X_CFG registers
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_c0_set_mrxlut(struct dw3000 *dw, const u32 *lut)
+{
+ int rc;
+
+ /* Update LUT registers */
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0x0, lut[0]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_1_CFG_ID, 0x0, lut[1]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_2_CFG_ID, 0x0, lut[2]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_3_CFG_ID, 0x0, lut[3]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_4_CFG_ID, 0x0, lut[4]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_5_CFG_ID, 0x0, lut[5]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_6_CFG_ID, 0x0, lut[6]);
+ if (rc)
+ return rc;
+
+ /* Update DGC CFG. Leave this at end for C0/D0 chip. */
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG0_ID, 0x0, DW3000_DGC_CFG0);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG1_ID, 0x0, DW3000_DGC_CFG1);
+ return rc;
+}
+
+/**
+ * dw3000_c0_kick_ops_table_on_wakeup() - kick the OPS table
+ * @dw: The DW device.
+ *
+ * kick the desired operating parameter set (OPS) table upon wakeup from sleep
+ * It will load the required OPS table configuration based upon what OPS table
+ * was set to be used
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_c0_kick_ops_table_on_wakeup(struct dw3000 *dw)
+{
+ return 0;
+}
+
+/**
+ * dw3000_c0_kick_dgc_on_wakeup() - kick the DGC
+ * @dw: The DW device.
+ *
+ * kick the DGC upon wakeup from sleep
+ * It will load the required DGC configuration from OTP based upon what channel was set to be used
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_c0_kick_dgc_on_wakeup(struct dw3000 *dw)
+{
+ u8 channel = dw->config.chan;
+ u16 dgc_sel = (channel == 5 ? 0 : DW3000_NVM_CFG_DGC_SEL_BIT_MASK);
+
+ /* The DGC_SEL bit must be set to '0' for channel 5 and '1' for channel 9 */
+ return dw3000_reg_modify16(dw, DW3000_NVM_CFG_ID, 0,
+ (u16) ~(DW3000_NVM_CFG_DGC_SEL_BIT_MASK),
+ dgc_sel | DW3000_NVM_CFG_DGC_KICK_BIT_MASK);
+}
+
+/**
+ * dw3000_c0_compute_rssi() - Compute RSSI from its composites
+ * @dw: the DW device
+ * @rssi: RSSI composites
+ * @rx_tune: state of RX_TUNE_EN bit leads to use dgc_dec value or not
+ * @sts: current sts mode
+ *
+ * Because RSSI cannot be positive in our case (would mean that signals have
+ * been amplified) we return an unsigned integer considered as always negative.
+ *
+ * Return: 0 on error, else the RSSI in absolute value and as an integer.
+ * expressed in dBm, Q32.0.
+ */
+static u32 dw3000_c0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ bool rx_tune, u8 sts)
+{
+ /* Details are logged into UWB-3455
+ * DW3700 User Manual v0.3 and 4 section 4.7.2 gives that RSSI
+ * can be computed using this formula:
+ * rssi = 10 * log10 ((cir_pwr * 2^21) / pacc_cnt ^ 2) + 6D - A(prf)
+ * But log10 isn't implemented ; let's use ilog2 instead, because it is
+ * easy to do on binary numbers. Thus, formula becomes:
+ * rssi = 3*log2(cir_pwr) - 6*log2(pacc_cnt) + 3*log2(2^21) + 6*D - A(prf)
+ * Notice that 3*log2(2^21) +6*D - A(prf) can be pre-computed.
+ * Factor 3 comes from ln(2) / ln(10) almost equal to 3 / 10 ; this is an
+ * approximation done in equation turning log10 into log2 to avoid floating point
+ */
+
+ /* u32 is used because ilog2 macro cannot work on bitfield */
+ u32 pwr = rssi->cir_pwr;
+ u32 cnt = rssi->pacc_cnt;
+ s32 r, rssi_constant;
+
+ /* Do not consider bad packets */
+ if (unlikely(!pwr || !cnt))
+ return 0;
+
+ rssi_constant = DW3000_RSSI_CONSTANT + 6 * rx_tune * rssi->dgc_dec;
+
+ if (!rssi->prf_64mhz) {
+ rssi_constant -= DW3000_RSSI_OFFSET_PRF16;
+ } else
+ rssi_constant -= ((sts == DW3000_STS_MODE_OFF) ?
+ DW3000_RSSI_OFFSET_PRF64_IPATOV :
+ DW3000_RSSI_OFFSET_PRF64_STS);
+
+ r = 3 * ilog2(pwr) - 6 * ilog2(cnt) + rssi_constant;
+ if (unlikely(r > 0)) {
+ dev_err(dw->dev, "bad rssi value. Forced to 0\n");
+ r = 0;
+ }
+
+ return (u32)-r;
+}
+
+const struct dw3000_chip_ops dw3000_chip_c0_ops = {
+ .softreset = dw3000_c0_softreset,
+ .init = dw3000_c0_init,
+ .coex_init = dw3000_c0_coex_init,
+ .coex_gpio = dw3000_c0_coex_gpio,
+ .check_tx_ok = dw3000_c0_check_tx_ok,
+ .prog_ldo_and_bias_tune = dw3000_c0_prog_ldo_and_bias_tune,
+ .get_config_mrxlut_chan = dw3000_c0_get_config_mrxlut_chan,
+ .get_dgc_dec = dw3000_c0_get_dgc_dec,
+ .pre_read_sys_time = dw3000_c0_pre_read_sys_time,
+ .pll_calibration_from_scratch = dw3000_c0_pll_calibration_from_scratch,
+ .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code,
+ .set_mrxlut = dw3000_c0_set_mrxlut,
+ .kick_ops_table_on_wakeup = dw3000_c0_kick_ops_table_on_wakeup,
+ .kick_dgc_on_wakeup = dw3000_c0_kick_dgc_on_wakeup,
+ .get_registers = dw3000_c0_get_registers,
+ .compute_rssi = dw3000_c0_compute_rssi,
+};
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_c0.h b/kernel/drivers/net/ieee802154/dw3000_chip_c0.h
new file mode 100644
index 0000000..25734f9
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_c0.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CHIP_C0_H
+#define __DW3000_CHIP_C0_H
+
+/* Register PLL_COARSE_CODE */
+#define DW3000_PLL_COARSE_CODE_ID 0x90004
+#define DW3000_PLL_COARSE_CODE_LEN (4U)
+#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (5U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x1fU
+
+/* Time to wait before reading the calibration status register
+ * when a calibration from scratch is executed */
+#define DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400)
+
+/* RSSI constants */
+#define DW3000_RSSI_OFFSET_PRF64_STS 121
+#define DW3000_RSSI_OFFSET_PRF64_IPATOV 122
+#define DW3000_RSSI_OFFSET_PRF16 114
+#define DW3000_RSSI_CONSTANT \
+ 63 /* 3 * log2(2^21) because log2 used instead of log10 */
+
+#endif /* __DW3000_CHIP_C0_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_d0.c b/kernel/drivers/net/ieee802154/dw3000_chip_d0.c
new file mode 100644
index 0000000..8bf3fb8
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_d0.c
@@ -0,0 +1,331 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000.h"
+#include "dw3000_core.h"
+#include "dw3000_core_reg.h"
+#include "dw3000_chip_d0.h"
+#include "dw3000_nfcc_coex_core.h"
+#include "dw3000_trc.h"
+
+int dw3000_c0_get_dgc_dec(struct dw3000 *dw, u8 *value);
+int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw);
+int dw3000_c0_set_mrxlut(struct dw3000 *dw, const u32 *lut);
+
+static const struct dw3000_chip_register d0_registers[] = {
+ /* registres virtuels pour dump des fileID */
+ { "GEN_CFG0", 0x00, 0x7e, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "GEN_CFG1", 0x01, 0x64, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "STS_CFG", 0x02, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RX_TUNE", 0x03, 0x54, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "EXT_SYNC", 0x04, 0x21, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "GPIO_CTRL", 0x05, 0x2e, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "DRX", 0x06, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RF_CONF", 0x07, 0x51, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RF_CAL", 0x08, 0x1e, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "FS_CTRL", 0x09, 0x15, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "AON", 0x0a, 0x15, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "OTP_IF", 0x0b, 0x18, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_1", 0x0c, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_2", 0x0d, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "CIA_3", 0x0e, 0x20, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "DIG_DIAG", 0x0f, 0x51, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "PMSC", 0x11, 0x4a, 0, DW3000_CHIPREG_DUMP, NULL },
+ /* TODO: RX/TX buffers limited to first 128bytes only.
+ Shall we dump the whole 1024 bytes? */
+ { "RX_BUFFER", 0x12, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "RX_BUFFER1", 0x13, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ /* No TX_BUFFER as read isn't supported */
+ /* CIR memory require 2 bits configured elsewhere, so removed. */
+ { "SCRATCH_RAM", 0x16, 0x7f, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "AES RAM", 0x17, 0x80, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "SET_X", 0x18, 0x1d0, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "IN_PTR_CFG", 0x1f, 0x12, 0, DW3000_CHIPREG_DUMP, NULL },
+ { "euid", 0x000004, 0x08, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "frame_filter", 0x000010, 0x01, 0x01, DW3000_CHIPREG_WP, NULL },
+ { "rx_phrmode", 0x000010, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "tx_phrrate", 0x000010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL },
+ { "rx_sts_mode", 0x000011, 0x01, 0x8, DW3000_CHIPREG_WP, NULL },
+ { "pdoa_mode", 0x000012, 0x01, 0x3, DW3000_CHIPREG_WP, NULL },
+ { "role", 0x000015, 0x01, 0x1, DW3000_CHIPREG_WP, NULL },
+ { "frame_filter_cfg", 0x000014, 0x02, 0xfeff, DW3000_CHIPREG_WP, NULL },
+ { "datarate", 0x000021, 0x01, 0x4, DW3000_CHIPREG_WP, NULL },
+ { "tx_pream_len", 0x000021, 0x01, 0xf0, DW3000_CHIPREG_WP, NULL },
+ { "tx_antdly", 0x00007c, 0x02, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "txrf_pwrfin", 0x010004, 0x01, 0xfc, DW3000_CHIPREG_WP, NULL },
+ { "tx_pwr", 0x010004, 0x04, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "rx_sfdtype", 0x010008, 0x01, 0x6, DW3000_CHIPREG_WP, NULL },
+ { "channel", 0x010008, 0x01, 0x01, DW3000_CHIPREG_WP, NULL },
+ { "tx_pream_ch", 0x010008, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL },
+ { "prf", 0x010008, 0x01, 0x1f, DW3000_CHIPREG_WP, NULL },
+ { "rx_phr_rate", 0x010010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL },
+ { "rx_sts_len", 0x020000, 0x01, 0xff, DW3000_CHIPREG_WP, NULL },
+ { "ext_clkdly", 0x040000, 0x02, 0x7f8, DW3000_CHIPREG_WP, NULL },
+ { "ext_clkdly_en", 0x040001, 0x01, 0x8, DW3000_CHIPREG_WP, NULL },
+ { "gpiodir", 0x050008, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL },
+ { "gpioout", 0x05000c, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL },
+ { "rx_paclen", 0x060000, 0x01, 0x03, DW3000_CHIPREG_WP, NULL },
+ { "rx_sfd_to", 0x060002, 0x02, 0x00, DW3000_CHIPREG_WP, NULL },
+ { "chan_pg_delay", 0x07001c, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL },
+ { "chan_pll_cfg", 0x090001, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "xtal_trim", 0x090014, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL },
+ { "rx_antdly", 0x0e0000, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL },
+ { "rx_diag", 0x0e0002, 0x01, 0x10, DW3000_CHIPREG_WP, NULL },
+ { "digi_diag_en", 0x0f0000, 0x01, 0x1, DW3000_CHIPREG_WP, NULL },
+ { "digi_diag_clr", 0x0f0000, 0x01, 0x2, DW3000_CHIPREG_WP, NULL },
+};
+
+const struct dw3000_chip_register *dw3000_d0_get_registers(struct dw3000 *dw,
+ size_t *count)
+{
+ *count = ARRAY_SIZE(d0_registers);
+ return d0_registers;
+}
+
+const u32 *dw3000_d0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel)
+{
+ /* Lookup table default values for channel 5 */
+ static const u32 dw3000_d0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x1c0fd, 0x1c43e, 0x1c6be, 0x1c77e, 0x1cf36, 0x1cfb5, 0x1cff5
+ };
+
+ /* Lookup table default values for channel 9 */
+ static const u32 dw3000_d0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x2a8fe, 0x2ac36, 0x2a5fe, 0x2af3e, 0x2af7d, 0x2afb5, 0x2afb5
+ };
+
+ switch (channel) {
+ case 5:
+ return dw3000_d0_configmrxlut_ch5;
+ case 9:
+ return dw3000_d0_configmrxlut_ch9;
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * dw3000_d0_softreset() - D0 chip specific software reset
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_d0_softreset(struct dw3000 *dw)
+{
+ /* D0 require a FAST command to start soft-reset */
+ return dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_RESET);
+}
+
+/**
+ * dw3000_d0_init() - D0 chip specific initialisation
+ * @dw: The DW device.
+ *
+ * Note: Still used by dw3000_e0_init().
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_d0_init(struct dw3000 *dw)
+{
+ int rc = 0;
+
+ if (dw->current_operational_state != DW3000_OP_STATE_DEEP_SLEEP) {
+ /* Disable nfcc_coex mail box only if the chip is not in deep sleep.
+ * Else, on wake up, the nfcc_coex state will be not detectable. */
+ rc = dw3000_nfcc_coex_disable(dw);
+ }
+ return rc;
+}
+
+/**
+ * dw3000_d0_coex_init() - Configure the device's WiFi coexistence GPIO
+ * @dw: The DW device.
+ *
+ * Note: Still used by dw3000_e0_coex_init() as GPIO pin need to be configured.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_d0_coex_init(struct dw3000 *dw)
+{
+ u32 modemask;
+ u16 dirmask;
+ int rc;
+
+ if (dw->coex_gpio < 0)
+ return 0;
+ /* Ensure selected GPIO is well configured */
+ modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK
+ << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * dw->coex_gpio);
+ rc = dw3000_set_gpio_mode(dw, modemask, 0);
+ if (rc)
+ return rc;
+ dirmask = DW3000_GPIO_DIR_GDP0_BIT_MASK
+ << (DW3000_GPIO_DIR_GDP0_BIT_LEN * dw->coex_gpio);
+ rc = dw3000_set_gpio_dir(dw, dirmask, 0);
+ return rc;
+}
+
+/**
+ * dw3000_d0_coex_gpio() - Update the device's WiFi coexistence GPIO
+ * @dw: The DW device.
+ * @state: The WiFi coexistence GPIO state to apply.
+ * @delay_us: The delay in us before changing GPIO state.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_d0_coex_gpio(struct dw3000 *dw, bool state, int delay_us)
+{
+ int offset;
+ /* /!\ could be called first with (true, 1000), then before end of 1000
+ microseconds could be called with (false, 0), should handle this case
+ with stopping the timer if any */
+ if (delay_us) {
+ /* Wait to ensure GPIO is toggle on time */
+ if (delay_us > 10)
+ usleep_range(delay_us - 10, delay_us);
+ else
+ udelay(delay_us);
+ }
+ offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * dw->coex_gpio;
+ trace_dw3000_coex_gpio(dw, state, delay_us, 0, dw->coex_status);
+ dw3000_set_gpio_out(dw, !state << offset, state << offset);
+ return 0;
+}
+
+static int dw3000_d0_check_tx_ok(struct dw3000 *dw)
+{
+ return 0;
+}
+
+/**
+ * dw3000_d0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_d0_prog_ldo_and_bias_tune(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_otp_data *otp = &dw->otp_data;
+ if (otp->ldo_tune_lo && otp->ldo_tune_hi) {
+ dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0, DW3000_LDO_BIAS_KICK);
+ /* Save the kicks for the on-wake configuration */
+ local->sleep_mode |= DW3000_LOADLDO;
+ }
+ /* Use DGC_CFG from OTP */
+ local->dgc_otp_set = otp->dgc_addr == DW3000_DGC_CFG0 ? true : false;
+ return 0;
+}
+
+/**
+ * dw3000_d0_pll_calibration_from_scratch() - Calibrate the PLL from scratch
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_d0_pll_calibration_from_scratch(struct dw3000 *dw)
+{
+ int rc = 0;
+
+ /* Run the PLL calibration from scratch.
+ * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start
+ * its calculation. This is just in order to fasten the process.
+ */
+ rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0,
+ DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Wait for the PLL calibration (needed before read the calibration status register) */
+ usleep_range(DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US,
+ DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10);
+ return rc;
+}
+
+/**
+ * dw3000_d0_compute_rssi() - Compute RSSI from its composites
+ * @dw: the DW device
+ * @rssi: RSSI composites
+ * @rx_tune: state of RX_TUNE_EN bit leads to use dgc_dec value or not
+ * @sts: current sts mode
+ *
+ * Because RSSI cannot be positive in our case (would mean that signals have
+ * been amplified) we return an unsigned integer considered as always negative.
+ *
+ * Return: 0 on error, else the RSSI in absolute value and as an integer.
+ * expressed in dBm, Q32.0.
+ */
+u32 dw3000_d0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ bool rx_tune, u8 sts)
+{
+ /* Details are logged into UWB-3455
+ * DW3700 User Manual v0.4 section 4.7.2 gives that RSSI
+ * can be computed using this formula:
+ * rssi = 10 * log10 ((cir_pwr * 2^17) / pacc_cnt ^ 2) + 6D - A(prf)
+ * But log10 isn't implemented ; let's use ilog2 instead, because it is
+ * easy to do on binary numbers. Thus, formula becomes:
+ * rssi = 3*log2(cir_pwr) - 6*log2(pacc_cnt) + 3*log2(2^17) + 6*D - A(prf)
+ * Notice that 3*log2(2^17) +6*D - A(prf) can be pre-computed.
+ * Factor 3 comes from ln(2) / ln(10) almost equal to 3 / 10 ; this is an
+ * approximation done in equation turning log10 into log2 to avoid floating point
+ */
+
+ /* u32 is used because ilog2 macro cannot work on bitfield */
+ u32 pwr = rssi->cir_pwr;
+ u32 cnt = rssi->pacc_cnt;
+ s32 r, rssi_constant;
+
+ /* Do not consider bad packets */
+ if (unlikely(!pwr || !cnt))
+ return 0;
+
+ rssi_constant = DW3000_RSSI_CONSTANT + 6 * rx_tune * rssi->dgc_dec;
+
+ if (!rssi->prf_64mhz) {
+ rssi_constant -= DW3000_RSSI_OFFSET_PRF16;
+ } else
+ rssi_constant -= ((sts == DW3000_STS_MODE_OFF) ?
+ DW3000_RSSI_OFFSET_PRF64_IPATOV :
+ DW3000_RSSI_OFFSET_PRF64_STS);
+
+ r = 3 * ilog2(pwr) - 6 * ilog2(cnt) + rssi_constant;
+ if (unlikely(r > 0)) {
+ dev_err(dw->dev, "bad rssi value. Forced to 0\n");
+ r = 0;
+ }
+
+ return (u32)-r;
+}
+
+const struct dw3000_chip_ops dw3000_chip_d0_ops = {
+ .softreset = dw3000_d0_softreset,
+ .init = dw3000_d0_init,
+ .coex_init = dw3000_d0_coex_init,
+ .coex_gpio = dw3000_d0_coex_gpio,
+ .check_tx_ok = dw3000_d0_check_tx_ok,
+ .prog_ldo_and_bias_tune = dw3000_d0_prog_ldo_and_bias_tune,
+ .get_config_mrxlut_chan = dw3000_d0_get_config_mrxlut_chan,
+ .get_dgc_dec = dw3000_c0_get_dgc_dec,
+ .pll_calibration_from_scratch = dw3000_d0_pll_calibration_from_scratch,
+ .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code,
+ .set_mrxlut = dw3000_c0_set_mrxlut,
+ .get_registers = dw3000_d0_get_registers,
+ .compute_rssi = dw3000_d0_compute_rssi,
+};
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_d0.h b/kernel/drivers/net/ieee802154/dw3000_chip_d0.h
new file mode 100644
index 0000000..4804623
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_d0.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CHIP_D0_H
+#define __DW3000_CHIP_D0_H
+
+/* Register PLL_COARSE_CODE */
+#define DW3000_PLL_COARSE_CODE_ID 0x90004
+#define DW3000_PLL_COARSE_CODE_LEN (4U)
+#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (5U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x1fU
+
+/* Time to wait before reading the calibration status register
+ * when a calibration from scratch is executed */
+#define DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400)
+
+/* RSSI constants */
+#define DW3000_RSSI_OFFSET_PRF64_STS 121
+#define DW3000_RSSI_OFFSET_PRF64_IPATOV 122
+#define DW3000_RSSI_OFFSET_PRF16 114
+#define DW3000_RSSI_CONSTANT \
+ 51 /* 3 * log2(2^17) because log2 used instead of log10 */
+
+#endif /* __DW3000_CHIP_D0_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_e0.c b/kernel/drivers/net/ieee802154/dw3000_chip_e0.c
new file mode 100644
index 0000000..f4158be
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_e0.c
@@ -0,0 +1,711 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000.h"
+#include "dw3000_core.h"
+#include "dw3000_core_reg.h"
+#include "dw3000_chip_e0.h"
+#include "dw3000_trc.h"
+
+int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw);
+int dw3000_d0_softreset(struct dw3000 *dw);
+int dw3000_d0_init(struct dw3000 *dw);
+int dw3000_d0_coex_init(struct dw3000 *dw);
+const struct dw3000_chip_register *dw3000_d0_get_registers(struct dw3000 *dw,
+ size_t *count);
+u32 dw3000_d0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ bool rx_tune, u8 sts);
+
+const u32 *dw3000_e0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel)
+{
+ /* Lookup table default values for channel 5 */
+ static const u32 dw3000_e0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x3803e, 0x3876e, 0x397fe, 0x38efe, 0x39c7e, 0x39dfe, 0x39ff6
+ };
+
+ /* Lookup table default values for channel 9 */
+ static const u32 dw3000_e0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = {
+ 0x5407e, 0x547be, 0x54d36, 0x55e36, 0x55f36, 0x55df6, 0x55ffe
+ };
+
+ switch (channel) {
+ case 5:
+ return dw3000_e0_configmrxlut_ch5;
+ case 9:
+ return dw3000_e0_configmrxlut_ch9;
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * DW3000_COEX_TIMER_XTAL - Timer frequency to use for WiFi coexistence
+ *
+ * Max delay is 1s during ranging (1Hz), so require to use per 64 divisor
+ * to ensure that calculated expire value is lower than 21bits, the max
+ * register value.
+ */
+#define DW3000_COEX_TIMER_XTAL DW3000_TIMER_XTAL_DIV64
+
+/**
+ * dw3000_e0_init() - E0 chip specific initialisation
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_init(struct dw3000 *dw)
+{
+ int rc = dw3000_d0_init(dw);
+ if (rc)
+ return rc;
+ /* Ensure GPIO block clock is enabled */
+ return dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 2,
+ DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK >> 16);
+}
+
+/**
+ * dw3000_e0_coex_init() - Configure the device's WiFi coexistence GPIO
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_coex_init(struct dw3000 *dw)
+{
+ struct dw3000_timer_cfg cfg = { .divider = DW3000_COEX_TIMER_XTAL,
+ .mode = DW3000_SINGLE_MODE,
+ .gpio_stop = 0,
+ .coex_out = 1 };
+ int rc;
+ u8 tmp;
+
+ if (dw->coex_gpio < 0)
+ return 0;
+ /* Validate configured WiFi coex GPIO */
+ if (dw->coex_gpio < 4 || dw->coex_gpio > 5) {
+ /* Disable if badly configured and return an error */
+ dw->coex_gpio = -1;
+ return -EINVAL;
+ }
+ /* Ensure selected GPIO clock is enabled */
+ rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 2,
+ DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK >> 16);
+ if (rc)
+ return rc;
+ /* Ensure selected GPIO is well configured, same as D0 chip */
+ rc = dw3000_d0_coex_init(dw);
+ if (rc)
+ return rc;
+ /* Swap COEX GPIO if need to use GPIO 4 as COEX_OUT */
+ if (dw->coex_gpio == 4) {
+ tmp = DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK >> 24;
+ rc = dw3000_reg_or8(dw, DW3000_GPIO_MODE_ID, 3, tmp);
+ } else {
+ tmp = (u8)(~DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK >> 24);
+ rc = dw3000_reg_and8(dw, DW3000_GPIO_MODE_ID, 3, tmp);
+ }
+ if (rc)
+ return rc;
+
+ /* Configure E0 timer0 for use */
+ rc = dw3000_timers_enable(dw);
+ if (rc)
+ return rc;
+ rc = dw3000_timers_reset(dw);
+ if (rc)
+ return rc;
+ rc = dw3000_timer_configure(dw, DW3000_TIMER0, &cfg);
+ if (rc)
+ return rc;
+ return 0;
+}
+
+/**
+ * dw3000_e0_coex_gpio() - Update the device's WiFi coexistence GPIO
+ * @dw: The DW device.
+ * @state: The WiFi coexistence GPIO state to apply.
+ * @delay_us: The delay in us before changing GPIO state.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_coex_gpio(struct dw3000 *dw, bool state, int delay_us)
+{
+ int offset = DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * dw->coex_gpio;
+ u32 modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK << offset;
+ int rc;
+ /* /!\ could be called first with (true, 1000), then before end of 1000
+ microseconds could be called with (false, 0), should handle this case
+ with stopping the timer if any */
+ if (delay_us) {
+ const int factor =
+ (DW3000_TIMER_FREQ >> DW3000_COEX_TIMER_XTAL) / 1000;
+ const int divisor = 1000000 / 1000;
+ u32 expire;
+ /* Reconfigure selected GPIO for COEX mode */
+ rc = dw3000_set_gpio_mode(dw, modemask, 1 << offset);
+ if (rc)
+ return rc;
+ /* Re-configure COEX_OUT_MODE in SYS_CFG for Timer use */
+ rc = dw3000_reg_or32(dw, DW3000_SYS_CFG_ID, 0,
+ DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Launch timer0 */
+ expire = delay_us * factor / divisor;
+ rc = dw3000_timer_set_expiration(dw, DW3000_TIMER0, expire);
+ if (rc)
+ return rc;
+ trace_dw3000_coex_gpio(dw, state, delay_us, expire,
+ dw->coex_status);
+ return dw3000_timer_start(dw, DW3000_TIMER0);
+ }
+ if (!state) {
+ /* Stop/reset timer0 & 1 */
+ rc = dw3000_timers_reset(dw);
+ if (rc)
+ return rc;
+ /* Reset COEX_OUT_MODE in SYS_CFG to 0 */
+ rc = dw3000_reg_and32(
+ dw, DW3000_SYS_CFG_ID, 0,
+ ~(u32)DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK);
+ if (rc)
+ return rc;
+ }
+ /* Reconfigure COEX GPIO for GPIO mode */
+ rc = dw3000_set_gpio_mode(dw, modemask, 0);
+ if (rc)
+ return rc;
+ /* Update GPIO output state */
+ offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * dw->coex_gpio;
+ trace_dw3000_coex_gpio(dw, state, delay_us, 0, dw->coex_status);
+ return dw3000_set_gpio_out(dw, !state << offset, state << offset);
+}
+
+static int dw3000_e0_check_tx_ok(struct dw3000 *dw)
+{
+ return 0;
+}
+
+/**
+ * dw3000_e0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_e0_prog_ldo_and_bias_tune(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_otp_data *otp = &dw->otp_data;
+
+ if (otp->ldo_tune_lo && otp->ldo_tune_hi && otp->bias_tune) {
+ dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0, DW3000_LDO_BIAS_KICK);
+ /* Save the kicks for the on-wake configuration */
+ local->sleep_mode |= DW3000_LOADLDO | DW3000_LOADBIAS;
+ }
+ /* Ignore use of DGC from OTP */
+ local->dgc_otp_set = false;
+ return 0;
+}
+
+/**
+ * dw3000_timers_enable() - Enables the device's timers block
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timers_enable(struct dw3000 *dw)
+{
+ /* Enable LDO to run the timer - needed if not in IDLE state */
+ return dw3000_reg_or8(dw, DW3000_LDO_CTRL_ID, 0,
+ DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_MASK);
+}
+
+/**
+ * dw3000_timers_reset() - Reset the device's timers block
+ * @dw: the DW device
+ *
+ * It will reset both timers. It can be used to stop a timer running in repeat
+ * mode.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timers_reset(struct dw3000 *dw)
+{
+ return dw3000_reg_and16(dw, DW3000_SOFT_RST_ID, 0,
+ (u16)(~DW3000_SOFT_RST_TIM_RST_N_BIT_MASK));
+}
+
+/**
+ * dw3000_timers_read_and_clear_events() - Read the timers' event counts
+ * @dw: the DW device
+ * @evt0: pointer where to store timer0's event count
+ * @evt1: pointer where to store timer1's event count
+ *
+ * When reading from this register the values will be reset/cleared, thus the
+ * host needs to read both timers' event counts.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timers_read_and_clear_events(struct dw3000 *dw, u8 *evt0, u8 *evt1)
+{
+ int rc;
+ u16 status;
+ rc = dw3000_reg_read16(dw, DW3000_TIMER_STATUS_ID, 0, &status);
+ if (unlikely(rc))
+ return rc;
+ if (evt0)
+ *evt0 = (u8)status;
+ if (evt1)
+ *evt1 = (u8)(status >> 8);
+ return 0;
+}
+
+/**
+ * dw3000_timer_configure() - Configures the selected timer
+ * @dw: the DW device
+ * @timer: timer to configure
+ * @cfg: pointer to structure holding timer configuration
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timer_configure(struct dw3000 *dw, enum dw3000_timer timer,
+ struct dw3000_timer_cfg *cfg)
+{
+ u32 config =
+ ((u16)cfg->divider
+ << DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_OFFSET) |
+ ((u16)cfg->mode << DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_OFFSET) |
+ ((u16)cfg->gpio_stop
+ << DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_OFFSET) |
+ ((u16)cfg->coex_out
+ << DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_OFFSET);
+ /* For TIMER 1 we write the configuration at offset 2 */
+ config <<= (u8)timer * 8;
+ /* Ensure reading CNT register return current counter value! */
+ config |= DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_MASK << (u8)timer;
+ return dw3000_reg_write32(dw, DW3000_TIMER_CTRL_ID, 0, config);
+}
+
+/**
+ * dw3000_timer_set_expiration() - sets timer expiration delay
+ * @dw: the DW device
+ * @timer: timer to set expiration period (see enum dw3000_timer)
+ * @exp: expiry count in timer frequency unit
+ *
+ * This function set the 22 lower bits of expiration period.
+ * It take expiry count in timer frequency unit, e.g. if units are XTAL/64 (1.66 us)
+ * then setting 1024 ~= 1.7 ms delay.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timer_set_expiration(struct dw3000 *dw, enum dw3000_timer timer,
+ u32 exp)
+{
+ u32 reg = DW3000_TIMER0_CNT_SET_ID + (4 * timer);
+ return dw3000_reg_write32(
+ dw, reg, 0, exp & DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_MASK);
+}
+
+/**
+ * dw3000_timer_get_counter() - retrieve timer counter or expiration delay
+ * @dw: the DW device
+ * @timer: timer to set expiration period (see enum dw3000_timer)
+ * @counter: pointer where current counter value or expiry count is saved
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timer_get_counter(struct dw3000 *dw, enum dw3000_timer timer,
+ u32 *counter)
+{
+ u32 reg = DW3000_TIMER0_CNT_SET_ID + (4 * timer);
+ return dw3000_reg_read32(dw, reg, 0, counter);
+}
+
+/**
+ * dw3000_timer_start() - Enables the specified timer
+ * @dw: the DW device
+ * @timer: timer to enable
+ *
+ * In order to enable, the timer enable bit [0] for TIMER0 or [1] for TIMER1
+ * needs to transition from 0 -> 1.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_timer_start(struct dw3000 *dw, enum dw3000_timer timer)
+{
+ /* TODO: check if dw3000_reg_modify8() is working or not */
+ u8 val = 1 << timer;
+ int rc;
+ /* Set to '0' */
+ rc = dw3000_reg_and8(dw, DW3000_TIMER_CTRL_ID, 0, ~val);
+ if (rc)
+ return rc;
+ /* Set to '1' */
+ return dw3000_reg_or8(dw, DW3000_TIMER_CTRL_ID, 0, val);
+}
+
+/**
+ * dw3000_e0_adc_calibration_monitor_thresholds() - Monitors the thresholds
+ * @dw: the DW device
+ * @thresholds: the monitored thresholds
+ * @rx_event: true if any RX event triggered, else false
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_adc_calibration_monitor_thresholds(struct dw3000 *dw,
+ u32 *thresholds,
+ bool *rx_event)
+{
+ int rc, i;
+ u8 status;
+ u16 threshold_arr[4] = { 0, 0, 0, 0 };
+
+ /* Monitor thresholds */
+ for (i = 0; i < DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS; i++) {
+ /* Unfreeze */
+ rc = dw3000_reg_modify8(dw, DW3000_MRX_CFG_ID, 0, 0xFE, 0);
+ if (rc)
+ return rc;
+ /* Freeze */
+ rc = dw3000_reg_modify8(dw, DW3000_MRX_CFG_ID, 0, 0xFE, 1);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_ADC_THRESH_DBG_ID, 0,
+ thresholds);
+ if (rc)
+ return rc;
+ threshold_arr[0] += (*thresholds & 0xFF);
+ threshold_arr[1] += ((*thresholds >> 8) & 0xFF);
+ threshold_arr[2] += ((*thresholds >> 16) & 0xFF);
+ threshold_arr[3] += ((*thresholds >> 24) & 0xFF);
+ }
+ threshold_arr[0] =
+ (u8)(threshold_arr[0] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS);
+ threshold_arr[1] =
+ (u8)(threshold_arr[1] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS);
+ threshold_arr[2] =
+ (u8)(threshold_arr[2] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS);
+ threshold_arr[3] =
+ (u8)(threshold_arr[3] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS);
+ *thresholds = (threshold_arr[3] << 24) + (threshold_arr[2] << 16) +
+ (threshold_arr[1] << 8) + threshold_arr[0];
+ /* Once thresholds are monitored read the sys status */
+ rc = dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 0, &status);
+ if (rc)
+ return rc;
+ /* Check and return if any RX event triggered. */
+ *rx_event = !!(status & (DW3000_SYS_STATUS_ALL_RX_GOOD |
+ DW3000_SYS_STATUS_ALL_RX_ERR |
+ DW3000_SYS_STATUS_ALL_RX_TO));
+ return 0;
+}
+
+/**
+ * dw3000_e0_adc_offset_calibration() - Calibrate ADC
+ * @dw: the DW device
+ *
+ * TODO: This function comes from directly from Qorvo/Decawave.
+ * Hard coded values come with no documentation and should be converted into
+ * define when the documentation will be available.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_e0_adc_offset_calibration(struct dw3000 *dw)
+{
+ int rc, k;
+ u32 switch_control_reg_backup;
+ u32 sys_enable_lo;
+ u32 sys_enable_hi;
+ u32 agc_reg_backup;
+ u32 dgc_reg_backup;
+ u32 dgc_lut0_reg_backup;
+ u32 dgc_lut6_reg_backup;
+ u8 pgf_idx;
+ u32 thresholds;
+ bool rx_event;
+
+ /* Step 1: Get the current registers value used or modified by the calibration */
+ rc = dw3000_reg_read32(dw, DW3000_RF_SWITCH_CTRL_ID, 0,
+ &switch_control_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_AGC_CFG_ID, 0, &agc_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_DGC_CFG_ID, 0, &dgc_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_DGC_LUT_0_CFG_ID, 0,
+ &dgc_lut0_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_DGC_LUT_6_CFG_ID, 0,
+ &dgc_lut6_reg_backup);
+ if (rc)
+ return rc;
+
+ /* Step 2a: De-sensitise RX path by shunting the TXRX switch */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_RF_SWITCH_CTRL_ID, 0,
+ ~DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_MASK,
+ (0x38 << DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_OFFSET) |
+ DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Further de-sensitise the RX path by selecting a higher DGC setting */
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0,
+ dgc_lut6_reg_backup);
+ if (rc)
+ return rc;
+ /* Step 2b: Disable AGC and set PGF gain manually */
+ pgf_idx = dgc_lut0_reg_backup & 0x7;
+ rc = dw3000_reg_modify8(dw, DW3000_AGC_CFG_ID, 0, ~(0x40 | 0x38 | 0x1),
+ (0x40 | (pgf_idx << 3)));
+ if (rc)
+ return rc;
+ /* Step 2c: ADC independent thresholds */
+ rc = dw3000_reg_write8(dw, DW3000_AGC_CFG_ID, 3, 0);
+ if (rc)
+ return rc;
+ /* Step 2d: Disable DGC */
+ dw3000_reg_and8(dw, DW3000_DGC_CFG_ID, 0x0,
+ (u8)~DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK);
+ if (rc)
+ return rc;
+
+ /* 2e: Disable interrupt events */
+ rc = dw3000_reg_read32(dw, DW3000_SYS_ENABLE_HI_ID, 0, &sys_enable_hi);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_read32(dw, DW3000_SYS_ENABLE_LO_ID, 0, &sys_enable_lo);
+ if (rc)
+ return rc;
+ /* disable interrupts */
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, 0);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, 0);
+ if (rc)
+ return rc;
+
+ /* Step 3a: Enable RX (may need further work) */
+ for (k = 0; k < 2; k++) {
+ {
+ /* Ensure coex is disable to have immediate RX */
+ s8 gpio = dw->coex_gpio;
+ dw->coex_gpio = -1;
+ barrier();
+ rc = dw3000_rx_enable(dw, 0, 0, 0);
+ dw->coex_gpio = gpio;
+ if (rc)
+ return rc;
+ }
+ usleep_range(DW3000_E0_ADC_CALIBRATION_DELAY_US,
+ DW3000_E0_ADC_CALIBRATION_DELAY_US + 10);
+
+ /* Step 3b: monitor thresholds */
+ rc = dw3000_e0_adc_calibration_monitor_thresholds(
+ dw, &thresholds, &rx_event);
+ if (rc)
+ return rc;
+
+ /* Step 3c: disable receiver */
+ {
+ /* Ensure coex is disable */
+ s8 gpio = dw->coex_gpio;
+ dw->coex_gpio = -1;
+ barrier();
+ rc = dw3000_forcetrxoff(dw);
+ dw->coex_gpio = gpio;
+ if (rc)
+ return rc;
+ }
+
+ if (!rx_event)
+ break;
+ }
+
+ /* 3d: restore interrupts */
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, sys_enable_hi);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, sys_enable_lo);
+ if (rc)
+ return rc;
+
+ /* Step 3e: Set initial DAC indices to settled RMS values */
+ rc = dw3000_reg_write32(dw, DW3000_ADC_THRESH_CFG_ID, 0, thresholds);
+ if (rc)
+ return rc;
+ /* Step 4: restore initial register values */
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, sys_enable_hi);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, sys_enable_lo);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0,
+ switch_control_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_AGC_CFG_ID, 0, agc_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG_ID, 0, dgc_reg_backup);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0,
+ dgc_lut0_reg_backup);
+ if (rc)
+ return rc;
+ if (rx_event)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * dw3000_e0_pll_calibration_from_scratch() - Calibrate the PLL from scratch
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_pll_calibration_from_scratch(struct dw3000 *dw)
+{
+ int rc = 0;
+
+ /* Run the PLL calibration from scratch.
+ * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start
+ * its calculation. This is just in order to fasten the process.
+ */
+ rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0,
+ DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK |
+ DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Wait for the PLL calibration (needed before read the calibration status register) */
+ usleep_range(DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US,
+ DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10);
+ return rc;
+}
+
+/**
+ * dw3000_e0_get_dgc_dec() - Read DGC_DBG register content
+ * @dw: The DW device.
+ * @value: Pointer to store DGC DECISION value.
+ *
+ * Return: zero on succes, else a negative error code.
+ */
+static int dw3000_e0_get_dgc_dec(struct dw3000 *dw, u8 *value)
+{
+ int rc;
+ u32 dgc_dbg;
+
+ rc = dw3000_reg_read32(dw, DW3000_E0_DGC_DBG_ID, 0, &dgc_dbg);
+ if (unlikely(rc))
+ return rc;
+
+ /* DGC_DECISION is on bits 28 to 30 of DGC_CFG, cf 8.2.4.2 of DW3700
+ * User Manual, store it to right the rssi stats entry */
+ *value = (u8)((dgc_dbg & 0x70000000) >> 0x1c);
+ return 0;
+}
+
+/**
+ * dw3000_e0_pll_coarse_code() - Calibrate the PLL from scratch
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_pll_coarse_code(struct dw3000 *dw)
+{
+ u8 tmp;
+
+ /* Clear CH9_ICAS/RCAS bits */
+ tmp = (u8)(~(DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_MASK |
+ DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_MASK) >>
+ 24);
+ return dw3000_reg_and8(dw, DW3000_PLL_COARSE_CODE_ID, 3, tmp);
+}
+
+/**
+ * dw3000_e0_set_mrxlut() - Configure mrxlut
+ * @dw: The DW device.
+ * @lut: Pointer to LUT to write to DGC_LUT_X_CFG registers
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_e0_set_mrxlut(struct dw3000 *dw, const u32 *lut)
+{
+ int rc;
+
+ /* Update DGC CFG. Leave it at the beginning for E0 chip */
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG0_ID, 0x0, DW3000_DGC_CFG0);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG1_ID, 0x0, DW3000_DGC_CFG1);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_CFG2_ID, 0x0, DW3000_DGC_CFG2);
+ if (rc)
+ return rc;
+
+ /* Update LUT registers */
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0x0, lut[0]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_1_CFG_ID, 0x0, lut[1]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_2_CFG_ID, 0x0, lut[2]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_3_CFG_ID, 0x0, lut[3]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_4_CFG_ID, 0x0, lut[4]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_5_CFG_ID, 0x0, lut[5]);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_6_CFG_ID, 0x0, lut[6]);
+ return rc;
+}
+
+const struct dw3000_chip_ops dw3000_chip_e0_ops = {
+ .softreset = dw3000_d0_softreset,
+ .init = dw3000_e0_init,
+ .coex_init = dw3000_e0_coex_init,
+ .coex_gpio = dw3000_e0_coex_gpio,
+ .check_tx_ok = dw3000_e0_check_tx_ok,
+ .prog_ldo_and_bias_tune = dw3000_e0_prog_ldo_and_bias_tune,
+ .get_config_mrxlut_chan = dw3000_e0_get_config_mrxlut_chan,
+ .get_dgc_dec = dw3000_e0_get_dgc_dec,
+ .adc_offset_calibration = dw3000_e0_adc_offset_calibration,
+ .pll_calibration_from_scratch = dw3000_e0_pll_calibration_from_scratch,
+ .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code,
+ .pll_coarse_code = dw3000_e0_pll_coarse_code,
+ .set_mrxlut = dw3000_e0_set_mrxlut,
+ .get_registers = dw3000_d0_get_registers,
+ .compute_rssi = dw3000_d0_compute_rssi,
+};
diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_e0.h b/kernel/drivers/net/ieee802154/dw3000_chip_e0.h
new file mode 100644
index 0000000..c4a97ee
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_chip_e0.h
@@ -0,0 +1,112 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CHIP_E0_H
+#define __DW3000_CHIP_E0_H
+
+/* Forward declaration */
+struct dw3000;
+
+enum dw3000_timer { DW3000_TIMER0 = 0, DW3000_TIMER1 };
+
+enum dw3000_timer_mode { DW3000_SINGLE_MODE = 0, DW3000_REPEAT_MODE };
+
+/* Register PLL_COARSE_CODE */
+#define DW3000_PLL_COARSE_CODE_ID 0x90004
+#define DW3000_PLL_COARSE_CODE_LEN (4U)
+#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL
+#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_OFFSET (27U)
+#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_LEN (1U)
+#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_MASK 0x8000000UL
+#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_OFFSET (26U)
+#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_LEN (1U)
+#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_MASK 0x4000000UL
+#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_OFFSET (25U)
+#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_LEN (1U)
+#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_MASK 0x2000000UL
+#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_OFFSET (24U)
+#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_LEN (1U)
+#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_MASK 0x1000000UL
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U)
+#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (7U)
+#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x7fU
+
+/* Delay to wait after RX enable to calibrate the ADC on E0 chip */
+#define DW3000_E0_ADC_CALIBRATION_DELAY_US (200)
+
+/* Loops to compute the ADC threshold average on E0 chip */
+#define DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS (4)
+/* Time to wait before reading the calibration status register
+ * when a calibration from scratch is executed */
+#define DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400)
+
+#define DW3000_TIMER_FREQ 38400000
+
+#define DW3000_E0_DGC_DBG_ID 0x30054
+
+enum dw3000_timer_period {
+ /* 38.4 MHz */
+ DW3000_TIMER_XTAL_NODIV = 0,
+ /* 19.2 MHz */
+ DW3000_TIMER_XTAL_DIV2,
+ /* 9.6 MHz */
+ DW3000_TIMER_XTAL_DIV4,
+ /* 4.8 MHz */
+ DW3000_TIMER_XTAL_DIV8,
+ /* 2.4 MHz */
+ DW3000_TIMER_XTAL_DIV16,
+ /* 1.2 MHz */
+ DW3000_TIMER_XTAL_DIV32,
+ /* 0.6 MHz */
+ DW3000_TIMER_XTAL_DIV64,
+ /* 0.3 MHz */
+ DW3000_TIMER_XTAL_DIV128
+};
+
+struct dw3000_timer_cfg {
+ /* Select the timer frequency (divider) */
+ enum dw3000_timer_period divider;
+ /* Select the timer mode */
+ enum dw3000_timer_mode mode;
+ /* Set to '1' to halt this timer on GPIO interrupt */
+ u8 gpio_stop;
+ /* Configure GPIO for WiFi co-ex */
+ u8 coex_out;
+};
+
+/* Hardware timer functions */
+int dw3000_timers_enable(struct dw3000 *dw);
+int dw3000_timers_reset(struct dw3000 *dw);
+int dw3000_timers_read_and_clear_events(struct dw3000 *dw, u8 *evt0, u8 *evt1);
+
+int dw3000_timer_configure(struct dw3000 *dw, enum dw3000_timer timer,
+ struct dw3000_timer_cfg *cfg);
+int dw3000_timer_set_expiration(struct dw3000 *dw, enum dw3000_timer timer,
+ u32 exp);
+int dw3000_timer_get_counter(struct dw3000 *dw, enum dw3000_timer timer,
+ u32 *counter);
+int dw3000_timer_start(struct dw3000 *dw, enum dw3000_timer timer);
+
+#endif /* __DW3000_CHIP_E0_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_cir.h b/kernel/drivers/net/ieee802154/dw3000_cir.h
new file mode 100644
index 0000000..d66429c
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_cir.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CIR_H
+#define __DW3000_CIR_H
+
+#include <linux/completion.h>
+
+#include "dw3000_core_reg.h"
+
+/* Default count of CIR records stored in cir_data */
+#define DW3000_DEFAULT_CIR_RECORD_COUNT 20
+
+/**
+ * struct dw3000_cir_record - Hold a cir data record
+ * @real: real part of the cir record. Format 6.18 corresponding to sign:figure
+ * @imag: imaginary part of the cir record. Format 6.18 corresponding to sign:figure
+ */
+struct dw3000_cir_record {
+ u8 real[3];
+ u8 imag[3];
+} __attribute__((__packed__));
+
+/**
+ * struct dw3000_cir_data - Allow CIR memory records dumped through debugfs
+ * @complete: synchronize producer and consumer
+ * @mutex: ensure security of accesses to the instance of this structure
+ * @ciaregs: array storing all CIA registers
+ * @count: number of records in data member
+ * @filter: kind of frame selected and accumulated in CIR
+ * @ts: timestamp extracted from CIA registers
+ * @utime: rx timestamp for the cir measurement
+ * @fp_power1: first path power component f1
+ * @fp_power2: first path power component f2
+ * @fp_power3: first path power component f3
+ * @offset: user defined offset
+ * @fp_index: index of first path record in CIR register
+ * @pdoa: pdoa raw value
+ * @acc: number of symbols accumulated in CIR
+ * @type: CIR type field
+ * @dummy: store the dummy byte firstly received at each CIR memory reading
+ * @data: table storing 'count' records read from CIR data memory
+ */
+struct dw3000_cir_data {
+ struct completion complete;
+ struct mutex mutex;
+ __le32 ciaregs[DW3000_DB_DIAG_SET_LEN >> 2];
+ unsigned int count;
+ u32 filter;
+ u64 ts;
+ u64 utime;
+ u32 fp_power1;
+ u32 fp_power2;
+ u32 fp_power3;
+ s32 offset;
+ u16 fp_index;
+ u16 pdoa;
+ u16 acc;
+ u8 type;
+ u8 dummy;
+ struct dw3000_cir_record data[1];
+};
+
+#endif
diff --git a/kernel/drivers/net/ieee802154/dw3000_coex.h b/kernel/drivers/net/ieee802154/dw3000_coex.h
new file mode 100644
index 0000000..c2dc6b7
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_coex.h
@@ -0,0 +1,159 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_COEX_H
+#define __DW3000_COEX_H
+
+#include "dw3000.h"
+
+static inline int dw3000_coex_stop(struct dw3000 *dw);
+
+/**
+ * dw3000_coex_gpio - change the state of the GPIO used for WiFi coexistence
+ * @dw: the DW device
+ * @state: the new GPIO state to set
+ * @delay_us: delay before toggling the GPIO.
+ *
+ * This function only call the version dependent coex_gpio function.
+ *
+ * It cannot be called if dw3000_coex_init() has fail or if no coex gpio
+ * is defined. So no need to test chip_ops.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_coex_gpio(struct dw3000 *dw, int state, int delay_us)
+{
+ int rc;
+
+ /* Use SPI in queuing mode */
+ dw3000_spi_queue_start(dw);
+ rc = dw->chip_ops->coex_gpio(dw, state, delay_us);
+ if (rc)
+ return dw3000_spi_queue_reset(dw, rc);
+ rc = dw3000_spi_queue_flush(dw);
+ if (!rc)
+ dw->coex_status = state;
+ return rc;
+}
+
+/**
+ * dw3000_coex_start - Handle WiFi coex gpio at start of uwb exchange.
+ * @dw: the DW device
+ * @trx_delayed: pointer to tx/rx_delayed parameter to update
+ * @trx_date_dtu: pointer to tx/rx_date_dtu parameter to update
+ * @cur_time_dtu: current device time in DTU
+ *
+ * Calculate a timer delay in us and programme it to ensure the WiFi coex
+ * GPIO is asserted at least `coex_delay_us` before the next operation.
+ *
+ * This function also reset the GPIO immediatly if the calculated timer delay
+ * is more than `coex_interval_us`.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_coex_start(struct dw3000 *dw, bool *trx_delayed,
+ u32 *trx_date_dtu, u32 cur_time_dtu)
+{
+ int delay_us;
+ int timer_us = 0;
+
+ if (dw->coex_gpio < 0 || !dw->coex_enabled)
+ return 0;
+ /* Add a margin for required SPI transactions to the coex delay time
+ * to ensure GPIO change at right time. */
+ delay_us = dw->coex_delay_us + dw->coex_margin_us;
+ if (*trx_delayed == false) {
+ /* Change to delayed TX/RX with the minimum required delay */
+ *trx_date_dtu = cur_time_dtu + US_TO_DTU(delay_us);
+ *trx_delayed = true;
+ /* Leave timer_us to 0 to set gpio now. */
+ } else {
+ /* Calculate timer duration to program. */
+ /* V TX
+ * | time_difference_us |
+ * | margin | timer | delay |
+ * G
+ *
+ * timer = time_difference_us - (delay + margin)
+ */
+ int time_difference_dtu = *trx_date_dtu - cur_time_dtu;
+ int time_difference_us = DTU_TO_US(time_difference_dtu);
+ if (time_difference_us > delay_us)
+ timer_us = time_difference_us - delay_us;
+ /* else, too late for timer, set gpio now */
+ }
+ trace_dw3000_coex_gpio_start(dw, timer_us, dw->coex_status,
+ dw->coex_interval_us);
+ if (dw->coex_status) {
+ if (timer_us < dw->coex_interval_us)
+ return 0; /* Nothing more to do */
+ dw3000_coex_stop(dw);
+ }
+ /* Set coexistence gpio on chip */
+ return dw3000_coex_gpio(dw, true, timer_us);
+}
+
+/**
+ * dw3000_coex_stop - Handle WiFi coex gpio at end of uwb exchange.
+ * @dw: the DW device
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_coex_stop(struct dw3000 *dw)
+{
+ if (dw->coex_gpio < 0 || !dw->coex_enabled)
+ return 0;
+
+ trace_dw3000_coex_gpio_stop(dw, dw->coex_status);
+ if (!dw->coex_status)
+ return 0;
+ /* Reset coex GPIO on chip */
+ return dw3000_coex_gpio(dw, false, 0);
+}
+
+/**
+ * dw3000_coex_init - Initialise WiFi coex gpio
+ * @dw: the DW device
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_coex_init(struct dw3000 *dw)
+{
+ int rc;
+
+ if (dw->coex_gpio < 0)
+ return 0;
+ /* Sanity check chip dependent functions */
+ if (!dw->chip_ops || !dw->chip_ops->coex_gpio ||
+ !dw->chip_ops->coex_init)
+ return -ENOTSUPP;
+ /* Call chip dependent initialisation */
+ rc = dw->chip_ops->coex_init(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev,
+ "WiFi coexistence configuration has failed (%d)\n", rc);
+ }
+ dw->coex_status = false;
+ return rc;
+}
+
+#endif /* __DW3000_COEX_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_compat_reg.h b/kernel/drivers/net/ieee802154/dw3000_compat_reg.h
new file mode 100644
index 0000000..ae50102
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_compat_reg.h
@@ -0,0 +1,205 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_COMPAT_REG_H
+#define __DW3000_COMPAT_REG_H
+
+#define MAX_CHIP_VERSIONS 3
+
+#ifdef DEFINE_COMPAT_REGISTERS
+#define REG_TYPE unsigned int
+#define REG_ADDR(n, ...) REG_TYPE n[MAX_CHIP_VERSIONS] = { __VA_ARGS__ }
+#else
+#define REG_TYPE extern unsigned int
+#define REG_ADDR(n, ...) REG_TYPE n[MAX_CHIP_VERSIONS]
+#endif
+#define REG(n) n[__dw3000_chip_version]
+
+REG_TYPE __dw3000_chip_version;
+
+REG_ADDR(__tx_fctrl_id, 0x24, 0x20, 0x20);
+REG_ADDR(__tx_fctrl_hi_id, 0x28, 0x24, 0x24);
+REG_ADDR(__dx_time_id, 0x2c, 0x28, 0x28);
+REG_ADDR(__rx_ttcko_lo_id, 0x5c, 0x58, 0x58);
+REG_ADDR(__rx_ttcko_hi_id, 0x60, 0x5c, 0x5c);
+REG_ADDR(__rx_time_0_id, 0x64, 0x60, 0x60);
+REG_ADDR(__rx_time_1_id, 0x68, 0x64, 0x64);
+REG_ADDR(__rx_time_2_id, 0x6c, 0x68, 0x68);
+REG_ADDR(__rx_time_3_id, 0x70, 0x6c, 0x6c);
+REG_ADDR(__tx_time_lo_id, 0x74, 0x70, 0x70);
+REG_ADDR(__tx_time_hi_id, 0x78, 0x74, 0x74);
+REG_ADDR(__tx_time_2_id, 0x10000, 0x78, 0x78);
+REG_ADDR(__tx_antd_id, 0x10004, 0x7c, 0x7c);
+REG_ADDR(__ack_resp_id, 0x10008, 0x10000, 0x10000);
+REG_ADDR(__tx_power_id, 0x1000c, 0x10004, 0x10004);
+REG_ADDR(__chan_ctrl_id, 0x10014, 0x10008, 0x10008);
+REG_ADDR(__le_pend_01_id, 0x10018, 0x1000C, 0x1000C);
+REG_ADDR(__le_pend_23_id, 0x1001c, 0x10010, 0x10010);
+REG_ADDR(__spi_collision_status_id, 0x10020, 0x10014, 0x10014);
+REG_ADDR(__rdb_status_id, 0x10024, 0x10018, 0x10018);
+REG_ADDR(__rdb_diag_mode_id, 0x10028, 0x10020, 0x10020);
+REG_ADDR(__regmap_ver_id, 0x1002c, 0x10024, 0x10024);
+REG_ADDR(__sar_ctrl_sar_force_sel_bit_len, 3U, 4U, 4U);
+REG_ADDR(__sar_ctrl_sar_force_sel_bit_mask, 0x7000U, 0xf000U, 0xf000U);
+REG_ADDR(__nvm_cfg_gear_id_bit_offset, 11U, 12U, 12U);
+REG_ADDR(__nvm_cfg_gear_id_bit_mask, 0x1800U, 0x3000U, 0x3000U);
+REG_ADDR(__nvm_cfg_gear_kick_bit_offset, 10U, 0x11U, 0x11U);
+REG_ADDR(__nvm_cfg_gear_kick_bit_mask, 0x400U, 0x800U, 0x800U);
+REG_ADDR(__nvm_cfg_bias_kick_bit_offset, 8U, 10U, 10U);
+REG_ADDR(__nvm_cfg_bias_kick_bit_mask, 0x100U, 0x400U, 0x400U);
+REG_ADDR(__nvm_cfg_ldo_kick_bit_offset, 7U, 9U, 9U);
+REG_ADDR(__nvm_cfg_ldo_kick_bit_mask, 0x80U, 0x200U, 0x200U);
+REG_ADDR(__nvm_cfg_dgc_kick_bit_offset, 6U, 8U, 8U);
+REG_ADDR(__nvm_cfg_dgc_kick_bit_mask, 0x40U, 0x100U, 0x100U);
+REG_ADDR(__nvm_cfg_dgc_sel_bit_offset, 13U, 7U, 7U);
+REG_ADDR(__nvm_cfg_dgc_sel_bit_mask, 0x2000U, 0x80U, 0x80U);
+REG_ADDR(__nvm_cfg_nvm_pd_bit_offset, 9U, 6U, 6U);
+REG_ADDR(__nvm_cfg_nvm_pd_bit_mask, 0x200U, 0x40U, 0x40U);
+REG_ADDR(__ip_diag_1_ipchannelarea_bit_len, 17U, 19U, 19U);
+REG_ADDR(__ip_diag_1_ipchannelarea_bit_mask, 0x1ffffUL, 0x7ffffUL, 0x7ffffUL);
+REG_ADDR(__cy0_diag_1_cy0channelarea_bit_len, 16U, 19U, 19U);
+REG_ADDR(__cy0_diag_1_cy0channelarea_bit_mask, 0xffffU, 0x7ffffU, 0x7ffffU);
+REG_ADDR(__cy1_diag_1_cy1channelarea_bit_len, 16U, 19U, 19U);
+REG_ADDR(__cy1_diag_1_cy1channelarea_bit_mask, 0xffffU, 0x7ffffU, 0x7ffffU);
+REG_ADDR(__ip_config_hi_id, 0xe000e, 0xe0010, 0xe0010);
+REG_ADDR(__cy_config_lo_id, 0xe0012, 0xe0014, 0xe0014);
+REG_ADDR(__cy_config_hi_id, 0xe0016, 0xe0018, 0xe0018);
+REG_ADDR(__cia_coefficient_adjust_id, 0xe001a, 0xe001c, 0xe001c);
+REG_ADDR(__pgf_delay_comp_lo_id, 0xe001e, 0xe0020, 0xe0020);
+REG_ADDR(__pgf_delay_comp_hi_id, 0xe0022, 0xe0024, 0xe0024);
+REG_ADDR(__adc_mem_ptr_id, 0xf0020, 0xf0024, 0xf0024);
+REG_ADDR(__test_ctrl0_id, 0xf0024, 0xf0028, 0xf0028);
+REG_ADDR(__fcmd_status_id, 0xf003c, 0xf0040, 0xf0040);
+REG_ADDR(__test_logging_id, 0xf0040, 0xf0044, 0xf0044);
+REG_ADDR(__status_logging_id, 0xf0044, 0xf0048, 0xf0048);
+REG_ADDR(__ctr_dbg_id, 0xf0048, 0xf004C, 0xf004C);
+REG_ADDR(__led_ctrl_id, 0x110016, 0x110018, 0x110018);
+REG_ADDR(__rx_ppm_id, 0x11001a, 0x11001c, 0x11001c);
+REG_ADDR(__fosc_ctrl_id, 0x11001e, 0x110020, 0x110020);
+REG_ADDR(__bias_ctrl_id, 0x11001f, 0x110030, 0x110030);
+REG_ADDR(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_offset, 12U, 9U, 9U);
+REG_ADDR(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_mask, 0x3000U, 0x600U, 0x600U);
+/* LDO and BIAS tune kick */
+/* C0 : Writing to bit 7 and 8 */
+/* D0 OTP errata : only kick LDO not BIAS */
+/* E0 : only kick LDO bit 9 (Writing to bit 10 for BIAS and 9 for LDO) */
+REG_ADDR(__ldo_bias_kick, 0x180, 0x200, 0x600);
+
+/* E0 specific */
+REG_ADDR(__dgc_lut_0_cfg_id, 0x30038, 0x30038, 0x3002c);
+REG_ADDR(__dgc_lut_1_cfg_id, 0x3003c, 0x3003c, 0x30030);
+REG_ADDR(__dgc_lut_2_cfg_id, 0x30040, 0x30040, 0x30034);
+REG_ADDR(__dgc_lut_3_cfg_id, 0x30044, 0x30044, 0x30038);
+REG_ADDR(__dgc_lut_4_cfg_id, 0x30048, 0x30048, 0x3003c);
+REG_ADDR(__dgc_lut_5_cfg_id, 0x3004c, 0x3004c, 0x30040);
+REG_ADDR(__dgc_lut_6_cfg_id, 0x30050, 0x30050, 0x30044);
+REG_ADDR(__dgc_dbg_id, 0x30060, 0x30060, 0x30054);
+REG_ADDR(__cia_tdoa_0_tdoa_bit_len, 32U, 32U, 16U);
+REG_ADDR(__cia_tdoa_0_tdoa_bit_mask, 0xffffffffUL, 0xffffffffUL, 0xffffU);
+
+#define DW3000_TX_FCTRL_ID REG(__tx_fctrl_id)
+#define DW3000_TX_FCTRL_HI_ID REG(__tx_fctrl_hi_id)
+#define DW3000_DX_TIME_ID REG(__dx_time_id)
+#define DW3000_RX_TTCKO_LO_ID REG(__rx_ttcko_lo_id)
+#define DW3000_RX_TTCKO_HI_ID REG(__rx_ttcko_hi_id)
+#define DW3000_RX_TIME_0_ID REG(__rx_time_0_id)
+#define DW3000_RX_TIME_1_ID REG(__rx_time_1_id)
+#define DW3000_RX_TIME_2_ID REG(__rx_time_2_id)
+#define DW3000_RX_TIME_3_ID REG(__rx_time_3_id)
+#define DW3000_TX_TIME_LO_ID REG(__tx_time_lo_id)
+#define DW3000_TX_TIME_HI_ID REG(__tx_time_hi_id)
+#define DW3000_TX_TIME_2_ID REG(__tx_time_2_id)
+#define DW3000_TX_ANTD_ID REG(__tx_antd_id)
+#define DW3000_ACK_RESP_ID REG(__ack_resp_id)
+#define DW3000_TX_POWER_ID REG(__tx_power_id)
+#define DW3000_CHAN_CTRL_ID REG(__chan_ctrl_id)
+#define DW3000_LE_PEND_01_ID REG(__le_pend_01_id)
+#define DW3000_LE_PEND_23_ID REG(__le_pend_23_id)
+#define DW3000_SPI_COLLISION_STATUS_ID REG(__spi_collision_status_id)
+#define DW3000_RDB_STATUS_ID REG(__rdb_status_id)
+#define DW3000_RDB_DIAG_MODE_ID REG(__rdb_diag_mode_id)
+#define DW3000_REGMAP_VER_ID REG(__regmap_ver_id)
+#define DW3000_SAR_CTRL_SAR_FORCE_SEL_BIT_LEN \
+ REG(__sar_ctrl_sar_force_sel_bit_len)
+#define DW3000_SAR_CTRL_SAR_FORCE_SEL_BIT_MASK \
+ REG(__sar_ctrl_sar_force_sel_bit_mask)
+#define DW3000_NVM_CFG_GEAR_ID_BIT_OFFSET REG(__nvm_cfg_gear_id_bit_offset)
+#define DW3000_NVM_CFG_GEAR_ID_BIT_MASK REG(__nvm_cfg_gear_id_bit_mask)
+#define DW3000_NVM_CFG_GEAR_KICK_BIT_OFFSET REG(__nvm_cfg_gear_kick_bit_offset)
+#define DW3000_NVM_CFG_GEAR_KICK_BIT_MASK REG(__nvm_cfg_gear_kick_bit_mask)
+#define DW3000_NVM_CFG_BIAS_KICK_BIT_OFFSET REG(__nvm_cfg_bias_kick_bit_offset)
+#define DW3000_NVM_CFG_BIAS_KICK_BIT_MASK REG(__nvm_cfg_bias_kick_bit_mask)
+#define DW3000_NVM_CFG_LDO_KICK_BIT_OFFSET REG(__nvm_cfg_ldo_kick_bit_offset)
+#define DW3000_NVM_CFG_LDO_KICK_BIT_MASK REG(__nvm_cfg_ldo_kick_bit_mask)
+#define DW3000_NVM_CFG_DGC_KICK_BIT_OFFSET REG(__nvm_cfg_dgc_kick_bit_offset)
+#define DW3000_NVM_CFG_DGC_KICK_BIT_MASK REG(__nvm_cfg_dgc_kick_bit_mask)
+#define DW3000_NVM_CFG_DGC_SEL_BIT_OFFSET REG(__nvm_cfg_dgc_sel_bit_offset)
+#define DW3000_NVM_CFG_DGC_SEL_BIT_MASK REG(__nvm_cfg_dgc_sel_bit_mask)
+#define DW3000_NVM_CFG_NVM_PD_BIT_OFFSET REG(__nvm_cfg_nvm_pd_bit_offset)
+#define DW3000_NVM_CFG_NVM_PD_BIT_MASK REG(__nvm_cfg_nvm_pd_bit_mask)
+#define DW3000_IP_DIAG_1_IPCHANNELAREA_BIT_LEN \
+ REG(__ip_diag_1_ipchannelarea_bit_len)
+#define DW3000_IP_DIAG_1_IPCHANNELAREA_BIT_MASK \
+ REG(__ip_diag_1_ipchannelarea_bit_len)
+#define DW3000_CY0_DIAG_1_CY0CHANNELAREA_BIT_LEN \
+ REG(__cy0_diag_1_cy0channelarea_bit_len)
+#define DW3000_CY0_DIAG_1_CY0CHANNELAREA_BIT_MASK \
+ REG(__cy0_diag_1_cy0channelarea_bit_mask)
+#define DW3000_CY1_DIAG_1_CY1CHANNELAREA_BIT_LEN \
+ REG(__cy1_diag_1_cy1channelarea_bit_len)
+#define DW3000_CY1_DIAG_1_CY1CHANNELAREA_BIT_MASK \
+ REG(__cy1_diag_1_cy1channelarea_bit_mask)
+#define DW3000_IP_CONFIG_HI_ID REG(__ip_config_hi_id)
+#define DW3000_CY_CONFIG_LO_ID REG(__cy_config_lo_id)
+#define DW3000_CY_CONFIG_HI_ID REG(__cy_config_hi_id)
+#define DW3000_CIA_COEFFICIENT_ADJUST_ID REG(__cia_coefficient_adjust_id)
+#define DW3000_PGF_DELAY_COMP_LO_ID REG(__pgf_delay_comp_lo_id)
+#define DW3000_PGF_DELAY_COMP_HI_ID REG(__pgf_delay_comp_hi_id)
+#define DW3000_ADC_MEM_PTR_ID REG(__adc_mem_ptr_id)
+#define DW3000_TEST_CTRL0_ID REG(__test_ctrl0_id)
+#define DW3000_FCMD_STATUS_ID REG(__fcmd_status_id)
+#define DW3000_TEST_LOGGING_ID REG(__test_logging_id)
+#define DW3000_STATUS_LOGGING_ID REG(__status_logging_id)
+#define DW3000_CTR_DBG_ID REG(__ctr_dbg_id)
+#define DW3000_LED_CTRL_ID REG(__led_ctrl_id)
+#define DW3000_RX_PPM_ID REG(__rx_ppm_id)
+#define DW3000_FOSC_CTRL_ID REG(__fosc_ctrl_id)
+#define DW3000_BIAS_CTRL_ID REG(__bias_ctrl_id)
+#define DW3000_BIAS_CTRL_DIG_BIAS_CTRL_TC_R3_ULV_BIT_OFFSET \
+ REG(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_offset)
+#define DW3000_BIAS_CTRL_DIG_BIAS_CTRL_TC_R3_ULV_BIT_MASK \
+ REG(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_mask)
+#define DW3000_LDO_BIAS_KICK REG(__ldo_bias_kick)
+
+/* E0 specific */
+#define DW3000_DGC_LUT_0_CFG_ID REG(__dgc_lut_0_cfg_id)
+#define DW3000_DGC_LUT_1_CFG_ID REG(__dgc_lut_1_cfg_id)
+#define DW3000_DGC_LUT_2_CFG_ID REG(__dgc_lut_2_cfg_id)
+#define DW3000_DGC_LUT_3_CFG_ID REG(__dgc_lut_3_cfg_id)
+#define DW3000_DGC_LUT_4_CFG_ID REG(__dgc_lut_4_cfg_id)
+#define DW3000_DGC_LUT_5_CFG_ID REG(__dgc_lut_5_cfg_id)
+#define DW3000_DGC_LUT_6_CFG_ID REG(__dgc_lut_6_cfg_id)
+#define DW3000_DGC_DBG_ID REG(__dgc_dbg_id)
+#define DW3000_CIA_TDOA_0_TDOA_BIT_LEN REG(__cia_tdoa_0_tdoa_bit_len)
+#define DW3000_CIA_TDOA_0_TDOA_BIT_MASK REG(__cia_tdoa_0_tdoa_bit_mask)
+
+#endif /* __DW3000_COMPAT_REG_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_core.c b/kernel/drivers/net/ieee802154/dw3000_core.c
new file mode 100644
index 0000000..7e8e2cb
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_core.c
@@ -0,0 +1,8404 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/bitfield.h>
+#include <linux/log2.h>
+
+#include "dw3000.h"
+#include "dw3000_core.h"
+#define DEFINE_COMPAT_REGISTERS
+#include "dw3000_core_reg.h"
+#include "dw3000_cir.h"
+#include "dw3000_stm.h"
+#include "dw3000_trc.h"
+#include "dw3000_perf.h"
+#include "dw3000_pm.h"
+#include "dw3000_coex.h"
+#include "dw3000_nfcc_coex_core.h"
+#include "dw3000_txpower_adjustment.h"
+#include "dw3000_power_stats.h"
+
+/* Table of supported chip version and associated chip operations */
+const struct dw3000_chip_version dw3000_chip_versions[] = {
+ { .id = DW3000_C0_DEV_ID,
+ .ver = DW3000_C0_VERSION,
+ .ops = &dw3000_chip_c0_ops,
+ .name = "C0" },
+ { .id = DW3000_C0_PDOA_DEV_ID,
+ .ver = DW3000_C0_VERSION,
+ .ops = &dw3000_chip_c0_ops,
+ .name = "C0" },
+ { .id = DW3000_D0_DEV_ID,
+ .ver = DW3000_D0_VERSION,
+ .ops = &dw3000_chip_d0_ops,
+ .name = "D0" },
+ { .id = DW3000_D0_PDOA_DEV_ID,
+ .ver = DW3000_D0_VERSION,
+ .ops = &dw3000_chip_d0_ops,
+ .name = "D0" },
+ { .id = DW3000_E0_PDOA_DEV_ID,
+ .ver = DW3000_E0_VERSION,
+ .ops = &dw3000_chip_e0_ops,
+ .name = "E0" },
+};
+
+/* DW3000 hard reset delay (us) */
+#define DW3000_HARD_RESET_DELAY_US 10000
+
+/* DW3000 soft reset delay (us) */
+#define DW3000_SOFT_RESET_DELAY_US 1000
+
+/* DW3000 pgf calibration delay (us) */
+#define DW3000_PGFCAL_DELAY_US 1000
+
+/* The pin operates as the EXTTXE output (C0, output TX state) */
+#define DW3000_GPIO_PIN0_EXTTXE \
+ (((u32)0x2) << DW3000_GPIO_MODE_MSGP0_MODE_BIT_OFFSET)
+/* The pin operates as the EXTRXE output (C0, output RX state) */
+#define DW3000_GPIO_PIN1_EXTRXE \
+ (((u32)0x2) << DW3000_GPIO_MODE_MSGP1_MODE_BIT_OFFSET)
+
+/* The pin operates as the RXLED output (C0 & D0) */
+#define DW3000_GPIO_PIN2_RXLED \
+ (((u32)0x1) << DW3000_GPIO_MODE_MSGP2_MODE_BIT_OFFSET)
+/* The pin operates as the TXLED output (C0 & D0) */
+#define DW3000_GPIO_PIN3_TXLED \
+ (((u32)0x1) << DW3000_GPIO_MODE_MSGP3_MODE_BIT_OFFSET)
+
+/* The pin operates as the EXTTXE output (D0, output TX state) */
+#define DW3000_GPIO_PIN4_EXTTXE \
+ (((u32)0x2) << DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET)
+/* The pin operates as the EXTRXE output (D0, output RX state) */
+#define DW3000_GPIO_PIN5_EXTRXE \
+ (((u32)0x2) << DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET)
+
+/* The pin operates to support external DA/PA (C0) */
+#define DW3000_GPIO_PIN4_EXTDA \
+ (((u32)0x1) << DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET)
+/* The pin operates to support external PA (C0) */
+#define DW3000_GPIO_PIN5_EXTTX \
+ (((u32)0x1) << DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET)
+/* The pin operates to support external LNA (C0) */
+#define DW3000_GPIO_PIN6_EXTRX \
+ (((u32)0x1) << DW3000_GPIO_MODE_MSGP6_MODE_BIT_OFFSET)
+
+/* Defined constants for "mode" bit field parameter passed to dw3000_set_leds()
+ function. */
+#define DW3000_LEDS_DISABLE 0x00
+#define DW3000_LEDS_ENABLE 0x01
+#define DW3000_LEDS_INIT_BLINK 0x02
+/* Default blink time. Blink time is expressed in multiples of 14 ms.
+ The value defined here is ~225 ms. */
+#define DW3000_LEDS_BLINK_TIME_DEF 0x10
+
+/* Defined constants for "lna_pa" bit field parameter passed to
+ dw3000_set_lna_pa_mode() function */
+#define DW3000_LNA_PA_DISABLE 0x00
+#define DW3000_LNA_ENABLE 0x01
+#define DW3000_PA_ENABLE 0x02
+#define DW3000_TXRX_ENABLE 0x04
+
+/* Duration of the negative pulse of the SPI CS signal
+ to wake up the chip in microseconds */
+#define DW3000_SPI_CS_WAKEUP_DELAY_US 500
+
+/* DW3000 double buffered receiver mode */
+#define DW3000_DBL_BUFF_OFF 0x0
+#define DW3000_DBL_BUFF_SWAP 0x2
+#define DW3000_DBL_BUFF_ACCESS_BUFFER_A 0x1
+#define DW3000_DBL_BUFF_ACCESS_BUFFER_B 0x3
+
+#define DW3000_SFDTOC_DEF 129 /* default SFD timeout value */
+
+/* DW3000 OTP operating parameter set selection */
+#define DW3000_OPSET_LONG (0x0 << 11)
+#define DW3000_OPSET_SCP (0x1 << 11)
+#define DW3000_OPSET_SHORT (0x2 << 11)
+
+#define DW3000_RX_FINFO_STD_RXFLEN_MASK \
+ 0x0000007FUL /* Receive Frame Length (0 to 127) */
+
+/* Receive frame information register */
+#define DW3000_RX_FINFO_RXFLEN(val) (((val) >> 0) & 0x7ff)
+#define DW3000_RX_FINFO_RXNSPL(val) (((val) >> 11) & 0x3)
+#define DW3000_RX_FINFO_RXPSR(val) (((val) >> 18) & 0x3)
+#define DW3000_RX_FINFO_RXPACC(val) (((val) >> 20) & 0xfff)
+
+/* RX events mask relating to reception into RX buffer A & B, when double
+ buffer is used */
+#define DW3000_RDB_STATUS_CLEAR_BUFF0_EVENTS \
+ ((u8)0xf << DW3000_RDB_STATUS_RXFCG0_BIT_OFFSET)
+#define DW3000_RDB_STATUS_CLEAR_BUFF1_EVENTS \
+ ((u8)0xf << DW3000_RDB_STATUS_RXFCG1_BIT_OFFSET)
+
+#define DW3000_RDB_STATUS_RXOK \
+ (DW3000_RDB_STATUS_RXFR0_BIT_MASK | \
+ DW3000_RDB_STATUS_RXFCG0_BIT_MASK | \
+ DW3000_RDB_STATUS_RXFR1_BIT_MASK | DW3000_RDB_STATUS_RXFCG1_BIT_MASK)
+
+#define DW3000_SYS_STATUS_TX (DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK)
+#define DW3000_SYS_STATUS_RX \
+ (DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_MASK | \
+ DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_MASK)
+#define DW3000_SYS_STATUS_TRX (DW3000_SYS_STATUS_TX | DW3000_SYS_STATUS_RX)
+
+#define DW3000_RX_BUFFER_MAX_LEN (1023)
+#define DW3000_TX_BUFFER_MAX_LEN (1024)
+#define DW3000_REG_DIRECT_OFFSET_MAX_LEN (127)
+
+#define DW3000_CONFIG \
+ 0x0001 /* download the AON array into the HIF
+ (configuration download) */
+
+/* Enable/disable TX fine grain power sequencing */
+#define DW3000_PMSC_TXFINESEQ_ENABLE 0x4d28874
+#define DW3000_PMSC_TXFINESEQ_DISABLE 0x0d20874
+/* TXRX switch control register's configurations */
+#define DW3000_TXRXSWITCH_TX 0x01011100
+#define DW3000_TXRXSWITCH_AUTO 0x1C000000
+
+#define DW3000_RF_TXCTRL_CH5 0x1C081134UL
+#define DW3000_RF_TXCTRL_CH9 0x1C020034UL
+#define DW3000_RF_TXCTRL_LO_B2 0x0E
+#define DW3000_RF_PLL_CFG_CH5 0x1F3C
+#define DW3000_RF_PLL_CFG_CH9 0x0F3C
+#define DW3000_RF_PLL_CFG_LD 0x81
+#define DW3000_LDO_RLOAD_VAL_B1 0x14
+
+/* PD threshold for no data STS mode */
+#define DW3000_PD_THRESH_NO_DATA 0xAF5F35CC
+#define DW3000_PD_THRESH_DEFAULT 0xAF5F584C
+
+#define DW3000_NUM_DW_DEV (1)
+
+#define DW3000_SPI_FAC (0 << 6 | 1 << 0)
+#define DW3000_SPI_FARW (0 << 6 | 0 << 0)
+#define DW3000_SPI_EAMRW (1 << 6)
+
+/* Power management control's SYSCLK field */
+#define DW3000_FORCE_SYSCLK_FOSCDIV4 (1)
+#define DW3000_FORCE_SYSCLK_PLL (2)
+#define DW3000_FORCE_SYSCLK_FOSC (3)
+/* RX and TX CLK field */
+#define DW3000_FORCE_CLK_PLL (2)
+
+/* Defines for dw3000_force_clocks() function */
+#define DW3000_FORCE_CLK_SYS_TX (1)
+#define DW3000_FORCE_CLK_AUTO (5)
+
+#define DW3000_STD_FRAME_LEN (127)
+#define DW3000_EXT_FRAME_LEN (1023)
+
+/* CIA lower bound threshold values for 64 MHz PRF */
+#define DW3000_CIA_MANUALLOWERBOUND_TH_64 (0x10)
+
+/**
+ * DW3000_STSQUAL_THRESH_64() - get STS quality thereshold value for 64 MHz PRF
+ * @x: STS length in unit of 8-symbols block
+ *
+ * When using 64 MHz PRF the stsCpQual should be > 60 % of STS length
+ */
+#define DW3000_STSQUAL_THRESH_64(x) (((x)*8) * 6 / 10)
+/* Factor of sqrt(2) for calculation */
+#define DW3000_SQRT2_FACTOR 181
+#define DW3000_SQRT2_SHIFT_VAL 7
+#define DW3000_STS_MNTH_SHIFT 11
+#define DW3000_STS_MNTH_ROUND_SHIFT 1024
+/* The supported STS length options */
+#define DW3000_STS_LEN_SUPPORTED 9
+
+static const u16 dw3000_sts_length_factors[DW3000_STS_LEN_SUPPORTED] = {
+ 0, 0, 1024, 1448, 2048, 2896, 4096, 5793, 8192
+};
+
+#define DW3000_STS_ACC_CP_QUAL_SIGNTST 0x0800 /* sign test */
+#define DW3000_STS_ACC_CP_QUAL_SIGNEXT \
+ 0xF000 /* 12 bit to 16 bit sign extension */
+
+/**
+ * DW3000_GET_STS_LEN_REG_VALUE() - Convert STS length enum into register value
+ * @x: value from enum dw3000_sts_lengths
+ *
+ * Return: the value to set in CP_CFG0_ID for STS length.
+ */
+#define DW3000_GET_STS_LEN_REG_VALUE(x) ((u16)((1 << (x)) - 1))
+
+/* Delay in symbol used for auto-ack.
+ * The IEEE 802.15.4 standard specifies a 12 symbol +/- 0.5 symbols
+ * turnaroud time for ACK transmission. */
+#define DW3000_NUMBER_OF_SYMBOL_DELAY_AUTO_ACK (12)
+
+/* The PLL calibration should take less than 400us.
+ * Typically it is < 100us
+ * (however on some corners with ch9 it can take ~900us)
+ */
+#define DW3000_MAX_RETRIES_FOR_PLL (50)
+
+/* The empirical delay to lock the PLL after its activation */
+#define DW3000_PLL_LOCK_DELAY_US (10)
+
+/* SYS_STATE_LO register errors */
+/* TSE is in TX but TX is in IDLE in SYS_STATE_LO register */
+#define DW3000_SYS_STATE_TXERR 0xD0000
+/* TSE is in IDLE (IDLE_PLL) */
+#define DW3000_SYS_STATE_IDLE 0x3
+
+/**
+ * enum ciadiag_dbl_options - Enable CIA diagnostic's data log level options.
+ * @DW3000_CIA_DIAG_LOG_DBL_OFF:
+ * No CIA diagnostic option
+ * @DW3000_CIA_DIAG_LOG_DBL_MIN:
+ * CIA to copy to swinging set a minimal set of diagnostic registers
+ * in Double Buffer mode.
+ * @DW3000_CIA_DIAG_LOG_DBL_MID:
+ * CIA to copy to swinging set a medium set of diagnostic registers
+ * in Double Buffer mode.
+ * @DW3000_CIA_DIAG_LOG_DBL_MAX:
+ * CIA to copy to swinging set a maximum set of diagnostic registers
+ * in Double Buffer mode.
+ */
+enum ciadiag_dbl_options {
+ DW3000_CIA_DIAG_LOG_DBL_OFF = 0,
+ DW3000_CIA_DIAG_LOG_DBL_MIN = BIT(0),
+ DW3000_CIA_DIAG_LOG_DBL_MID = BIT(1),
+ DW3000_CIA_DIAG_LOG_DBL_MAX = BIT(2)
+};
+
+/* Disable CIA diagnostic. CIACONFIG's bit-4 in RX_ANTENNA_DELAY + 1 */
+#define DW3000_CIA_CONFIG_DIAG_OFF (0x1 << 4)
+
+/* LDO VOUT value */
+#define DW3000_RF_LDO_VOUT 0x0D7FFFFFUL
+/* RF SWITCH RX RF2 */
+#define DW3000_RF_SWITCH_RX_RF2 0x2131
+
+/* PLL common value */
+#define DW3000_RF_PLL_COMMON 0xE104
+
+struct dw3000_ciadiag_reg_info {
+ u32 diag1;
+ u32 diag12;
+};
+
+/* CIA diagnostic register selection according DW3000's configuration */
+static const struct dw3000_ciadiag_reg_info _ciadiag_reg_info[] = {
+ /* Without STS */
+ {
+ .diag1 = DW3000_IP_DIAG1_ID,
+ .diag12 = DW3000_IP_DIAG12_ID,
+ },
+ /* With STS */
+ {
+ .diag1 = DW3000_CP0_DIAG1_ID,
+ .diag12 = DW3000_CP0_DIAG12_ID,
+ },
+ /* PDOA Mode 3 */
+ {
+ .diag1 = DW3000_CP1_DIAG1_ID,
+ .diag12 = DW3000_CP1_DIAG12_ID,
+ },
+};
+
+static int dw3000_wakeup_done_to_tx(struct dw3000 *dw);
+static int dw3000_wakeup_done_to_rx(struct dw3000 *dw);
+/* sysfs variables handling */
+static ssize_t dw3000_sysfs_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf);
+static ssize_t dw3000_sysfs_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf,
+ size_t length);
+const struct kobj_attribute dw3000_attribute =
+ __ATTR(power_stats, 0664, dw3000_sysfs_show, dw3000_sysfs_store);
+
+/* Interrupt working options */
+enum int_options {
+ DW3000_DISABLE_INT = 0,
+ DW3000_ENABLE_INT,
+ DW3000_ENABLE_INT_ONLY
+};
+
+/* Indexes are DWT_PLEN_NNNN values - 1 */
+const struct dw3000_plen_info _plen_info[] = {
+ {
+ .symb = 64,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_64,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 1024,
+ .pac_symb = 32,
+ .dw_reg = DW3000_PLEN_1024,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 4096,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_4096,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 32,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_32,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 128,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_128,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 1536,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_1536,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 72,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_72,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ .symb = 256,
+ .pac_symb = 16,
+ .dw_reg = DW3000_PLEN_256,
+ .dw_pac_reg = DW3000_PAC16,
+ },
+ {
+ .symb = 2048,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_2048,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ .symb = 512,
+ .pac_symb = 16,
+ .dw_reg = DW3000_PLEN_512,
+ .dw_pac_reg = DW3000_PAC16,
+ },
+};
+
+/* Chip per symbol for 850kbps (512) and 6.8Mbps (64) */
+const int _chip_per_symbol_info[2] = { 512, 64 };
+
+const struct dw3000_prf_info _prf_info[] = {
+ {
+ /* Invalid PRF */
+ 0,
+ },
+ {
+ /* 16 MHz */
+ .chip_per_symb = 496,
+ },
+ {
+ /* 64 MHz */
+ .chip_per_symb = 508,
+ },
+};
+
+static int dw3000_transfers_reset(struct dw3000 *dw);
+static int dw3000_change_speed(struct dw3000 *dw, u32 new_speed);
+static int dw3000_wakeup(struct dw3000 *dw);
+static inline bool _dw3000_sts_is_enabled(struct dw3000 *dw);
+
+/**
+ * dw3000_get_dtu_time() - compute current DTU time
+ * @dw: the DW device
+ *
+ * This function compute the current DTU time, based on ktime,
+ * at 15.6MHz rate.
+ *
+ * Return: the current DTU time
+ */
+u32 dw3000_get_dtu_time(struct dw3000 *dw)
+{
+ s64 bt_ns = ktime_get_boottime_ns();
+ return dw3000_ktime_to_dtu(dw, bt_ns);
+}
+
+/**
+ * dw3000_resync_dtu_sys_time() - resync DTU time and SYS_TIME
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_resync_dtu_sys_time(struct dw3000 *dw)
+{
+ int rc;
+ /* Read SYS_TIME */
+ rc = dw3000_read_sys_time(dw, &dw->sys_time_sync);
+ if (rc)
+ dw->sys_time_sync = 1000; /* TODO: check value */
+ /* Save synchronisation time DTU */
+ dw->dtu_sync = dw3000_get_dtu_time(dw);
+ trace_dw3000_resync_dtu_sys_time(dw, dw->sys_time_sync, dw->dtu_sync);
+ /* Invalidate RCTU synchronisation */
+ dw3000_resync_rctu_conv_state(dw);
+ return rc;
+}
+
+/**
+ * dw3000_may_resync() - check if a resync is needed, if yes, do it
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_may_resync(struct dw3000 *dw)
+{
+ int rc = 0;
+ u32 now_dtu;
+
+ now_dtu = dw3000_get_dtu_time(dw);
+ if (now_dtu - dw->dtu_sync > DW3000_DTU_FREQ)
+ rc = dw3000_resync_dtu_sys_time(dw);
+ return rc;
+}
+
+/**
+ * dw3000_alloc_xfer() - Allocate a new spi_message including spi_tranfer.
+ * @trcount: the number of transfer to allocate with the new spi_message
+ * @len: the length of TX buffer to allocate for the first transfer
+ *
+ * Return: the spi_message struct or NULL if error.
+ */
+static inline struct spi_message *dw3000_alloc_xfer(unsigned int trcount,
+ unsigned int len)
+{
+ struct spi_message *msg = spi_message_alloc(trcount, GFP_KERNEL);
+
+#if ((KERNEL_VERSION(4, 12, 0) > LINUX_VERSION_CODE) && \
+ (KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE))
+ /* Initialize spi_message resources list to avoid null pointer
+ * dereference on some SPI controllers to fix a bug in spi_message_alloc()
+ * appeared in 4.6.0 and fixed in 4.12.0. */
+ if (msg)
+ INIT_LIST_HEAD(&msg->resources);
+#endif
+
+ if (msg && len) {
+ struct spi_transfer *transfer = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ transfer->tx_buf = kzalloc(len, GFP_KERNEL | GFP_DMA);
+ if (!transfer->tx_buf) {
+ /* failure to allocate requested buffer for header */
+ spi_message_free(msg);
+ msg = NULL;
+ } else {
+ transfer->len = len;
+ }
+ }
+ return msg;
+}
+
+/**
+ * dw3000_free_xfer() - Free an spi_message allocated by @dw3000_alloc_xfer
+ * @msg: the SPI message to free
+ * @len: the same length of TX buffer to automatically free it
+ */
+static inline void dw3000_free_xfer(struct spi_message *msg, unsigned int len)
+{
+ if (!msg)
+ return;
+ if (len) {
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ if (tr->rx_buf && tr->rx_buf != tr->tx_buf)
+ kfree(tr->rx_buf);
+ kfree(tr->tx_buf);
+ }
+ spi_message_free(msg);
+}
+
+/**
+ * dw3000_prepare_xfer() - Initialise an spi_message allocated by @dw3000_alloc_xfer
+ * @msg: the SPI message to initialise
+ * @reg_fileid: the register fileID to read/write
+ * @index: the index where to read/write
+ * @length: the length of provided buffer and SPI data transfer
+ * @buffer: the address where to read/write data
+ * @mode: operation mode, ie. RW, WR, AND_OR8, etc.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_prepare_xfer(struct spi_message *msg, u32 reg_fileid,
+ u16 index, u16 length, void *buffer,
+ enum spi_modes mode)
+{
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ u8 *header_buf = (u8 *)tr->tx_buf;
+ u16 header_len;
+
+ /* Extract register file and sub-address (+ offset) */
+ u16 reg_file = 0x1F & ((reg_fileid + index) >> 16);
+ u16 reg_offset = 0x7F & (reg_fileid + index);
+
+ /* Fast command not supported by this function */
+ if (unlikely(length == 0 && mode == DW3000_SPI_WR_BIT))
+ return -EINVAL;
+
+ /* Set header buffer */
+ if (reg_offset || (mode & DW3000_SPI_AND_OR_MSK)) {
+ /* 2-byte header */
+ u16 param = (reg_file << 9) | (reg_offset << 2) | mode;
+
+ header_buf[0] = (u8)(param >> 8) | DW3000_SPI_EAMRW;
+ header_buf[1] = (u8)param;
+ header_len = 2;
+ } else {
+ /* 1-byte header */
+ u8 param = reg_file << 1 | mode >> 8;
+
+ header_buf[0] = (u8)param | DW3000_SPI_FARW;
+ header_len = 1;
+ }
+ /* Adjust header len in the SPI message */
+ if (unlikely(header_len > tr->len))
+ return -EINVAL;
+ tr->len = header_len;
+
+ /* Set the data buffer in second transfer */
+ if (likely(!buffer)) {
+ /* Single spi_tranfer messages are used for full-fuplex register
+ read/write. Just update the transfer length.
+ The rx_buf is already set dw3000_alloc_prepare_xfer(). */
+ tr->len += length;
+ } else {
+ struct spi_transfer *tr2;
+
+ tr2 = list_next_entry(tr, transfer_list);
+ if (mode == DW3000_SPI_RD_BIT) {
+ /* RX transaction */
+ tr2->rx_buf = buffer;
+ } else {
+ /* TX transaction */
+ tr2->tx_buf = buffer;
+ }
+ /* Add data to the SPI message */
+ tr2->len = length;
+ }
+ return 0;
+}
+
+/**
+ * dw3000_alloc_prepare_xfer() - Allocate and prepare an spi_message
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read/write
+ * @index: the index where to read/write
+ * @length: expected length of data to read/write
+ * @mode: operation mode, ie. RW, WR, AND_OR8, etc.
+ *
+ * The prepared spi_message will have only one spi_transfer with enough space
+ * for read/write 16 bytes at least in full-duplex mode (including header).
+ *
+ * Return: the spi_message struct or NULL if error.
+ */
+static struct spi_message *dw3000_alloc_prepare_xfer(struct dw3000 *dw,
+ u32 reg_fileid, u16 index,
+ u16 length,
+ enum spi_modes mode)
+{
+ struct spi_message *msg;
+ int len = length < 16 ? 16 : length;
+ int rc;
+
+ /* Allocate a new SPI message with one transfer. */
+ msg = dw3000_alloc_xfer(1, len);
+ if (!msg)
+ goto err_alloc;
+ /* Prepare this message for given register access */
+ rc = dw3000_prepare_xfer(msg, reg_fileid, index, length, NULL, mode);
+ if (rc)
+ goto err_prepare;
+ /* Need to have a separated TX/RX buffer because initialised TX
+ buffer will be clobbered during first exchange if RX buffer
+ is the same. rx_buf = tx_buf is only right if message is used
+ once, or is re-initialised before each transfer like in
+ dw3000_reg_read_fast(). */
+ if (mode == DW3000_SPI_RD_BIT) {
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ tr->rx_buf = kzalloc(len, GFP_KERNEL | GFP_DMA);
+ if (!tr->rx_buf)
+ goto err_rxalloc;
+ }
+ return msg;
+
+err_rxalloc:
+err_prepare:
+ dw3000_free_xfer(msg, len);
+err_alloc:
+ dev_err(dw->dev,
+ "Failure to allocate message for reg 0x%x (index %u, len %d, mode %d)\n",
+ reg_fileid, index, length, mode);
+ return NULL;
+}
+
+/**
+ * dw3000_alloc_prepare_fastcmd() - Allocate and prepare a fastcmd spi_message
+ *
+ * Return: the spi_message struct or NULL if error.
+ */
+static struct spi_message *dw3000_alloc_prepare_fastcmd(void)
+{
+ /* Allocate a new SPI message with only one transfer. */
+ return dw3000_alloc_xfer(1, 1);
+}
+
+/**
+ * dw3000_free_fastcmd() - Free a fastcmd spi_message
+ * @msg: the SPI message to free
+ */
+static void dw3000_free_fastcmd(struct spi_message *msg)
+{
+ /* Free SPI message and tx_buf of included transfer */
+ dw3000_free_xfer(msg, 1);
+}
+
+static inline int dw3000_spi_sync(struct dw3000 *dw, struct spi_message *msg);
+
+/**
+ * dw3000_spi_queue_start() - Prepare SPI messages queue
+ * @dw: the DW device on which the SPI transfer will occurs
+ *
+ * Reset and initialise SPI messages queue to record next transfers until
+ * dw3000_spi_queue_flush() is called.
+ */
+void dw3000_spi_queue_start(struct dw3000 *dw)
+{
+#ifdef CONFIG_DW3000_SPI_OPTIMIZATION
+ struct spi_message *msg = dw->msg_queue;
+
+ spi_message_init(msg);
+ dw->msg_queue_xfer = (struct spi_transfer *)(msg + 1);
+ dw->msg_queue_xfer_count = 0;
+ dw->msg_queue_buf_pos = dw->msg_queue_buf;
+#endif
+}
+
+/**
+ * dw3000_spi_queue_msg() - Add one SPI message to messages queue
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @msg: the SPI message to add to SPI message queue
+ *
+ * All transfers of the provided SPI message are added to the local SPI messages
+ * queue. TX only transfer can be queued, so message queue must be flushed
+ * before any RX transfer is required.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_spi_queue_msg(struct dw3000 *dw,
+ struct spi_message *msg)
+{
+ struct spi_transfer *dxfer = dw->msg_queue_xfer;
+ struct spi_transfer *xfer;
+
+ if (dxfer == NULL)
+ return -ENOBUFS;
+ list_for_each_entry (xfer, &msg->transfers, transfer_list) {
+ /* Don't support queuing RX transfer */
+ if (xfer->rx_buf)
+ return -EINVAL;
+ if ((dw->msg_queue_buf_pos + xfer->len) >=
+ (dw->msg_queue_buf + DW3000_QUEUED_SPI_BUFFER_SZ))
+ return -EMSGSIZE;
+ /* Copy this message transfer to next empty transfer in queue */
+ memcpy(dw->msg_queue_buf_pos, xfer->tx_buf, xfer->len);
+ memset(dxfer, 0, sizeof *dxfer);
+ dxfer->len = xfer->len;
+ dxfer->tx_buf = dw->msg_queue_buf_pos;
+ if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
+ dxfer->cs_change = true;
+#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE)
+ dxfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+ dxfer->cs_change_delay.value = 0;
+#endif
+ }
+ /* add transfer to queue */
+ spi_message_add_tail(dxfer, dw->msg_queue);
+ dw->msg_queue_xfer_count++;
+ /* Prepare next */
+ dw->msg_queue_buf_pos += dxfer->len;
+ if (dw->msg_queue_xfer_count >= DW3000_MAX_QUEUED_SPI_XFER) {
+ dxfer = NULL;
+ break;
+ }
+ dxfer++;
+ }
+ /* Save new dest xfer for next call */
+ dw->msg_queue_xfer = dxfer;
+ if (!dxfer && !list_is_last(&xfer->transfer_list, &msg->transfers))
+ return -ENOBUFS;
+ return 0;
+}
+
+/**
+ * dw3000_spi_queue_reset() - Reset message queue
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @rc: the return value given by caller to return after reset
+ *
+ * Reset the SPI messages queue and return the provided error code.
+ * This must be called when an error happen while SPI message are
+ * queued to ensure queuing mode is disabled.
+ *
+ * This revert dw3000_spi_sync() to immediate transaction mode.
+ *
+ * Return: the rc given value
+ */
+int dw3000_spi_queue_reset(struct dw3000 *dw, int rc)
+{
+#ifdef CONFIG_DW3000_SPI_OPTIMIZATION
+ dw->msg_queue_xfer = NULL;
+ dw->msg_queue_xfer_count = 0;
+#endif
+ return rc;
+}
+
+/**
+ * dw3000_spi_queue_flush() - Flush messages queue to dw3000_spi_sync()
+ * @dw: the DW device on which the SPI transfer will occurs
+ *
+ * Flush all queued transfers in SPI message queue by calling the
+ * dw3000_spi_sync() again with the SPI messages queue.
+ *
+ * This revert dw3000_spi_sync() to immediate transaction mode.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_spi_queue_flush(struct dw3000 *dw)
+{
+#ifdef CONFIG_DW3000_SPI_OPTIMIZATION
+ struct spi_message *msg = dw->msg_queue;
+ struct spi_transfer *xfer;
+ int rc;
+
+ /* If nothing queued, nothing to do. Just reset to no-queuing mode. */
+ if (!dw->msg_queue_xfer_count)
+ return dw3000_spi_queue_reset(dw, 0);
+ /* Ensure last xfer don't have cs_change flag */
+ xfer = list_last_entry(&msg->transfers, struct spi_transfer,
+ transfer_list);
+ xfer->cs_change = false;
+ /* Ensure dw3000_spi_sync() stop queue messages */
+ dw->msg_queue_xfer = NULL;
+ /* Do saved SPI transfers */
+ rc = dw3000_spi_sync(dw, msg);
+ /* Cleanup */
+ return dw3000_spi_queue_reset(dw, rc);
+#else
+ return 0;
+#endif
+}
+
+/**
+ * dw3000_spi_sync() - Execute or queue an SPI synchronous transaction
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @msg: the SPI message to transmit
+ *
+ * Call directly spi_sync() with the provided SPI message and display an error
+ * message if any error occurs.
+ *
+ * If SPI messages queue mode was activated previously by a call to
+ * dw3000_spi_queue_start(), don't call spi_sync() directly but store the
+ * given transfers into the message queue.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static inline int dw3000_spi_sync(struct dw3000 *dw, struct spi_message *msg)
+{
+ int rc;
+ if (dw->msg_queue_xfer)
+ return dw3000_spi_queue_msg(dw, msg);
+ rc = spi_sync(dw->spi, msg);
+ if (rc)
+ dev_err(dw->dev, "could not transfer : %d\n", rc);
+ return rc;
+}
+
+/**
+ * dw3000_spi_queue_active() - Check SPI queuing mode status
+ * @dw: the DW device on which the SPI transfer will occurs
+ *
+ * Return: true if SPI message queuing mode is active
+ */
+static inline bool dw3000_spi_queue_active(struct dw3000 *dw)
+{
+ return (dw->msg_queue_xfer || dw->msg_queue_xfer_count);
+}
+
+/**
+ * dw3000_xfer() - Generic low-level slow transfer
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the fileID to read/write
+ * @reg_offset: the offset where to read/write
+ * @length: the length of provided buffer and SPI data transfer
+ * @buffer: the address where to read/write data
+ * @mode: operation mode, ie. RW, WR, AND_OR8, etc.
+ *
+ * This function initialise a new SPI message and two SPI transfer on the stack.
+ * First transfer is use for TX header to device. Second transfer is used for RX
+ * or TX data. Then spi_sync() is called.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_xfer(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u16 length,
+ void *buffer, enum spi_modes mode)
+{
+ struct {
+ struct spi_message msg;
+ struct spi_transfer header;
+ struct spi_transfer data;
+ u8 header_buf[2];
+ } xfer = {};
+
+ /* Init transfers first because spi_message_init_with_transfer don't! */
+ xfer.header.tx_buf = xfer.header_buf;
+ xfer.header.len = sizeof(xfer.header_buf);
+
+ /* Then construct SPI message */
+ spi_message_init_with_transfers(&xfer.msg, &xfer.header, 2);
+
+ /* Prepare header & data transfers */
+ dw3000_prepare_xfer(&xfer.msg, reg_fileid, reg_offset, length, buffer,
+ mode);
+ /* Now execute this spi message synchronously */
+ return dw3000_spi_sync(dw, &xfer.msg);
+}
+
+/**
+ * dw3000_write_fastcmd() - Send a fast command to the device
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @cmd: the fastcommand to send to the device
+ *
+ * This function use a prebuilt SPI message with a single transfer which is
+ * modified to send the required command.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_write_fastcmd(struct dw3000 *dw, u8 cmd)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_fast_command;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ u8 *header_buf = (u8 *)tr->tx_buf;
+
+ /* Set the command to send directly in first transfer TX buffer */
+ *header_buf =
+ (u8)((DW3000_SPI_WR_BIT >> 8) | (cmd << 1) | DW3000_SPI_FAC);
+ /* Now execute this spi message synchronously */
+ return dw3000_spi_sync(dw, msg);
+}
+
+/**
+ * dw3000_reg_read_fast() - Generic full-duplex register read
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read
+ * @reg_offset: the register offset to read
+ * @length: the length of provided buffer and SPI data transfer
+ * @buffer: the address where to store the read data
+ *
+ * This function read the specified register using a dedicated full-duplex
+ * message. It configure it first, call spi_sync() and then copy read data
+ * from the spi_tranfer RX buffer to the provided buffer.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_reg_read_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 length, void *buffer)
+{
+ struct spi_message *msg = dw->msg_readwrite_fdx;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ int hlen;
+ int rc;
+
+ mutex_lock(&dw->msg_mutex);
+ /* Update header and length */
+ dw3000_prepare_xfer(msg, reg_fileid, reg_offset, length, NULL,
+ DW3000_SPI_RD_BIT);
+ /* Retrieve header length */
+ hlen = tr->len - length;
+ /* Ensure all data bits are 0 */
+ memset((void *)tr->tx_buf + hlen, 0, length);
+ /* Execute SPI transfer */
+ rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ /* Get back the data that are after the header in RX buffer */
+ memcpy(buffer, tr->rx_buf + hlen, length);
+ mutex_unlock(&dw->msg_mutex);
+ return rc;
+}
+
+/**
+ * dw3000_reg_read32() - 32 bits register read
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read
+ * @reg_offset: the register offset to read
+ * @val: the u32 value's pointer
+ *
+ * Just a little wrapper to the dw3000_reg_read_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_read32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 *val)
+{
+ __le32 buffer;
+ int rc;
+ /* Read 4 bytes (32-bits) register into buffer */
+ rc = dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(buffer),
+ &buffer);
+ if (likely(!rc))
+ *val = le32_to_cpu(buffer);
+ return rc;
+}
+
+/**
+ * dw3000_reg_read16() - 16 bits register read
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read
+ * @reg_offset: the register offset to read
+ * @val: the u16 value's pointer
+ *
+ * Just a little wrapper to the dw3000_reg_read_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_read16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 *val)
+{
+ __le16 buffer;
+ int rc;
+ /* Read 2 bytes (16-bits) register into buffer */
+ rc = dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(buffer),
+ &buffer);
+ if (likely(!rc))
+ *val = le16_to_cpu(buffer);
+ return rc;
+}
+
+/**
+ * dw3000_reg_read8() - 8 bits register read
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read
+ * @reg_offset: the register offset to read
+ * @val: the u8 value's pointer
+ *
+ * Just a little wrapper to the dw3000_reg_read_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_read8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u8 *val)
+{
+ /* Read 1 byte (8-bits) register into data */
+ return dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(*val),
+ val);
+}
+
+/**
+ * dw3000_reg_write_fast() - Generic single-transfer register write
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to read
+ * @reg_offset: the register offset to read
+ * @length: the length of provided buffer and SPI data transfer
+ * @buffer: the address where to store the read data
+ * @mode: the write operation mode to use (direct, bitmask and/or)
+ *
+ * This function write the specified register using a dedicated single-transfer
+ * message. It configure it first, copy data from provided buffer in it, then
+ * call spi_sync().
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_reg_write_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 length, const void *buffer, enum spi_modes mode)
+{
+ struct spi_message *msg = dw->msg_readwrite_fdx;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ void *rx_buf;
+ int hlen;
+ int rc;
+
+ mutex_lock(&dw->msg_mutex);
+ /* Update header and length */
+ dw3000_prepare_xfer(msg, reg_fileid, reg_offset, length, NULL, mode);
+ /* Data are after the header in TX buffer */
+ hlen = tr->len - length;
+ memcpy((void *)tr->tx_buf + hlen, buffer, length);
+ /* We don't want to receive data, so remove unused RX buffer to avoid
+ unrequired fifo read in controller. */
+ rx_buf = tr->rx_buf; /* save RX buffer */
+ tr->rx_buf = NULL;
+ /* Execute SPI transfer */
+ rc = dw3000_spi_sync(dw, msg);
+ /* Restore RX buffer */
+ tr->rx_buf = rx_buf;
+ mutex_unlock(&dw->msg_mutex);
+ return rc;
+}
+
+/**
+ * _dw3000_reg_write - Generic register write
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to write
+ * @reg_offset: the register offset to write
+ * @length: the length of provided buffer and SPI data transfer
+ * @buffer: the address of the data to write
+ *
+ * Just a little wrapper to the dw3000_reg_write_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int _dw3000_reg_write(struct dw3000 *dw, u32 reg_fileid,
+ u16 reg_offset, u16 length,
+ const void *buffer)
+{
+ return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, length, buffer,
+ DW3000_SPI_WR_BIT);
+}
+
+/**
+ * dw3000_reg_write32() - 32 bits register write
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to write
+ * @reg_offset: the register offset to write
+ * @data: value to write to the register
+ *
+ * Just a little wrapper to the dw3000_reg_write() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_write32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 data)
+{
+ __le32 buffer = cpu_to_le32(data);
+
+ return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(buffer),
+ &buffer);
+}
+
+/**
+ * dw3000_reg_write16() - 16 bits register write
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to write
+ * @reg_offset: the register offset to write
+ * @data: value to write to the register
+ *
+ * Just a little wrapper to the dw3000_reg_write() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_write16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 data)
+{
+ __le16 buffer = cpu_to_le16(data);
+
+ return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(buffer),
+ &buffer);
+}
+
+/**
+ * dw3000_reg_write8() - 8 bits register write
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to write
+ * @reg_offset: the register offset to write
+ * @data: value to write to the register
+ *
+ * Just a little wrapper to the dw3000_reg_write() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_write8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u8 data)
+{
+ return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(data),
+ &data);
+}
+
+/**
+ * dw3000_reg_modify32() - 32 bits register modify
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to modify
+ * @reg_offset: the register offset to modify
+ * @_and: bitmask to clear
+ * @_or: bitmask to set
+ *
+ * Just a little wrapper to the dw3000_reg_write_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_modify32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 _and, u32 _or)
+{
+ __le32 buffer[2];
+
+ buffer[0] = cpu_to_le32(_and);
+ buffer[1] = cpu_to_le32(_or);
+ return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer),
+ buffer, DW3000_SPI_AND_OR_32);
+}
+
+/**
+ * dw3000_reg_modify16() - 16 bits register modify
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to modify
+ * @reg_offset: the register offset to modify
+ * @_and: bitmask to clear
+ * @_or: bitmask to set
+ *
+ * Just a little wrapper to the dw3000_reg_write_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_modify16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 _and, u16 _or)
+{
+ __le16 buffer[2];
+
+ buffer[0] = cpu_to_le16(_and);
+ buffer[1] = cpu_to_le16(_or);
+ return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer),
+ buffer, DW3000_SPI_AND_OR_16);
+}
+
+/**
+ * dw3000_reg_modify8() - 8 bits register modify
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @reg_fileid: the register fileID to modify
+ * @reg_offset: the register offset to modify
+ * @_and: bitmask to clear
+ * @_or: bitmask to set
+ *
+ * Just a little wrapper to the dw3000_reg_write_fast() function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_reg_modify8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u8 _and, u8 _or)
+{
+ u8 buffer[2];
+
+ buffer[0] = _and;
+ buffer[1] = _or;
+ return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer),
+ buffer, DW3000_SPI_AND_OR_8);
+}
+
+/**
+ * dw3000_clear_sys_status() - Fast clearing of SYS_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @clear_bits: the bitmask of bits to clear
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_clear_sys_status(struct dw3000 *dw, u32 clear_bits)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_write_sys_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(clear_bits);
+ /* Prepared message have only header & length set, need to set data part */
+ put_unaligned_le32(clear_bits, (void *)tr->tx_buf + hlen);
+ return dw3000_spi_sync(dw, msg);
+}
+
+/**
+ * dw3000_read_sys_status() - Fast read of SYS_STATUS register (4 low bytes upon 6)
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @status: address where to put read status
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_sys_status(struct dw3000 *dw, u32 *status)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_read_sys_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(*status);
+ int rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ *status = get_unaligned_le32(tr->rx_buf + hlen);
+ return rc;
+}
+
+/**
+ * dw3000_clear_all_sys_status() - Fast clearing of SYS_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @clear_bits: the bitmask of bits to clear
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_clear_all_sys_status(struct dw3000 *dw, u64 clear_bits)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_write_all_sys_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(clear_bits);
+ /* Prepared message have only header & length set, need to set data part */
+ put_unaligned_le64(clear_bits, (void *)tr->tx_buf + hlen);
+ return dw3000_spi_sync(dw, msg);
+}
+
+/**
+ * dw3000_read_all_sys_status() - Fast read of SYS_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @status: address where to put read status
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_all_sys_status(struct dw3000 *dw, u64 *status)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_read_all_sys_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(*status);
+ int rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ *status = get_unaligned_le64(tr->rx_buf + hlen);
+ return rc;
+}
+
+/**
+ * dw3000_clear_dss_status() - Fast clearing of DSS_STAT register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @clear_bits: the bitmask of bits to clear
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_clear_dss_status(struct dw3000 *dw, u8 clear_bits)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_write_dss_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(clear_bits);
+ int rc;
+ /* Prepared message have only header & length set, need to set data part */
+ *((u8 *)tr->tx_buf + hlen) = clear_bits;
+ rc = spi_sync(dw->spi, msg);
+ if (rc)
+ dev_err(dw->dev, "could not transfer : %d\n", rc);
+ return rc;
+}
+
+/**
+ * dw3000_read_dss_status() - Fast read of DSS_STAT register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @status: address where to put read status
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_dss_status(struct dw3000 *dw, u8 *status)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_read_dss_status;
+ struct spi_transfer *tr = list_last_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(*status);
+ int rc = spi_sync(dw->spi, msg);
+
+ if (rc)
+ dev_err(dw->dev, "could not transfer : %d\n", rc);
+ else
+ *status = *((u8 *)tr->rx_buf + hlen);
+ return rc;
+}
+
+/**
+ * dw3000_clear_spi_collision_status() - Fast clearing of SPI_COLLISION_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @clear_bits: the bitmask of bits to clear
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_clear_spi_collision_status(struct dw3000 *dw, u8 clear_bits)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_write_spi_collision_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(clear_bits);
+ int rc;
+ /* Prepared message have only header & length set, need to set data part */
+ *((u8 *)tr->tx_buf + hlen) = clear_bits;
+ rc = spi_sync(dw->spi, msg);
+ if (rc)
+ dev_err(dw->dev, "could not transfer : %d\n", rc);
+ return rc;
+}
+
+/**
+ * dw3000_change_tx_rf_port() - Enable TX on RF2 port.
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @use_rf2: variable to trigger the SWITCH to RF2
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_change_tx_rf_port(struct dw3000 *dw, bool use_rf2)
+{
+ int rc;
+ u32 val = DW3000_TXRXSWITCH_AUTO |
+ ((u32)use_rf2
+ << DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_OFFSET) |
+ ((u32)use_rf2
+ << DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET);
+ rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, val);
+ if (!rc)
+ dw->tx_rf2 = use_rf2;
+ return rc;
+}
+
+/**
+ * dw3000_change_rx_rf_port() - Enable RX on RF2 port.
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @use_rf2: variable to trigger the SWITCH to RF2
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_change_rx_rf_port(struct dw3000 *dw, bool use_rf2)
+{
+ int rc = 0;
+ u32 val = DW3000_TXRXSWITCH_AUTO |
+ ((u32)use_rf2
+ << DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET);
+ rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, val);
+ if (!rc)
+ dw->rx_rf2 = use_rf2;
+ return rc;
+}
+
+/**
+ * dw3000_read_rdb_status() - Fast read of RDB_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @status: address where to put read status
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_rdb_status(struct dw3000 *dw, u8 *status)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_read_rdb_status;
+ struct spi_transfer *tr = list_last_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(*status);
+ int rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ *status = *((u8 *)tr->rx_buf + hlen);
+ return rc;
+}
+
+/**
+ * dw3000_check_devid() - Read and check the DEVID register
+ * @dw: the DW device on which the SPI transfer will occurs
+ *
+ * Return: 0 on success, else -ENODEV error code.
+ */
+int dw3000_check_devid(struct dw3000 *dw)
+{
+ u32 devid;
+ int i;
+
+ int rc = dw3000_reg_read32(dw, DW3000_DEV_ID_ID, 0, &devid);
+ if (unlikely(rc))
+ return rc;
+ for (i = 0; i < ARRAY_SIZE(dw3000_chip_versions); i++) {
+ if (devid == dw3000_chip_versions[i].id) {
+ if (!dw->chip_dev_id) {
+ dev_info(dw->dev, "chip version found : %x\n",
+ devid);
+ dw->chip_dev_id = devid;
+ dw->chip_idx = i;
+ }
+ __dw3000_chip_version = dw3000_chip_versions[i].ver;
+ dw->chip_ops = dw3000_chip_versions[i].ops;
+ return 0;
+ }
+ }
+ dev_warn(dw->dev, "unknown DEV_ID : %x\n", devid);
+ return -ENODEV;
+}
+
+/**
+ * dw3000_check_idlerc() - Read and check the RCINIT bit in SYS_STATUS register
+ * @dw: the DW device on which the SPI transfer will occurs
+ *
+ * Return: True if bit is set else false if unset or SPI error.
+ */
+static inline bool dw3000_check_idlerc(struct dw3000 *dw)
+{
+ u32 low_sys_status;
+
+ if (dw3000_read_sys_status(dw, &low_sys_status))
+ return false;
+
+ trace_dw3000_check_idlerc(dw, low_sys_status);
+ return low_sys_status & (DW3000_SYS_STATUS_RCINIT_BIT_MASK);
+}
+
+/**
+ * dw3000_read_sys_time() - Read current system time
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @sys_time: the address where to store current time
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_sys_time(struct dw3000 *dw, u32 *sys_time)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg = dw->msg_read_sys_time;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ const int hlen = tr->len - sizeof(*sys_time);
+ int rc;
+
+ if (dw->chip_ops->pre_read_sys_time) {
+ rc = dw->chip_ops->pre_read_sys_time(dw);
+ if (rc)
+ return rc;
+ }
+ rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ *sys_time = get_unaligned_le32(tr->rx_buf + hlen);
+ return rc;
+}
+
+/**
+ * dw3000_read_rx_timestamp() - Read precise RX timestamp
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @rx_ts: the address where to store RX timestamp value
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_rx_timestamp(struct dw3000 *dw, u64 *rx_ts)
+{
+ /* Use a prebuilt SPI message to be as fast as possible. */
+ struct spi_message *msg;
+ struct spi_transfer *tr;
+ int hlen; /* depend on selected message */
+ int rc;
+
+ trace_dw3000_read_rx_timestamp(dw);
+ switch (dw->data.dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ msg = dw->msg_read_rx_timestamp_a;
+ break;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ msg = dw->msg_read_rx_timestamp_b;
+ break;
+ default:
+ msg = dw->msg_read_rx_timestamp;
+ }
+ tr = list_first_entry(&msg->transfers, struct spi_transfer,
+ transfer_list);
+ /* Calc header len using known data size (smaller than sizeof(*rx_ts)) */
+ hlen = tr->len - DW3000_RX_TIME_RX_STAMP_LEN;
+ /* Execute this spi message synchronously */
+ rc = dw3000_spi_sync(dw, msg);
+ if (!rc)
+ *rx_ts = get_unaligned_le64(tr->rx_buf + hlen) &
+ ((1ull << (DW3000_RX_TIME_RX_STAMP_LEN * 8)) - 1);
+ trace_dw3000_return_int_u64(dw, rc, *rx_ts);
+ return rc;
+}
+
+/**
+ * dw3000_configure_ciadiag() - Enable CIA diagnostic data
+ * @dw: the DW device
+ * @on: Enable/disable CIA to log all diagnostic registers
+ * @opt: option specified in ciadiag_dbl_options enum.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_configure_ciadiag(struct dw3000 *dw, bool on,
+ enum ciadiag_dbl_options opt)
+{
+ struct dw3000_local_data *data = &dw->data;
+ int rc;
+
+ if (on) {
+ rc = dw3000_reg_and8(dw, DW3000_CIA_CONF_ID, 2,
+ ~(DW3000_CIA_CONFIG_DIAG_OFF));
+
+ } else {
+ rc = dw3000_reg_or8(dw, DW3000_CIA_CONF_ID, 2,
+ DW3000_CIA_CONFIG_DIAG_OFF);
+ }
+ if (unlikely(rc))
+ return rc;
+
+ if (!data->dblbuffon) {
+ if (opt != 0) {
+ dev_warn(
+ dw->dev,
+ "cannot set CIA diagnostic option while the double buffering is disabled\n");
+ }
+ goto skip_opt;
+ }
+ /* Apply double buffering option */
+ rc = dw3000_reg_write8(dw, DW3000_RDB_DIAG_MODE_ID, 0, opt);
+ if (unlikely(rc))
+ return rc;
+skip_opt:
+ data->ciadiag_enabled = on;
+ return rc;
+}
+
+/**
+ * dw3000_rx_stats_enable() - Enable or disable all statistics
+ * @dw: the DW device
+ * @on: true to enable statistics, otherwise disable it.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+inline int dw3000_rx_stats_enable(struct dw3000 *dw, const bool on)
+{
+ dw->stats.enabled = on;
+ /**
+ * Enable the CIA diagnostic to get diagnostic values as CIR power
+ * and PACC count needed to calculate the RSSI in userspace.
+ */
+ return dw3000_configure_ciadiag(dw, on, DW3000_CIA_DIAG_LOG_DBL_OFF);
+}
+
+/**
+ * dw3000_rx_stats_clear() - Clear all statistics
+ * @dw: the DW device
+ */
+inline void dw3000_rx_stats_clear(struct dw3000 *dw)
+{
+ struct dw3000_stats *stats = &dw->stats;
+
+ memset(stats->count, 0, sizeof(stats->count));
+}
+
+static bool dw3000_stats_enabled = false;
+module_param_named(stats_enabled, dw3000_stats_enabled, bool, 0644);
+MODULE_PARM_DESC(stats_enabled,
+ "Enable statistics gathering, to be used with traces, only"
+ " provides RSSI at this time");
+
+/**
+ * dw3000_rx_store_rssi() - Get RSSI data from last good RX frame
+ * @dw: the DW device
+ * @rssi: points to current rssi record in stats structure
+ * @pkt_sts: sts mode of current packet
+ *
+ * The RSSI data must be read before incrementing counter.
+ * It requires at least one received frame to get any data from
+ * register. Both 'cir_pwr' and 'pacc_cnt' values cannot be null
+ * regarding the RSSI formula in the DW3700 User Manual v0.3 section 4.7.2.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_rx_store_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ u8 pkt_sts)
+{
+ struct dw3000_config *config = &dw->config;
+ /* Only read RSSI after good RX frame */
+ int rc;
+ u32 cir_pwr;
+ u16 pacc_cnt;
+ u8 dgc_dec;
+ u32 diag1_addr;
+
+ /* No data available */
+ if (!rssi)
+ return -EAGAIN;
+ /* Get required data to calculate RSSI */
+ if (dw->full_cia_read) {
+ /* Get values from already retrieved CIA registers */
+ diag1_addr = (pkt_sts == DW3000_STS_MODE_OFF) ?
+ (DW3000_DB_DIAG_IP_DIAG1 >> 2) :
+ (DW3000_DB_DIAG_STS_DIAG1 >> 2);
+ cir_pwr = dw->cir_data->ciaregs[diag1_addr];
+ pacc_cnt = dw->cir_data->acc;
+ } else {
+ /* Read CIR power value */
+ rc = dw3000_reg_read32(
+ dw,
+ _ciadiag_reg_info[dw->data.ciadiag_reg_select].diag1, 0,
+ &cir_pwr);
+ if (unlikely(rc))
+ return rc;
+ /* Read preamble accumulation count value */
+ rc = dw3000_reg_read16(
+ dw,
+ _ciadiag_reg_info[dw->data.ciadiag_reg_select].diag12,
+ 0, &pacc_cnt);
+ if (unlikely(rc))
+ return rc;
+ /* Reset minidiag to allow a new measure */
+ rc = dw3000_reg_modify32(dw, DW3000_CIA_CONF_ID, 0,
+ ~DW3000_CIA_CONF_MINDIAG_BIT_MASK,
+ 0x0);
+ if (unlikely(rc))
+ return rc;
+ }
+
+ /* Store in provided buffer */
+ rssi->cir_pwr = cir_pwr;
+ rssi->pacc_cnt = pacc_cnt;
+ rssi->prf_64mhz = ((config->rxCode >= 9) && (config->rxCode <= 24));
+
+ /* Read chip specific DGC_DBG */
+ rc = dw->chip_ops->get_dgc_dec(dw, &dgc_dec);
+ if (unlikely(rc))
+ return rc;
+ rssi->dgc_dec = dgc_dec;
+
+ return 0;
+}
+
+/**
+ * dw3000_rx_stats_inc() - Increment statistics.
+ * @dw: The DW device.
+ * @item: Statistics item.
+ * @rssi: The RSSI information to store.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_rx_stats_inc(struct dw3000 *dw, const enum dw3000_stats_items item,
+ struct dw3000_rssi *rssi)
+{
+ struct dw3000_stats *stats = &dw->stats;
+ const char *chip_name = dw3000_get_chip_name(dw);
+ bool sts_enabled = _dw3000_sts_is_enabled(dw);
+ u16 idx;
+
+ /* Increment per-item counter */
+ stats->count[item]++;
+
+ /* Save provided RSSI data */
+ if (!rssi)
+ return 0;
+
+ /* Retrieve current idx */
+ idx = stats->indexes[item];
+ /* RSSI array is divided in two parts, RX_GOOD at first */
+ idx += (DW3000_RSSI_REPORTS_MAX >> 1) * (item == DW3000_STATS_RX_ERROR);
+ stats->rssi[idx] = *rssi;
+
+ /* Update where to store next RSSI */
+ stats->indexes[item]++;
+ if (stats->indexes[item] >= (DW3000_RSSI_REPORTS_MAX >> 1))
+ stats->indexes[item] = 0;
+ /* TODO(UWB-3455): Shall we reset count[item] too to maintain current behaviour?
+ * See also do_tm_cmd_get_rx_diag() function.
+ */
+
+ /* Trace RSSI data*/
+ trace_dw3000_rx_rssi(dw, chip_name, sts_enabled, rssi->cir_pwr,
+ rssi->pacc_cnt, rssi->prf_64mhz, rssi->dgc_dec);
+ return 0;
+}
+
+/**
+ * dw3000_rx_calc_rssi() - RSSI computation.
+ * @dw: The DW device.
+ * @rssi: The RSSI related informations.
+ * @info: the MCPS information structure to fill with RSSI.
+ * @sts: Current STS mode.
+ *
+ * This function checks if RSSI is required: by explicit MAC request or by
+ * stats, then retrieve, store and output it by tracepoint
+ * or in a structure field.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_rx_calc_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ struct mcps802154_rx_frame_info *info, u8 sts)
+{
+ bool rssi_required = info->flags & MCPS802154_RX_FRAME_INFO_RSSI;
+ int rc = 0;
+ bool rx_tune;
+ u8 reg;
+
+ if (!rssi_required)
+ return 0;
+
+ /* Get RX_TUNE_EN bit required by the RSSI formula */
+ /* TODO: move this in dw3000_configure_dgc() to cache this value in
+ struct dw3000_local_data and avoid this read. */
+ rc = dw3000_reg_read8(dw, DW3000_DGC_CFG_ID, 0, &reg);
+ if (rc)
+ return rc;
+ rx_tune = reg & DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK;
+ /* Compute RSSI and give the result to the upper layer in Q7.1 */
+ info->rssi = dw->chip_ops->compute_rssi(dw, rssi, rx_tune, sts) << 1;
+ if (!info->rssi)
+ info->flags &= ~MCPS802154_RX_FRAME_INFO_RSSI;
+ return 0;
+}
+
+static int dw3000_power_supply_one(struct regulator *regulator, bool onoff)
+{
+ int rc;
+
+ if (!regulator)
+ return 0;
+ if (onoff)
+ rc = regulator_enable(regulator);
+ else
+ rc = regulator_disable(regulator);
+ return rc;
+}
+
+/**
+ * dw3000_power_supply() - Set attached regulators state
+ * @dw: the DW device
+ * @onoff: the new expected regulators state
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_power_supply(struct dw3000 *dw, int onoff)
+{
+ struct dw3000_power_control *power = &dw->regulators;
+ int rc = 0;
+
+ /* Early return if no regulator defined */
+ if (!power->regulator_1p8 && !power->regulator_2p5 &&
+ !power->regulator_vdd)
+ return 0;
+ /* Change defined regulators state */
+ rc = dw3000_power_supply_one(power->regulator_1p8, onoff);
+ if (rc < 0) {
+ dev_err(dw->dev, "regulator %s failed for 1p8 (%d)\n",
+ onoff ? "enable" : "disable", rc);
+ return rc;
+ }
+
+ rc = dw3000_power_supply_one(power->regulator_2p5, onoff);
+ if (rc < 0) {
+ dev_err(dw->dev, "regulator %s failed for 2p5 (%d)\n",
+ onoff ? "enable" : "disable", rc);
+ return rc;
+ }
+ /* Set 2p5 reg to 2.7V */
+ rc = regulator_set_voltage(power->regulator_2p5, 2700000, 2700000);
+ if (rc < 0) {
+ dev_err(dw->dev, "regulator failed to set voltage :%d\n", rc);
+ return rc;
+ }
+
+ rc = dw3000_power_supply_one(power->regulator_vdd, onoff);
+ if (rc < 0) {
+ dev_err(dw->dev, "regulator %s failed for vdd (%d)\n",
+ onoff ? "enable" : "disable", rc);
+ return rc;
+ }
+
+ /* Add some delay to wait regulator stable */
+ usleep_range(dw3000_regulator_delay_us,
+ dw3000_regulator_delay_us + 100);
+ return rc;
+}
+
+/**
+ * dw3000_reset_assert() - Reset gpio control
+ * @dw: the DW device
+ * @reset: the DW device
+ *
+ * The configured reset gpio is switched to input to ensure it is not
+ * driven low.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_reset_assert(struct dw3000 *dw, bool reset)
+{
+ int rc;
+
+ if (!gpio_is_valid(dw->reset_gpio)) {
+ dev_err(dw->dev, "invalid reset gpio\n");
+ return -EINVAL;
+ }
+
+ if (reset) {
+ /* Assert RESET GPIO */
+ rc = gpio_direction_output(dw->reset_gpio, 0);
+ if (rc)
+ dev_err(dw->dev,
+ "Could not set reset gpio as output\n");
+ } else {
+ /* Release RESET GPIO.
+ * Reset should be open drain, or switched to input whenever not driven
+ * low. It should not be driven high. */
+ rc = gpio_direction_input(dw->reset_gpio);
+ if (rc)
+ dev_err(dw->dev, "Could not set reset gpio as input\n");
+ }
+ return rc;
+}
+
+/**
+ * dw3000_set_operational_state() - Set new operational state for the device
+ * @dw: the DW device
+ * @st: the new operational state to set
+ *
+ * This ensure waiting thread are wake-up when operational state is changed.
+ */
+static void dw3000_set_operational_state(struct dw3000 *dw,
+ enum operational_state st)
+{
+ trace_dw3000_set_operational_state(dw, st);
+ dw->current_operational_state = st;
+ wake_up(&dw->operational_state_wq);
+}
+
+/**
+ * dw3000_wait_idle_state() - Wait device powered-on and ready
+ * @dw: the DW device to power-on
+ *
+ * Device signal it is ready by issuing SPIRDY IRQ. This function waits
+ * for this IRQ to be handled and current_operational_state changed.
+ *
+ * Context: Must be call outside the thread handling device IRQ.
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_wait_idle_state(struct dw3000 *dw)
+{
+ int timeout = msecs_to_jiffies(500);
+ int rc = 0;
+
+ /* Force slow SPI clock speed (at device level) */
+ dw3000_change_speed(dw, DW3000_SPI_SLOW_HZ);
+ /* Enable interrupt so we can catch the SPI ready IRQ */
+ enable_irq(dw->spi->irq);
+
+ /* Now, wait for SPI ready interrupt */
+ if (!wait_event_timeout(dw->operational_state_wq,
+ dw->current_operational_state >=
+ DW3000_OP_STATE_IDLE_RC,
+ timeout)) {
+ dev_err(dw->dev, "Timeout waiting poweron event.\n");
+ rc = -EIO;
+ }
+
+ /* No IRQs after this point until device is enabled */
+ disable_irq(dw->spi->irq);
+ /* Restore max SPI clock speed */
+ dw3000_change_speed(dw, dw->of_max_speed_hz);
+
+ if (!rc)
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ return rc;
+}
+
+/**
+ * dw3000_poweron() - Power-on device
+ * @dw: the DW device to power-on
+ *
+ * Power-on device using configured regulators and de-assert reset gpio.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_poweron(struct dw3000 *dw)
+{
+ int rc;
+
+ if (dw->is_powered) {
+ dev_info(dw->dev, "Device already powered on\n");
+ return 0;
+ }
+ /* Power up regulators */
+ rc = dw3000_power_supply(dw, true);
+ if (rc) {
+ dev_err(dw->dev, "Could not enable regulator\n");
+ return rc;
+ }
+ dw->is_powered = true;
+ /* De-assert RESET GPIO */
+ return dw3000_reset_assert(dw, false);
+}
+
+/**
+ * dw3000_poweroff() - Power-off device using configured reset gpio
+ * @dw: the DW device to power-on
+ *
+ * The configured reset gpio is switched to output, at low state.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_poweroff(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+
+ if (!dw->is_powered) {
+ dev_info(dw->dev, "Device already powered off\n");
+ return 0;
+ }
+ /* Power down regulators */
+ rc = dw3000_power_supply(dw, false);
+ if (rc) {
+ dev_err(dw->dev, "Could not disable regulator\n");
+ return rc;
+ }
+ dw->is_powered = false;
+
+ /* Assert RESET GPIO */
+ rc = dw3000_reset_assert(dw, true);
+ if (rc)
+ return rc;
+
+ /* Clear security registers related cache */
+ memset(local->sts_key, 0, AES_KEYSIZE_128);
+ memset(local->sts_iv, 0, AES_BLOCK_SIZE);
+ /* Reset STS mode */
+ dw->config.stsMode = DW3000_STS_MODE_OFF | DW3000_STS_MODE_SDC;
+ /* Update operational state and power stats */
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_OFF);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+
+ /* Ensure RESET is asserted at least the required time */
+ usleep_range(DW3000_HARD_RESET_DELAY_US,
+ DW3000_HARD_RESET_DELAY_US + 100);
+ return 0;
+}
+
+/**
+ * dw3000_forcetrxoff() - Force device in idle mode, TX/RX off
+ * @dw: the DW device
+ *
+ * According to the DW3000 manual, this command must be send with IRQ disable
+ * to avoid a race condition.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_forcetrxoff(struct dw3000 *dw)
+{
+ int rc;
+ u8 idle = 0;
+
+ /* Check if in TX or RX state before forcing device into IDLE state */
+ rc = dw3000_reg_read8(dw, DW3000_SYS_STATE_LO_ID, 2, &idle);
+ if (idle < DW3000_SYS_STATE_IDLE) {
+ /* Device is already in IDLE_RC ... must not force to IDLE if in IDLE_RC */
+ return rc;
+ }
+ disable_irq(dw->spi->irq);
+ rc = dw3000_write_fastcmd(dw, DW3000_CMD_TXRXOFF);
+ enable_irq(dw->spi->irq);
+ if (!rc)
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ return rc;
+}
+
+/**
+ * dw3000_setpreambledetecttimeout() - Set the preamble detection timeout
+ * @dw: the DW device
+ * @timeout: preamble detection timeout in units of PAC size symbols.
+ *
+ * The counter automatically adds 1 PAC size to the value set. Min value that
+ * can be set is 1 (i.e. a timeout of 2 PAC size).
+ *
+ * The default/reset value is zero which disables the preamble detection
+ * timeout.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_setpreambledetecttimeout(struct dw3000 *dw,
+ u16 timeout)
+{
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+ /*
+ * Compare with the previous value stored if register access
+ * is necessary.
+ */
+ if (local->rx_timeout_pac == timeout)
+ return 0;
+ rc = dw3000_reg_write16(dw, DW3000_DRX_PRETOC_ID, 0, timeout);
+ if (unlikely(rc))
+ return rc;
+ local->rx_timeout_pac = timeout;
+ return 0;
+}
+
+/**
+ * dw3000_setrxtimeout() - Set the reception timeout
+ * @dw: the DW device
+ * @timeout_dly: reception total time timeout in dly unit.
+ *
+ * timeout value 0 will disable the timeout.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_setrxtimeout(struct dw3000 *dw, u32 timeout_dly)
+{
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+ if (local->rx_frame_timeout_dly == timeout_dly)
+ return 0;
+ if (timeout_dly) {
+ rc = dw3000_reg_write32(dw, DW3000_RX_FWTO_ID, 0, timeout_dly);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_reg_or16(dw, DW3000_SYS_CFG_ID, 0,
+ DW3000_SYS_CFG_RXWTOE_BIT_MASK);
+ } else {
+ rc = dw3000_reg_and16(dw, DW3000_SYS_CFG_ID, 0,
+ (u16)~DW3000_SYS_CFG_RXWTOE_BIT_MASK);
+ }
+ if (unlikely(rc))
+ return rc;
+ local->rx_frame_timeout_dly = timeout_dly;
+ return 0;
+}
+
+static inline int dw3000_setdelayedtrxtime(struct dw3000 *dw, u32 starttime)
+{
+ u32 sys_starttime = dw3000_dtu_to_sys_time(dw, starttime);
+ return dw3000_reg_write32(dw, DW3000_DX_TIME_ID, 0, sys_starttime);
+}
+
+/**
+ * dw3000_can_deep_sleep() - check delay before next operation
+ * @dw: the DW device
+ * @delay_us: the delay before which RX/TX must be executed
+ *
+ * Return: zero if not enough time to enter deep sleep, else a positive delay
+ * in us.
+ */
+int dw3000_can_deep_sleep(struct dw3000 *dw, int delay_us)
+{
+ /* We can't enter DEEP_SLEEP if previous operation has
+ * required ranging clock or if deep-sleep is disabled. */
+ if (dw->need_ranging_clock || (dw->auto_sleep_margin_us < 0))
+ return 0;
+ if (delay_us < max(DW3000_WAKEUP_LATENCY_US, dw->auto_sleep_margin_us))
+ return 0;
+ /* Take care of wakeup latency in returned result */
+ return delay_us - DW3000_WAKEUP_LATENCY_US;
+}
+
+/**
+ * dw3000_wakeup() - wake-up device by forcing CS line down long enough
+ * @dw: the DW device
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+static int dw3000_wakeup(struct dw3000 *dw)
+{
+ /* Wake UP require a minimum time of 500us CS low. Use one of prebuilt
+ SPI messages to be as fast as possible and modify it to include CS
+ required delay in microseconds. */
+ struct spi_message *msg = dw->msg_read_sys_status;
+ struct spi_transfer *tr = list_first_entry(
+ &msg->transfers, struct spi_transfer, transfer_list);
+ int rc;
+
+ /* Avoid race condition with dw3000_poweroff while chip is stopped just
+ when dw3000_idle_timeout() HR timer callback is executed. Do nothing
+ if not in DEEP-SLEEP state. */
+ if (dw->current_operational_state != DW3000_OP_STATE_DEEP_SLEEP)
+ return 0;
+
+ trace_dw3000_wakeup(dw);
+
+ /* Add a delay after transfer. See spi_transfer_delay_exec() called by
+ spi_transfer_one_message(). */
+#if (KERNEL_VERSION(5, 13, 0) > LINUX_VERSION_CODE)
+ tr->delay_usecs = DW3000_SPI_CS_WAKEUP_DELAY_US;
+#else
+ tr->delay.unit = SPI_DELAY_UNIT_USECS;
+ tr->delay.value = DW3000_SPI_CS_WAKEUP_DELAY_US;
+#endif
+ /* Now, execute SPI modified message/transfer */
+ rc = dw3000_spi_sync(dw, msg);
+ if (!rc) {
+ /* We are waking up the chip, update state according */
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_WAKE_UP);
+ /* Re-enable spi's irqs. deepsleep disable them */
+ enable_irq(dw->spi->irq);
+ }
+ /* Reset delay in transfer */
+#if (KERNEL_VERSION(5, 13, 0) > LINUX_VERSION_CODE)
+ tr->delay_usecs = 0;
+#else
+ tr->delay.value = 0;
+#endif
+ return rc;
+ /* The next part of the wake process is located in
+ dw3000_isr_handle_spi_ready(), executed when SPIRDY interrupt is
+ triggered */
+}
+
+static int do_wakeup(struct dw3000 *dw, const void *in, void *out)
+{
+ int err;
+ err = dw3000_wakeup(dw);
+ if (err)
+ mcps802154_broken(dw->llhw);
+
+ return err;
+}
+
+int dw3000_deepsleep_wakeup_now(struct dw3000 *dw,
+ dw3000_idle_timeout_cb idle_timeout_cb,
+ u32 timestamp_dtu,
+ enum operational_state next_operational_state)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ int r;
+
+ r = dw3000_wakeup(dw);
+ if (r)
+ return r;
+
+ dss->next_operational_state = next_operational_state;
+ dw->idle_timeout_cb = idle_timeout_cb;
+ dw->idle_timeout_dtu = timestamp_dtu;
+ return 0;
+}
+
+/**
+ * dw3000_handle_idle_timeout() - Idle expired handler
+ * @dw: the DW device.
+ * @in: ignored input.
+ * @out: ignored output.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int dw3000_handle_idle_timeout(struct dw3000 *dw, const void *in,
+ void *out)
+{
+ dw3000_idle_timeout_cb idle_timeout_cb = dw->idle_timeout_cb;
+
+ /* Consume/remove registered handler before call it.*/
+ dw->idle_timeout_cb = NULL;
+ /* Call handler registered. */
+ if (idle_timeout_cb)
+ return idle_timeout_cb(dw);
+ return 0;
+}
+
+/**
+ * dw3000_deepsleep_wakeup() - Handle wake-up.
+ * @dw: the DW device.
+ *
+ * Return: True when the wakeup is started, false otherwise.
+ */
+bool dw3000_deepsleep_wakeup(struct dw3000 *dw)
+{
+ trace_dw3000_deepsleep_wakeup(dw);
+ if (dw->current_operational_state == DW3000_OP_STATE_DEEP_SLEEP &&
+ dw->deep_sleep_state.next_operational_state >
+ DW3000_OP_STATE_IDLE_PLL) {
+ struct dw3000_stm_command cmd = { do_wakeup, NULL, NULL };
+ /* The chip is about to wake up, let's request the best QoS
+ latency early */
+ dw3000_pm_qos_update_request(dw, dw3000_qos_latency);
+ /* Must wakeup to execute stored operation, so run the
+ wake up function in state machine thread. */
+ dw3000_enqueue_timer(dw, &cmd);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * dw3000_idle_timeout() - idle timeout handler
+ * @timer: the idle_timer field in struct dw3000
+ *
+ * Return: always timer not restarted.
+ */
+enum hrtimer_restart dw3000_idle_timeout(struct hrtimer *timer)
+{
+ struct dw3000 *dw = container_of(timer, struct dw3000, idle_timer);
+ bool wakeup_started;
+
+ trace_dw3000_idle_timeout(dw);
+ wakeup_started = dw3000_deepsleep_wakeup(dw);
+ if (!wakeup_started && dw->idle_timeout_cb) {
+ struct dw3000_stm_command cmd = { dw3000_handle_idle_timeout,
+ NULL, NULL };
+ dw3000_enqueue_timer(dw, &cmd);
+ }
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * dw3000_idle_cancel_timer() - Cancel idle timer.
+ * @dw: the DW device
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int dw3000_idle_cancel_timer(struct dw3000 *dw)
+{
+ int r;
+
+ trace_dw3000_idle_cancel_timer(dw);
+ /* Remember: return value of hrtimer_try_to_cancel.
+ * 0 when the timer was not active.
+ * 1 when the timer was active.
+ * -1 when the timer is currently executing the callback function and
+ * cannot be stopped. */
+ r = hrtimer_try_to_cancel(&dw->idle_timer);
+ if (r < 0)
+ return -EBUSY;
+ else if (!r)
+ return -ENOENT;
+ /* Ensure wakeup ISR don't call the mcps802154_timer_expired() */
+ dw->idle_timeout_cb = NULL;
+ dw->idle_timeout = false;
+ return 0;
+}
+
+/**
+ * dw3000_wakeup_and_wait() - Check current device operational state
+ * @dw: the DW device to execute an RX/TX/configuration operation
+ *
+ * Can't program parameters in device if not in IDLE_PLL state, so
+ * we need to wakeup it at different places.
+ */
+void dw3000_wakeup_and_wait(struct dw3000 *dw)
+{
+ trace_dw3000_wakeup_and_wait(dw, dw->current_operational_state);
+ if (dw->current_operational_state >= DW3000_OP_STATE_IDLE_PLL)
+ return;
+ /* Ensure wakeup ISR don't call the mcps802154_timer_expired() since
+ this will result in dead lock! */
+ dw3000_idle_cancel_timer(dw);
+ /* Ensure force call to dw3000_wakeup() is made. */
+ dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_MAX;
+ /* Now wakeup device, and stop timer if running */
+ dw3000_deepsleep_wakeup(dw);
+ dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_IDLE_PLL;
+ /* And wait for good state */
+ if (current != dw->stm.mthread)
+ wait_event(dw->operational_state_wq,
+ dw->current_operational_state ==
+ DW3000_OP_STATE_IDLE_PLL);
+ else
+ /* TODO: find a better solution to allow wakeup isr processing
+ recursively.*/
+ mdelay(2);
+}
+
+/**
+ * dw3000_check_operational_state() - Check current device operational state
+ * @dw: the DW device to execute an RX/TX/configuration operation
+ * @delay_dtu: delay before expected operation
+ * @can_sync: true when it's possible to sync the clock
+ *
+ * This function will decide if device should enter/leave DEEP SLEEP
+ * state according current state and delay before next operation.
+ *
+ * Context: called from dw3000-spi thread only.
+ * Return: 0 if ready, 1 if in deep-sleep or waking-up, or a negative error
+ * code.
+ */
+int dw3000_check_operational_state(struct dw3000 *dw, int delay_dtu,
+ bool can_sync)
+{
+ int delay_us = DTU_TO_US(delay_dtu);
+ int rc;
+
+ trace_dw3000_check_operational_state(
+ dw, delay_dtu, dw->current_operational_state,
+ dw->deep_sleep_state.next_operational_state);
+
+ if (dw->current_operational_state == DW3000_OP_STATE_OFF)
+ return -ENODEV;
+ /* In deep sleep or wake up in progress, we can store parameters only
+ if no other operation queued. */
+ if ((dw->current_operational_state < DW3000_OP_STATE_IDLE_PLL) &&
+ (dw->deep_sleep_state.next_operational_state !=
+ DW3000_OP_STATE_IDLE_PLL))
+ return -EIO;
+
+ switch (dw->current_operational_state) {
+ case DW3000_OP_STATE_DEEP_SLEEP:
+ /* Update delay_us with wakeup margin */
+ delay_us = dw3000_can_deep_sleep(dw, delay_us);
+ if (delay_us) {
+ /* No need to wakeup now! Reprogram timer only. */
+ dw3000_wakeup_timer_start(dw, delay_us);
+ /* Stay in deep sleep, inform caller to save params. */
+ return 1;
+ }
+ /* The chip is about to wake up, request the best QoS latency */
+ dw3000_pm_qos_update_request(dw, dw3000_qos_latency);
+ /* Cancel wakeup timer launch by idle() */
+ dw3000_idle_cancel_timer(dw);
+ /* Wakeup now since delay isn't enough */
+ rc = dw3000_wakeup(dw);
+ if (unlikely(rc))
+ return rc;
+ /* Use kernel defined statement which exist since kernel 5.4. */
+ fallthrough;
+ case DW3000_OP_STATE_WAKE_UP:
+ /* Inform caller to save parameters. Stored operation will redo
+ deep sleep if needed. */
+ return 1;
+ default:
+ /* May need to resync when deep sleep is not enabled */
+ if (can_sync)
+ dw3000_may_resync(dw);
+ /* Update delay_us with wakeup margin */
+ delay_us = dw3000_can_deep_sleep(dw, delay_us);
+ if (!delay_us)
+ break;
+ /* Enter DEEP SLEEP and setup wakeup timer */
+ rc = dw3000_deep_sleep_and_wakeup(dw, delay_us);
+ if (!rc)
+ return 1; /* And save params. */
+ /* Failure to enter deep sleep, continue without it */
+ }
+ /* Cancel wakeup timer launch by idle() */
+ dw3000_idle_cancel_timer(dw);
+ return 0;
+}
+
+/**
+ * dw3000_check_hpdwarn() - check HPDWARN event status bit
+ * @dw: the DW device
+ *
+ * The HPDWARN event status bit relates to the use of delayed transmit and
+ * delayed receive functionality. It indicates the delay is more than half
+ * a period of the system clock. The HPDWARN status flag can be polled after a
+ * delayed transmission or reception is commanded, to check whether the delayed
+ * send/receive invocation was given in time (0) or not (1).
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_check_hpdwarn(struct dw3000 *dw)
+{
+ u8 status;
+ int rc;
+
+ rc = dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 3, &status);
+ if (rc)
+ return rc;
+ if (status & (DW3000_SYS_STATUS_HPDWARN_BIT_MASK >> 24)) {
+ dw3000_forcetrxoff(dw);
+ return -ETIME;
+ }
+ return 0;
+}
+
+/**
+ * dw3000_rx_disable() - Disable RX
+ * @dw: the DW device to put back in IDLE state
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_rx_disable(struct dw3000 *dw)
+{
+ return dw3000_forcetrxoff(dw);
+}
+
+/**
+ * dw3000_rx_busy() - Change RX busy state
+ * @dw: the DW device to change RX busy state
+ * @busy: the new value to set in busy field
+ *
+ * Return: true if busy flag already set to specified value.
+ */
+bool dw3000_rx_busy(struct dw3000 *dw, bool busy)
+{
+ unsigned long flags;
+ /* Check and set busy flag */
+ spin_lock_irqsave(&dw->rx.lock, flags);
+ if (dw->rx.busy == busy) {
+ spin_unlock_irqrestore(&dw->rx.lock, flags);
+ return true;
+ }
+ dw->rx.busy = busy;
+ spin_unlock_irqrestore(&dw->rx.lock, flags);
+ return false;
+}
+
+/**
+ * dw3000_rx_enable() - Enable RX
+ * @dw: the DW device to put in RX mode
+ * @rx_delayed: true if RX delayed must be use
+ * @date_dtu: the date at which RX must be enabled if @rx_delayed is true
+ * @timeout_pac: the preamble detect timeout
+ *
+ * This function enable RX on DW3000, delayed or not, with a configurable
+ * preamble detection timeout.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_rx_enable(struct dw3000 *dw, bool rx_delayed, u32 date_dtu,
+ u32 timeout_pac)
+{
+ u32 cur_time_dtu = 0;
+ int rc;
+
+ /* Read current DTU time to compare with next transfer time */
+ if (dw->coex_gpio >= 0)
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+
+ /* Configure timeout */
+ rc = dw3000_setpreambledetecttimeout(dw, timeout_pac);
+ if (unlikely(rc))
+ return rc;
+
+ /* Flush SPI queue before dw3000_coex_start() because coex use it
+ already.
+ NOTE: Since dw3000_rx_enable() also called during ADC calibration,
+ we may have nothing to flush. Call it only if SPI queuing is active */
+ if (dw3000_spi_queue_active(dw)) {
+ rc = dw3000_spi_queue_flush(dw);
+ if (unlikely(rc))
+ return rc;
+ }
+
+ /* Update RX parameters according to WiFi coexistence */
+ rc = dw3000_coex_start(dw, &rx_delayed, &date_dtu, cur_time_dtu);
+ if (unlikely(rc))
+ return rc;
+
+ if (!rx_delayed) {
+ /* Enter immediate reception mode */
+ rc = dw3000_write_fastcmd(dw, DW3000_CMD_RX);
+ if (unlikely(rc))
+ goto stop_coex;
+ dw3000_power_stats(dw, DW3000_PWR_RX, 0);
+ return 0;
+ }
+
+ /* Restart SPI queuing mode */
+ dw3000_spi_queue_start(dw);
+ /* Set reception date */
+ rc = dw3000_setdelayedtrxtime(dw, date_dtu);
+ if (unlikely(rc))
+ goto stop_coex;
+ /* Enter delayed reception mode */
+ rc = dw3000_write_fastcmd(dw, DW3000_CMD_DRX);
+ if (unlikely(rc))
+ goto stop_coex;
+ /* Execute SPI queued transfers */
+ rc = dw3000_spi_queue_flush(dw);
+ if (unlikely(rc))
+ goto stop_coex;
+
+ /* Apply power stats now, will goes back to IDLE in dw3000_forcetrxoff() */
+ dw3000_power_stats(dw, DW3000_PWR_RX, date_dtu);
+ /* Check if late */
+ rc = dw3000_check_hpdwarn(dw);
+ if (unlikely(rc)) {
+ if (rc == -ETIME) {
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+ dev_err(dw->dev,
+ "cannot program delayed rx date_dtu=%x current_dtu=%x\n",
+ date_dtu, cur_time_dtu);
+ }
+ goto stop_coex;
+ }
+ return 0;
+stop_coex:
+ dw3000_coex_stop(dw);
+ return rc;
+}
+
+/**
+ * dw3000_do_rx_enable() - handle RX enable MCPS operation
+ * @dw: the DW device to put in RX mode
+ * @config: RX enable parameters from MCPS
+ * @frame_idx: Frame index in a continuous block
+ *
+ * This function is called to execute all required operation to enable RX on
+ * the device using provided parameters.
+ *
+ * Since RX enable may be deferred until device is wokenup, it may be also
+ * called a second time with same parameters from the wakeup ISR.
+ *
+ * This function calls dw3000_check_operational_state() and save and defer
+ * the operation if DEEP-SLEEP is possible according delay.
+ *
+ * If RX must be enable immediately, this function configure STS, PDOA and
+ * Antenna pair before enable RX by calling dw3000_rx_enable().
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_do_rx_enable(struct dw3000 *dw,
+ const struct mcps802154_rx_frame_config *config,
+ int frame_idx)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ struct mcps802154_llhw *llhw = dw->llhw;
+ u32 cur_time_dtu = 0;
+ u32 rx_date_dtu = 0;
+ u32 timeout_pac = 0;
+ u32 frame_timeout_dly = 0;
+ bool rx_delayed = true;
+ int delay_dtu = 0;
+ bool can_sync = false;
+ int rc;
+ bool pdoa_enabled;
+ u8 sts_mode;
+
+ trace_dw3000_mcps_rx_enable(dw, config->flags, config->timeout_dtu);
+
+ /* Ensure CFO is checked if responder wait first frame of round. */
+ dw->data.check_cfo =
+ !!(config->flags & MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND);
+ /* Calculate the transfer date. */
+ if (config->flags & MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU) {
+ rx_date_dtu =
+ config->timestamp_dtu - DW3000_RX_ENABLE_STARTUP_DTU;
+ } else {
+ /* Receive immediately. */
+ rx_delayed = false;
+ }
+
+ if (rx_delayed) {
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+ delay_dtu = (int)(rx_date_dtu - cur_time_dtu);
+ if (delay_dtu < 0) {
+ trace_dw3000_mcps_rx_enable_too_late(dw, rx_date_dtu,
+ cur_time_dtu);
+ return -ETIME;
+ }
+ }
+
+ /* We are always allowed to sleep & sync at the beginning of a block. */
+ if (frame_idx == 0) {
+ dw->need_ranging_clock = false;
+ can_sync = true;
+ }
+
+ /* For delayed RX, where delay_dtu != 0, enter/leave deep sleep */
+ rc = dw3000_check_operational_state(dw, delay_dtu, can_sync);
+ if (rc) {
+ /*
+ * Handle error cases first :
+ * - Do not fail if we are in deep sleep/wakeup state
+ */
+ if (rc < 0 && rc != -EIO)
+ return rc;
+ /* Save parameters to activate RX delayed when
+ wakeup later */
+
+ dw->wakeup_done_cb = dw3000_wakeup_done_to_rx;
+ dss->next_operational_state = DW3000_OP_STATE_RX;
+ dss->rx_config = *config;
+ dss->frame_idx = frame_idx;
+ return 0;
+ }
+ /* All operation below require the DW chip is in IDLE_PLL state */
+
+ /* Calculate the preamble timeout. */
+ if (config->timeout_dtu == 0) {
+ timeout_pac = dw->pre_timeout_pac;
+ } else if (config->timeout_dtu != -1) {
+ timeout_pac = dw->pre_timeout_pac +
+ dtu_to_pac(llhw, config->timeout_dtu);
+ } else {
+ /* No timeout. */
+ }
+
+ /* Add the frame timeout if needed. */
+ if (timeout_pac && config->frame_timeout_dtu) {
+ frame_timeout_dly =
+ dtu_to_dly(llhw, config->frame_timeout_dtu) +
+ pac_to_dly(llhw, timeout_pac);
+ }
+
+ /* Start SPI queuing mode to minimise number of SPI messages
+ Queue will be flushed by dw3000_tx_frame() itself. */
+ dw3000_spi_queue_start(dw);
+
+ /* Enable STS */
+ pdoa_enabled =
+ !!(config->flags & MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA);
+ sts_mode = FIELD_GET(MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK,
+ config->flags);
+ rc = dw3000_set_sts_pdoa(dw, sts_mode,
+ sts_to_pdoa(sts_mode, pdoa_enabled));
+ if (unlikely(rc))
+ goto fail;
+ /* Ensure correct RX antennas are selected. */
+ rc = dw3000_set_rx_antennas(dw, config->ant_set_id, pdoa_enabled, frame_idx);
+ if (unlikely(rc))
+ goto fail;
+ if (config->flags & MCPS802154_RX_FRAME_CONFIG_AACK) {
+ dw3000_enable_autoack(dw, false);
+ } else {
+ dw3000_disable_autoack(dw, false);
+ }
+ rc = dw3000_setrxtimeout(dw, frame_timeout_dly);
+ if (unlikely(rc))
+ goto fail;
+ rc = dw3000_rx_enable(dw, rx_delayed, rx_date_dtu, timeout_pac);
+ if (unlikely(rc))
+ goto fail;
+
+ /* Store ranging clock requirement for next operation */
+ dw->need_ranging_clock =
+ (config->flags &
+ MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK) != 0;
+
+fail:
+ if (rc)
+ /* Ensure SPI queuing mode disabled on error */
+ dw3000_spi_queue_reset(dw, rc);
+ trace_dw3000_return_int(dw, rc);
+ return rc;
+}
+
+static irqreturn_t dw3000_irq_handler(int irq, void *context)
+{
+ struct dw3000 *dw = context;
+
+ atomic64_inc(&dw->power.interrupts);
+ dw3000_enqueue_irq(dw);
+
+ return IRQ_HANDLED;
+}
+
+static void dw3000_setup_one_regulator(const char *name, struct device *dev,
+ struct regulator **out)
+{
+ struct regulator *regulator;
+
+ regulator = devm_regulator_get_optional(dev, name);
+ if (IS_ERR(regulator)) {
+ int err = PTR_ERR(regulator);
+ dev_notice(dev, "Fail to get %s regulator (%d)\n", name, err);
+ regulator = NULL;
+ }
+ *out = regulator;
+}
+
+/**
+ * dw3000_setup_regulators() - request regulator
+ * @dw: the DW device to get regulator config from DT
+ *
+ * This will search for configured regulators to use from the device tree.
+ * All of them are optional, so missing one will just be noticed to dmesg.
+ */
+void dw3000_setup_regulators(struct dw3000 *dw)
+{
+ struct dw3000_power_control *power = &dw->regulators;
+
+ dw3000_setup_one_regulator("power_reg_1p8", dw->dev,
+ &power->regulator_1p8);
+ dw3000_setup_one_regulator("power_reg_2p5", dw->dev,
+ &power->regulator_2p5);
+ dw3000_setup_one_regulator("power_reg", dw->dev, &power->regulator_vdd);
+
+ if (!power->regulator_1p8 && !power->regulator_2p5 &&
+ !power->regulator_vdd) {
+ dev_warn(dw->dev, "No regulators, assuming always on\n");
+ }
+}
+
+/**
+ * dw3000_setup_reset_gpio() - request reset GPIO
+ * @dw: the DW device to get reset GPIO config from DT
+ *
+ * Get the reset GPIO to use from the DT and configure it in OUTPUT
+ * open-drain mode and activate it. This ensure the DW device is
+ * in reset state, IRQ pin low from the end of this function.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_reset_gpio(struct dw3000 *dw)
+{
+ /* Initialise reset GPIO pin as output */
+ dw->reset_gpio = of_get_named_gpio(dw->dev->of_node, "reset-gpio", 0);
+ if (!gpio_is_valid(dw->reset_gpio)) {
+ /* Try with old name */
+ dw->reset_gpio = of_get_named_gpio(dw->dev->of_node,
+ "uwbhal,reset-gpio", 0);
+ }
+ if (!gpio_is_valid(dw->reset_gpio)) {
+ dev_warn(dw->dev, "device does not support GPIO RESET control");
+ return 0;
+ }
+ return devm_gpio_request_one(dw->dev, dw->reset_gpio,
+ GPIOF_DIR_OUT | GPIOF_OPEN_DRAIN |
+ GPIOF_INIT_LOW,
+ "dw3000-reset");
+}
+
+/**
+ * dw3000_setup_irq() - request IRQ for the device
+ * @dw: the DW device
+ *
+ * Ensure the IRQ is correctly configured and install the hard IRQ handler.
+ * The IRQ is immediately disabled, waiting the device to be started by MCPS.
+ *
+ * Note: If the reset GPIO is deasserted before this function, a spurious
+ * IRQ may be handled and dw3000_irq_handler() called. This IRQ is ignored
+ * if occurs before dw3000_init() call.
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_irq(struct dw3000 *dw)
+{
+ int rc;
+ int irq_flags, irq_gpio;
+
+ /* Check the presence of irq-gpio in DT. If so,
+ * use it as irq, if not, "interrupt-parent" or
+ * "interrupt-extended" should be provided and then used.
+ */
+ irq_gpio = of_get_named_gpio(dw->dev->of_node, "irq-gpio", 0);
+ if (irq_gpio > 0) {
+ if (!gpio_is_valid(irq_gpio))
+ return -EINVAL;
+
+ devm_gpio_request_one(dw->dev, irq_gpio, GPIOF_IN,
+ dev_name(dw->dev));
+ dw->spi->irq = gpio_to_irq(irq_gpio);
+ }
+
+ irq_flags = irq_get_trigger_type(dw->spi->irq);
+ if (!irq_flags) {
+ irq_flags = IRQF_TRIGGER_HIGH;
+ }
+
+ /* Hook interruption */
+ rc = devm_request_irq(dw->dev, dw->spi->irq, dw3000_irq_handler,
+ irq_flags, dev_name(dw->dev), dw);
+ if (rc) {
+ dev_err(dw->dev, "could not request the IRQ %d: %d\n",
+ dw->spi->irq, rc);
+ return rc;
+ }
+
+ /* Disable interrupt before enabling the device */
+ disable_irq_nosync(dw->spi->irq);
+
+ return 0;
+}
+
+/**
+ * dw3000_setup_wifi_coex() - request wifi coex parameters for the device
+ * @dw: the DW device to get wifi coex parameters from DT
+ *
+ * Get the wifi coex parameters from the DT :
+ *
+ * wificoex_gpio : wifi coexistence GPIO number
+ * If not set, it is disabled.
+ *
+ * wificoex_delay_us : delay between wifi coexistence GPIO activation and TX/RX in us.
+ * Default is 1000us.
+ *
+ * wificoex_interval_us : minimum interval between two operations in us under which
+ * wifi coexistence GPIO is kept active.
+ * Default is 2000us.
+ *
+ * wificoex_margin_us: margin to add to the wifi coexistece delay
+ * for SPI transactions
+ * Default is 300us.
+ *
+ * All these parameters are optional
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_wifi_coex(struct dw3000 *dw)
+{
+ int rc;
+ struct device_node *node = dw->dev->of_node;
+
+ /* Initialize wifi coex parameters to default values */
+ dw->coex_gpio = -1;
+ dw->coex_delay_us = 1000;
+ dw->coex_margin_us = 300;
+ dw->coex_interval_us = 2000;
+
+ /*
+ * Get wificoex_gpio :
+ * If not set stop here to get parameters from DT
+ * because the feature is disabled
+ */
+ if (of_find_property(node, "wificoex_gpio", NULL)) {
+ rc = of_property_read_u8(node, "wificoex_gpio", &dw->coex_gpio);
+ if (rc) {
+ dev_err(dw->dev, "fail to get 'wificoex_gpio' %s\n",
+ node->name);
+ return rc;
+ }
+ } else {
+ dev_info(dw->dev, "gpio coex disabled\n");
+ return 0;
+ }
+ /* Get wificoex_delay_us */
+ if (of_find_property(node, "wificoex_delay_us", NULL)) {
+ rc = of_property_read_u32(node, "wificoex_delay_us",
+ &dw->coex_delay_us);
+ if (rc) {
+ dev_err(dw->dev, "fail to get 'wificoex_delay_us' %s\n",
+ node->name);
+ return rc;
+ }
+ }
+ /* Get wificoex_interval_us */
+ if (of_find_property(node, "wificoex_interval_us", NULL)) {
+ rc = of_property_read_u32(node, "wificoex_interval_us",
+ &dw->coex_interval_us);
+ if (rc) {
+ dev_err(dw->dev,
+ "fail to get 'wificoex_interval_us' %s\n",
+ node->name);
+ return rc;
+ }
+ }
+ /* Get wificoex_margin_us */
+ if (of_find_property(node, "wificoex_margin_us", NULL)) {
+ rc = of_property_read_u32(node, "wificoex_margin_us",
+ &dw->coex_margin_us);
+ if (rc) {
+ dev_err(dw->dev,
+ "fail to get 'wificoex_margin_us' %s\n",
+ node->name);
+ return rc;
+ }
+ }
+ dev_info(dw->dev,
+ "gpio coex enabled : "
+ "gpio %u, delay %u, interval %u, margin %u\n",
+ dw->coex_gpio, dw->coex_delay_us, dw->coex_interval_us,
+ dw->coex_margin_us);
+
+ return 0;
+}
+
+/**
+ * dw3000_setup_thread_cpu() - request thread cpu for the device
+ * @dw: the DW device to get thread cpu from DT
+ * @dw3000_thread_cpu: pointer to the thread cpu value read in DT or -1
+ *
+ * Get the thread cpu# from the DT :
+ *
+ * CPU# on which the DW state machine's thread will run.
+ * If not set, processing thread is not bound to a specific CPU and
+ * the scheduler may move it, adding migration latencies.
+ *
+ * This property is optional
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_thread_cpu(struct dw3000 *dw, int *dw3000_thread_cpu)
+{
+ int rc;
+ struct device_node *node = dw->dev->of_node;
+
+ *dw3000_thread_cpu = -1;
+
+ if (of_find_property(node, "cpu", NULL)) {
+ rc = of_property_read_u32(node, "cpu", dw3000_thread_cpu);
+ if (rc) {
+ dev_err(dw->dev, "fail to get 'cpu' %s\n", node->name);
+ return rc;
+ }
+ }
+
+ dev_info(dw->dev, "thread cpu %d\n", *dw3000_thread_cpu);
+
+ return 0;
+}
+
+/**
+ * dw3000_setup_qos_latency() - request qos latency for the device
+ * @dw: the DW device to get qos latency from DT
+ *
+ * Get the qos latency from the DT :
+ *
+ * Latency request to PM QoS on active ranging in microseconds.
+ * Default is 0, the minimum PM QoS latency.
+ *
+ * This property is optional
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_qos_latency(struct dw3000 *dw)
+{
+ int rc;
+ struct device_node *node = dw->dev->of_node;
+
+ if (of_find_property(node, "qos_latency", NULL)) {
+ rc = of_property_read_u32(node, "qos_latency",
+ &dw3000_qos_latency);
+ if (rc) {
+ dev_err(dw->dev, "fail to get 'qos_latency' %s\n",
+ node->name);
+ return rc;
+ }
+ }
+
+ dev_info(dw->dev, "qos latency %d\n", dw3000_qos_latency);
+
+ return 0;
+}
+
+/**
+ * dw3000_setup_regulator_delay() - request regulator delay for the device
+ * @dw: the DW device to get regulator delay from DT
+ *
+ * Get the regulator delay in us from the DT :
+ *
+ * Delay in us to wait after regulator(s) state has changed.
+ * Default is 1000 us.
+ *
+ * This property is optional
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_setup_regulator_delay(struct dw3000 *dw)
+{
+ int rc;
+ struct device_node *node = dw->dev->of_node;
+
+ if (of_find_property(node, "regulator_delay_us", NULL)) {
+ rc = of_property_read_u32(node, "regulator_delay_us",
+ &dw3000_regulator_delay_us);
+ if (rc) {
+ dev_err(dw->dev,
+ "fail to get 'regulator_delay_us' %s\n",
+ node->name);
+ return rc;
+ }
+ }
+
+ dev_info(dw->dev, "regulator delay %d us\n", dw3000_regulator_delay_us);
+
+ return 0;
+}
+
+int dw3000_hardreset(struct dw3000 *dw)
+{
+ int rc;
+
+ rc = dw3000_reset_assert(dw, true);
+ if (rc)
+ return rc;
+
+ usleep_range(DW3000_HARD_RESET_DELAY_US,
+ DW3000_HARD_RESET_DELAY_US + 100);
+
+ return dw3000_reset_assert(dw, false);
+}
+
+static inline int dw3000_clear_aonconfig(struct dw3000 *dw)
+{
+ int rc;
+ /* Clear any AON auto download bits (as reset will trigger AON
+ * download). */
+ rc = dw3000_reg_write16(dw, DW3000_AON_DIG_CFG_ID, 0, 0x00);
+ if (rc)
+ return rc;
+ /* Clear the wake-up configuration */
+ rc = dw3000_reg_write8(dw, DW3000_AON_CFG_ID, 0, 0x00);
+ if (rc)
+ return rc;
+ /* Upload the new configuration */
+ rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, 0);
+ if (rc)
+ return rc;
+ return dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0,
+ DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK);
+}
+
+static void _dw3000_softreset(struct dw3000 *dw)
+{
+ /* Clear any AON configurations (this will leave the device at FOSC/4,
+ * thus we need low SPI rate)
+ */
+ dw3000_clear_aonconfig(dw);
+ /* Make sure the new AON array config has been set */
+ usleep_range(DW3000_SOFT_RESET_DELAY_US,
+ DW3000_SOFT_RESET_DELAY_US + 100);
+ /* Need to make sure clock is not PLL as the PLL will be switched off
+ * as part of reset.
+ */
+ dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0, DW3000_FORCE_SYSCLK_FOSC);
+
+ /* Call version specific softreset */
+ dw->chip_ops->softreset(dw);
+
+ /* DW3000 needs a 10us sleep to let clk PLL lock after reset
+ * - the PLL will automatically lock after the reset
+ * Could also have polled the PLL lock flag,
+ * but then the SPI needs to be <= 7MHz !! So a simple delay is easier.
+ */
+ usleep_range(DW3000_SOFT_RESET_DELAY_US,
+ DW3000_SOFT_RESET_DELAY_US + 100);
+ /* DW3000 not in sleep_mode anymore */
+ dw->data.sleep_mode = 0;
+}
+
+static int dw3000_tx_write_data(struct dw3000 *dw, u8 *buffer, u16 len)
+{
+ if (len >= DW3000_TX_BUFFER_MAX_LEN)
+ return -1;
+ /* Directly write the data to the IC TX buffer */
+ dw3000_xfer(dw, DW3000_TX_BUFFER_ID, 0, len, buffer, DW3000_SPI_WR_BIT);
+ return 0;
+}
+
+static int do_change_speed(struct dw3000 *dw, const void *in, void *out)
+{
+ /* Need to reset pre-allocated message which hold speed */
+ return dw3000_transfers_reset(dw);
+}
+
+/**
+ * dw3000_change_speed() - change SPI speed and update transfers
+ * @dw: the DW device to change the speed
+ * @new_speed: the new speed to use for all transfers
+ *
+ * The speed is first changed into the SPI device structure than
+ * all pre-computed SPI transfers are updated if their speed isn't
+ * the same.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_change_speed(struct dw3000 *dw, u32 new_speed)
+{
+ struct dw3000_stm_command cmd_change_speed = { do_change_speed, NULL,
+ NULL };
+
+ /* Setup new SPI speed only if changed */
+ if (new_speed == dw->spi->max_speed_hz)
+ return 0;
+ /* Change SPI max speed */
+ dw->spi->max_speed_hz = new_speed;
+
+ /*
+ * Must call dw3000_transfers_reset() from our thread
+ * to guarantee no SPI transfer occurs during
+ * transfers structure are re-allocated
+ */
+ return dw3000_enqueue_generic(dw, &cmd_change_speed);
+}
+
+/**
+ * dw3000_softreset() - perform soft-reset of the device
+ * @dw: the device to soft-reset
+ *
+ * The SPI speed is changed to low-speed before doing the soft-reset and is
+ * restored to full-speed (the speed configured in device tree) after.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_softreset(struct dw3000 *dw)
+{
+ int rc;
+ /* Force slow SPI clock speed (at device level) */
+ dw3000_change_speed(dw, DW3000_SPI_SLOW_HZ);
+
+ /* Soft reset (require to known chip version) */
+ _dw3000_softreset(dw);
+
+ /* Re-read device ID to ensure bus is operational at low-speed */
+ rc = dw3000_check_devid(dw);
+ if (rc)
+ return rc;
+
+ /* Switch back to full SPI clock speed */
+ dw3000_change_speed(dw, dw->of_max_speed_hz);
+
+ /* Check device ID to ensure bus is operational at high-speed */
+ return dw3000_check_devid(dw);
+}
+
+static inline int dw3000_ctrl_rftx_blocks(struct dw3000 *dw, u32 chan,
+ int reg_fileid)
+{
+ u32 val;
+
+ if (reg_fileid != DW3000_RF_ENABLE_ID &&
+ reg_fileid != DW3000_RF_CTRL_MASK_ID)
+ return -EINVAL;
+ val = DW3000_RF_ENABLE_TX_SW_EN_BIT_MASK |
+ DW3000_RF_ENABLE_TX_EN_BIT_MASK |
+ DW3000_RF_ENABLE_TX_EN_BUF_BIT_MASK |
+ DW3000_RF_ENABLE_TX_BIAS_EN_BIT_MASK |
+ DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_MASK;
+ return dw3000_reg_or32(dw, reg_fileid, 0, val);
+}
+
+/**
+ * dw3000_rftx_blocks_autoseq_disable() - Disables automatic sequencing
+ * of the tx-blocks
+ * @dw: the DW device
+ * @chan: specifies the operating channel (e.g. 5 or 9)
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_rftx_blocks_autoseq_disable(struct dw3000 *dw,
+ u32 chan)
+{
+ return dw3000_ctrl_rftx_blocks(dw, chan, DW3000_RF_CTRL_MASK_ID);
+}
+
+/**
+ * dw3000_rftx_blocks_enable() - Enable RF blocks for TX
+ * @dw: the DW device
+ * @chan: specifies the operating channel (e.g. 5 or 9)
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_rftx_blocks_enable(struct dw3000 *dw, u32 chan)
+{
+ return dw3000_ctrl_rftx_blocks(dw, chan, DW3000_RF_ENABLE_ID);
+}
+
+/**
+ * dw3000_enable_rf_tx() - Turn on TX LDOs and enable RF blocks for TX
+ * @dw: the DW device
+ * @chan: specifies the operating channel (e.g. 5 or 9)
+ * @switch_ctrl: specifies whether the switch needs to be configured for TX
+ *
+ * Enable TX LDOs and allow TX blocks to be manually turned on by
+ * dw3000_rftx for a given channel.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_enable_rf_tx(struct dw3000 *dw, u32 chan, u8 switch_ctrl)
+{
+ int rc;
+ /* Turn on TX LDOs */
+ rc = dw3000_reg_or32(dw, DW3000_LDO_CTRL_ID, 0,
+ (DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_MASK));
+ rc = dw3000_reg_or32(dw, DW3000_LDO_CTRL_ID, 0,
+ (DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_MASK));
+ /* Enable RF blocks for TX (configure RF_ENABLE_ID register) */
+ rc = dw3000_rftx_blocks_enable(dw, chan);
+ if (rc)
+ return rc;
+ if (switch_ctrl) {
+ /* Configure the TXRX switch for TX mode */
+ return dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0x0,
+ DW3000_TXRXSWITCH_TX);
+ }
+ return 0;
+}
+
+/**
+ * dw3000_force_clocks() - Enable/disable clocks to particular digital blocks/system
+ * @dw: the DW device
+ * @clocks: set of clocks to enable/disable
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_force_clocks(struct dw3000 *dw, int clocks)
+{
+ if (clocks == DW3000_FORCE_CLK_SYS_TX) {
+ /* TX_BUF_CLK = ON & RX_BUF_CLK = ON */
+ u16 regvalue0 = DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_MASK |
+ DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_MASK;
+ /* SYS_CLK_SEL = PLL */
+ regvalue0 |= (u16)DW3000_FORCE_SYSCLK_PLL
+ << DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_OFFSET;
+ /* TX_CLK_SEL = ON */
+ regvalue0 |= (u16)DW3000_FORCE_CLK_PLL
+ << DW3000_CLK_CTRL_TX_CLK_SEL_BIT_OFFSET;
+ return dw3000_reg_write16(dw, DW3000_CLK_CTRL_ID, 0x0,
+ regvalue0);
+ }
+ if (clocks == DW3000_FORCE_CLK_AUTO) {
+ /* Restore auto clock mode */
+ return dw3000_reg_write16(
+ dw, DW3000_CLK_CTRL_ID, 0x0,
+ (u16)(DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_MASK |
+ DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_MASK |
+ DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_MASK));
+ }
+ return -EINVAL;
+}
+
+/**
+ * dw3000_setfinegraintxseq() - Enable/disable the fine grain TX sequencing
+ * @dw: the DW device
+ * @on: true to enable fine grain TX sequencing, false to disable it.
+ *
+ * This is enabled by default in the DW3000.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_setfinegraintxseq(struct dw3000 *dw, bool on)
+{
+ if (on) {
+ return dw3000_reg_write32(dw, DW3000_PWR_UP_TIMES_LO_ID, 2,
+ DW3000_PMSC_TXFINESEQ_ENABLE);
+ }
+ return dw3000_reg_write32(dw, DW3000_PWR_UP_TIMES_LO_ID, 2,
+ DW3000_PMSC_TXFINESEQ_DISABLE);
+}
+
+/**
+ * dw3000_repeated_cw() - Enable a repeated continuous waveform on the device
+ * @dw: the DW device
+ * @cw_enable: CW mode enable
+ * @cw_mode_config: CW configuration mode.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_repeated_cw(struct dw3000 *dw, int cw_enable,
+ int cw_mode_config)
+{
+ int rc;
+ /* Turn off TX Seq */
+ dw3000_setfinegraintxseq(dw, 0);
+ /* Correct if passing a threshold */
+ if (cw_mode_config > 0xF)
+ cw_mode_config = 0xF;
+ if (cw_enable > 3 || cw_enable < 1)
+ cw_enable = 4;
+
+ rc = dw3000_reg_write32(dw, DW3000_TX_TEST_ID, 0x0, 0x10 >> cw_enable);
+ if (unlikely(rc))
+ return rc;
+ return dw3000_reg_write32(dw, DW3000_PG_TEST_ID, 0x0,
+ cw_mode_config << ((cw_enable - 1) * 4));
+}
+
+/**
+ * dw3000_tx_setcwtone() - Start TX CW signal at specific channel frequency
+ * @dw: the DW device
+ * @on: true, enable the test mode and start transmitting a CW signal on the
+ * device, otherwise disable it and back to normal mode.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_tx_setcwtone(struct dw3000 *dw, bool on)
+{
+ struct dw3000_txconfig *txconfig = &dw->txconfig;
+ int rc;
+ /* Enable test mode */
+ if (on) {
+ u8 chan = dw->config.chan;
+
+ rc = dw3000_enable_rf_tx(dw, chan, 1);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_rftx_blocks_autoseq_disable(dw, chan);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_SYS_TX);
+ if (unlikely(rc))
+ return rc;
+ /* Go to test mode. PulseGen Channel 1, full power. */
+ rc = dw3000_repeated_cw(dw, 1, 0xF);
+ if (unlikely(rc))
+ return rc;
+ txconfig->testmode_enabled = true;
+ return 0;
+ }
+ /* Back to normal mode */
+ rc = dw3000_repeated_cw(dw, false, 0);
+ if (unlikely(rc))
+ return rc;
+ txconfig->testmode_enabled = false;
+ return 0;
+}
+
+static int dw3000_writetxfctrl(struct dw3000 *dw, u16 txFrameLength,
+ u16 txBufferOffset, bool ranging)
+{
+ struct dw3000_local_data *local = &dw->data;
+ u32 fctrl =
+ txFrameLength |
+ ((u32)txBufferOffset << DW3000_TX_FCTRL_TXB_OFFSET_BIT_OFFSET) |
+ ((u32)ranging << DW3000_TX_FCTRL_TR_BIT_OFFSET);
+ int rc;
+ /*
+ * Compare with the previous value stored if register access
+ * is necessary.
+ */
+ if (local->tx_fctrl == fctrl)
+ return 0;
+ /* Write the frame length to the TX frame control register */
+ rc = dw3000_reg_modify32(dw, DW3000_TX_FCTRL_ID, 0,
+ ~(u32)(DW3000_TX_FCTRL_TXB_OFFSET_BIT_MASK |
+ DW3000_TX_FCTRL_TR_BIT_MASK |
+ DW3000_TX_FCTRL_TXFLEN_BIT_MASK),
+ fctrl);
+ if (unlikely(rc))
+ return rc;
+ local->tx_fctrl = fctrl;
+ return 0;
+}
+
+/** dw3000_write_txctrl - Runtime configuration of TX parameters
+ * @dw: the DW device
+ *
+ * This function is called before packet transmission in order to set TX
+ * parameters (pgdelay, channel, pulse shape) according to the current antenna.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_write_txctrl(struct dw3000 *dw)
+{
+ struct dw3000_txconfig *txconfig = &dw->txconfig;
+ struct dw3000_config *config = &dw->config;
+ u32 txctrl;
+
+ /* Get default values and insert wanted pgdelay */
+ txctrl = config->chan == 9 ? DW3000_RF_TXCTRL_CH9 :
+ DW3000_RF_TXCTRL_CH5;
+ txctrl = (txctrl & ~DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK) |
+ (txconfig->PGdly & DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK);
+
+ /* Configure pulse shape */
+ if (config->alternate_pulse_shape) {
+ txctrl |= DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK;
+ } else {
+ txctrl &= ~DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK;
+ }
+
+ return dw3000_reg_write32(dw, DW3000_TX_CTRL_HI_ID, 0, txctrl);
+}
+
+/**
+ * dw3000_setrxaftertxdelay() - Set time Wait-for-Response Time
+ * @dw: the DW device
+ * @rx_delay_time: Wait-for-Response time in units of approx 1us
+ * or 128 system clock cycles
+ *
+ * It is used to configure the turn-around time between TX complete and RX
+ * enable when the wait for response function is being used.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_setrxaftertxdelay(struct dw3000 *dw, u32 rx_delay_time)
+{
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+
+ if (unlikely(rx_delay_time > DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_MASK))
+ return -EINVAL;
+ if (local->w4r_time == rx_delay_time)
+ return 0;
+ rc = dw3000_reg_write32(
+ dw, DW3000_ACK_RESP_ID, 0,
+ local->ack_time << DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET |
+ rx_delay_time);
+ if (unlikely(rc))
+ return rc;
+ local->w4r_time = rx_delay_time;
+ return 0;
+}
+
+/**
+ * dw3000_tx_frame() - prepare, execute or program TX
+ * @dw: the DW device
+ * @skb: TX socket buffer
+ * @tx_delayed: true if TX delayed must be use
+ * @tx_date_dtu: the date at which TX must be executed
+ * @rx_delay_dly: positive if needed to active RX after TX otherwise null
+ * @rx_timeout_pac: the preamble detect timeout
+ * @ranging: true if transmitting ranging frame
+ *
+ * This function prepares, executes or programs TX according to a given socket
+ * buffer pointer provided by the MCPS.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_tx_frame(struct dw3000 *dw, struct sk_buff *skb, bool tx_delayed,
+ u32 tx_date_dtu, int rx_delay_dly, u32 rx_timeout_pac,
+ bool ranging)
+{
+ u32 cur_time_dtu = 0;
+ int rc, len;
+ u8 cmd;
+
+ /* Print the transmitted frame in hexadecimal characters */
+ if (unlikely(DEBUG)) {
+ if (skb)
+ print_hex_dump_bytes(
+ "dw3000: ieee802154: transmitted frame:",
+ DUMP_PREFIX_NONE, skb->data, skb->len);
+ else
+ dev_dbg(dw->dev,
+ "dw3000: ieee802154: transmitted frame without data");
+ }
+
+ /* Read current DTU time to compare with next transfer time */
+ if (dw->coex_gpio >= 0)
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+
+ /* Activate RX after TX ? */
+ if (rx_delay_dly >= 0) {
+ rc = dw3000_setrxaftertxdelay(dw, rx_delay_dly);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_setpreambledetecttimeout(dw, rx_timeout_pac);
+ if (unlikely(rc))
+ return rc;
+ }
+
+ if (skb) {
+ /* FCS is already included while pctt is enabled */
+ len = skb->len + (dw->pctt.enabled ? 0 : IEEE802154_FCS_LEN);
+ /* Write frame properties to the transmit frame control register */
+ if (WARN_ON(len > dw->data.max_frames_len))
+ return -EINVAL;
+ /* Write frame data to the DW IC buffer */
+ if (dw3000_tx_write_data(dw, skb->data, skb->len) != 0) {
+ dev_err(dw->dev, "cannot write frame data to DW IC\n");
+ return -EINVAL;
+ }
+ } else
+ len = 0;
+
+ if (dw->txconfig.smart)
+ dw3000_adjust_tx_power(dw, len);
+
+ rc = dw3000_writetxfctrl(dw, len, 0, ranging);
+ if (unlikely(rc))
+ return rc;
+
+ rc = dw3000_write_txctrl(dw);
+ if (unlikely(rc))
+ return rc;
+
+ /* Flush SPI queue before dw3000_coex_start() because coex use it
+ already. */
+ rc = dw3000_spi_queue_flush(dw);
+ if (unlikely(rc))
+ return rc;
+
+ /* Update TX parameters according to Wifi coexistence */
+ rc = dw3000_coex_start(dw, &tx_delayed, &tx_date_dtu, cur_time_dtu);
+ if (unlikely(rc))
+ return rc;
+
+ if (!tx_delayed) {
+ /* Program immediate transmission. */
+ cmd = rx_delay_dly >= 0 ? DW3000_CMD_TX_W4R : DW3000_CMD_TX;
+ rc = dw3000_write_fastcmd(dw, cmd);
+ if (unlikely(rc))
+ goto stop_coex;
+ /* W4R mode are handled by TX event IRQ handler */
+ dw3000_power_stats(dw, DW3000_PWR_TX, len);
+ return 0;
+ }
+
+ /* Restart SPI queuing mode */
+ dw3000_spi_queue_start(dw);
+ /* Set transmission date. */
+ rc = dw3000_setdelayedtrxtime(dw, tx_date_dtu);
+ if (unlikely(rc))
+ goto stop_coex;
+ /* Program delayed transmission. */
+ cmd = rx_delay_dly >= 0 ? DW3000_CMD_DTX_W4R : DW3000_CMD_DTX;
+ rc = dw3000_write_fastcmd(dw, cmd);
+ if (unlikely(rc))
+ goto stop_coex;
+ /* Execute SPI queued transfers */
+ rc = dw3000_spi_queue_flush(dw);
+ if (unlikely(rc))
+ goto stop_coex;
+
+ /* W4R mode are handled by TX event IRQ handler */
+ dw3000_power_stats(dw, DW3000_PWR_TX, len);
+ /* Check if late */
+ rc = dw3000_check_hpdwarn(dw);
+ if (unlikely(rc)) {
+ if (rc == -ETIME) {
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+ trace_dw3000_mcps_tx_frame_too_late(dw, tx_date_dtu,
+ cur_time_dtu);
+ }
+ goto stop_coex;
+ }
+ return dw->chip_ops->check_tx_ok(dw);
+stop_coex:
+ dw3000_coex_stop(dw);
+ return rc;
+}
+
+/**
+ * dw3000_do_tx_frame() - handle TX frame MCPS operation
+ * @dw: the device on which transmit frame
+ * @config: TX parameters from MCPS
+ * @skb: the frame to transmit
+ * @frame_idx: Frame index in a continuous block
+ *
+ * This function is called to execute all required operation to transmit the
+ * given frame using provided parameters.
+ *
+ * Since TX may be deferred until device is wokenup, it may be also called a
+ * second time with same parameters from the wakeup ISR.
+ *
+ * This function calls dw3000_check_operational_state() and save and defer
+ * the operation if DEEP-SLEEP is possible according delay.
+ *
+ * If TX frame must be done immediately, this function configure STS, PDOA,
+ * Antenna pair, and delay for auto-RX before sending SKB by calling
+ * dw3000_tx_frame().
+ *
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_do_tx_frame(struct dw3000 *dw,
+ const struct mcps802154_tx_frame_config *config,
+ struct sk_buff *skb, int frame_idx)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ struct mcps802154_llhw *llhw = dw->llhw;
+ u32 cur_time_dtu = 0;
+ u32 tx_date_dtu = 0;
+ int rx_delay_dly = -1;
+ u32 rx_timeout_pac = 0;
+ bool tx_delayed = true;
+ bool ranging = false;
+ int delay_dtu = 0;
+ bool can_sync = false;
+ int rc;
+ u8 sts_mode;
+
+ trace_dw3000_mcps_tx_frame(dw, config->flags, skb ? skb->len : 0);
+
+ /* Calculate the transfer date.*/
+ if (config->flags & MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU) {
+ tx_date_dtu = config->timestamp_dtu + llhw->shr_dtu;
+ } else {
+ /* Send immediately. */
+ tx_delayed = false;
+ }
+ if (config->flags & MCPS802154_TX_FRAME_CONFIG_RANGING)
+ ranging = true;
+
+ if (tx_delayed) {
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+ delay_dtu = (int)(tx_date_dtu - cur_time_dtu);
+ if (delay_dtu < 0) {
+ dev_err(dw->dev,
+ "too late to program delayed tx date_dtu=%x current_dtu=%x\n",
+ tx_date_dtu, cur_time_dtu);
+ return -ETIME;
+ }
+ }
+
+ /* We are always allowed to sleep & sync at the beginning of a block. */
+ if (!frame_idx) {
+ dw->need_ranging_clock = false;
+ can_sync = true;
+ }
+
+ /* For delayed TX, where delay_dtu != 0, enter/leave deep sleep */
+ rc = dw3000_check_operational_state(dw, delay_dtu, can_sync);
+ if (rc) {
+ /* Handle error cases first */
+ if (rc < 0)
+ return rc;
+ /* Save parameters to activate TX delayed when
+ wakeup later */
+ dw->wakeup_done_cb = dw3000_wakeup_done_to_tx;
+ dss->next_operational_state = DW3000_OP_STATE_TX;
+ dss->tx_config = *config;
+ dss->tx_skb = skb;
+ dss->frame_idx = frame_idx;
+ return 0;
+ }
+ /* All operation below require the DW chip is in IDLE_PLL state */
+
+ /* Start SPI queuing mode to minimise number of SPI messages
+ Queue will be flushed by dw3000_tx_frame() itself. */
+ dw3000_spi_queue_start(dw);
+
+ /* Oscillate XTAL around calibrated value to maximise successful PDoA probability */
+ if (DW3000_XTAL_BIAS &&
+ (config->flags & MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND)) {
+ dw->data.xtal_bias = (dw->data.xtal_bias > 0 ?
+ -DW3000_XTAL_BIAS :
+ DW3000_XTAL_BIAS);
+ rc = dw3000_prog_xtrim(dw);
+ if (unlikely(rc))
+ goto fail;
+ }
+ /* Enable STS */
+ sts_mode = FIELD_GET(MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK,
+ config->flags);
+ rc = dw3000_set_sts_pdoa(
+ dw, sts_mode,
+ sts_to_pdoa(sts_mode,
+ config->flags &
+ MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA));
+ if (unlikely(rc))
+ goto fail;
+ /* Ensure correct TX antenna is selected. */
+ rc = dw3000_set_tx_antenna(dw, config->ant_set_id);
+ if (unlikely(rc))
+ goto fail;
+
+ if (config->rx_enable_after_tx_dtu > 0) {
+ /* Disable auto-ack if it was previously enabled. */
+ dw3000_disable_autoack(dw, false);
+ /* Calculate the after tx rx delay. */
+ rx_delay_dly =
+ dtu_to_dly(llhw, config->rx_enable_after_tx_dtu) -
+ DW3000_RX_ENABLE_STARTUP_DLY;
+ rx_delay_dly = rx_delay_dly >= 0 ? rx_delay_dly : 0;
+ /* Calculate the after tx rx timeout. */
+ if (config->rx_enable_after_tx_timeout_dtu == 0) {
+ rx_timeout_pac = dw->pre_timeout_pac;
+ } else if (config->rx_enable_after_tx_timeout_dtu != -1) {
+ rx_timeout_pac =
+ dw->pre_timeout_pac +
+ dtu_to_pac(
+ llhw,
+ config->rx_enable_after_tx_timeout_dtu);
+ } else {
+ /* No timeout. */
+ }
+ }
+ rc = dw3000_tx_frame(dw, skb, tx_delayed, tx_date_dtu, rx_delay_dly,
+ rx_timeout_pac, ranging);
+ if (unlikely(rc))
+ goto fail;
+
+ /* Store ranging clock requirement for next operation */
+ dw->need_ranging_clock =
+ (config->flags &
+ MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK) != 0;
+fail:
+ if (rc)
+ /* Ensure SPI queuing mode disabled on error */
+ dw3000_spi_queue_reset(dw, rc);
+ trace_dw3000_return_int(dw, rc);
+ return rc;
+}
+
+static int dw3000_rx_read_data(struct dw3000 *dw, u8 *buffer, u16 len,
+ u16 offset)
+{
+ u32 rx_buff_addr;
+
+ /* If the flag is 0x4 we are reading from RX_BUFFER_B */
+ if (dw->data.dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B) {
+ rx_buff_addr = DW3000_RX_BUFFER_B_ID;
+ /* Reading from RX_BUFFER_A - also when non-double buffer mode */
+ } else {
+ rx_buff_addr = DW3000_RX_BUFFER_A_ID;
+ }
+ if ((offset + len) > DW3000_RX_BUFFER_MAX_LEN)
+ return -EINVAL;
+ if (offset <= DW3000_REG_DIRECT_OFFSET_MAX_LEN) {
+ /* Directly read data from the IC to the buffer */
+ return dw3000_xfer(dw, rx_buff_addr, offset, len, buffer,
+ DW3000_SPI_RD_BIT);
+ } else {
+ /* Program the indirect offset registers B
+ * for specified offset to RX buffer
+ */
+ dw3000_reg_write32(dw, DW3000_INDIRECT_ADDR_A_ID, 0,
+ (rx_buff_addr >> 16));
+ dw3000_reg_write32(dw, DW3000_ADDR_OFFSET_A_ID, 0, offset);
+
+ /* Indirectly read data from the IC to the buffer */
+ return dw3000_xfer(dw, DW3000_INDIRECT_POINTER_A_ID, 0, len,
+ buffer, DW3000_SPI_RD_BIT);
+ }
+}
+
+static int dw3000_rx_frame(struct dw3000 *dw,
+ const struct dw3000_isr_data *data)
+{
+ struct dw3000_rx *rx = &dw->rx;
+ size_t len = data->datalength;
+ struct sk_buff *skb;
+ unsigned long flags;
+ u8 *buffer;
+ int rc;
+
+ /* If rx_disable() callback was called, we can't call mcps802154_rx_frame() */
+ if (dw3000_rx_busy(dw, true))
+ return 0;
+ /* Read frame data into skb */
+ if (len) {
+ /* Allocate new skb (including space for FCS added by ieee802154_rx) */
+ skb = dev_alloc_skb(len + IEEE802154_FCS_LEN);
+ if (!skb) {
+ dev_err(dw->dev, "RX buffer allocation failed\n");
+ rc = -ENOMEM;
+ goto err_spi;
+ }
+ if (dw->pctt.enabled)
+ len += IEEE802154_FCS_LEN;
+ buffer = skb_put(skb, len);
+ /* Directly read data from the IC to the buffer */
+ rc = dw3000_rx_read_data(dw, buffer, len, 0);
+ if (rc)
+ goto err_spi;
+ } else {
+ /* SP3 case: Frame with STS and without data */
+ skb = NULL;
+ }
+ /* Store received frame */
+ spin_lock_irqsave(&rx->lock, flags);
+ WARN_ON(rx->skb);
+ rx->skb = skb;
+ rx->flags = data->rx_flags | (skb ? 0 : DW3000_RX_FLAG_ND);
+ rx->ts_rctu = data->ts_rctu;
+ spin_unlock_irqrestore(&rx->lock, flags);
+ /* Print the received frame in hexadecimal characters */
+ if (unlikely(DEBUG)) {
+ dev_dbg(dw->dev, "frame config: len=%lu, rxflags=0x%.2x", len,
+ data->rx_flags);
+ if (skb)
+ print_hex_dump_bytes("dw3000: frame data: ",
+ DUMP_PREFIX_NONE, skb->data, len);
+ }
+ /* Inform MCPS 802.15.4 that we received a frame */
+ mcps802154_rx_frame(dw->llhw);
+ WARN_ON_ONCE(dw3000_rx_busy(dw, false));
+ return 0;
+
+err_spi:
+ /* Release the socker buffer */
+ if (skb)
+ dev_kfree_skb_any(skb);
+ WARN_ON_ONCE(dw3000_rx_busy(dw, false));
+ return rc;
+}
+
+/**
+ * dw3000_set_interrupt() - Select the interruption's events to mask or unmask
+ * @dw: the DW device
+ * @bitmask: bitmap of selected events
+ * @opt:
+ * - DW3000_DISABLE_INT: unmask the selected events
+ * - DW3000_ENABLE_INT: mask the selected events
+ * - DW3000_ENABLE_INT_ONLY: mask the selected events and unmask the rest
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_set_interrupt(struct dw3000 *dw, u32 bitmask,
+ const enum int_options opt)
+{
+ if (opt == DW3000_ENABLE_INT_ONLY) {
+ /* Overriding */
+ return dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0,
+ bitmask);
+ } else {
+ if (opt == DW3000_ENABLE_INT) {
+ /* Set the bits */
+ return dw3000_reg_or32(dw, DW3000_SYS_ENABLE_LO_ID, 0,
+ bitmask);
+ } else {
+ /* Clear the bits */
+ return dw3000_reg_and32(dw, DW3000_SYS_ENABLE_LO_ID, 0,
+ (u32)(~bitmask));
+ }
+ }
+}
+
+/**
+ * dw3000_setplenfine() - Configure frame preamble length
+ * @dw: the DW device
+ * @preamble_len: length of the preamble
+ *
+ * A preamble_len value of 0 disables this setting and the length of the frame
+ * will be dependent on the TXPSR_PE setting as configured
+ * by dw3000_configure() function.
+ * The frame premable length can be configured in steps of 8, from 16
+ * to 2048 symbols. If a non-zero value is configured, then the TXPSR_PE
+ * setting is ignored.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_setplenfine(struct dw3000 *dw, u8 preamble_len)
+{
+ return dw3000_reg_write8(dw, DW3000_TX_FCTRL_HI_ID, 1, preamble_len);
+}
+
+/**
+ * _dw3000_get_sts_mnth() - Calculate the adjusted STS minimum threshold
+ * @cipher: the STS length's factor
+ * @threshold: the STS default threshold
+ * @shift_val: shift value for adjustment
+ *
+ * Return: the value of the adjusted STS minimum threshold.
+ */
+static u16 _dw3000_get_sts_mnth(u16 cipher, u8 threshold, u8 shift_val)
+{
+ u32 value = cipher * (u32)threshold;
+
+ if (shift_val == 3) {
+ /* Factor to sqrt(2) */
+ value *= DW3000_SQRT2_FACTOR;
+ value >>= DW3000_SQRT2_SHIFT_VAL;
+ }
+ /* Round the result of the shift by 11 (or division by 2048) */
+ return (u16)((value + DW3000_STS_MNTH_ROUND_SHIFT) >>
+ DW3000_STS_MNTH_SHIFT);
+}
+
+/**
+ * _dw3000_sts_is_enabled() - Return true if STS is enabled
+ * @dw: the DW device
+ *
+ * Return: true is STS is enable.
+ */
+static inline bool _dw3000_sts_is_enabled(struct dw3000 *dw)
+{
+ return (dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) !=
+ DW3000_STS_MODE_OFF;
+}
+
+/**
+ * dw3000_configure_otp() - set device's OTP configuration
+ * @dw: the DW device
+ * @config: the DW device's configuration
+ *
+ * This function depend on stsLength if STS is enabled, so require
+ * stsLength is set before stsMode is updated.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_configure_otp(struct dw3000 *dw, struct dw3000_config *config)
+{
+ u16 preamble_len = _plen_info[config->txPreambLength - 1].symb;
+ /* Update the preamble length regarding STS mode */
+ if (_dw3000_sts_is_enabled(dw))
+ preamble_len +=
+ DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength) * 8;
+ /* Configure gearing tables for non-SCP mode */
+ if (preamble_len >= 256) {
+ dw->data.sleep_mode |= DW3000_ALT_GEAR | DW3000_SEL_GEAR0;
+ return dw3000_reg_modify32(
+ dw, DW3000_NVM_CFG_ID, 0,
+ ~(DW3000_NVM_CFG_GEAR_ID_BIT_MASK),
+ DW3000_OPSET_LONG | DW3000_NVM_CFG_GEAR_KICK_BIT_MASK);
+ } else {
+ return dw3000_reg_modify32(
+ dw, DW3000_NVM_CFG_ID, 0,
+ ~(DW3000_NVM_CFG_GEAR_ID_BIT_MASK),
+ DW3000_OPSET_SHORT | DW3000_NVM_CFG_GEAR_KICK_BIT_MASK);
+ }
+}
+
+/**
+ * dw3000_configure_sts() - set device's STS configuration
+ * @dw: the DW device
+ * @config: the DW device's configuration
+ *
+ * STS Minimum Threshold `sts_mnth` needs to be adjusted with changing
+ * STS length. To adjust the `sts_mnth` following formula can be used:
+ *
+ * sts_mnth = sqrt(x/y)*default_sts_mnth
+ *
+ * where
+ * - `default_sts_mnth` is 0x10
+ * - `x` is the length of the STS in units of 8 (i.e. 8 for 64 length,
+ * 16 for 128 length etc..)
+ * - `y` is either 8 or 16, 8 when no PDOA or PDOA mode 1 and 16
+ * for PDOA mode 3.
+ *
+ * The API does not use the formula and the `sts_mnth` value is derived
+ * from approximation formula as given by _dw3000_get_sts_mnth() function.
+ * The API here supports STS lengths as listed in `dw3000_sts_lengths enum`,
+ * which are 32, 64, 128, 256, 512, 1024 and 2048.
+ * The enum value is used as the index into`dw3000_sts_length_factors` array.
+ * The array has values which are generated by:
+ *
+ * val = sqrt(stsLength/16)*2048
+ *
+ * where
+ * - `stsLength` value of the given `dw3000_sts_lengths enum`
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_configure_sts(struct dw3000 *dw, struct dw3000_config *config)
+{
+ u16 sts_mnth;
+
+ if (!_dw3000_sts_is_enabled(dw))
+ return 0;
+ /* Update default minimum STS threshold according STS length. */
+ dw->data.ststhreshold = (s16)DW3000_STSQUAL_THRESH_64(
+ (u32)(DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength)));
+ /* Configure CIA STS lower bound */
+ if ((config->pdoaMode == DW3000_PDOA_M1) ||
+ (config->pdoaMode == DW3000_PDOA_M0)) {
+ /**
+ * In PDOA mode 1, number of accumulated symbols
+ * is the whole length of the STS
+ */
+ sts_mnth = _dw3000_get_sts_mnth(
+ dw3000_sts_length_factors[(u8)(config->stsLength)],
+ DW3000_CIA_MANUALLOWERBOUND_TH_64, 3);
+ } else {
+ /**
+ * In PDOA mode 3 number of accumulated symbols
+ * is half of the length of STS symbols
+ */
+ sts_mnth = _dw3000_get_sts_mnth(
+ dw3000_sts_length_factors[(u8)(config->stsLength)],
+ DW3000_CIA_MANUALLOWERBOUND_TH_64, 4);
+ }
+ /* TODO: put register value in cache */
+ return dw3000_reg_modify16(
+ dw, DW3000_CY_CONFIG_LO_ID, 2,
+ (u16) ~(DW3000_CY_CONFIG_LO_MANUALLOWERBOUND_BIT_MASK >> 16),
+ sts_mnth & 0x7F);
+}
+
+/**
+ * _swap128() - swap bytes of a 128 bits unaligned buffer
+ * @dst: the in-stack 64 bits aligned 128 bits destination buffer
+ * @src: the 128 bits buffer with MSB first to swap
+ */
+static inline void _swap128(__le64 *dst, const void *src)
+{
+ u64 val = get_unaligned_be64(src);
+ dst[1] = cpu_to_le64(val);
+ val = get_unaligned_be64(src + sizeof(u64));
+ dst[0] = cpu_to_le64(val);
+}
+
+/**
+ * dw3000_configure_sts_key() - set device's STS KEY
+ * @dw: the DW device
+ * @key: the 128 bits STS KEY to configure (MSB first)
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_sts_key(struct dw3000 *dw, const u8 *key)
+{
+ static const u8 nul_key[AES_KEYSIZE_128] = {
+ 0,
+ };
+ /* TODO: Transfer the STS key securely to DW3000 using the AES engine.
+ * See 5.8.4.3 Decrypt STS KEY into STS KEY registers. */
+ struct dw3000_local_data *data = &dw->data;
+ struct dw3000_config *config = &dw->config;
+ bool changed;
+ bool is_nul_key;
+ int rc;
+ /* Check NUL key */
+ is_nul_key = memcmp(key, nul_key, AES_KEYSIZE_128) == 0;
+ if (is_nul_key) {
+ /* DW3000 specific. Use Super Deterministic Code if NUL key is
+ set. Check if change required. */
+ changed = (config->stsMode & DW3000_STS_MODE_SDC) == 0;
+ /* No need to send key. */
+ } else {
+ bool kc = true;
+ /* Need to remove SDC flag. Check if change is required. */
+ changed = (config->stsMode & DW3000_STS_MODE_SDC) != 0;
+ /* Update Key. */
+ if (!changed)
+ kc = memcmp(key, data->sts_key, AES_KEYSIZE_128) != 0;
+ if (kc) {
+ __le64 swapped_key[AES_KEYSIZE_128 / sizeof(__le64)];
+ _swap128(swapped_key, key);
+ /* Key has changed, update it. */
+ rc = _dw3000_reg_write(dw, DW3000_STS_KEY_ID, 0,
+ AES_KEYSIZE_128, swapped_key);
+ if (rc)
+ return rc;
+ /* Update cached key. */
+ memcpy(data->sts_key, key, AES_KEYSIZE_128);
+ }
+ }
+ if (changed) {
+ /* SDC bit had changed. Sync register. */
+ rc = dw3000_reg_modify8(
+ dw, DW3000_SYS_CFG_ID, 1,
+ (u8)(~DW3000_SYS_CFG_CP_SDC_BIT_MASK >> 8),
+ is_nul_key ? (DW3000_SYS_CFG_CP_SDC_BIT_MASK >> 8) : 0);
+ if (rc)
+ return rc;
+ /* Finally, save configured STS mode. */
+ if (is_nul_key)
+ config->stsMode |= DW3000_STS_MODE_SDC;
+ else
+ config->stsMode &= ~DW3000_STS_MODE_SDC;
+ }
+ return 0;
+}
+
+/**
+ * dw3000_configure_sts_iv() - set device's STS IV
+ * @dw: the DW device
+ * @iv: the 128 bits STS IV to configure (MSB first)
+ *
+ * The four least significant bytes are always sent.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_sts_iv(struct dw3000 *dw, const u8 *iv)
+{
+ struct dw3000_local_data *data = &dw->data;
+ __le64 swapped_iv[AES_BLOCK_SIZE / sizeof(__le64)];
+ bool changed;
+ int rc;
+ /* Check if IV MSB had changed. */
+ changed = memcmp(iv, data->sts_iv, AES_BLOCK_SIZE - sizeof(u32)) != 0;
+ /* Convert to Little-endian. */
+ _swap128(swapped_iv, iv);
+ /* Update it (Only reset counter in LSB if unchanged). */
+ rc = _dw3000_reg_write(dw, DW3000_STS_IV_ID, 0,
+ changed ? AES_BLOCK_SIZE : sizeof(u32),
+ swapped_iv);
+ if (rc)
+ return rc;
+ /* Update cached IV. */
+ memcpy(data->sts_iv, iv, AES_KEYSIZE_128);
+ return 0;
+}
+
+/**
+ * dw3000_load_sts_iv() - force device's STS IV to be loaded
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_load_sts_iv(struct dw3000 *dw)
+{
+ return dw3000_reg_or8(dw, DW3000_STS_CTRL_ID, 0,
+ DW3000_STS_CTRL_LOAD_IV_BIT_MASK);
+}
+
+/**
+ * _dw3000_ciadiag_update_reg_select() - Update CIA diagnostic register selector
+ * @dw: the DW device
+ *
+ * According to DW3000's configuration, we must read some values
+ * (e.g: channel impulse response power, preamble accumulation count)
+ * in different registers in the CIA interface. It on depending the STS
+ * and PDOA configurations.
+ */
+static void _dw3000_ciadiag_update_reg_select(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_local_data *data = &dw->data;
+
+ if (config->pdoaMode == DW3000_PDOA_M3) {
+ data->ciadiag_reg_select =
+ DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3;
+ } else {
+ if (_dw3000_sts_is_enabled(dw))
+ data->ciadiag_reg_select =
+ DW3000_CIA_DIAG_REG_SELECT_WITH_STS;
+ else
+ data->ciadiag_reg_select =
+ DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS;
+ }
+}
+
+int dw3000_configure_sys_cfg(struct dw3000 *dw, struct dw3000_config *config)
+{
+ u8 mode = (config->phrMode == DW3000_PHRMODE_EXT) ?
+ DW3000_SYS_CFG_PHR_MODE_BIT_MASK :
+ 0;
+ u8 dw_pac_reg = _plen_info[config->txPreambLength - 1].dw_pac_reg;
+ int rc;
+ /*
+ * SYS_CFG :
+ * - Clear the PHR Mode, PHR Rate, STS Protocol, SDC, PDOA Mode,
+ * - Set the relevant bits according to configuration of the PHR Mode,
+ * PHR Rate, STS Protocol, SDC, PDOA Mode
+ */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_SYS_CFG_ID, 0,
+ ~(u32)(DW3000_SYS_CFG_PHR_MODE_BIT_MASK |
+ DW3000_SYS_CFG_PHR_6M8_BIT_MASK |
+ DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK |
+ DW3000_SYS_CFG_PDOA_MODE_BIT_MASK |
+ DW3000_SYS_CFG_CP_SDC_BIT_MASK),
+ (((u32)config->pdoaMode)
+ << DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET) |
+ (((u16)config->stsMode & DW3000_STS_CONFIG_MASK)
+ << DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET) |
+ (DW3000_SYS_CFG_PHR_6M8_BIT_MASK &
+ ((u32)config->phrRate
+ << DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET)) |
+ mode);
+ if (rc)
+ return rc;
+ /* Update CIA diagnostic register selection */
+ _dw3000_ciadiag_update_reg_select(dw);
+ /* Configure OTP */
+ rc = dw3000_configure_otp(dw, config);
+ if (rc)
+ return rc;
+ /* Configure PAC */
+ if (config->pdoaMode == DW3000_PDOA_M1) {
+ /* Disable STS CMF, and configure PAC size */
+ rc = dw3000_reg_modify8(
+ dw, DW3000_DRX_TUNE0_ID, 0,
+ (u8) ~(DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK |
+ DW3000_DRX_TUNE0_DT0B4_BIT_MASK),
+ dw_pac_reg);
+ } else {
+ /* Enable STS CMF, and configure PAC size */
+ rc = dw3000_reg_modify8(
+ dw, DW3000_DRX_TUNE0_ID, 0,
+ (u8)~DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK,
+ dw_pac_reg | DW3000_DRX_TUNE0_DT0B4_BIT_MASK);
+ }
+ if (rc)
+ return rc;
+ if (config->txPreambLength == DW3000_PLEN_72) {
+ /**
+ * Value 9 sets fine premable length to 72 symbols
+ * This is needed to set 72 length.
+ */
+ rc = dw3000_setplenfine(dw, 9);
+ if (rc)
+ return rc;
+ } else {
+ /* Clear the setting in the FINE_PLEN register. */
+ rc = dw3000_setplenfine(dw, 0);
+ if (rc)
+ return rc;
+ }
+ if ((config->stsMode & DW3000_STS_MODE_ND) == DW3000_STS_MODE_ND) {
+ /**
+ * Configure lower preamble detection threshold for no data
+ * STS mode.
+ */
+ return dw3000_reg_write32(dw, DW3000_DRX_TUNE3_ID, 0,
+ DW3000_PD_THRESH_NO_DATA);
+ } else {
+ /**
+ * Configure default preamble detection threshold for other
+ * modes.
+ */
+ return dw3000_reg_write32(dw, DW3000_DRX_TUNE3_ID, 0,
+ DW3000_PD_THRESH_DEFAULT);
+ }
+}
+
+/**
+ * dw3000_configure_chan_ctrl() - configure the channel control register
+ * @dw: the DW device
+ * @config: the DW device's configuration
+ *
+ * Set the DW3000_CHAN_CTRL_ID register.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_configure_chan_ctrl(struct dw3000 *dw,
+ struct dw3000_config *config)
+{
+ u8 chan = config->chan;
+ u32 temp;
+ int rc;
+
+ /* Adjust configuration according channel/txCode and calib_data */
+ dw3000_calib_update_config(dw);
+
+ /* Read current CHAN_CTRL register value*/
+ rc = dw3000_reg_read32(dw, DW3000_CHAN_CTRL_ID, 0, &temp);
+ if (rc)
+ return rc;
+ /* Change value */
+ temp &= (~(DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK |
+ DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK |
+ DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK |
+ DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK));
+ if (chan == 9)
+ temp |= DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK;
+ temp |= (DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK &
+ ((u32)config->rxCode << DW3000_CHAN_CTRL_RX_PCODE_BIT_OFFSET));
+ temp |= (DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK &
+ ((u32)config->txCode << DW3000_CHAN_CTRL_TX_PCODE_BIT_OFFSET));
+ temp |= (DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK &
+ ((u32)config->sfdType
+ << DW3000_CHAN_CTRL_SFD_TYPE_BIT_OFFSET));
+ /* Reprogram new value */
+ return dw3000_reg_write32(dw, DW3000_CHAN_CTRL_ID, 0, temp);
+}
+
+/**
+ * dw3000_configure_rf() - configure the device's radio frequency
+ * @dw: the DW device
+ *
+ * According to the channel (ex: 5 or 9), it sets the device's configuration
+ * of the radio frequency.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_configure_rf(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_txconfig *txconfig = &dw->txconfig;
+ u8 chan = config->chan;
+ u32 rf_pll_cfg;
+ int rc;
+ /* Get default values */
+ if (chan == 9) {
+ rf_pll_cfg = DW3000_RF_PLL_CFG_CH9;
+ } else {
+ rf_pll_cfg = DW3000_RF_PLL_CFG_CH5;
+ }
+ rc = dw3000_write_txctrl(dw);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reg_write16(dw, DW3000_PLL_CFG_ID, 0, rf_pll_cfg);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reg_write16(dw, DW3000_PLL_COMMON_ID, 0, DW3000_RF_PLL_COMMON);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reg_write8(dw, DW3000_LDO_RLOAD_ID, 1,
+ DW3000_LDO_RLOAD_VAL_B1);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write8(dw, DW3000_TX_CTRL_LO_ID, 2,
+ DW3000_RF_TXCTRL_LO_B2);
+ if (rc)
+ return rc;
+ /* Setup TX power */
+ rc = dw3000_reg_write32(dw, DW3000_TX_POWER_ID, 0, txconfig->power);
+ if (unlikely(rc))
+ return rc;
+ /* Extend the lock delay */
+ rc = dw3000_reg_write8(dw, DW3000_PLL_CAL_ID, 0, DW3000_RF_PLL_CFG_LD);
+ if (unlikely(rc))
+ return rc;
+
+ /* Configure PLL coarse code, if needed. */
+ if (dw->chip_ops->pll_coarse_code) {
+ rc = dw->chip_ops->pll_coarse_code(dw);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int dw3000_configmrxlut(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ u8 chan = config->chan;
+ const u32 *lut;
+ int rc;
+
+ if (!dw->chip_ops->set_mrxlut || !dw->chip_ops->get_config_mrxlut_chan)
+ return 0;
+
+ lut = dw->chip_ops->get_config_mrxlut_chan(dw, chan);
+ if (!lut)
+ return -EINVAL;
+
+ rc = dw->chip_ops->set_mrxlut(dw, lut);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "mrxlut configuration has failed (%d)\n", rc);
+ }
+
+ return rc;
+}
+
+int dw3000_configure_dgc(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ /* Only enable DGC for PRF 64. */
+ if ((config->rxCode >= 9) && (config->rxCode <= 24)) {
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+ /* Load RX LUTs */
+ if (local->dgc_otp_set) {
+ /* If the OTP has DGC info programmed into it, do a
+ * manual kick from OTP. */
+ u16 dgc_sel = (config->chan == 5 ?
+ 0 :
+ DW3000_NVM_CFG_DGC_SEL_BIT_MASK);
+ rc = dw3000_reg_modify16(
+ dw, DW3000_NVM_CFG_ID, 0,
+ (u16) ~(DW3000_NVM_CFG_DGC_SEL_BIT_MASK),
+ dgc_sel | DW3000_NVM_CFG_DGC_KICK_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Configure kick bits for when waking up. */
+ local->sleep_mode |= DW3000_LOADDGC;
+ } else {
+ /* Else we manually program hard-coded values into the
+ * DGC registers. */
+ rc = dw3000_configmrxlut(dw);
+ if (rc)
+ return rc;
+ local->sleep_mode &= ~DW3000_LOADDGC;
+ }
+ return dw3000_reg_modify16(
+ dw, DW3000_DGC_CFG_ID, 0x0,
+ (u16)~DW3000_DGC_CFG_THR_64_BIT_MASK,
+ DW3000_DGC_CFG << DW3000_DGC_CFG_THR_64_BIT_OFFSET);
+ } else {
+ return dw3000_reg_and8(dw, DW3000_DGC_CFG_ID, 0x0,
+ (u8)~DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK);
+ }
+}
+
+static int dw3000_restore_dgc(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ int rc = 0;
+
+ /* Restore OPS table configuration */
+ if (dw->chip_ops->kick_ops_table_on_wakeup) {
+ rc = dw->chip_ops->kick_ops_table_on_wakeup(dw);
+ if (rc)
+ return rc;
+ }
+
+ /* Only enable DGC for PRF 64. */
+ if ((config->rxCode >= 9) && (config->rxCode <= 24)) {
+ struct dw3000_local_data *local = &dw->data;
+ /* Load RX LUTs only if not already loaded from OTP */
+ if (!local->dgc_otp_set)
+ rc = dw3000_configmrxlut(dw);
+ else if (dw->chip_ops->kick_dgc_on_wakeup) {
+ /*
+ * If the OTP has DGC info programmed into it,
+ * do a manual kick from OTP
+ */
+ rc = dw->chip_ops->kick_dgc_on_wakeup(dw);
+ if (rc)
+ return rc;
+ }
+ }
+ return rc;
+}
+
+/**
+ * dw3000_configure_chan() - configure the device's RF channel
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_chan(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ int rc;
+ /* Configure the CHAN_CTRL register */
+ rc = dw3000_configure_chan_ctrl(dw, config);
+ if (rc)
+ return rc;
+ /* Set TX/RX analogs for given channel */
+ rc = dw3000_configure_rf(dw);
+ if (rc)
+ return rc;
+ /* Disable AGC */
+ dw3000_reg_modify32(dw, DW3000_AGC_CFG_ID, 0, DW3000_AGC_DIS_MASK, 0);
+ /* Configure DGC. */
+ return dw3000_configure_dgc(dw);
+}
+
+/**
+ * dw3000_configure_pcode() - change the device's RF preamble code
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_pcode(struct dw3000 *dw)
+{
+ /* Just reconfigure the CHAN_CTRL register */
+ return dw3000_configure_chan_ctrl(dw, &dw->config);
+}
+
+/**
+ * dw3000_configure_sfd_type() - change the device's RF SFD type
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_sfd_type(struct dw3000 *dw)
+{
+ /* Just reconfigure the CHAN_CTRL register */
+ return dw3000_configure_chan_ctrl(dw, &dw->config);
+}
+
+/**
+ * dw3000_configure_phr_rate() - change the device's phr rate
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_phr_rate(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ u8 mode = (config->phrMode == DW3000_PHRMODE_EXT) ?
+ DW3000_SYS_CFG_PHR_MODE_BIT_MASK :
+ 0;
+
+ return dw3000_reg_modify32(
+ dw, DW3000_SYS_CFG_ID, 0,
+ ~(u32)(DW3000_SYS_CFG_PHR_MODE_BIT_MASK |
+ DW3000_SYS_CFG_PHR_6M8_BIT_MASK |
+ DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK |
+ DW3000_SYS_CFG_PDOA_MODE_BIT_MASK |
+ DW3000_SYS_CFG_CP_SDC_BIT_MASK),
+ (((u32)config->pdoaMode)
+ << DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET) |
+ (((u16)config->stsMode & DW3000_STS_CONFIG_MASK)
+ << DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET) |
+ (DW3000_SYS_CFG_PHR_6M8_BIT_MASK &
+ ((u32)config->phrRate
+ << DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET)) |
+ mode);
+}
+
+/**
+ * dw3000_configure_preamble_length_and_datarate() - change the device's RF preamble length (PSR) and data rate
+ * @dw: the DW device
+ * @update_datarate_only: if true, update only the datarate
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_preamble_length_and_datarate(struct dw3000 *dw,
+ bool update_datarate_only)
+{
+ struct dw3000_config *config = &dw->config;
+ int rc;
+
+ /* Setup TX preamble size, and data rate */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_TX_FCTRL_ID, 0,
+ ~(DW3000_TX_FCTRL_TXBR_BIT_MASK |
+ DW3000_TX_FCTRL_TXPSR_PE_BIT_MASK),
+ ((u32)config->dataRate << DW3000_TX_FCTRL_TXBR_BIT_OFFSET) |
+ ((u32)config->txPreambLength)
+ << DW3000_TX_FCTRL_TXPSR_PE_BIT_OFFSET);
+ if (rc)
+ return rc;
+
+ if (!update_datarate_only) {
+ int symb = _plen_info[config->txPreambLength - 1].symb;
+ int pac_symb = _plen_info[config->txPreambLength - 1].pac_symb;
+ u8 dw_pac_reg =
+ _plen_info[config->txPreambLength - 1].dw_pac_reg;
+ int sfd_symb = dw3000_get_sfd_symb(dw);
+
+ /**
+ * DTUNE (SFD timeout):
+ * SFD timeout = PLEN + 1 + sfd len - pac size
+ */
+ config->sfdTO = symb + 1 + sfd_symb - pac_symb;
+ rc = dw3000_reg_write16(dw, DW3000_DRX_SFDTOC_ID, 0,
+ config->sfdTO);
+ if (rc)
+ return rc;
+ /* Reconfigure PAC */
+ rc = dw3000_reg_modify8(
+ dw, DW3000_DRX_TUNE0_ID, 0,
+ (u8)~DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK, dw_pac_reg);
+ if (rc)
+ return rc;
+
+ if (config->txPreambLength == DW3000_PLEN_72) {
+ /**
+ * Value 9 sets fine premable length to 72 symbols
+ * This is needed to set 72 length.
+ */
+ rc = dw3000_setplenfine(dw, 9);
+ if (rc)
+ return rc;
+ } else {
+ /* Clear the setting in the FINE_PLEN register. */
+ rc = dw3000_setplenfine(dw, 0);
+ if (rc)
+ return rc;
+ }
+ if (symb > 64)
+ rc = dw3000_reg_write8(dw, DW3000_DTUNE4_ID, 0x3,
+ DW3000_RX_SFD_HLDOFF);
+ else /* set default value for <= 64 */
+ rc = dw3000_reg_write8(dw, DW3000_DTUNE4_ID, 0x3,
+ DW3000_RX_SFD_HLDOFF_DEF);
+ if (rc)
+ return rc;
+ }
+ return rc;
+}
+
+static int dw3000_setdwstate(struct dw3000 *dw, enum operational_state state)
+{
+ int rc;
+ if (state == dw->current_operational_state)
+ return 0;
+
+ switch (state) {
+ case DW3000_OP_STATE_DEEP_SLEEP:
+ /* Disable IRQ to ensure no IRQ storm in case pull-down is too weak */
+ disable_irq(dw->spi->irq);
+
+ /* Clear SPIRDY and RCINIT interrupts to avoid spurious SPIRDY and
+ * RCINIT interrupts (at startup, for example) that will trigger the
+ * wake up process of the chip too early.
+ */
+ rc = dw3000_clear_sys_status(
+ dw, DW3000_SYS_STATUS_SPIRDY_BIT_MASK |
+ DW3000_SYS_STATUS_RCINIT_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Enable the RCINIT interrupt for wake up. */
+ rc = dw3000_reg_or8(dw, DW3000_SYS_ENABLE_LO_ID, 3,
+ DW3000_SYS_STATUS_RCINIT_BIT_MASK >> 24);
+ if (rc)
+ return rc;
+ /* Store the current DTU */
+ dw->sleep_enter_dtu = dw3000_get_dtu_time(dw);
+ trace_dw3000_deep_sleep_enter(dw, dw->sleep_enter_dtu);
+ /*
+ * Set up the deep sleep configuration
+ */
+ /* Step 1:
+ * - enable configuration copy from AON memory to host registers.
+ * - set ONW_GO2IDLE to get DW3000_OP_STATE_IDLE_PLL on wakeup.
+ */
+ /* Step 1.1
+ * - clear DW3000_RUNSAR bit from DW3000_AON_DIG_CFG_ID
+ * - turn off auto PGF cal on wake up
+ */
+ dw->data.sleep_mode &= ~DW3000_RUNSAR;
+ dw->data.sleep_mode |= DW3000_PGFCAL;
+
+ rc = dw3000_reg_write16(
+ dw, DW3000_AON_DIG_CFG_ID, 0,
+ dw->data.sleep_mode |
+ DW3000_AON_DIG_CFG_ONW_AONDLD_MASK |
+ DW3000_AON_DIG_CFG_ONW_GO2IDLE_MASK);
+ if (rc)
+ return rc;
+ /*
+ * Step 2:
+ * - enable the sleep configuration bit.
+ * - disable sleep counter to get deep sleep.
+ * - enable wake up using SPI access.
+ */
+ rc = dw3000_reg_write8(dw, DW3000_AON_CFG_ID, 0,
+ DW3000_AON_SLEEP_EN_MASK |
+ DW3000_AON_WAKE_CSN_MASK);
+ if (rc)
+ return rc;
+ /*
+ * Step 3
+ * - Set INIT2IDLE bit, to get DW3000_OP_STATE_IDLE_PLL on wakeup.
+ */
+ rc = dw3000_reg_or8(dw, DW3000_SEQ_CTRL_ID, 0x01,
+ DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK >>
+ 8);
+ if (rc)
+ return rc;
+ /* Step 4
+ * - backup ALL registers to AON memory (include AON config block)
+ * - and enter DEEP-SLEEP
+ */
+ rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, 0);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0,
+ DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Step 5
+ * Here the chip is in DW3000_OP_STATE_DEEP_SLEEP so now
+ * update power statistics and operational state.
+ */
+ dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0);
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_DEEP_SLEEP);
+ break;
+
+ case DW3000_OP_STATE_IDLE_PLL:
+ /**
+ * Set the auto INIT2IDLE bit so that DW3000 enters DW3000_OP_STATE_IDLE_PLL mode before
+ * switching clocks to system_PLL.
+ *
+ * NOTE: PLL should be configured prior to this, and the device
+ * should be in DW3000_OP_STATE_IDLE_RC (if the PLL does not lock device will
+ * remain in DW3000_OP_STATE_IDLE_RC)
+ *
+ * Switch clock to auto, if coming here from DW3000_OP_STATE_IDLE_RC the clock
+ * will be FOSC/4, need to switch to auto prior to setting auto
+ * INIT2IDLE bit
+ */
+ rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO);
+ if (rc)
+ return rc;
+ /* Calibrate the PLL from scratch, if needed. */
+ if (dw->chip_ops->pll_calibration_from_scratch) {
+ rc = dw->chip_ops->pll_calibration_from_scratch(dw);
+ if (rc)
+ return rc;
+ }
+ rc = dw3000_reg_or8(dw, DW3000_SEQ_CTRL_ID, 0x01,
+ DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK >>
+ 8);
+ if (rc)
+ return rc;
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_PLL);
+ break;
+
+ case DW3000_OP_STATE_IDLE_RC:
+ /**
+ * Change state to IDLE_RC and clear auto INIT2IDLE bit
+ * switch clock to FOSC
+ */
+ rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0,
+ DW3000_FORCE_SYSCLK_FOSC);
+ if (rc)
+ return rc;
+ /* Clear the auto INIT2IDLE bit and set FORCE2INIT */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_SEQ_CTRL_ID, 0x0,
+ (u32)~DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK,
+ DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Clear force bits (device will stay in IDLE_RC) */
+ rc = dw3000_reg_and8(
+ dw, DW3000_SEQ_CTRL_ID, 0x2,
+ (u8) ~(DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK >> 16));
+ if (rc)
+ return rc;
+ /* Switch clock to auto */
+ rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO);
+ if (rc)
+ return rc;
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_RC);
+ break;
+
+ case DW3000_OP_STATE_INIT_RC:
+ /**
+ * The SPI rate needs to be <= 7MHz as device is switching
+ * to INIT_RC state
+ */
+ rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0,
+ DW3000_FORCE_SYSCLK_FOSCDIV4);
+ if (rc)
+ return rc;
+ /* Clear the auto INIT2IDLE bit and set FORCE2INIT */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_SEQ_CTRL_ID, 0x0,
+ (u32)~DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK,
+ DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Clear force bits (device will stay in IDLE_RC) */
+ rc = dw3000_reg_and8(
+ dw, DW3000_SEQ_CTRL_ID, 0x2,
+ (u8) ~(DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK >> 16));
+ if (rc)
+ return rc;
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_INIT_RC);
+ break;
+
+ default:
+ /* Invalid or unmanaged state */
+ dev_err(dw->dev,
+ "Invalid or unmanaged operational state %d, current state: %d\n",
+ state, dw->current_operational_state);
+ rc = -EINVAL;
+ break;
+ }
+ return rc;
+}
+
+#ifdef CONFIG_DW3000_DEBUG
+static unsigned dw3000_check_fileid = 0;
+module_param_named(checkfid, dw3000_check_fileid, uint, 0644);
+MODULE_PARM_DESC(checkfid,
+ "Selected register fileid to backup and check on wakeup");
+
+static void dw3000_wakeup_compare(struct work_struct *work)
+{
+ struct dw3000 *dw = container_of(work, struct dw3000,
+ deep_sleep_state.compare_work);
+ u32 *before = (u32 *)dw->deep_sleep_state.regbackup;
+ u32 *after = (u32 *)(dw->deep_sleep_state.regbackup + 512);
+ unsigned offset = dw3000_check_fileid << 16;
+ const struct dw3000_chip_register *reg;
+ size_t count;
+ int length;
+ /* retrieve registers length */
+ reg = dw->chip_ops->get_registers(dw, &count);
+ while (reg->address != dw3000_check_fileid && count) {
+ reg++;
+ count--;
+ }
+ length = (reg->size + 3) & ~4;
+ dev_notice(dw->dev, "compare registers in fileid %u on wakeup\n",
+ dw3000_check_fileid);
+ while (length > 0) {
+ if (*after != *before)
+ dev_notice(dw->dev, "0x%05x : 0x%x -> 0x%x\n", offset,
+ *before, *after);
+ after++;
+ before++;
+ offset += 4;
+ length -= 4;
+ }
+ return;
+}
+
+static int dw3000_backup_registers(struct dw3000 *dw, bool after)
+{
+ const struct dw3000_chip_register *reg;
+ size_t count;
+ int offset = after ? 512 : 0;
+ int rc;
+
+ reg = dw->chip_ops->get_registers(dw, &count);
+ while (reg->address != dw3000_check_fileid && count) {
+ reg++;
+ count--;
+ }
+
+ rc = dw3000_xfer(dw, dw3000_check_fileid << 16, 0, reg->size,
+ dw->deep_sleep_state.regbackup + offset,
+ DW3000_SPI_RD_BIT);
+ if (rc)
+ return rc;
+
+ /* Now compare and dump both parts */
+ if (after)
+ schedule_work(&dw->deep_sleep_state.compare_work);
+
+ return 0;
+}
+#endif /* CONFIG_DW3000_DEBUG */
+
+/**
+ * dw3000_wakeup_timer_start() - Program wakeup timer
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @delay_us: the delay after the DW device is woken up
+ *
+ * The timer is configured to wake-up the device after the specified delay.
+ * Caller should take care of wake-up latency.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+void dw3000_wakeup_timer_start(struct dw3000 *dw, int delay_us)
+{
+ hrtimer_start(&dw->idle_timer, ns_to_ktime(delay_us * 1000ull),
+ HRTIMER_MODE_REL);
+ trace_dw3000_wakeup_timer_start(dw, delay_us);
+}
+
+/**
+ * dw3000_deep_sleep_and_wakeup() - Put device in DEEP SLEEP state
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @delay_us: the delay after the DW device is woken up
+ *
+ * The caller must save the next operational state and required data before
+ * the DW device is woken-up by the timer.
+ *
+ * The timer is configured to wake-up the device after the specified delay.
+ * Caller should take care of wake-up latency.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_deep_sleep_and_wakeup(struct dw3000 *dw, int delay_us)
+{
+ int rc;
+#ifdef CONFIG_DW3000_DEBUG
+ /* Backup config before changing state */
+ rc = dw3000_backup_registers(dw, false);
+ if (rc)
+ return rc;
+#endif
+ /* Always launch timer, even if entering deep-sleep fail. */
+ if (delay_us)
+ dw3000_wakeup_timer_start(dw, delay_us);
+ dw3000_pm_qos_update_request(dw, PM_QOS_RESUME_LATENCY_NO_CONSTRAINT);
+ /* Put the chip in deep sleep immediately */
+ rc = dw3000_setdwstate(dw, DW3000_OP_STATE_DEEP_SLEEP);
+ trace_dw3000_deep_sleep(dw, rc);
+ return rc;
+}
+
+/**
+ * dw3000_wakeup_done_to_tx() - Wakeup done, continue to program TX.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_wakeup_done_to_tx(struct dw3000 *dw)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+
+ trace_dw3000_wakeup_done_to_tx(dw);
+ dss->next_operational_state = dw->current_operational_state;
+ return dw3000_do_tx_frame(dw, &dss->tx_config, dss->tx_skb,
+ dss->frame_idx);
+}
+
+/**
+ * dw3000_wakeup_done_to_rx() - Wakeup done, continue to program RX.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_wakeup_done_to_rx(struct dw3000 *dw)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+
+ trace_dw3000_wakeup_done_to_rx(dw);
+ dss->next_operational_state = dw->current_operational_state;
+ return dw3000_do_rx_enable(dw, &dss->rx_config, dss->frame_idx);
+}
+
+/**
+ * dw3000_wakeup_done_to_idle() - Wakeup done, continue on idle timeout.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_wakeup_done_to_idle(struct dw3000 *dw)
+{
+ struct dw3000_stm_command cmd = { dw3000_handle_idle_timeout, NULL,
+ NULL };
+
+ trace_dw3000_wakeup_done_to_idle(dw);
+ if (dw->idle_timeout) {
+ u32 now_dtu = dw3000_get_dtu_time(dw);
+ int idle_duration_dtu = dw->idle_timeout_dtu - now_dtu;
+
+ /* TODO:
+ * 2 timers implemented (idle, deep_sleep),
+ * or a better FSM (enter, leave, events, state)
+ * should help to follow/respect timestamp expected. */
+ /* WORKAROUND: On a delayed wake-up (by CPU high load),
+ * the idle_timeout_dtu date can be in the past. And to avoid
+ * to program a timer in the past, process the idle_timeout_cb
+ * immediately when duration is negative or equal to 0.
+ * For NFCC_COEX, the NFC software will enter in "LATE"
+ * processing, and return immediately too. Thus the CPU high
+ * load will not broken dw3000 driver and mac layer. */
+ if (idle_duration_dtu > 0) {
+ int idle_duration_us = DTU_TO_US(idle_duration_dtu);
+
+ dw3000_wakeup_timer_start(dw, idle_duration_us);
+ return 0;
+ }
+ trace_dw3000_wakeup_done_to_idle_late(dw);
+ }
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+/**
+ * dw3000_idle() - Go into idle.
+ * @dw: Driver context.
+ * @timestamp: Idle duration have a end date (timestamp_dtu).
+ * @timestamp_dtu: End date for this idle duration.
+ * @idle_timeout_cb: idle timeout callback.
+ * @next_operational_state: next operation state wanted.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_idle(struct dw3000 *dw, bool timestamp, u32 timestamp_dtu,
+ dw3000_idle_timeout_cb idle_timeout_cb,
+ enum operational_state next_operational_state)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ u32 cur_time_dtu = 0;
+ int delay_us = 0, rc;
+
+ trace_dw3000_idle(dw, timestamp, timestamp_dtu, next_operational_state);
+
+ if (WARN_ON(next_operational_state < DW3000_OP_STATE_IDLE_PLL))
+ return -EINVAL;
+
+ dw->wakeup_done_cb = dw3000_wakeup_done_to_idle;
+ /* Reset ranging clock requirement */
+ dw->need_ranging_clock = false;
+ dw3000_reset_rctu_conv_state(dw);
+ /* Ensure dw3000_idle_timeout() handler does the right thing. */
+ dss->next_operational_state = next_operational_state;
+
+ /* Release Wifi coexistence. */
+ dw3000_coex_stop(dw);
+ /* Check if enough idle time to enter DEEP SLEEP */
+ dw->idle_timeout = timestamp;
+ dw->idle_timeout_dtu = timestamp_dtu;
+ if (timestamp) {
+ int deepsleep_delay_us;
+ int idle_delay_dtu;
+ int idle_delay_us;
+ bool is_sleeping = dw->current_operational_state ==
+ DW3000_OP_STATE_DEEP_SLEEP;
+
+ cur_time_dtu = dw3000_get_dtu_time(dw);
+ idle_delay_dtu =
+ timestamp_dtu - cur_time_dtu - dw->llhw->anticip_dtu;
+ if (idle_delay_dtu < 0) {
+ rc = -ETIME;
+ goto eof;
+ }
+ idle_delay_us = DTU_TO_US(idle_delay_dtu);
+ /* Warning: timestamp_dtu have already taken in consideration
+ * WakeUp latency! */
+ deepsleep_delay_us = idle_delay_us;
+ if (is_sleeping)
+ deepsleep_delay_us -= DW3000_WAKEUP_LATENCY_US;
+
+ /* TODO/FIXME:
+ * Timer is used for idle timeout and deepsleep timeout,
+ * which haven't the same timeout_dtu! */
+ if (deepsleep_delay_us < 0) {
+ dw3000_deepsleep_wakeup_now(dw, idle_timeout_cb,
+ dw->idle_timeout_dtu,
+ DW3000_OP_STATE_MAX);
+ rc = 0;
+ goto eof;
+ }
+
+ delay_us = dw3000_can_deep_sleep(dw, deepsleep_delay_us);
+ if (!delay_us) {
+ u32 timer_duration_dtu = is_sleeping ?
+ deepsleep_delay_us :
+ idle_delay_us;
+ /* Provided date isn't far enough to enter deep-sleep,
+ just launch the timer to have it call the MCPS timer
+ expired event function. */
+ dw->idle_timeout_cb = idle_timeout_cb;
+ dw3000_wakeup_timer_start(dw, timer_duration_dtu);
+ rc = 0;
+ goto eof;
+ }
+ } else if (dw->auto_sleep_margin_us < 0) {
+ /* Deep-sleep is completely disable, so do nothing here! */
+ rc = 0;
+ goto eof;
+ }
+ /* Enter DEEP SLEEP */
+ rc = dw3000_deep_sleep_and_wakeup(dw, delay_us);
+ if (!rc)
+ dw->idle_timeout_cb = idle_timeout_cb;
+eof:
+ return rc;
+}
+
+/**
+ * dw3000_lock_pll() - Auto calibrate the PLL and change to IDLE_PLL state
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @sys_status: the last known sys_status register LSB value
+ *
+ * It also checks on the CPLOCK bit field to be set in the SYS_STATUS register
+ * which means the PLL is locked (for D0/E0).
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_lock_pll(struct dw3000 *dw, u8 sys_status)
+{
+ u8 flag;
+ int cnt;
+ int rc;
+
+ if (sys_status & DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK) {
+ /* PLL already locked! Just change current_operational_state */
+ dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_PLL);
+ goto resync_dtu;
+ }
+
+ if (__dw3000_chip_version == DW3000_E0_VERSION) {
+ /* Verify PLL lock bit is cleared */
+ int rc = dw3000_reg_write8(
+ dw, DW3000_SYS_STATUS_ID, 0,
+ DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK);
+ if (rc)
+ return rc;
+ }
+ rc = dw3000_setdwstate(dw, DW3000_OP_STATE_IDLE_PLL);
+ if (rc)
+ return rc;
+
+ /* For C0, wait for PLL lock, else SYS_TIME is 0 */
+ if (__dw3000_chip_version == DW3000_C0_VERSION) {
+ udelay(DW3000_PLL_LOCK_DELAY_US);
+ goto resync_dtu;
+ }
+ /* For D0/E0, wait PLL locked bit in SYS_STATUS */
+ for (flag = 1, cnt = 0; cnt < DW3000_MAX_RETRIES_FOR_PLL; cnt++) {
+ u8 status;
+
+ usleep_range(10, 40);
+ /* Verify PLL lock bit is locked */
+ dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 0, &status);
+ if ((status & DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK)) {
+ /* PLL is locked. */
+ flag = 0;
+ break;
+ }
+ }
+ if (flag)
+ return -EAGAIN;
+resync_dtu:
+ /* Re-sync SYS_TIME and DTU when chip is in IDLE_PLL */
+ rc = dw3000_resync_dtu_sys_time(dw);
+ return rc;
+}
+
+/**
+ * dw3000_run_pgfcal() - Run PGF calibration
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_run_pgfcal(struct dw3000 *dw)
+{
+ u32 val = 0;
+ u8 cal;
+ int rc;
+
+ /* Put into calibration mode */
+ rc = dw3000_reg_modify8(dw, DW3000_PGF_CAL_CFG_ID, 0x0,
+ (u8)~DW3000_PGF_CAL_CFG_PGF_MODE_BIT_MASK, 0x1);
+ if (rc)
+ return rc;
+ /* Trigger PGF calibration */
+ rc = dw3000_reg_or8(dw, DW3000_PGF_CAL_CFG_ID, 0x0,
+ DW3000_PGF_CAL_CFG_CAL_EN_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Calibration will be done within ~30 us (add some margin) */
+ /* TODO: On D0 active wait with lower delays. */
+ usleep_range(DW3000_PGFCAL_DELAY_US, DW3000_PGFCAL_DELAY_US + 100);
+ /* Check if calibration is done.*/
+ rc = dw3000_reg_read8(dw, DW3000_PGF_CAL_STS_ID, 0x0, &cal);
+ if (rc)
+ return rc;
+ if (cal != 1) {
+ goto calib_err;
+ }
+ /* Put into normal mode */
+ rc = dw3000_reg_write8(dw, DW3000_PGF_CAL_CFG_ID, 0x0, 0);
+ if (rc)
+ return rc;
+ /* Clear the status */
+ rc = dw3000_reg_write8(dw, DW3000_PGF_CAL_STS_ID, 0x0, 1);
+ if (rc)
+ return rc;
+ /* Enable reading */
+ rc = dw3000_reg_or8(dw, DW3000_PGF_CAL_CFG_ID, 0x2, 0x1);
+ if (rc)
+ return rc;
+ /* PFG I calibration */
+ rc = dw3000_reg_read32(dw, DW3000_PGF_I_CTRL1_ID, 0x0, &val);
+ if (rc)
+ return rc;
+ if (val == 0x1fffffff) {
+ goto calib_err;
+ }
+ /* PFG Q calibration */
+ rc = dw3000_reg_read32(dw, DW3000_PGF_Q_CTRL1_ID, 0x0, &val);
+ if (rc)
+ return rc;
+ if (val == 0x1fffffff) {
+ goto calib_err;
+ }
+ /* Disable reading */
+ rc = dw3000_reg_and8(dw, DW3000_PGF_CAL_CFG_ID, 0x2, ~0x1);
+ if (rc)
+ return rc;
+ return 0;
+calib_err:
+ dev_err(dw->dev, "PGF calibration failed\n");
+ return -EREMOTEIO;
+}
+
+/**
+ * dw3000_pgf_cal() - Run PGF calibration
+ * @dw: the DW device
+ * @ldoen: if set to true the function will enable LDOs prior to calibration
+ * and disable afterwards.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_pgf_cal(struct dw3000 *dw, bool ldoen)
+{
+ u16 val;
+ int rc;
+
+ /* Turn on delay mode (ensure good initial value all the time) */
+ rc = dw3000_reg_write16(dw, DW3000_PGF_CAL_CFG_ID, 0x2, 0x02);
+ if (rc)
+ return rc;
+
+ if (__dw3000_chip_version >= DW3000_D0_VERSION) {
+ u32 resi;
+ /* Enable reading of CAL result */
+ rc = dw3000_reg_or8(dw, DW3000_RX_CAL_CFG_ID, 0x2, 0x1);
+ if (rc)
+ return rc;
+ /* Read calibration */
+ rc = dw3000_reg_read32(dw, DW3000_RX_CAL_RESI_ID, 0x0, &resi);
+ if (rc)
+ return rc;
+ /* Disable reading */
+ rc = dw3000_reg_and8(dw, DW3000_RX_CAL_CFG_ID, 0x2, ~0x1);
+ if (rc)
+ return rc;
+ /* If not D0 and not soft reset, no need to continue */
+ if ((__dw3000_chip_version != DW3000_D0_VERSION) && (resi != 0))
+ return 0;
+ }
+
+ /* PGF needs LDOs turned on - ensure PGF LDOs are enabled */
+ if (ldoen) {
+ rc = dw3000_reg_read16(dw, DW3000_LDO_CTRL_ID, 0, &val);
+ if (rc)
+ return rc;
+ rc = dw3000_reg_or16(dw, DW3000_LDO_CTRL_ID, 0,
+ (DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_MASK |
+ DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_MASK));
+ if (rc)
+ return rc;
+ }
+ dw->pgf_cal_running = true;
+ /* Run PGF Cal */
+ rc = dw3000_run_pgfcal(dw);
+ dw->pgf_cal_running = false;
+ /* Turn off RX LDOs if previously off */
+ if (ldoen) {
+ /* restore LDO values */
+ return dw3000_reg_and16(dw, DW3000_LDO_CTRL_ID, 0, val);
+ }
+ return rc;
+}
+
+/**
+ * dw3000_configure() - configure the whole device
+ * @dw: the DW device
+ *
+ * Configure with no STS and SCP mode and without API errors.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_configure(struct dw3000 *dw)
+{
+ struct dw3000_config *config = &dw->config;
+ int rc;
+
+ /* Clear the sleep mode ALT_GEAR bit */
+ dw->data.sleep_mode &= (~(DW3000_ALT_GEAR | DW3000_SEL_GEAR3));
+ dw->data.max_frames_len = config->phrMode ? DW3000_EXT_FRAME_LEN :
+ DW3000_STD_FRAME_LEN;
+ /* Configure the SYS_CFG register */
+ rc = dw3000_configure_sys_cfg(dw, config);
+ if (rc)
+ return rc;
+ /* Configure the RF channel */
+ rc = dw3000_configure_chan(dw);
+ if (rc)
+ return rc;
+ /* Setup TX preamble size, data rate and SDF timeout count */
+ rc = dw3000_configure_preamble_length_and_datarate(dw, false);
+ if (rc)
+ return rc;
+
+ /* Auto calibrate the PLL and change to IDLE_PLL state */
+ rc = dw3000_lock_pll(dw, 0);
+ if (rc)
+ return rc;
+ /**
+ * PGF: If the RX calibration routine fails the device receiver
+ * performance will be severely affected, the application should reset
+ * and try again.
+ * PGF NOT needed for E0 as routine automatically runs on startup/hw reset, on wakeup
+ * however if reconfiguring after soft reset then the calibration needs to be run
+ */
+ rc = dw3000_pgf_cal(dw, 1);
+ if (rc)
+ return rc;
+ /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock.
+ * If this calibration is executed before the PLL lock, the PLL lock failed.
+ */
+ if (dw->chip_ops->adc_offset_calibration)
+ rc = dw->chip_ops->adc_offset_calibration(dw);
+
+ /* Update configuration dependent timings */
+ dw3000_update_timings(dw);
+ /* update VOUT */
+ rc = dw3000_reg_write32(dw, DW3000_LDO_VOUT_ID, 0, DW3000_RF_LDO_VOUT);
+ return rc;
+}
+
+static int dw3000_setrxantennadelay(struct dw3000 *dw, u16 rxDelay)
+{
+ /* Set the RX antenna delay for auto TX timestamp adjustment */
+ return dw3000_reg_write16(dw, DW3000_RX_ANTENNA_DELAY_ID, 0, rxDelay);
+}
+
+static int dw3000_settxantennadelay(struct dw3000 *dw, u16 txDelay)
+{
+ /* Set the TX antenna delay for auto TX timestamp adjustment */
+ return dw3000_reg_write16(dw, DW3000_TX_ANTD_ID, 0, txDelay);
+}
+
+static int dw3000_set_antenna_delay(struct dw3000 *dw, u16 delay)
+{
+ int rc = dw3000_setrxantennadelay(dw, delay);
+
+ if (rc)
+ return rc;
+ return dw3000_settxantennadelay(dw, delay);
+}
+
+/**
+ * dw3000_set_eui64() - Set device's Extended Unique Identifier
+ * @dw: the DW device
+ * @val: the EUI
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_eui64(struct dw3000 *dw, __le64 val)
+{
+ return dw3000_reg_write_fast(dw, DW3000_EUI_64_ID, 0, 8, &val,
+ DW3000_SPI_WR_BIT);
+}
+
+/**
+ * dw3000_set_pancoord() - Enable/disable the device as PAN coordinator
+ * @dw: the DW device
+ * @active: enables the device as PAN coordinator if true, otherwise disables
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_pancoord(struct dw3000 *dw, bool active)
+{
+ if (active) {
+ return dw3000_reg_or8(dw, DW3000_ADR_FILT_CFG_ID, 1,
+ DW3000_AS_PANCOORD);
+ } else {
+ return dw3000_reg_and8(dw, DW3000_ADR_FILT_CFG_ID, 1,
+ ~DW3000_AS_PANCOORD);
+ }
+}
+
+/**
+ * dw3000_set_panid() - Set device's PAN Identifier
+ * @dw: the DW device
+ * @val: the PAN ID
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_panid(struct dw3000 *dw, __le16 val)
+{
+ return dw3000_reg_write_fast(dw, DW3000_PANADR_ID,
+ (DW3000_PANADR_PAN_ID_BIT_OFFSET / 8),
+ DW3000_PANADR_PAN_ID_BIT_LEN, &val,
+ DW3000_SPI_WR_BIT);
+}
+
+/**
+ * dw3000_set_shortaddr() - Set device's short address
+ * @dw: the DW device
+ * @val: the short address
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_shortaddr(struct dw3000 *dw, __le16 val)
+{
+ return dw3000_reg_write_fast(dw, DW3000_PANADR_ID,
+ (DW3000_PANADR_SHORT_ADDR_BIT_OFFSET / 8),
+ DW3000_PANADR_SHORT_ADDR_BIT_LEN, &val,
+ DW3000_SPI_WR_BIT);
+}
+
+/**
+ * dw3000_configure_hw_addr_filt() - Set device's hardware address filtering
+ * parameters
+ * @dw: the DW device
+ * @changed: bitfields of flags indicating parameters which have changed
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_configure_hw_addr_filt(struct dw3000 *dw, unsigned long changed)
+{
+ struct ieee802154_hw_addr_filt *filt = &dw->config.hw_addr_filt;
+ int rc;
+
+ if (!changed)
+ return 0;
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ rc = dw3000_set_shortaddr(dw, filt->short_addr);
+ if (rc)
+ return rc;
+ }
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ rc = dw3000_set_panid(dw, filt->pan_id);
+ if (rc)
+ return rc;
+ }
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ rc = dw3000_set_pancoord(dw, filt->pan_coord);
+ if (rc)
+ return rc;
+ }
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ rc = dw3000_set_eui64(dw, filt->ieee_addr);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int dw3000_reconfigure_hw_addr_filt(struct dw3000 *dw)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ struct ieee802154_hw_addr_filt *filt = &dw->config.hw_addr_filt;
+ unsigned long changed = dss->config_changed;
+
+ /* Always force setup of ieee_addr if non 0 on wakeup
+ since not saved in AON. */
+ if (filt->ieee_addr)
+ changed |= DW3000_AFILT_IEEEADDR_CHANGED;
+ dss->config_changed &=
+ ~(DW3000_AFILT_SADDR_CHANGED | DW3000_AFILT_IEEEADDR_CHANGED |
+ DW3000_AFILT_PANID_CHANGED | DW3000_AFILT_PANC_CHANGED);
+ return dw3000_configure_hw_addr_filt(dw, changed);
+}
+
+/**
+ * dw3000_framefilter_enable() - Enable and set the device's frame filter
+ * @dw: the DW device
+ * @filtermode: filter mode
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_framefilter_enable(struct dw3000 *dw, u16 filtermode)
+{
+ /* Use 802.15.4 filtering rules */
+ int rc = dw3000_reg_or8(dw, DW3000_SYS_CFG_ID, 0,
+ (u8)(DW3000_SYS_CFG_FFEN_BIT_MASK));
+ if (rc)
+ return rc;
+ /* Set the frame filter configuration */
+ return dw3000_reg_write16(dw, DW3000_ADR_FILT_CFG_ID, 0, filtermode);
+}
+
+/**
+ * dw3000_framefilter_disable() - Disable the device's frame filter
+ * @dw: the DW device
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_framefilter_disable(struct dw3000 *dw)
+{
+ /* Disable frame filter */
+ int rc = dw3000_reg_and8(dw, DW3000_SYS_CFG_ID, 0,
+ (u8)(~(DW3000_SYS_CFG_FFEN_BIT_MASK)));
+ if (rc)
+ return rc;
+ /* Clear the configuration */
+ return dw3000_reg_write16(dw, DW3000_ADR_FILT_CFG_ID, 0, 0x0);
+}
+
+/**
+ * dw3000_read_clockoffset() - Read the clock offset for last frame received
+ * @dw: the DW device on which the SPI transfer will occurs
+ * @cfo: the address where to store read CFO
+ *
+ * This is used to read the crystal offset (relating to the frequency offset of
+ * the far DW3720 device compared to this one).
+ *
+ * Note: the returned signed number must be divided by 2^26 to get ppm offset.
+ * Return: 0 on success, else a negative error code.
+ */
+int dw3000_read_clockoffset(struct dw3000 *dw, s16 *cfo)
+{
+ int rc;
+ switch (dw->data.dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ /* !!! Assumes that Indirect pointer register B was already set. */
+ rc = dw3000_reg_read16(dw, DW3000_INDIRECT_POINTER_B_ID,
+ DW3000_DB_DIAG_CIA_DIAG0, (u16 *)cfo);
+ break;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ rc = dw3000_reg_read16(dw, DW3000_DB_DIAG_SET_1,
+ DW3000_DB_DIAG_CIA_DIAG0, (u16 *)cfo);
+ break;
+ default:
+ rc = dw3000_reg_read16(dw, DW3000_CIA_DIAG0_ID, 0, (u16 *)cfo);
+ }
+ if (rc)
+ return rc;
+ /* Bit 12 is sign, make the number to be sign extended if this bit is '1' */
+ *cfo <<= sizeof(*cfo) * 8 - DW3000_CIA_DIAG0_COE_PPM_BIT_LEN;
+ *cfo >>= sizeof(*cfo) * 8 - DW3000_CIA_DIAG0_COE_PPM_BIT_LEN;
+ trace_dw3000_read_clockoffset(dw, *cfo);
+ return 0;
+}
+
+/**
+ * dw3000_read_pdoa() - Read the PDOA result
+ * @dw: The DW device.
+ *
+ * This is used to read the PDOA result, it is the phase difference between
+ * either the Ipatov and STS POA, or the two STS POAs, depending on the PDOA
+ * mode of operation. (POA - Phase Of Arrival).
+ *
+ * NOTE: To convert to degrees: float pdoa_deg =
+ * ((float)pdoa / (1 << 11)) * 180 / M_PI.
+ *
+ * Return: the PDOA result (signed in q11 radian units).
+ */
+s16 dw3000_read_pdoa(struct dw3000 *dw)
+{
+ const u16 b12_sign_extend_test = 0x2000;
+ const u16 b12_sign_extend_mask = 0xc000;
+ const u16 pi_floored_q11 = 3.141592653589 * (1 << 11);
+ const u16 pix2_rounded_q11 = 3.141592653589 * 2 * (1 << 11) + 0.5;
+ u16 val;
+ s16 pdoa;
+
+ switch (dw->data.dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ dw3000_reg_read16(dw, DW3000_INDIRECT_POINTER_B_ID,
+ DW3000_DB_DIAG_PDOA + 2, &val);
+ pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16);
+ break;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ dw3000_reg_read16(dw, DW3000_DB_DIAG_SET_1,
+ DW3000_DB_DIAG_PDOA + 2, &val);
+ pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16);
+ break;
+ default:
+ dw3000_reg_read16(dw, DW3000_CIA_TDOA_1_PDOA_ID, 2, &val);
+ /* Phase difference of the 2 POAs. */
+ pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16);
+ }
+ trace_dw3000_read_pdoa(dw, pdoa);
+ if (pdoa & b12_sign_extend_test)
+ pdoa |= b12_sign_extend_mask;
+ pdoa -= dw->config.pdoaOffset;
+ if (pdoa < -pi_floored_q11)
+ pdoa += pix2_rounded_q11;
+ if (pdoa > pi_floored_q11)
+ pdoa -= pix2_rounded_q11;
+ return pdoa;
+}
+
+/**
+ * dw3000_pdoa_to_aoa_lut() - Convert PDoA to AoA.
+ * @dw: the DW device
+ * @pdoa_rad_q11: the PDoA value as returned by dw3000_read_pdoa()
+ *
+ * Convert PDoA (in radian, encoded as a Q11 fixed
+ * point number) to AoA value using calibration look-up table.
+ *
+ * Return: AoA value interpolated from LUT values.
+ */
+s16 dw3000_pdoa_to_aoa_lut(struct dw3000 *dw, s16 pdoa_rad_q11)
+{
+ const dw3000_pdoa_lut_t *lut = dw->config.pdoaLut;
+ int a = 0, b = DW3000_CALIBRATION_PDOA_LUT_MAX - 1;
+ s16 delta_pdoa, delta_aoa;
+
+ if (pdoa_rad_q11 < (*lut)[0][0])
+ return (*lut)[0][1];
+ if (pdoa_rad_q11 >= (*lut)[DW3000_CALIBRATION_PDOA_LUT_MAX - 1][0])
+ return (*lut)[DW3000_CALIBRATION_PDOA_LUT_MAX - 1][1];
+
+ while (a != b) {
+ int m = (a + b) / 2;
+ if (pdoa_rad_q11 < (*lut)[m][0])
+ b = m;
+ else
+ a = m + 1;
+ }
+
+ delta_pdoa = (*lut)[a][0] - (*lut)[a - 1][0];
+ delta_aoa = (*lut)[a][1] - (*lut)[a - 1][1];
+
+ return (*lut)[a][1] +
+ (delta_aoa * (pdoa_rad_q11 - (*lut)[a][0])) / delta_pdoa;
+}
+
+/**
+ * dw3000_set_sts_pdoa() - set device's STS & PDOA mode
+ * @dw: the DW device
+ * @sts_mode: the STS mode
+ * @pdoa_mode: the PDOA mode
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_sts_pdoa(struct dw3000 *dw, u8 sts_mode, u8 pdoa_mode)
+{
+ struct dw3000_config *config = &dw->config;
+ bool changed = false;
+ u8 prev_sts = config->stsMode;
+ u8 prev_pdoa = config->pdoaMode;
+ int rc = 0;
+
+ /* This configuration is reserved or not supported
+ * (c.f DW3000 User Manual) */
+ if (pdoa_mode == DW3000_PDOA_M2)
+ return -EOPNOTSUPP;
+
+ if ((config->stsMode ^ sts_mode) & DW3000_STS_BASIC_MODES_MASK) {
+ /**
+ * Enable the Super Deterministic Code according current STS
+ * key value set by the MCPS. Setting NUL key will set the
+ * SDC flag in stsMode.
+ */
+ u8 sts_sdc = config->stsMode & DW3000_STS_MODE_SDC;
+ config->stsMode = sts_mode | sts_sdc;
+ changed = true;
+ }
+ if (config->pdoaMode != pdoa_mode) {
+ config->pdoaMode = pdoa_mode;
+ changed = true;
+ trace_dw3000_set_pdoa(dw, pdoa_mode);
+ }
+ /* Re-configure the device with new STS & PDOA mode */
+ if (changed) {
+ rc = dw3000_configure_sys_cfg(dw, config);
+ if (rc) {
+ /* Restore initial value */
+ config->stsMode = prev_sts;
+ config->pdoaMode = prev_pdoa;
+ }
+ }
+ return rc;
+}
+
+/**
+ * dw3000_set_sts_length() - set device's STS length
+ * @dw: the DW device
+ * @len: length of the STS in blocks of 8 symbols
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_sts_length(struct dw3000 *dw, enum dw3000_sts_lengths len)
+{
+ struct dw3000_config *config = &dw->config;
+ bool changed = false;
+ int rc;
+
+ if (config->stsLength != len) {
+ rc = dw3000_reg_write8(dw, DW3000_CP_CFG0_ID, 0,
+ DW3000_GET_STS_LEN_REG_VALUE(len));
+ if (unlikely(rc))
+ return rc;
+ config->stsLength = len;
+ changed = true;
+ }
+ /* Re-configure the device with new STS length */
+ if (changed)
+ return dw3000_configure_sts(dw, config);
+ return 0;
+}
+
+/**
+ * dw3000_read_sts_timestamp() - read frame STS timestamp
+ * @dw: the DW device
+ * @sts_ts: the address where to store STS timestamp value
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_read_sts_timestamp(struct dw3000 *dw, u64 *sts_ts)
+{
+ int rc;
+ int fileid;
+ int offset;
+ __le64 buffer;
+
+ switch (dw->data.dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ fileid = DW3000_DB_DIAG_SET_1;
+ offset = DW3000_DB_DIAG_STS_TS;
+ break;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ fileid = DW3000_INDIRECT_POINTER_B_ID;
+ offset = DW3000_DB_DIAG_STS_TS;
+ break;
+ default:
+ fileid = DW3000_STS_TOA_LO_ID;
+ offset = 0;
+ }
+ /* Read 8 bytes (64-bits) register into buffer. */
+ rc = dw3000_reg_read_fast(dw, fileid, offset, sizeof(buffer), &buffer);
+ if (rc)
+ return rc;
+ *sts_ts = le64_to_cpu(buffer) &
+ ((u64)DW3000_STS_TOA_LO_STS_TOA_BIT_MASK |
+ ((u64)DW3000_STS_TOA_HI_STS_TOA_BIT_MASK
+ << DW3000_STS_TOA_LO_STS_TOA_BIT_LEN));
+ return 0;
+}
+
+/**
+ * dw3000_read_sts_quality() - read received frame STS quality
+ * @dw: the DW device
+ * @acc_qual: the STS accumulation quality of the frame
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_read_sts_quality(struct dw3000 *dw, s16 *acc_qual)
+{
+ int rc;
+ u16 qual;
+ rc = dw3000_reg_read16(dw, DW3000_STS_STS_ID, 0, &qual);
+ if (rc)
+ return rc;
+ if (qual & DW3000_STS_ACC_CP_QUAL_SIGNTST)
+ qual |= DW3000_STS_ACC_CP_QUAL_SIGNEXT;
+ *acc_qual = (s16)qual;
+ return 0;
+}
+
+/**
+ * dw3000_set_promiscuous() - Enable or disable the promiscuous mode
+ * @dw: the DW device
+ * @on: enable the promiscuous mode if true, otherwise disable it
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_promiscuous(struct dw3000 *dw, bool on)
+{
+ if (on)
+ return dw3000_framefilter_disable(dw);
+ return dw3000_framefilter_enable(
+ dw, DW3000_FF_BEACON_EN | DW3000_FF_DATA_EN | DW3000_FF_ACK_EN |
+ DW3000_FF_MAC_EN);
+}
+
+/**
+ * dw3000_set_autoack_reply_delay() - Select the delay used for auto-ack.
+ * @dw: The DW device.
+ * @response_delay_time_symbols: Delay in symbols.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_autoack_reply_delay(struct dw3000 *dw,
+ u8 response_delay_time_symbols)
+{
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+
+ if (local->ack_time == response_delay_time_symbols)
+ return 0;
+ rc = dw3000_reg_write8(dw, DW3000_ACK_RESP_ID,
+ DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET / 8,
+ response_delay_time_symbols);
+ if (unlikely(rc))
+ return rc;
+ local->ack_time = response_delay_time_symbols;
+ return 0;
+}
+
+/**
+ * dw3000_enable_autoack() - Enable the autoack for futures rx.
+ * @dw: The DW device.
+ * @force: Enable even if it was already in enable state.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_enable_autoack(struct dw3000 *dw, bool force)
+{
+ int r = 0;
+
+ if (!dw->autoack || force) {
+ /* Set the AUTO_ACK bit. */
+ r = dw3000_reg_or32(
+ dw, DW3000_SYS_CFG_ID, 0,
+ DW3000_SYS_CFG_AUTO_ACK_BIT_MASK |
+ DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK);
+ if (!r)
+ dw->autoack = true;
+ }
+ return r;
+}
+
+/**
+ * dw3000_disable_autoack() - Disable the autoack for futures rx.
+ * @dw: The DW device.
+ * @force: Disable even if it was already in disable state.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_disable_autoack(struct dw3000 *dw, bool force)
+{
+ int r = 0;
+
+ if (dw->autoack || force) {
+ /* Clear the AUTO_ACK bit.*/
+ r = dw3000_reg_and32(dw, DW3000_SYS_CFG_ID, 0,
+ (~DW3000_SYS_CFG_AUTO_ACK_BIT_MASK |
+ DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK));
+ if (!r)
+ dw->autoack = false;
+ }
+ return r;
+}
+
+/**
+ * dw3000_enable_auto_fcs() - Enable or disable the automatic FCS adding
+ * @dw: the DW device
+ * @on: enable the auto FCS adding if true, otherwise disable it
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_enable_auto_fcs(struct dw3000 *dw, bool on)
+{
+ if (!on) {
+ return dw3000_reg_or32(dw, DW3000_SYS_CFG_ID, 0,
+ DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK);
+ }
+ return dw3000_reg_and32(dw, DW3000_SYS_CFG_ID, 0,
+ (~DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK));
+}
+
+/**
+ * dw3000_otp_read32() - 32 bits OTP memory read.
+ * @dw: the DW device
+ * @addr: address in the OTP memory
+ * @val: value read from the OTP memory
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_otp_read32(struct dw3000 *dw, u16 addr, u32 *val)
+{
+ int rc;
+ u32 tmp = 0;
+
+ /* Set manual access mode */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0001);
+ if (unlikely(rc))
+ return rc;
+ /* Set the address */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_ADDR_ID, 0, addr);
+ if (unlikely(rc))
+ return rc;
+ /* Assert the read strobe */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0002);
+ if (unlikely(rc))
+ return rc;
+ /* Attempt a read from OTP address */
+ rc = dw3000_reg_read32(dw, DW3000_NVM_RDATA_ID, 0, &tmp);
+ if (unlikely(rc))
+ return rc;
+ /* Return the 32 bits of read data */
+ *val = tmp;
+ return 0;
+}
+
+/**
+ * dw3000_read_otp() - Read all required data from OTP.
+ * @dw: The DW device.
+ * @mode: bitfield to select information to retrieve from OTP memory
+ * See @DW3000_READ_OTP_PID and other values.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_read_otp(struct dw3000 *dw, int mode)
+{
+ struct dw3000_otp_data *otp = &dw->otp_data;
+ u32 val;
+ int rc;
+
+ /* Check if recalled with same parameters */
+ if ((mode | 1) == otp->mode)
+ return 0; /* No need to read OTP again. */
+
+ /* Reset OTP local data */
+ memset(otp, 0, sizeof(*otp));
+ otp->mode = mode | 1;
+ /* Values used by dw3000_prog_ldo_and_bias_tune() */
+ rc = dw3000_otp_read32(dw, DW3000_LDOTUNELO_ADDRESS, &otp->ldo_tune_lo);
+ if (rc)
+ return rc;
+ rc = dw3000_otp_read32(dw, DW3000_LDOTUNEHI_ADDRESS, &otp->ldo_tune_hi);
+ if (rc)
+ return rc;
+ rc = dw3000_otp_read32(dw, DW3000_BIAS_TUNE_ADDRESS, &otp->bias_tune);
+ if (rc)
+ return rc;
+ /* Values used by dw3000_prog_xtrim() */
+ rc = dw3000_otp_read32(dw, DW3000_XTRIM_ADDRESS, &val);
+ if (unlikely(rc))
+ return rc;
+ otp->xtal_trim = val & DW3000_XTAL_TRIM_BIT_MASK;
+ /* Load optional values according to mode parameter */
+ if (mode & DW3000_READ_OTP_PID) {
+ rc = dw3000_otp_read32(dw, DW3000_PARTID_ADDRESS, &otp->partID);
+ if (unlikely(rc))
+ return rc;
+ }
+ if (mode & DW3000_READ_OTP_LID) {
+ rc = dw3000_otp_read32(dw, DW3000_LOTID_ADDRESS, &otp->lotID);
+ if (unlikely(rc))
+ return rc;
+ }
+ if (mode & DW3000_READ_OTP_BAT) {
+ rc = dw3000_otp_read32(dw, DW3000_VBAT_ADDRESS, &val);
+ if (unlikely(rc))
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ /* We save three voltage levels in OTP during production testing:
+ * [7:0] = Vbat @ 1.62V
+ * [15:8] = Vbat @ 3.6V
+ * [23:16] = Vbat @ 3.0V
+ */
+ otp->vBatP = (val >> 16) & 0xff;
+ }
+ if (mode & DW3000_READ_OTP_TMP) {
+ rc = dw3000_otp_read32(dw, DW3000_VTEMP_ADDRESS, &val);
+ if (unlikely(rc))
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ otp->tempP = val & 0xff;
+ }
+ /* If the reference temperature has not been programmed in OTP (early
+ eng samples) set to default value */
+ /* TODO: avoid hard number, and replace it. */
+ if (otp->tempP == 0)
+ otp->tempP = 0x85; /* @temp of 22 deg */
+ /* If the reference voltage has not been programmed in OTP (early eng
+ samples) set to default value */
+ /* TODO: avoid hard number, and replace it. */
+ if (otp->vBatP == 0)
+ otp->vBatP = 0x74; /* @Vref of 3.0V */
+ /* OTP revision */
+ rc = dw3000_otp_read32(dw, DW3000_OTPREV_ADDRESS, &val);
+ if (unlikely(rc))
+ return rc;
+ otp->rev = val & 0xff;
+ /* Some chip depending adjustment */
+ if (__dw3000_chip_version >= DW3000_D0_VERSION) {
+ if (otp->xtal_trim == 0)
+ /* Set the default value for D0 if none set in OTP. */
+ otp->xtal_trim = DW3000_DEFAULT_XTAL_TRIM;
+ /* Read DGC tune addr */
+ rc = dw3000_otp_read32(dw, DW3000_DGC_TUNE_ADDRESS,
+ &otp->dgc_addr);
+ if (rc)
+ return rc;
+ }
+ /* PLL coarse code : starting code for calibration procedure */
+ return dw3000_otp_read32(dw, DW3000_PLL_CC_ADDRESS,
+ &otp->pll_coarse_code);
+}
+
+/**
+ * _dw3000_otp_wdata_write() - Configure and write value to the OTP
+ * memory block.
+ * @dw: The DW device.
+ * @val: 16-bit value to write to the OTP block.
+ *
+ * This function is used to configure the OTM memory block for memory
+ * writing operations and also to store the data value to be programmed
+ * into an OTP location. It is involved in the OTP memory writing procedure
+ * that is implemented into the '_dw3000_otp_write32()' function.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int _dw3000_otp_wdata_write(struct dw3000 *dw, u16 val)
+{
+ /**
+ * Pull the CS high to enable user interface for programming.
+ * 'val' is ignored in this instance by the OTP block.
+ */
+ /* TODO: avoid hard number, and replace it. */
+ int rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0200 | val);
+
+ if (unlikely(rc))
+ return rc;
+ /* Send the relevant command to the OTP block */
+ /* TODO: avoid hard number, and replace it. */
+ return dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000 | val);
+}
+
+/**
+ * _dw3000_otp_write32() - Program 32-bit value in OTP memory.
+ * @dw: The DW device.
+ * @data: data to write to given address.
+ * @addr: address to write to.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int _dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data)
+{
+ u32 ldo_tune;
+ u32 rd_buf;
+ u16 wr_buf[4];
+ u16 i;
+ int rc;
+
+ /* Read current register value */
+ rc = dw3000_reg_read32(dw, DW3000_LDO_TUNE_HI_ID, 0, &ldo_tune);
+ if (rc)
+ return rc;
+
+ /* Set VDDHV_TX LDO to max */
+ rc = dw3000_reg_or32(dw, DW3000_LDO_TUNE_HI_ID, 0,
+ DW3000_LDO_TUNE_HI_LDO_HVAUX_TUNE_BIT_MASK);
+ if (rc)
+ return rc;
+
+ /* Configure mode for programming */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x018);
+ if (rc)
+ return rc;
+
+ /* Select fast programming */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0025);
+ if (rc)
+ return rc;
+
+ /* Apply instruction to write the address */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0002);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x01fc);
+ if (rc)
+ return rc;
+
+ /* Sending the OTP address data (2 bytes) */
+ /* TODO: avoid hard number, and replace it. */
+ wr_buf[0] = 0x0100 | (addr & 0xff);
+ rc = _dw3000_otp_wdata_write(dw, wr_buf[0]);
+ if (rc)
+ return rc;
+
+ /* Write data (upper byte of address) */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0100);
+ if (rc)
+ return rc;
+
+ /* Clean up */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0000);
+ if (rc)
+ return rc;
+
+ /* Apply instruction to write data */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0002);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x01c0);
+ if (rc)
+ return rc;
+
+ /* Write the data */
+ /* TODO: use standard function to get expected endianness. */
+ wr_buf[0] = 0x100 | ((data >> 24) & 0xff);
+ wr_buf[1] = 0x100 | ((data >> 16) & 0xff);
+ wr_buf[2] = 0x100 | ((data >> 8) & 0xff);
+ wr_buf[3] = 0x100 | (data & 0xff);
+ rc = _dw3000_otp_wdata_write(dw, wr_buf[3]);
+ if (rc)
+ return rc;
+ rc = _dw3000_otp_wdata_write(dw, wr_buf[2]);
+ if (rc)
+ return rc;
+ rc = _dw3000_otp_wdata_write(dw, wr_buf[1]);
+ if (rc)
+ return rc;
+ rc = _dw3000_otp_wdata_write(dw, wr_buf[0]);
+ if (rc)
+ return rc;
+
+ /* Clean up */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0000);
+ if (rc)
+ return rc;
+
+ /* Enter prog mode */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x003a);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x01ff);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x010a);
+ if (rc)
+ return rc;
+ /* Clean up */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0000);
+ if (rc)
+ return rc;
+
+ /* Enable state/status output */
+ /* TODO: avoid hard number, and replace it. */
+ _dw3000_otp_wdata_write(dw, 0x003a);
+ _dw3000_otp_wdata_write(dw, 0x01bf);
+ _dw3000_otp_wdata_write(dw, 0x0100);
+
+ /* Start prog mode */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x003a);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0101);
+ if (rc)
+ return rc;
+
+ /* Different to previous one */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0002);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000);
+ if (rc)
+ return rc;
+
+ /**
+ * Read status after program command.
+ * 1000 is more than sufficient for max OTP programming delay and max
+ * supported DW3000 SPI rate.
+ * Instead a delay of 2ms (as commented out below) can be used.
+ * Burn time is about 1.76ms
+ */
+ /* TODO: avoid hard number, and replace it. */
+ for (i = 0; i < 1000; i++) {
+ dw3000_reg_read32(dw, DW3000_NVM_STATUS_ID, 0, &rd_buf);
+ if (!(rd_buf & DW3000_NVM_STATUS_NVM_PROG_DONE_BIT_MASK))
+ break;
+ }
+
+ /* Stop prog mode */
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x003a);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = _dw3000_otp_wdata_write(dw, 0x0102);
+ if (rc)
+ return rc;
+ /* Different to previous one */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0002);
+ if (rc)
+ return rc;
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000);
+ if (rc)
+ return rc;
+
+ /* Configure mode for reading */
+ /* TODO: avoid hard number, and replace it. */
+ rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0000);
+ if (rc)
+ return rc;
+
+ /* Restore LDO tune register */
+ return dw3000_reg_write32(dw, DW3000_LDO_TUNE_HI_ID, 0, ldo_tune);
+}
+
+/**
+ * dw3000_otp_write32() - 32 bits OTP memory write.
+ * @dw: The DW device.
+ * @addr: 16-bit OTP address into which the 32-bit data is programmed.
+ * @data: 32-bit data to be programmed into OTP.
+ *
+ * This function write a 32-bit data at a given address in the OTP memory
+ * and checks that that data is correctly written.
+ *
+ * Return: zero on success, one if the given data does not match the current
+ * register value, else a negative error code.
+ */
+int dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data)
+{
+ u32 tmp;
+ int rc;
+
+ /* Program the word */
+ rc = _dw3000_otp_write32(dw, addr, data);
+ if (rc)
+ return rc;
+
+ /* Check it is programmed correctly */
+ rc = dw3000_otp_read32(dw, addr, &tmp);
+ if (rc)
+ return rc;
+
+ return data != tmp;
+}
+
+/**
+ * dw3000_prog_xtrim() - Programs the device's crystal frequency
+ * @dw: The DW device.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_prog_xtrim(struct dw3000 *dw)
+{
+ struct dw3000_otp_data *otp = &dw->otp_data;
+ struct dw3000_local_data *local = &dw->data;
+ int rc;
+
+ if (otp->xtal_trim) {
+ int value = (int)otp->xtal_trim + local->xtal_bias;
+ if (value < 0)
+ value = 0;
+ else if (value > DW3000_XTAL_TRIM_BIT_MASK)
+ value = DW3000_XTAL_TRIM_BIT_MASK;
+ /* Set the XTAL trim value */
+ rc = dw3000_reg_write8(dw, DW3000_XTAL_ID, 0, (u8)value);
+ trace_dw3000_prog_xtrim(dw, rc, value, local->xtal_bias);
+ if (unlikely(rc))
+ return rc;
+ }
+ return 0;
+}
+
+/**
+ * dw3000_set_gpio_mode() - Configure GPIO for selected device
+ * @dw: The DW device.
+ * @mask: the bits to reset
+ * @mode: the bits to set
+ *
+ * Clear GPIOs which have been specified by the mask and set GPIOs mode as
+ * specified.
+ *
+ * Return: 0 on success or errno.
+ */
+int dw3000_set_gpio_mode(struct dw3000 *dw, u32 mask, u32 mode)
+{
+ return dw3000_reg_modify32(dw, DW3000_GPIO_MODE_ID, 0, ~mask, mode);
+}
+
+/**
+ * dw3000_set_gpio_dir() - Configure GPIO direction for selected device
+ * @dw: The DW device.
+ * @mask: the direction bits to reset
+ * @dir: the direction bits to set, 1 for input, 0 for output
+ *
+ * Return: 0 on success or errno.
+ */
+int dw3000_set_gpio_dir(struct dw3000 *dw, u16 mask, u16 dir)
+{
+ return dw3000_reg_modify16(dw, DW3000_GPIO_DIR_ID, 0, ~mask, dir);
+}
+
+/**
+ * dw3000_set_gpio_out() - Set GPIO output state for selected device
+ * @dw: The DW device.
+ * @reset: the output bits to reset
+ * @set: the output bits to set
+ *
+ * Return: 0 on success or errno.
+ */
+int dw3000_set_gpio_out(struct dw3000 *dw, u16 reset, u16 set)
+{
+ return dw3000_reg_modify16(dw, DW3000_GPIO_OUT_ID, 0, ~reset, set);
+}
+
+/**
+ * dw3000_set_lna_pa_mode() - Enable GPIO for external LNA or PA functionality
+ * @dw: The DW device.
+ * @lna_pa: LNA/PA configuration to set.
+ * Can be DW3000_LNA_PA_DISABLE, or an or-ed combination of
+ * DW3000_LNA_ENABLE, DW3000_PA_ENABLE and DW3000_TXRX_ENABLE.
+ *
+ * This is HW dependent, consult the DW3000/DW3700 User Manual.
+ *
+ * This can also be used for debug as enabling TX and RX GPIOs is quite handy
+ * to monitor DW3000/DW3700's activity.
+ *
+ * NOTE: Enabling PA functionality requires that fine grain TX sequencing is
+ * deactivated. This can be done using d3000_setfinegraintxseq().
+ *
+ * Return: 0 on success or errno.
+ */
+static int dw3000_set_lna_pa_mode(struct dw3000 *dw, int lna_pa)
+{
+ /* Clear GPIO 4, 5 configuration */
+ u32 gpio_mask = (DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK);
+ u32 gpio_mode = 0;
+
+ if (__dw3000_chip_version >= DW3000_D0_VERSION) {
+ if (lna_pa & (DW3000_LNA_ENABLE | DW3000_TXRX_ENABLE))
+ gpio_mode |= DW3000_GPIO_PIN5_EXTRXE;
+ if (lna_pa & (DW3000_PA_ENABLE | DW3000_TXRX_ENABLE))
+ gpio_mode |= DW3000_GPIO_PIN4_EXTTXE;
+ } else {
+ /* Also clear GPIO 0, 1, 6 configuration */
+ gpio_mask |= (DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP1_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK);
+ if (lna_pa & DW3000_LNA_ENABLE)
+ gpio_mode |= DW3000_GPIO_PIN6_EXTRX;
+ if (lna_pa & DW3000_PA_ENABLE)
+ gpio_mode |= (DW3000_GPIO_PIN4_EXTDA |
+ DW3000_GPIO_PIN5_EXTTX);
+ if (lna_pa & DW3000_TXRX_ENABLE)
+ gpio_mode |= (DW3000_GPIO_PIN0_EXTTXE |
+ DW3000_GPIO_PIN1_EXTRXE);
+ }
+ return dw3000_set_gpio_mode(dw, gpio_mask, gpio_mode);
+}
+
+/**
+ * dw3000_set_leds() - Configure TX/RX GPIOs to control LEDs
+ * @dw: The DW device.
+ * @mode: LEDs configuration to set
+ *
+ * This is used to set up Tx/Rx GPIOs which could be used to control LEDs
+ *
+ * Note: not completely IC dependent, also needs board with LEDS fitted on
+ * right I/O lines this function enables GPIOs 2 and 3 which are connected
+ * to LED3 and LED4 on EVB1000
+ *
+ * Return: 0 on success or errno.
+ */
+static int dw3000_set_leds(struct dw3000 *dw, u8 mode)
+{
+ u32 gpio_mask = (DW3000_GPIO_MODE_MSGP3_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP2_MODE_BIT_MASK);
+ int rc;
+
+ if (mode & DW3000_LEDS_ENABLE) {
+ u32 reg;
+ /* Set up GPIO for LED output. */
+ rc = dw3000_set_gpio_mode(dw, gpio_mask,
+ DW3000_GPIO_PIN2_RXLED |
+ DW3000_GPIO_PIN3_TXLED);
+ if (unlikely(rc))
+ return rc;
+ /* Enable LP Oscillator to run from counter and turn on
+ de-bounce clock. */
+ rc = dw3000_reg_or32(
+ dw, DW3000_CLK_CTRL_ID, 0,
+ DW3000_CLK_CTRL_LP_CLK_EN_BIT_MASK |
+ DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_MASK);
+ if (unlikely(rc))
+ return rc;
+ /* Enable LEDs to blink and set default blink time. */
+ reg = DW3000_LED_CTRL_BLINK_EN_BIT_MASK |
+ DW3000_LEDS_BLINK_TIME_DEF;
+ /* Make LEDs blink once if requested. */
+ if (mode & DW3000_LEDS_INIT_BLINK) {
+ reg |= DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK;
+ }
+ rc = dw3000_reg_write32(dw, DW3000_LED_CTRL_ID, 0, reg);
+ /* Clear force blink bits if needed. */
+ if (!rc && (mode & DW3000_LEDS_INIT_BLINK)) {
+ reg &= ~DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK;
+ rc = dw3000_reg_write32(dw, DW3000_LED_CTRL_ID, 0, reg);
+ }
+ } else {
+ /* Clear the GPIO bits that are used for LED control. */
+ rc = dw3000_set_gpio_mode(dw, gpio_mask, 0);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_reg_and16(dw, DW3000_LED_CTRL_ID, 0,
+ (u16)~DW3000_LED_CTRL_BLINK_EN_BIT_MASK);
+ }
+ return rc;
+}
+
+/**
+ * dw3000_config_antenna_gpio() - Set configuration for the given GPIO
+ * @dw: The DW device.
+ * @gpio: GPIO number to configure for antenna switching.
+ *
+ * The given GPIO is configured for GPIO mode in output direction.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_config_antenna_gpio(struct dw3000 *dw, int gpio)
+{
+ int rc = 0;
+ u32 modemask;
+ u32 modegpio;
+ u16 dirmask;
+
+ if (gpio < 4)
+ modegpio = 2 << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio);
+ else if (gpio > 6)
+ modegpio = 1 << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio);
+ else
+ modegpio = 0;
+ /* Configure selected GPIO for GPIO mode */
+ modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK
+ << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio);
+ rc = dw3000_set_gpio_mode(dw, modemask, modegpio);
+ if (rc)
+ return rc;
+ /* Configure selected GPIO for output direction */
+ dirmask = DW3000_GPIO_DIR_GDP0_BIT_MASK
+ << (DW3000_GPIO_DIR_GDP0_BIT_LEN * gpio);
+ rc = dw3000_set_gpio_dir(dw, dirmask, 0);
+ return rc;
+}
+
+/**
+ * dw3000_config_antenna_gpios() - Set configuration for all used GPIO
+ * @dw: The DW device.
+ *
+ * This function configure all GPIO found in antenna table.
+ * It is called before enabling the DW device in start() MCPS API.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_config_antenna_gpios(struct dw3000 *dw)
+{
+ char used_gpios[DW3000_GPIO_COUNT] = { 0 };
+ int rc, i;
+ /* Read all used GPIO used as antenna selectors */
+ for (i = 0; i < DW3000_CALIBRATION_ANTENNA_MAX; i++) {
+ u8 selector = dw->calib_data.ant[i].selector_gpio;
+ if ((selector < DW3000_GPIO_COUNT) && !used_gpios[selector]) {
+ /* Ensure selected GPIO is well configured */
+ rc = dw3000_config_antenna_gpio(dw, selector);
+ if (rc)
+ return rc;
+ /* Ensure it is configured only once in this loop */
+ used_gpios[selector] = 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * dw3000_set_antenna_gpio() - Set GPIO for the given antenna
+ * @dw: The DW device.
+ * @ant_calib: Calibration data for the selected antenna.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_set_antenna_gpio(struct dw3000 *dw,
+ struct dw3000_antenna_calib *ant_calib)
+{
+ int gpio = ant_calib->selector_gpio;
+ int value = ant_calib->selector_gpio_value;
+ int rc = 0;
+ if (gpio < DW3000_GPIO_COUNT) {
+ int offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * gpio;
+ /* Set GPIO state according config to select this antenna */
+ rc = dw3000_set_gpio_out(dw, !value << offset, value << offset);
+ }
+ trace_dw3000_set_antenna_gpio(dw, rc, gpio, value);
+ return rc;
+}
+
+/**
+ * dw3000_set_tx_antenna() - Configure device to use selected antenna for TX
+ * @dw: The DW device.
+ * @ant_set_id: The antennas set id to use
+ *
+ * Prepare the DW device to transmit frame using the specified antenna.
+ * The required HW information (port, gpio and gpio value) must be set
+ * correctly inside calibration data structure.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_tx_antenna(struct dw3000 *dw, int ant_set_id)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_antenna_calib *ant_calib;
+ int rc;
+ s8 ant_idx1, ant_idx2;
+ /* Sanity checks first */
+ if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX)
+ return -EINVAL;
+ /* Retrieve TX antenna from antenna set */
+ dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2);
+ if (ant_idx1 < 0 && ant_idx2 >= 0)
+ ant_idx1 = ant_idx2; /* use ant_idx2 if ant_idx1 undefined */
+ if (ant_idx1 < 0) {
+ /* Specified TX antenna must be valid */
+ dev_warn(dw->dev, "Bad antennas set id selected (%d)\n",
+ ant_set_id);
+ return -EINVAL;
+ }
+ /* Retrieve antenna GPIO configuration from calibration data */
+ ant_calib = &dw->calib_data.ant[ant_idx1];
+
+ /* switching to RF2 port for TX if necessary */
+ if (ant_calib->port == 1)
+ dw3000_change_tx_rf_port(dw, ant_calib->port == 1);
+
+ /* Set GPIO state according config to select this antenna */
+ rc = dw3000_set_antenna_gpio(dw, ant_calib);
+
+ if (rc)
+ return rc;
+ config->ant[ant_calib->port] = ant_idx1;
+ /* Switching antenna require changing some calibration parameters */
+ return dw3000_calib_update_config(dw);
+}
+
+/**
+ * dw3000_set_rx_antennas() - Set GPIOs to use selected antennas for RX
+ * @dw: The DW device.
+ * @ant_set_id: The antennas set id to use
+ * @pdoa_enabled: True if PDoA is enabled
+ * @frame_idx: the id of the frame to be rcvd
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_set_rx_antennas(struct dw3000 *dw, int ant_set_id, bool pdoa_enabled, int frame_idx)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_antenna_calib *ant_calib;
+ int rc = 0, port = -1, changed = 0;
+ s8 ant_idx1, ant_idx2;
+ /* Sanity checks first */
+ if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX)
+ return -EINVAL;
+ /* Retrieve RX antennas configuration from antenna set id */
+ dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2);
+ if (pdoa_enabled && (ant_idx1 < 0 || ant_idx2 < 0)) {
+ dev_warn(dw->dev,
+ "Bad antennas set id selected (%d) for pdoa\n",
+ ant_set_id);
+ return -EINVAL;
+ }
+ /* Apply config for first antenna */
+ if (ant_idx1 >= 0) {
+ ant_calib = &dw->calib_data.ant[ant_idx1];
+ port = ant_calib->port; /* Save port for later check */
+ if (ant_calib->port == 1) {
+ dw3000_change_rx_rf_port(dw, true);
+ } else {
+ dw3000_change_rx_rf_port(dw, false);
+ }
+ if (ant_idx1 != config->ant[port]) {
+ /* Set GPIO state according config for this first antenna */
+ rc = dw3000_set_antenna_gpio(dw, ant_calib);
+ if (rc)
+ return rc;
+ config->ant[port] = ant_idx1;
+ changed++;
+ }
+ }
+ /* Apply config for second antenna */
+ if (ant_idx2 >= 0) {
+ ant_calib = &dw->calib_data.ant[ant_idx2];
+
+ if (port == ant_calib->port) {
+ /* Specified RX antenna must be on different port */
+ dev_warn(
+ dw->dev,
+ "Bad antennas selected or bad configuration ant1=%d (port=%d), ant2=%d (port=%d)\n",
+ ant_idx1, port, ant_idx2, ant_calib->port);
+ return -EINVAL;
+ }
+ port = ant_calib->port;
+ if (ant_idx2 != config->ant[port]) {
+ /* Set GPIO state according config for this second antenna */
+ rc = dw3000_set_antenna_gpio(dw, ant_calib);
+ if (rc)
+ return rc;
+ config->ant[port] = ant_idx2;
+ changed++;
+ }
+ }
+ /* Switching antenna require changing some calibration parameters */
+ if (changed)
+ rc = dw3000_calib_update_config(dw);
+ return rc;
+}
+
+static void dw3000_mcps_timer_expired(struct work_struct *work)
+{
+ struct dw3000 *dw =
+ container_of(work, struct dw3000, timer_expired_work);
+ mcps802154_timer_expired(dw->llhw);
+}
+
+/**
+ * dw3000_complete_cir_data - Release consummer when data is ready to be dumped
+ * @dw: DW device
+ * Context: cir_data mutex already taken
+ */
+static void dw3000_complete_cir_data(struct dw3000 *dw)
+{
+ complete(&dw->cir_data->complete);
+}
+
+/**
+ * dw3000_read_ciaregs - Collect once all CIA regs required by cir_data
+ * @dw: DW device
+ * Return: 0 or negative error code
+ * Context: cir_data mutex already taken
+ */
+static int dw3000_read_ciaregs(struct dw3000 *dw)
+{
+ int rc;
+ unsigned int length;
+ unsigned int cia_bank;
+
+ /* Find witch bank is storing CIA data */
+ switch (dw->data.dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ dev_warn_once(dw->dev, "double banking not supported\n");
+ return -ENOSYS;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ cia_bank = DW3000_DB_DIAG_SET_1;
+ length = DW3000_DB_DIAG_SET_LEN;
+ break;
+ default:
+ cia_bank = 0;
+ }
+
+ /* Read whole active CIA data bank */
+ if (cia_bank)
+ return dw3000_xfer(dw, cia_bank, 0, length, dw->cir_data->ciaregs,
+ DW3000_SPI_RD_BIT);
+
+ /* From IP_TS -> STS1_TS_ */
+ length = DW3000_DB_DIAG_Reserved4 - DW3000_DB_DIAG_IP_TS;
+ rc = dw3000_xfer(dw, DW3000_IP_TS_LO_ID, 0, length,
+ dw->cir_data->ciaregs +
+ (DW3000_DB_DIAG_IP_TS >> 2),
+ DW3000_SPI_RD_BIT);
+ if (rc)
+ return rc;
+ /* From IP_DIAG0 -> IP_DIAG8 */
+ length = DW3000_DB_DIAG_IP_DIAG8 - DW3000_DB_DIAG_IP_DIAG0 +
+ DW3000_IP_DIAG8_LEN;
+ rc = dw3000_xfer(dw, DW3000_IP_DIAG0_ID, 0, length,
+ dw->cir_data->ciaregs +
+ (DW3000_DB_DIAG_IP_DIAG0 >> 2),
+ DW3000_SPI_RD_BIT);
+ if (rc)
+ return rc;
+ /* From STS_DIAG0 -> STS_DIAG3 */
+ length = DW3000_DB_DIAG_STS_DIAG3 - DW3000_DB_DIAG_STS_DIAG0 +
+ DW3000_STS_DIAG_3_LEN;
+ rc = dw3000_xfer(dw, DW3000_STS_DIAG_0_ID, 0, length,
+ dw->cir_data->ciaregs +
+ (DW3000_DB_DIAG_STS_DIAG0 >> 2),
+ DW3000_SPI_RD_BIT);
+ if (rc)
+ return rc;
+ /* From STS_DIAG4 -> STS1_DIAG12 */
+ length = DW3000_DB_DIAG_STS1_DIAG12 - DW3000_DB_DIAG_STS_DIAG4 +
+ DW3000_STS_DIAG_4_LEN;
+ rc = dw3000_xfer(dw, DW3000_STS_DIAG_4_ID, 0, length,
+ dw->cir_data->ciaregs +
+ (DW3000_DB_DIAG_STS_DIAG4 >> 2),
+ DW3000_SPI_RD_BIT);
+ if (rc)
+ return rc;
+ /* IP_DIAG_12 */
+ length = DW3000_IP_DIAG12_LEN;
+ rc = dw3000_xfer(dw, DW3000_IP_DIAG12_ID, 0, length,
+ dw->cir_data->ciaregs +
+ (DW3000_DB_DIAG_IP_DIAG_12 >> 2),
+ DW3000_SPI_RD_BIT);
+ return rc;
+}
+
+/**
+ * dw3000_read_cir_acc - Get how many records have been stored in the CIR accumulator
+ * @dw: DW device
+ * @stsMode: sts mode
+ * Context: cir_data mutex already taken
+ */
+static void dw3000_read_cir_acc(struct dw3000 *dw, u8 stsMode)
+{
+ u32 reg_acc;
+ u32 mask;
+
+ if (stsMode != DW3000_STS_MODE_OFF) {
+ reg_acc = DW3000_DB_DIAG_STS_DIAG12 >> 2;
+ mask = DW3000_STS_DIAG_12_MASK;
+ } else {
+ reg_acc = DW3000_DB_DIAG_IP_DIAG_12 >> 2;
+ mask = DW3000_IP_DIAG12_MASK;
+ }
+
+ dw->cir_data->acc = __le32_to_cpu(dw->cir_data->ciaregs[reg_acc]) &
+ mask;
+}
+
+/**
+ * dw3000_read_fp_power - Get current package power registers
+ * @dw: DW device
+ * @stsMode: sts mode
+ * Context: cir_data mutex already taken
+ */
+static void dw3000_read_fp_power(struct dw3000 *dw, u8 stsMode)
+{
+ struct dw3000_cir_data *cir = dw->cir_data;
+ u32 reg_idx;
+ u32 reg_f1;
+
+ if (stsMode != DW3000_STS_MODE_OFF) {
+ reg_idx = DW3000_DB_DIAG_STS_DIAG8 >> 2;
+ reg_f1 = DW3000_DB_DIAG_STS_DIAG2 >> 2;
+ } else {
+ reg_idx = DW3000_DB_DIAG_IP_DIAG8 >> 2;
+ reg_f1 = DW3000_DB_DIAG_IP_DIAG2 >> 2;
+ }
+
+ /* Fixed point variable, the First Path index is the integer part only */
+ cir->fp_index = __le32_to_cpu(cir->ciaregs[reg_idx]);
+ cir->fp_index = (cir->fp_index & (1 << 5)) + (cir->fp_index >> 6);
+
+ cir->fp_power1 = __le32_to_cpu(cir->ciaregs[reg_f1]);
+ cir->fp_power2 = __le32_to_cpu(cir->ciaregs[reg_f1 + 1]);
+ cir->fp_power3 = __le32_to_cpu(cir->ciaregs[reg_f1 + 2]);
+}
+
+/**
+ * dw3000_read_cir_data - Read the user requested number of complex numbers from CIR memory
+ * @dw: DW device
+ * @stsMode: sts mode
+ * @prf: prf of current package
+ * Return: 0 or negative error code
+ * Context: cir_data mutex already taken
+ */
+static int dw3000_read_cir_data(struct dw3000 *dw, u8 stsMode, u8 prf)
+{
+ int rc;
+ u16 offset;
+ u16 banksize;
+ s32 firstpath_idx = dw->cir_data->fp_index;
+ u8 nrecord = dw->cir_data->count;
+
+ if (stsMode != DW3000_STS_MODE_OFF) {
+ offset = DW3000_ACC_MEM_STS_OFFSET;
+ banksize = DW3000_ACC_MEM_STS_SIZE;
+ } else {
+ offset = DW3000_ACC_MEM_IPATOV_OFFSET;
+ if (prf == DW3000_PRF_64M)
+ banksize = DW3000_ACC_MEM_PRF64_SIZE;
+ else if (prf == DW3000_PRF_16M)
+ banksize = DW3000_ACC_MEM_PRF16_SIZE;
+ else {
+ dev_info(
+ dw->dev,
+ "custom prf used so set default cir bank size\n");
+ banksize = DW3000_ACC_MEM_PRF16_SIZE;
+ }
+ }
+
+ /* Apply user offset, goes at bank start if out of bank */
+ if (dw->cir_data->offset + firstpath_idx < 0) {
+ firstpath_idx = 0;
+ } else {
+ firstpath_idx += dw->cir_data->offset;
+ }
+
+ /* Avoid going out of bank */
+ if (firstpath_idx + nrecord > banksize)
+ nrecord = banksize - firstpath_idx;
+ offset += firstpath_idx;
+
+ /* Use indirect addressing to reach data in accumulator */
+ rc = dw3000_reg_write32(dw, DW3000_INDIRECT_ADDR_A_ID, 0,
+ (DW3000_CIR_RAM_ID >> 16));
+ if (rc)
+ return rc;
+ rc = dw3000_reg_write32(dw, DW3000_ADDR_OFFSET_A_ID, 0, offset);
+ if (rc)
+ return rc;
+
+ /* Indirectly read data from the IC to the buffer */
+ rc = dw3000_xfer(dw, DW3000_INDIRECT_POINTER_A_ID, 0,
+ sizeof(struct dw3000_cir_record) * nrecord + 1,
+ (u8 *)dw->cir_data->data - 1, DW3000_SPI_RD_BIT);
+
+ return rc;
+}
+
+/**
+ * dw3000_acc_clken - Control clock state to allow cir accumulator dumping
+ * @dw: DW device
+ * @enable: true to allow CIR accumulator access
+ * Return: 0 or negative error code
+ */
+static int dw3000_acc_clken(struct dw3000 *dw, bool enable)
+{
+ u16 mask = DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_MASK |
+ DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_MASK;
+
+ return dw3000_reg_modify16(dw, DW3000_CLK_CTRL_ID, 0, ~mask,
+ (!!enable) * mask);
+}
+
+/**
+ * dw3000_read_frame_cir_data - Producer of debugfs CIR data retrieving mecanism
+ * @dw: DW device
+ * @info: mac information about the current packet
+ * @utime: packet timestamp
+ *
+ * This function is called when a packet is received and the associated
+ * consummer is waiting for data because its debugfs interface file is
+ * in a blocked read call. If the wanted number of records is correctly
+ * read from CIR bank this function wakes up the consummer
+ *
+ * Return: 0 or negative error code
+ */
+int dw3000_read_frame_cir_data(struct dw3000 *dw,
+ struct mcps802154_rx_frame_info *info, u64 utime)
+{
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_cir_data *cir = dw->cir_data;
+ u8 prf = config->txCode >= 9 ? DW3000_PRF_64M : DW3000_PRF_16M;
+ u8 stsMode = config->stsMode & DW3000_STS_BASIC_MODES_MASK;
+ int rc = 0;
+
+ /* Check if CIR data gathering is enabled */
+ if (!dw->cir_data)
+ return 0;
+
+ trace_dw3000_read_frame_cir_data(dw, prf, stsMode, utime);
+
+ rc = mutex_lock_interruptible(&cir->mutex);
+ if (rc)
+ return -EINTR;
+
+ /* Get packet type */
+ cir->type = stsMode;
+
+ /* CIA algorithm have to finish before results reading */
+ if (!(dw->rx.flags & DW3000_RX_FLAG_CIA)) {
+ rc = -ENODATA;
+ goto read_frame_cir_error;
+ }
+
+ dw->full_cia_read = false;
+ rc = dw3000_read_ciaregs(dw);
+ if (rc)
+ goto read_frame_cir_error;
+ dw->full_cia_read = true;
+ dw3000_read_fp_power(dw, stsMode);
+ dw3000_read_cir_acc(dw, stsMode);
+ rc = dw3000_acc_clken(dw, true);
+ if (rc)
+ goto read_frame_cir_error;
+ rc = dw3000_read_cir_data(dw, stsMode, prf);
+ if (rc)
+ goto read_frame_cir_error;
+ rc = dw3000_acc_clken(dw, false);
+ if (rc)
+ goto read_frame_cir_error;
+ cir->utime = utime;
+ cir->ts = ktime_get_boottime_ns();
+
+ dw3000_complete_cir_data(dw);
+
+ /* Reset minidiag to allow a new measure */
+ rc = dw3000_reg_modify32(dw, DW3000_CIA_CONF_ID, 0,
+ ~DW3000_CIA_CONF_MINDIAG_BIT_MASK, 0x0);
+
+read_frame_cir_error:
+ mutex_unlock(&dw->cir_data->mutex);
+ trace_dw3000_read_frame_cir_data_return(dw, cir->ts, cir->fp_power1,
+ cir->fp_power2, cir->fp_power3,
+ cir->acc, cir->fp_index,
+ cir->offset, cir->filter);
+ return rc;
+}
+
+/**
+ * dw3000_cir_data_alloc_count - Allocation or release ressources for CIR exploitation
+ * @dw: DW device
+ * @nrecord: number of imaginary number retrieved per call ; 0 means release
+ * Return: 0 if ok, a negative error code else
+ */
+int dw3000_cir_data_alloc_count(struct dw3000 *dw, u16 nrecord)
+{
+ if (dw->cir_data) {
+ /* Avoid using dw->cir_data elsewhere and force consumer release
+ * because pointer target is changed */
+ struct dw3000_cir_data *cir = dw->cir_data;
+ dw->cir_data = NULL;
+ dw->cir_data_changed = true;
+ smp_wmb();
+ mutex_lock(&cir->mutex);
+ cir->count = 0;
+ complete(&cir->complete);
+ mutex_destroy(&cir->mutex);
+ kfree(cir);
+ }
+
+ if (nrecord > 0) {
+ size_t cirsz = sizeof(struct dw3000_cir_data) +
+ sizeof(struct dw3000_cir_record) * (nrecord - 1);
+ dw->cir_data =
+ (struct dw3000_cir_data *)kzalloc(cirsz, GFP_KERNEL);
+ if (likely(dw->cir_data)) {
+ dw->cir_data_changed = true;
+ init_completion(&dw->cir_data->complete);
+ mutex_init(&dw->cir_data->mutex);
+ dw->cir_data->count = nrecord;
+ } else {
+ dev_err(dw->dev,
+ "memory allocation failed for cir data buffer\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * dw3000_initialise() - Initialise the DW local data.
+ * @dw: The DW device.
+ *
+ * Make sure the local data is completely reset before starting initialisation.
+ */
+static void dw3000_initialise(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_stats *stats = &dw->stats;
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+
+ /* Double buffer mode off by default / clear the flag */
+ local->dblbuffon = DW3000_DBL_BUFF_OFF;
+ local->sleep_mode = DW3000_RUNSAR;
+ local->spicrc = DW3000_SPI_CRC_MODE_NO;
+ /* Clear all register cache variables */
+ local->rx_timeout_pac = 0;
+ local->w4r_time = 0;
+ local->ack_time = 0;
+ local->tx_fctrl = 0;
+ /* Initialise statistics, disabled by default, can be enabled by module
+ * parameter on load, or via testmode */
+ stats->enabled = dw3000_stats_enabled;
+ memset(stats->count, 0, sizeof(stats->count));
+ INIT_WORK(&dw->timer_expired_work, dw3000_mcps_timer_expired);
+
+#ifdef CONFIG_DW3000_DEBUG
+ if (dss->regbackup)
+ kfree(dss->regbackup);
+ memset(dss, 0, sizeof(*dss));
+ dss->regbackup = kzalloc(1024, GFP_KERNEL);
+ INIT_WORK(&dss->compare_work, dw3000_wakeup_compare);
+#else
+ memset(dss, 0, sizeof(*dss));
+#endif
+ /* CIR data initialization */
+ if (!dw->cir_data)
+ dw3000_cir_data_alloc_count(dw,
+ DW3000_DEFAULT_CIR_RECORD_COUNT);
+ /* Reset time origin for DTU calculation */
+ dw->time_zero_ns = ktime_get_boottime_ns();
+}
+
+/**
+ * dw3000_transfers_free() - Free allocated SPI messages
+ * @dw: the DW device to free the SPI messages
+ */
+void dw3000_transfers_free(struct dw3000 *dw)
+{
+ /* fast command message, only one transfer */
+ dw3000_free_fastcmd(dw->msg_fast_command);
+ dw->msg_fast_command = NULL;
+ /* specific pre-computed read/write full-duplex messages */
+ dw3000_free_xfer(dw->msg_read_rdb_status, 1);
+ dw->msg_read_rdb_status = NULL;
+ dw3000_free_xfer(dw->msg_read_rx_timestamp, 1);
+ dw->msg_read_rx_timestamp = NULL;
+ dw3000_free_xfer(dw->msg_read_rx_timestamp_a, 1);
+ dw->msg_read_rx_timestamp_a = NULL;
+ dw3000_free_xfer(dw->msg_read_rx_timestamp_b, 1);
+ dw->msg_read_rx_timestamp_b = NULL;
+ dw3000_free_xfer(dw->msg_read_sys_status, 1);
+ dw->msg_read_sys_status = NULL;
+ dw3000_free_xfer(dw->msg_read_all_sys_status, 1);
+ dw->msg_read_all_sys_status = NULL;
+ dw3000_free_xfer(dw->msg_read_sys_time, 1);
+ dw->msg_read_sys_time = NULL;
+ dw3000_free_xfer(dw->msg_write_sys_status, 1);
+ dw->msg_write_sys_status = NULL;
+ dw3000_free_xfer(dw->msg_write_all_sys_status, 1);
+ dw->msg_write_all_sys_status = NULL;
+ dw3000_free_xfer(dw->msg_read_dss_status, 1);
+ dw->msg_read_dss_status = NULL;
+ dw3000_free_xfer(dw->msg_write_dss_status, 1);
+ dw->msg_write_dss_status = NULL;
+ dw3000_free_xfer(dw->msg_write_spi_collision_status, 1);
+ dw->msg_write_spi_collision_status = NULL;
+ /* generic read/write full-duplex message */
+ dw3000_free_xfer(dw->msg_readwrite_fdx, 1);
+ dw->msg_readwrite_fdx = NULL;
+ /* message queue */
+ kfree(dw->msg_queue_buf);
+ dw->msg_queue_buf = NULL;
+ kfree(dw->msg_queue);
+ dw->msg_queue = NULL;
+}
+
+/**
+ * dw3000_transfers_init() - Allocate SPI messages
+ * @dw: the DW device to allocate SPI message for
+ *
+ * Allocate and pre-compute SPI messages to allow minimum overhead
+ * each time a specific read/write operation occurs.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_transfers_init(struct dw3000 *dw)
+{
+ /* fast command message, only one transfer */
+ dw->msg_fast_command = dw3000_alloc_prepare_fastcmd();
+ if (!dw->msg_fast_command)
+ goto alloc_err;
+ /* specific pre-computed read/write full-duplex messages */
+ dw->msg_read_rdb_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_RDB_STATUS_ID, 0, 1, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_rdb_status)
+ goto alloc_err;
+ dw->msg_read_rx_timestamp = dw3000_alloc_prepare_xfer(
+ dw, DW3000_RX_TIME_0_ID, 0, DW3000_RX_TIME_RX_STAMP_LEN,
+ DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_rx_timestamp)
+ goto alloc_err;
+ dw->msg_read_rx_timestamp_a = dw3000_alloc_prepare_xfer(
+ dw, DW3000_DB_DIAG_SET_1, DW3000_DB_DIAG_RX_TIME,
+ DW3000_RX_TIME_RX_STAMP_LEN, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_rx_timestamp_a)
+ goto alloc_err;
+ dw->msg_read_rx_timestamp_b = dw3000_alloc_prepare_xfer(
+ dw, DW3000_INDIRECT_POINTER_B_ID, DW3000_DB_DIAG_RX_TIME,
+ DW3000_RX_TIME_RX_STAMP_LEN, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_rx_timestamp_b)
+ goto alloc_err;
+ dw->msg_read_sys_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SYS_STATUS_ID, 0, 4, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_sys_status)
+ goto alloc_err;
+ dw->msg_read_all_sys_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SYS_STATUS_ID, 0, 8, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_sys_status)
+ goto alloc_err;
+ dw->msg_read_sys_time = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SYS_TIME_ID, 0, 4, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_sys_time)
+ goto alloc_err;
+ dw->msg_write_sys_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SYS_STATUS_ID, 0, 4, DW3000_SPI_WR_BIT);
+ if (!dw->msg_write_sys_status)
+ goto alloc_err;
+ dw->msg_write_all_sys_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SYS_STATUS_ID, 0, 8, DW3000_SPI_WR_BIT);
+ if (!dw->msg_write_all_sys_status)
+ goto alloc_err;
+ dw->msg_read_dss_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_DSS_STAT_ID, 0, 1, DW3000_SPI_RD_BIT);
+ if (!dw->msg_read_dss_status)
+ goto alloc_err;
+ dw->msg_write_dss_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_DSS_STAT_ID, 0, 1, DW3000_SPI_WR_BIT);
+ if (!dw->msg_write_dss_status)
+ goto alloc_err;
+ dw->msg_write_spi_collision_status = dw3000_alloc_prepare_xfer(
+ dw, DW3000_SPI_COLLISION_STATUS_ID, 0, 1, DW3000_SPI_WR_BIT);
+ if (!dw->msg_write_spi_collision_status)
+ goto alloc_err;
+ /* generic read/write full-duplex message */
+ dw->msg_readwrite_fdx =
+ dw3000_alloc_prepare_xfer(dw, 0, 0, 16, DW3000_SPI_RD_BIT);
+ if (!dw->msg_readwrite_fdx)
+ goto alloc_err;
+ /* mutex protecting msg_readwrite_fdx */
+ mutex_init(&dw->msg_mutex);
+ /* message queue */
+ dw->msg_queue_buf = kzalloc(DW3000_QUEUED_SPI_BUFFER_SZ, GFP_KERNEL);
+ if (!dw->msg_queue_buf)
+ goto alloc_err;
+ dw->msg_queue = kzalloc(sizeof(struct spi_message) +
+ DW3000_MAX_QUEUED_SPI_XFER *
+ sizeof(struct spi_transfer),
+ GFP_KERNEL);
+ return 0;
+
+alloc_err:
+ dw3000_transfers_free(dw);
+ return -ENOMEM;
+}
+
+/**
+ * dw3000_transfers_reset() - Reset allocated SPI messages
+ * @dw: the DW device for which SPI messages speed must be reset
+ *
+ * This ensure ALL transfers are clean, without an existing SPI speed.
+ * This function must be called each time after SPI speed is changed.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static int dw3000_transfers_reset(struct dw3000 *dw)
+{
+ dw3000_transfers_free(dw);
+ return dw3000_transfers_init(dw);
+}
+
+/**
+ * dw3000_init() - Initialise device
+ * @dw: DW3000 device
+ * @check_idlerc: Check if the device is in IDLE_RC state
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_init(struct dw3000 *dw, bool check_idlerc)
+{
+ int rc;
+
+ /* Initialise device structure first */
+ dw3000_initialise(dw);
+
+ if (check_idlerc) {
+ /* The DW IC should be in IDLE_RC state and ready */
+ if (!dw3000_check_idlerc(dw)) {
+ dev_err(dw->dev, "device not in IDLE_RC state\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Read LDO_TUNE and BIAS_TUNE from OTP */
+ /* This is device specific */
+ rc = dw->chip_ops->prog_ldo_and_bias_tune(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev,
+ "device LDO & bias tune setup has failed (%d)\n", rc);
+ return rc;
+ }
+ /* Read and init XTRIM */
+ rc = dw3000_prog_xtrim(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "device XTRIM setup has failed (%d)\n", rc);
+ return rc;
+ }
+ /* Read and init coarse code from OTP*/
+ /* Configure PLL coarse code, if needed. */
+ if (dw->chip_ops->prog_pll_coarse_code) {
+ rc = dw->chip_ops->prog_pll_coarse_code(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev,
+ "device coarse code setup has failed (%d)\n",
+ rc);
+ return rc;
+ }
+ }
+ /* Ensure STS fields are double-buffered if enabled, also enable stats
+ * if configured in module parameters */
+ rc = dw3000_configure_ciadiag(dw, dw->stats.enabled,
+ dw->data.dblbuffon ?
+ DW3000_CIA_DIAG_LOG_DBL_MID :
+ DW3000_CIA_DIAG_LOG_DBL_OFF);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "device CIA DIAG setup has failed (%d)\n", rc);
+ return rc;
+ }
+ /* Do some device specific initialisation if any */
+ rc = dw->chip_ops->init(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "device chip specific init has failed (%d)\n",
+ rc);
+ return rc;
+ }
+ /* Configure radio frequency */
+ rc = dw3000_configure(dw);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "device configuration has failed (%d)\n", rc);
+ return rc;
+ }
+ /* Initialise LEDs */
+ rc = dw3000_set_leds(dw, DW3000_LEDS_DISABLE);
+ if (unlikely(rc))
+ return rc;
+ /* Initialise LNA/PA modes */
+ rc = dw3000_set_lna_pa_mode(dw, dw->lna_pa_mode);
+ if (unlikely(rc))
+ return rc;
+ /* Configure delays */
+ rc = dw3000_set_antenna_delay(dw, 0);
+ if (unlikely(rc))
+ return rc;
+ /* Set auto-ack delay. */
+ rc = dw3000_set_autoack_reply_delay(
+ dw, DW3000_NUMBER_OF_SYMBOL_DELAY_AUTO_ACK);
+ if (unlikely(rc))
+ return rc;
+ rc = dw3000_disable_autoack(dw, true);
+ if (unlikely(rc))
+ return rc;
+ /* WiFi coexistence initialisation */
+ rc = dw3000_coex_init(dw);
+ if (unlikely(rc))
+ return rc;
+ /* Configure antenna selection GPIO if any */
+ return dw3000_config_antenna_gpios(dw);
+}
+
+void dw3000_remove(struct dw3000 *dw)
+{
+ struct dw3000_rx *rx = &dw->rx;
+ unsigned long flags;
+
+ /* Free RX's socket buffer if not claimed */
+ spin_lock_irqsave(&rx->lock, flags);
+ if (rx->skb)
+ dev_kfree_skb_any(rx->skb);
+ rx->skb = NULL;
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ /* Stop device */
+ dw3000_disable(dw);
+}
+
+/**
+ * dw3000_enable() - Enable the device
+ * @dw: the DW device
+ *
+ * This function masks the device's internal interruptions (fixed
+ * configuration) then enables the IRQ linked to the device interruption line.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_enable(struct dw3000 *dw)
+{
+ /* Select the events that will generate an interruption */
+ int rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX,
+ DW3000_ENABLE_INT_ONLY);
+ if (rc)
+ return rc;
+ /* Enable interrupt that will call the worker */
+ enable_irq(dw->spi->irq);
+
+ /* Update interface status */
+ atomic_set(&dw->iface_is_started, true);
+ return 0;
+}
+
+/**
+ * dw3000_disable() - Disable the device
+ * @dw: the DW device
+ *
+ * This function disables the IRQ linked to the device interruption line,
+ * unmasks the device's internal interruptions then clears its pending
+ * interruptions.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_disable(struct dw3000 *dw)
+{
+ int rc;
+ /* Update internal device's status */
+ atomic_set(&dw->iface_is_started, false);
+ /* Handle DEEP-SLEEP active case early */
+ if (dw->current_operational_state < DW3000_OP_STATE_INIT_RC) {
+ /* Seems chip is sleeping or already power-off. Just ensure
+ wake-up timer will not fire since not required anymore. */
+ dw3000_idle_cancel_timer(dw);
+ /* No need to call disable_irq() since already called and this
+ will require calling two times enable_irq() later because
+ enable/disable_irq are nested! */
+ return 0;
+ }
+ /* No IRQs after this point */
+ disable_irq(dw->spi->irq);
+ /* Disable further interrupt generation */
+ rc = dw3000_set_interrupt(dw, 0, DW3000_ENABLE_INT_ONLY);
+ if (rc)
+ goto err_spi;
+ /* Release Wifi coexistence. */
+ dw3000_coex_stop(dw);
+ /* Disable receiver and transmitter */
+ rc = dw3000_forcetrxoff(dw);
+ if (rc)
+ goto err_spi;
+ /* Clear pending ALL interrupts */
+ rc = dw3000_clear_sys_status(dw, (u32)-1);
+ if (rc)
+ goto err_spi;
+ return 0;
+
+err_spi:
+ return rc;
+}
+
+void dw3000_init_config(struct dw3000 *dw)
+{
+ int i, j, k;
+ /* Default configuration */
+ const struct dw3000_txconfig txconfig = {
+ .PGdly = 0x34,
+ .PGcount = 0,
+ .power = 0xfefefefe,
+ .smart = false,
+ .testmode_enabled = false,
+ };
+ const struct dw3000_config config = {
+ .chan = 5,
+ .txPreambLength = DW3000_PLEN_64,
+ .txCode = 9,
+ .rxCode = 9,
+ .sfdType = DW3000_SFD_TYPE_4Z,
+ .dataRate = DW3000_BR_6M8,
+ .phrMode = DW3000_PHRMODE_STD,
+ .phrRate = DW3000_PHRRATE_STD,
+ .sfdTO = DW3000_SFDTOC_DEF,
+ .stsMode = DW3000_STS_MODE_OFF | DW3000_STS_MODE_SDC,
+ .stsLength = DW3000_STS_LEN_64,
+ .pdoaMode = DW3000_PDOA_M0,
+ .pdoaOffset = 0,
+ .ant = { -1, -1 },
+ };
+ /* Set default configuration */
+ dw->config = config;
+ dw->txconfig = txconfig;
+ for (i = 0; i < DW3000_CALIBRATION_ANTENNA_MAX; i++) {
+ /* Ensure no GPIO pin are configured by default */
+ dw->calib_data.ant[i].selector_gpio = 0xff;
+ /* Ensure all antennas have the default antenna delay */
+ for (j = 0; j < DW3000_CALIBRATION_CHANNEL_MAX; j++) {
+ for (k = 0; k < DW3000_CALIBRATION_PRF_MAX; k++)
+ dw->calib_data.ant[i].ch[j].prf[k].ant_delay =
+ DW3000_DEFAULT_ANT_DELAY;
+ }
+ }
+ for (i = 0; i < ANTPAIR_MAX; i++) {
+ memcpy(dw->calib_data.antpair[i]
+ .ch[DW3000_CALIBRATION_CHANNEL_5]
+ .pdoa_lut,
+ dw3000_default_lut_ch5, sizeof(dw3000_pdoa_lut_t));
+ memcpy(dw->calib_data.antpair[i]
+ .ch[DW3000_CALIBRATION_CHANNEL_9]
+ .pdoa_lut,
+ dw3000_default_lut_ch9, sizeof(dw3000_pdoa_lut_t));
+ }
+ /* Set default antenna ports configuration */
+ dw->calib_data.ant[0].port = 0;
+ dw->calib_data.ant[1].port = 1;
+ /* By default WiFi Coex is enabled */
+ dw->coex_enabled = true;
+ for (i = 0; i < DW3000_CALIBRATION_CHANNEL_MAX; i++)
+ dw->calib_data.ch[i].wifi_coex_enabled = true;
+ /* Ensure power stats timing start at load time */
+ dw->power.cur_state = DW3000_PWR_OFF;
+ dw->power.stats[DW3000_PWR_OFF].count = 1;
+ dw->power.start_time = ktime_get_boottime_ns();
+ dw3000_nfcc_coex_init(dw);
+ /* Set interface status */
+ atomic_set(&dw->iface_is_started, false);
+
+ /* Initialize dw3000_rx spinlock */
+ spin_lock_init(&dw->rx.lock);
+}
+
+static inline int dw3000_isr_handle_spi_ready(struct dw3000 *dw,
+ struct dw3000_isr_data *isr)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ const dw3000_wakeup_done_cb wakeup_done_cb = dw->wakeup_done_cb;
+ int rc;
+
+ if (dw->current_operational_state != DW3000_OP_STATE_WAKE_UP) {
+ /* Not waking-up from DEEP SLEEP state, just change state and
+ return */
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC &&
+ isr->status & DW3000_SYS_STATUS_SPIRDY_BIT_MASK) {
+ /* This is a normal SPIRDY IRQ after power-on. Wake-up
+ * thread waiting for it. Chip is at least in IDLE_RC */
+ dw3000_set_operational_state(dw,
+ DW3000_OP_STATE_IDLE_RC);
+ }
+ return 0;
+ }
+
+ /*
+ * Second part of the Wake-up from DEEP SLEEP below.
+ *
+ * TODO: The RX with auto ACK is broken after DEEP SLEEP and WAKEUP.
+ * Please see UWB-1037.
+ */
+
+ /* Update power statistics */
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+
+ /* TODO: A full initialization with dw3000_init() CANNOT be used
+ * because it reset all cached value and won't allow put back DW
+ * in exactly same state than before entering DEEP SLEEP.
+ *
+ * dw3000_configure_chan() can be enough, but it hangs the remote
+ * host on TX. Please see UWB-1032.
+ *
+ * The AON memory was copied to register, so only STS & AES keys
+ * and some others non saved register need to be re-initialised.
+ */
+
+ if (dss->config_changed &
+ (DW3000_CHANNEL_CHANGED | DW3000_PCODE_CHANGED)) {
+ /* Channel or preamble code was changed during DEEP-SLEEP.
+ * Need to apply required configuration BEFORE PLL LOCK */
+ if (dss->config_changed & DW3000_CHANNEL_CHANGED)
+ /* Reconfigure all channel dependent */
+ rc = dw3000_configure_chan(dw);
+ else if (dss->config_changed & DW3000_PCODE_CHANGED)
+ /* Only change CHAN_CTRL with new code */
+ rc = dw3000_configure_pcode(dw);
+ else
+ rc = 0; /* remove uninit variable error */
+ if (rc)
+ return rc;
+ dss->config_changed &=
+ ~(DW3000_CHANNEL_CHANGED | DW3000_PCODE_CHANGED);
+ /* TODO: If channel is changed, the ongoing automatic PLL
+ * locking (see SEQ_CTRL & AON_DIG_CFG) might be stopped!
+ * If required, do it here and let the call below redo it
+ * properly. */
+ }
+
+ /* Preamble length or data rate were changed during DEEP-SLEEP. */
+ if (dss->config_changed &
+ (DW3000_PREAMBLE_LENGTH_CHANGED | DW3000_DATA_RATE_CHANGED)) {
+ rc = dw3000_configure_preamble_length_and_datarate(
+ dw, !(dss->config_changed &
+ DW3000_PREAMBLE_LENGTH_CHANGED));
+ if (rc)
+ return rc;
+ }
+
+ /* SFD was changed during DEEP-SLEEP. */
+ if (dss->config_changed & DW3000_SFD_CHANGED) {
+ rc = dw3000_configure_sfd_type(dw);
+ if (rc)
+ return rc;
+ }
+
+ /* PHR rate was changed during DEEP-SLEEP. */
+ if (dss->config_changed & DW3000_PHR_RATE_CHANGED) {
+ rc = dw3000_configure_phr_rate(dw);
+ if (rc)
+ return rc;
+ }
+
+ /* Auto calibrate the PLL and change to IDLE_PLL state */
+ rc = dw3000_lock_pll(dw, isr->status);
+ if (rc)
+ return rc;
+
+ /* Trace Wakeup after DTU/SYS_TIME resync */
+ {
+ u32 next_date_dtu;
+ s64 sleep_ns;
+ sleep_ns = dw3000_dtu_to_ktime(dw, dw->dtu_sync) -
+ dw3000_dtu_to_ktime(dw, dw->sleep_enter_dtu);
+ next_date_dtu = dss->next_operational_state ==
+ DW3000_OP_STATE_RX ?
+ dss->rx_config.timestamp_dtu :
+ dss->tx_config.timestamp_dtu;
+ trace_dw3000_wakeup_done(dw, sleep_ns / 1000,
+ dw->sleep_enter_dtu, next_date_dtu,
+ dss->next_operational_state);
+ }
+
+ /* PGF calibration */
+ rc = dw3000_pgf_cal(dw, 1);
+ if (rc)
+ return rc;
+ /* DGC LUT */
+ rc = dw3000_restore_dgc(dw);
+ if (rc)
+ return rc;
+ /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock.
+ * If this calibration is executed before the PLL lock, the PLL lock failed.
+ */
+ if (dw->chip_ops->adc_offset_calibration) {
+ rc = dw->chip_ops->adc_offset_calibration(dw);
+ if (rc)
+ goto setuperror;
+ }
+ /* WiFi coexistence initialisation */
+ rc = dw3000_coex_init(dw);
+ if (rc)
+ goto setuperror;
+ /* Configure antenna selection GPIO if any */
+ rc = dw3000_config_antenna_gpios(dw);
+ if (rc)
+ goto setuperror;
+ /* Reset cached antenna config to ensure GPIO are well reconfigured */
+ dw->config.ant[0] = -1;
+ dw->config.ant[1] = -1;
+
+ /* Select the events that will generate an interruption */
+ rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX,
+ DW3000_ENABLE_INT_ONLY);
+ if (rc)
+ goto setuperror;
+
+ rc = dw3000_reconfigure_hw_addr_filt(dw);
+ if (rc)
+ goto setuperror;
+
+ if ((dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) &&
+ !(dw->config.stsMode & DW3000_STS_MODE_SDC)) {
+ /* Resend key non-NUL STS KEY */
+ __le64 swapped_key[AES_KEYSIZE_128 / sizeof(__le64)];
+ _swap128(swapped_key, dw->data.sts_key);
+ rc = _dw3000_reg_write(dw, DW3000_STS_KEY_ID, 0,
+ AES_KEYSIZE_128, swapped_key);
+ if (rc)
+ goto setuperror;
+ } else {
+ /* STS wasn't activate before entering DEEP-SLEEP, invalidate
+ STS key to ensure it is resent to the chip the next time it
+ is changed by MCPS. */
+ memset(dw->data.sts_key, 0, AES_KEYSIZE_128);
+ dw->config.stsMode |= DW3000_STS_MODE_SDC;
+ }
+
+ /* TODO: So, just add below this line more required unsaved registers
+ * setup. */
+ rc = dw3000_reg_write32(dw, DW3000_LDO_VOUT_ID, 0, DW3000_RF_LDO_VOUT);
+ if (rc)
+ return rc;
+
+setuperror:
+#ifdef CONFIG_DW3000_DEBUG
+ /* Read and check configuration */
+ rc = dw3000_backup_registers(dw, true);
+ if (rc)
+ return rc;
+#endif
+ if (wakeup_done_cb) {
+ /* Consume the callback handler before execution. */
+ dw->wakeup_done_cb = NULL;
+ rc = wakeup_done_cb(dw);
+ if (rc == -ETIME) {
+ if (wakeup_done_cb == dw3000_wakeup_done_to_tx)
+ mcps802154_tx_too_late(dw->llhw);
+ else
+ mcps802154_rx_too_late(dw->llhw);
+
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static inline int dw3000_isr_handle_timer_events(struct dw3000 *dw)
+{
+ /* TODO: call chip specific method if special work is required */
+ return 0;
+}
+
+static inline int dw3000_isr_handle_spi_error(struct dw3000 *dw)
+{
+ dev_warn(dw->dev, "no support for callback %s", __func__);
+ return 0;
+}
+
+/**
+ * dw3000_signal_rx_buff_free() - signal the current RX buffer is free
+ * @dw: the DW device
+ * @dblbuffon: double buffer mode
+ *
+ * Check if in double buffer mode and if so which buffer host is currently
+ * accessing. Then Free up the current buffer and let the device know that
+ * it can receive into this buffer again
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_signal_rx_buff_free(struct dw3000 *dw, u8 *dblbuffon)
+{
+ if (*dblbuffon != DW3000_DBL_BUFF_OFF) {
+ /* Toggle buffer on chip */
+ int rc = dw3000_write_fastcmd(dw, DW3000_CMD_DB_TOGGLE);
+
+ if (likely(!rc)) {
+ /* Update the current buffer status */
+ *dblbuffon ^= DW3000_DBL_BUFF_SWAP;
+ }
+ }
+ return 0;
+}
+
+/**
+ * dw3000_read_frame_info16() - read the 16-bit frame information
+ * @dw: the DW device
+ * @dblbuffon: double buffer mode
+ * @finfo: the 16-bit value of the RX frame information
+ *
+ * Check if in double buffer mode and if so which buffer host is
+ * currently accessing to clear corresponding DB status register
+ * and return lower 16 bits of RX frame info register.
+ *
+ * Return: zero on success, else a negative error code.
+ */
+static inline int dw3000_read_frame_info16(struct dw3000 *dw, u8 dblbuffon,
+ u16 *finfo)
+{
+ int regfile_id;
+ int rc;
+
+ switch (dblbuffon) {
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_B:
+ /* clear DB status register bits corresponding to RX_BUFFER_B */
+ rc = dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, 0x70);
+ if (unlikely(rc))
+ return rc;
+ /* accessing frame info relating to the second buffer (RX_BUFFER_B) */
+ regfile_id = DW3000_INDIRECT_POINTER_B_ID;
+ break;
+ case DW3000_DBL_BUFF_ACCESS_BUFFER_A:
+ /* clear DB status register bits corresponding to RX_BUFFER_A */
+ rc = dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, 0x7);
+ if (unlikely(rc))
+ return rc;
+ /* accessing frame info relating to the first buffer (RX_BUFFER_A) */
+ regfile_id = DW3000_DB_DIAG_SET_1;
+ break;
+ default:
+ /* accessing frame info in single buffer mode */
+ regfile_id = DW3000_RX_FINFO_ID;
+ break;
+ }
+ return dw3000_reg_read16(dw, regfile_id, 0, finfo);
+}
+
+static inline int dw3000_isr_handle_rx_call_handler(struct dw3000 *dw,
+ struct dw3000_isr_data *isr)
+{
+ u32 eof_dtu;
+ int rc;
+
+ /* Store LDE/STS RX errors in rx_flags */
+ if (isr->status & DW3000_SYS_STATUS_CIAERR_BIT_MASK)
+ isr->rx_flags |= DW3000_RX_FLAG_CER;
+ else if (isr->status & DW3000_SYS_STATUS_CIA_DONE_BIT_MASK)
+ isr->rx_flags |= DW3000_RX_FLAG_CIA;
+ if (isr->status & DW3000_SYS_STATUS_CPERR_BIT_MASK)
+ isr->rx_flags |= DW3000_RX_FLAG_CPER;
+ /* In case of automatic ack reply. */
+ if (isr->status & DW3000_SYS_STATUS_AAT_BIT_MASK)
+ isr->rx_flags |= DW3000_RX_FLAG_AACK;
+ /* Read frame timestamp */
+ rc = dw3000_read_rx_timestamp(dw, &isr->ts_rctu);
+ if (unlikely(rc))
+ return rc;
+ isr->rx_flags |= DW3000_RX_FLAG_TS; /* don't read it again later */
+ eof_dtu = dw3000_sys_time_rctu_to_dtu(dw, isr->ts_rctu) +
+ dw3000_frame_duration_dtu(dw, isr->datalength, false);
+ /* Update power statistics */
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, eof_dtu);
+ /* Report received frame */
+ rc = dw3000_rx_frame(dw, isr);
+ if (unlikely(rc))
+ return rc;
+ /* Handle double buffering */
+ return dw3000_signal_rx_buff_free(dw, &dw->data.dblbuffon);
+}
+
+static inline int dw3000_isr_handle_rxfcg_event(struct dw3000 *dw,
+ struct dw3000_isr_data *isr)
+{
+ const u32 clear = DW3000_SYS_STATUS_ALL_RX_GOOD |
+ DW3000_SYS_STATUS_CIAERR_BIT_MASK |
+ DW3000_SYS_STATUS_CPERR_BIT_MASK;
+ u16 finfo16;
+ int rc;
+
+ /* Read frame info, only the first two bytes of the register are used
+ here. */
+ rc = dw3000_read_frame_info16(dw, dw->data.dblbuffon, &finfo16);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "could not read the frame info : %d\n", rc);
+ return rc;
+ }
+ /* Report frame length, standard frame length up to 127, extended frame
+ length up to 1023 bytes */
+ isr->datalength =
+ (finfo16 & dw->data.max_frames_len) - IEEE802154_FCS_LEN;
+ /* Report ranging bit */
+ if (finfo16 & DW3000_RX_FINFO_RNG_BIT_MASK)
+ isr->rx_flags = DW3000_RX_FLAG_RNG;
+ else
+ isr->rx_flags = 0;
+ rc = dw3000_isr_handle_rx_call_handler(dw, isr);
+ /* Clear errors (as we do not want to go back into cbRxErr) */
+ isr->status &= ~clear;
+ return rc;
+}
+
+static inline int dw3000_isr_handle_rxfr_sts_event(struct dw3000 *dw,
+ struct dw3000_isr_data *isr)
+{
+ const u32 clear = DW3000_SYS_STATUS_ALL_RX_GOOD |
+ DW3000_SYS_STATUS_RXFCE_BIT_MASK |
+ DW3000_SYS_STATUS_CIAERR_BIT_MASK |
+ DW3000_SYS_STATUS_CPERR_BIT_MASK;
+ int rc;
+ isr->datalength = 0;
+ rc = dw3000_isr_handle_rx_call_handler(dw, isr);
+ /* Clear errors (as we do not want to go back into cbRxErr) */
+ isr->status &= ~clear;
+ return rc;
+}
+
+static inline int dw3000_isr_handle_rxto_event(struct dw3000 *dw, u32 status)
+{
+ u32 end_dtu;
+ int rc;
+ /* If rx_disable() callback was called, we can't call mcps802154_rx_timeout() */
+ if (dw3000_rx_busy(dw, true))
+ return 0;
+ /* Update power statistics */
+ end_dtu = dw->power.rx_start + (dw->data.rx_timeout_pac + 1) *
+ dw->chips_per_pac /
+ DW3000_CHIP_PER_DTU;
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, end_dtu);
+ /* Report statistics */
+ rc = dw3000_rx_stats_inc(dw, DW3000_STATS_RX_TO, NULL);
+ if (unlikely(rc))
+ goto err;
+ /* Inform upper layer */
+ if (status & DW3000_SYS_STATUS_RXFTO_BIT_MASK)
+ dev_dbg(dw->dev, "rx frame timeout");
+ else
+ dev_dbg(dw->dev, "rx preamble timeout");
+ mcps802154_rx_timeout(dw->llhw);
+err:
+ WARN_ON_ONCE(dw3000_rx_busy(dw, false));
+ return rc;
+}
+
+static inline int dw3000_isr_handle_rxerr_event(struct dw3000 *dw, u32 status)
+{
+ struct mcps802154_llhw *llhw = dw->llhw;
+ enum mcps802154_rx_error_type error;
+
+ /* If rx_disable() callback was called, we can't call mcps802154_rx_error() */
+ if (dw3000_rx_busy(dw, true))
+ return 0;
+ /* Update power statistics */
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ /* Map error to mcps802154_rx_error_type enum */
+ if (status & (DW3000_SYS_STATUS_RXPTO_BIT_MASK |
+ DW3000_SYS_STATUS_RXFTO_BIT_MASK)) {
+ dev_dbg(dw->dev, "rx preamble timeout\n");
+ error = MCPS802154_RX_ERROR_TIMEOUT;
+ } else if (status & DW3000_SYS_STATUS_RXSTO_BIT_MASK) {
+ dev_dbg(dw->dev, "rx sfd timeout\n");
+ error = MCPS802154_RX_ERROR_SFD_TIMEOUT;
+ } else if (status & DW3000_SYS_STATUS_ARFE_BIT_MASK) {
+ u32 time;
+
+ dw3000_reg_read32(dw, DW3000_RX_TIME_0_ID, 0, &time);
+ dev_dbg(dw->dev, "rx rejected %08x\n", time);
+ error = MCPS802154_RX_ERROR_FILTERED;
+ } else if (status & DW3000_SYS_STATUS_RXFCE_BIT_MASK) {
+ dev_dbg(dw->dev, "bad checksum\n");
+ error = MCPS802154_RX_ERROR_BAD_CKSUM;
+ } else if (status & DW3000_SYS_STATUS_RXPHE_BIT_MASK) {
+ dev_dbg(dw->dev, "rx phr error\n");
+ error = MCPS802154_RX_ERROR_PHR_DECODE;
+ } else if (status & DW3000_SYS_STATUS_RXFSL_BIT_MASK) {
+ dev_dbg(dw->dev, "rx sync loss\n");
+ error = MCPS802154_RX_ERROR_OTHER;
+ } else {
+ dev_dbg(dw->dev, "rx error 0x%x\n", status);
+ error = MCPS802154_RX_ERROR_OTHER;
+ }
+ /* Report RX error event */
+ mcps802154_rx_error(llhw, error);
+
+ WARN_ON_ONCE(dw3000_rx_busy(dw, false));
+ return 0;
+}
+
+static inline int dw3000_isr_handle_tx_event(struct dw3000 *dw,
+ struct dw3000_isr_data *isr)
+{
+ /* Update power statistics */
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ if (dw->data.w4r_time) {
+ /* W4R configured, switch automatically to RX state.
+ RX start time is unknown here but we need it, so get current
+ DTU time. This is subject to IRQ delay but don't known better
+ solution here. */
+ u32 cur_dtu_time = dw3000_get_dtu_time(dw);
+ dw3000_power_stats(dw, DW3000_PWR_RX,
+ cur_dtu_time + dw->data.w4r_time *
+ DW3000_DTU_PER_DLY);
+ }
+ /* Report completion to MCPS 802.15.4 stack */
+ mcps802154_tx_done(dw->llhw);
+ /* Clear TXFRS status to not handle it a second time. */
+ isr->status &= ~DW3000_SYS_STATUS_TXFRS_BIT_MASK;
+ return 0;
+}
+
+static inline int dw3000_clear_db_events(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+
+ if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_A) {
+ /* clear RX events relating to buffer A */
+ return dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0,
+ DW3000_RDB_STATUS_CLEAR_BUFF0_EVENTS);
+ } else if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B) {
+ /* clear RX events relating to buffer B */
+ return dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0,
+ DW3000_RDB_STATUS_CLEAR_BUFF1_EVENTS);
+ }
+ return 0;
+}
+
+void dw3000_isr(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_isr_data isr; /* in-stack */
+ int rc = 0;
+ bool stsnd = ((dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) ==
+ DW3000_STS_MODE_ND);
+
+ /* Don't read if spurious DEEP-SLEEP IRQ */
+ if (dw->current_operational_state == DW3000_OP_STATE_DEEP_SLEEP) {
+ trace_dw3000_isr(dw, 0);
+ return;
+ }
+
+ /* Read status register(64bits). */
+ rc = dw3000_read_all_sys_status(dw, &isr.status);
+ if (rc)
+ goto spi_err;
+ trace_dw3000_isr(dw, isr.status);
+ if (dw->nfcc_coex.enabled) {
+ rc = dw3000_read_dss_status(dw, &isr.dss_stat);
+ if (rc)
+ goto spi_err;
+ trace_dw3000_isr_dss_stat(dw, isr.dss_stat);
+ }
+ /* Early clear all status bits since saved locally */
+ rc = dw3000_clear_all_sys_status(dw, isr.status);
+ if (rc)
+ goto spi_err;
+ /* RX double-buffering enabled */
+ if (local->dblbuffon) {
+ u8 status_db;
+ /* RDB status register */
+ rc = dw3000_read_rdb_status(dw, &status_db);
+ if (rc)
+ goto spi_err;
+ /*
+ * If accessing the second buffer (RX_BUFFER_B then read second
+ * nibble of the DB status reg)
+ */
+ if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B)
+ status_db >>= 4;
+ /*
+ * Setting the relevant bits in the main status register
+ * according to RDB status register.
+ */
+ if (status_db & DW3000_RDB_STATUS_RXFCG0_BIT_MASK)
+ isr.status |= DW3000_SYS_STATUS_RXFCG_BIT_MASK;
+ if (status_db & DW3000_RDB_STATUS_RXFR0_BIT_MASK)
+ isr.status |= DW3000_SYS_STATUS_RXFR_BIT_MASK;
+ if (status_db & DW3000_RDB_STATUS_CIADONE0_BIT_MASK)
+ isr.status |= DW3000_SYS_STATUS_CIA_DONE_BIT_MASK;
+ if (status_db & DW3000_RDB_STATUS_CP_ERR0_BIT_MASK)
+ isr.status |= DW3000_SYS_STATUS_CPERR_BIT_MASK;
+ /* We can clear event early since converted to status */
+ rc = dw3000_clear_db_events(dw);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* If automatic acknowledgement is not enabled, then the AAT status bit
+ must be ignored */
+ if (!dw->autoack)
+ isr.status &= ~DW3000_SYS_STATUS_AAT_BIT_MASK;
+
+ /* Handle TX confirmation event before RX in case of not an ACK. */
+ if ((isr.status & (DW3000_SYS_STATUS_AAT_BIT_MASK |
+ DW3000_SYS_STATUS_TXFRS_BIT_MASK)) ==
+ DW3000_SYS_STATUS_TXFRS_BIT_MASK) {
+ /* Report TX completion */
+ rc = dw3000_isr_handle_tx_event(dw, &isr);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle RX good frame event */
+ /* When using No Data STS mode, we do not get RXFCG but RXFR */
+ if (stsnd && (isr.status & DW3000_SYS_STATUS_RXFR_BIT_MASK)) {
+ rc = dw3000_isr_handle_rxfr_sts_event(dw, &isr);
+ if (unlikely(rc))
+ goto spi_err;
+ } else if (isr.status & DW3000_SYS_STATUS_RXFCG_BIT_MASK) {
+ /* Handle RX frame */
+ rc = dw3000_isr_handle_rxfcg_event(dw, &isr);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle TX confirmation event after RX in case of an ACK. */
+ if (isr.status & DW3000_SYS_STATUS_TXFRS_BIT_MASK) {
+ /* Report TX completion */
+ rc = dw3000_isr_handle_tx_event(dw, &isr);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle frame reception/preamble detect timeout events */
+ if (isr.status & DW3000_SYS_STATUS_ALL_RX_TO) {
+ /* Report RX timeout */
+ rc = dw3000_isr_handle_rxto_event(dw, isr.status);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle RX errors events */
+ if (isr.status & DW3000_SYS_STATUS_ALL_RX_ERR) {
+ if (isr.status & DW3000_SYS_STATUS_LCSSERR_BIT_MASK) {
+ /* LCSS error will not stop the receiver, however
+ because STS timestamp will be wrong the reception
+ is aborted */
+ rc = dw3000_forcetrxoff(dw);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+ /* Report RX error */
+ rc = dw3000_isr_handle_rxerr_event(dw, isr.status);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle SPI CRC errors events */
+ if (local->spicrc &&
+ (isr.status & DW3000_SYS_STATUS_SPICRCERR_BIT_MASK)) {
+ /* Handle SPI error */
+ rc = dw3000_isr_handle_spi_error(dw);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* SPI ready and IDLE_RC bit gets set when device powers on, or on wake
+ up */
+ if (isr.status & (DW3000_SYS_STATUS_SPIRDY_BIT_MASK |
+ DW3000_SYS_STATUS_RCINIT_BIT_MASK)) {
+ /* Handle SPI ready */
+ rc = dw3000_isr_handle_spi_ready(dw, &isr);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ /* Handle the SPI1 Available events in nfcc_coex mode only */
+ if (dw->nfcc_coex.enabled &&
+ (isr.dss_stat & DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK)) {
+ rc = dw3000_clear_dss_status(
+ dw, DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK);
+ if (rc)
+ goto spi_err;
+ /* Handle SPI available. */
+ rc = dw3000_nfcc_coex_spi1_avail(dw);
+ if (rc)
+ goto spi_err;
+ }
+
+ /* TIMER0/1 event will also set the SYS_EVENT bit */
+ if (isr.status & (DW3000_SYS_STATUS_TIMER0_BIT_MASK |
+ DW3000_SYS_STATUS_TIMER1_BIT_MASK)) {
+ /* Handle SPI ready */
+ rc = dw3000_isr_handle_timer_events(dw);
+ if (unlikely(rc))
+ goto spi_err;
+ }
+
+ trace_dw3000_return_int(dw, 0);
+ return;
+
+spi_err:
+ mcps802154_broken(dw->llhw);
+ /* TODO: handle SPI error */
+ trace_dw3000_return_int(dw, rc);
+ return;
+}
+
+int dw3000_testmode_continuous_tx_start(struct dw3000 *dw, u32 frame_length,
+ u32 rate)
+{
+ int rc;
+ int i;
+ static u8 tx_buf[DW3000_EXT_FRAME_LEN] = { 0 };
+
+ tx_buf[0] = 0xC5; /* 802.15.4 "blink" frame */
+
+ if (dw->txconfig.smart) {
+ rc = dw3000_adjust_tx_power(dw, frame_length);
+ if (rc)
+ return rc;
+ }
+
+ if (frame_length > dw->data.max_frames_len)
+ return -EINVAL;
+
+ for (i = 2; i < frame_length - IEEE802154_FCS_LEN; i++)
+ tx_buf[i] = i & 0xFF;
+
+ rc = dw3000_enable_rf_tx(dw, dw->config.chan, 1);
+ if (rc)
+ return rc;
+ rc = dw3000_ctrl_rftx_blocks(dw, dw->config.chan,
+ DW3000_RF_CTRL_MASK_ID);
+ if (rc)
+ return rc;
+ rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_SYS_TX);
+ if (rc)
+ return rc;
+
+ /* enable repeated frames */
+ rc = dw3000_reg_or8(dw, DW3000_TEST_CTRL0_ID, 0x0,
+ DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK);
+ if (rc)
+ return rc;
+
+ if (rate < 2)
+ rate = 2;
+
+ rc = dw3000_reg_write32(dw, DW3000_DX_TIME_ID, 0x0, rate);
+ if (rc)
+ return rc;
+ rc = dw3000_tx_write_data(dw, tx_buf, frame_length);
+ if (rc)
+ return rc;
+ rc = dw3000_writetxfctrl(dw, frame_length, 0, false);
+ if (rc)
+ return rc;
+
+ trace_dw3000_testmode_continuous_tx_start(dw, dw->config.chan,
+ frame_length, rate);
+ /* start TX immediately */
+ return dw3000_write_fastcmd(dw, DW3000_CMD_TX);
+}
+
+int dw3000_testmode_continuous_tx_stop(struct dw3000 *dw)
+{
+ int rc;
+
+ trace_dw3000_testmode_continuous_tx_stop(dw);
+ /* disable repeated frames */
+ rc = dw3000_reg_and8(dw, DW3000_TEST_CTRL0_ID, 0x0,
+ (uint8_t)(~DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK));
+ rc |= dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO);
+ rc |= dw3000_reg_write32(dw, DW3000_LDO_CTRL_ID, 0, 0x00000000);
+ /* Disable RF blocks for TX (configure RF_ENABLE_ID reg) */
+ rc |= dw3000_reg_write32(dw, DW3000_RF_ENABLE_ID, 0, 0x00000000);
+ /* Restore the TXRX switch to auto */
+ rc |= dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0x0,
+ DW3000_TXRXSWITCH_AUTO);
+ rc |= dw3000_reg_write32(dw, DW3000_RF_CTRL_MASK_ID, 0x0, 0x00000000);
+ return rc;
+}
+
+static int dw3000_spi_tests;
+module_param_named(spitests, dw3000_spi_tests, int, 0644);
+MODULE_PARM_DESC(spitests, "Activate SPI & GPIO test mode loop in RT thread");
+
+bool dw3000_spitests_enabled(struct dw3000 *dw)
+{
+ ((void)dw);
+ return dw3000_spi_tests != 0;
+}
+
+void dw3000_spitests(struct dw3000 *dw)
+{
+ const int count = 16384;
+ u32 mode_mask, dir_mask;
+ int test = 0;
+
+ if (!dw3000_spi_tests)
+ return;
+
+ perf_event_create_all(dw);
+
+ /* Setup DW3000 GPIO 4-6 in GPIO mode in output direction */
+ mode_mask = (DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK |
+ DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK);
+ dir_mask =
+ (DW3000_GPIO_DIR_GDP6_BIT_MASK | DW3000_GPIO_DIR_GDP5_BIT_MASK |
+ DW3000_GPIO_DIR_GDP4_BIT_MASK);
+ dw3000_set_gpio_mode(dw, mode_mask, 0);
+ dw3000_set_gpio_dir(dw, dir_mask, 0);
+
+ /* Loop until SPI test mode is disabled */
+ while (dw3000_spi_tests) {
+ u64 perfval[PERF_EVT_COUNT];
+ u64 start, duration;
+ u32 status = 0;
+ int i;
+ /* Bypass current test if not selected */
+ if (!(dw3000_spi_tests & (1 << test))) {
+ test = (test + 1) % 3;
+ continue;
+ }
+ dev_warn(dw->dev, "test mode: start test %d\n", test);
+ start = get_jiffies_64();
+ perf_event_start_all();
+ dw3000_set_gpio_out(
+ dw, 0, 1 << (test + DW3000_GPIO_DIR_GDP4_BIT_OFFSET));
+ switch (test) {
+ case 0:
+ /* 32bit register read loop */
+ for (i = 0; i < count; i++)
+ dw3000_reg_read_fast(dw, DW3000_SYS_STATUS_ID,
+ 0, sizeof(status),
+ &status);
+ break;
+ case 1:
+ /* 32bit optimised read loop */
+ for (i = 0; i < count; i++)
+ dw3000_read_sys_status(dw, &status);
+ break;
+ case 2:
+ /* 32bit generic read loop */
+ for (i = 0; i < count; i++)
+ dw3000_xfer(dw, DW3000_SYS_STATUS_ID, 0,
+ sizeof(status), &status,
+ DW3000_SPI_RD_BIT);
+ break;
+ }
+ dw3000_set_gpio_out(
+ dw, 1 << (test + DW3000_GPIO_DIR_GDP4_BIT_OFFSET), 0);
+ perf_event_stop_all(perfval);
+ duration = jiffies_to_usecs(get_jiffies_64() - start);
+ dev_warn(
+ dw->dev,
+ "test mode: test %d done in %llu ms, %llu us per read (status %x)\n",
+ test, duration / 1000, duration / count, status);
+ for (i = 0; i < PERF_EVT_COUNT; i++)
+ dev_warn(dw->dev, "\t%s: %llu\n", perf_hw_evt_name[i],
+ perfval[i]);
+ /* Set next test */
+ test = (test + 1) % 3;
+ }
+ perf_event_release_all();
+}
+
+static ssize_t dw3000_sysfs_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct dw3000 *dw = container_of(kobj, struct dw3000, sysfs_power_dir);
+ u64 idle_dur, rx_ns, tx_ns;
+ int ret;
+ if (dw->power.cur_state <= DW3000_PWR_IDLE)
+ dw3000_power_stats(dw, dw->power.cur_state, 0);
+ /* TX/RX are kept in DTU unit. Convert it here to limit conversion error */
+ rx_ns = dw->power.stats[DW3000_PWR_RX].dur * 10000 /
+ (DW3000_DTU_FREQ / 100000);
+ tx_ns = dw->power.stats[DW3000_PWR_TX].dur * 10000 /
+ (DW3000_DTU_FREQ / 100000);
+ idle_dur = dw->power.stats[DW3000_PWR_RUN].dur - tx_ns - rx_ns;
+ ret = scnprintf(buf, PAGE_SIZE,
+ "Off state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Deep sleep state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Run state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Idle state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Tx state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Rx state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n"
+ "Interrupts:\n\tcount:\t%lld\n",
+ dw->power.stats[DW3000_PWR_OFF].count,
+ dw->power.stats[DW3000_PWR_OFF].dur,
+ dw->power.stats[DW3000_PWR_DEEPSLEEP].count,
+ dw->power.stats[DW3000_PWR_DEEPSLEEP].dur,
+ dw->power.stats[DW3000_PWR_RUN].count,
+ dw->power.stats[DW3000_PWR_RUN].dur,
+ dw->power.stats[DW3000_PWR_IDLE].count, idle_dur,
+ dw->power.stats[DW3000_PWR_TX].count, tx_ns,
+ dw->power.stats[DW3000_PWR_RX].count, rx_ns,
+ (s64)atomic64_read(&dw->power.interrupts));
+ return ret;
+}
+
+static ssize_t dw3000_sysfs_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf,
+ size_t length)
+{
+ struct dw3000 *dw = container_of(kobj, struct dw3000, sysfs_power_dir);
+ if (attr == &dw3000_attribute) {
+ /* Reset statistics on buffer write */
+ int cstate = min(dw->power.cur_state, DW3000_PWR_RUN);
+ memset(dw->power.stats, 0, sizeof(dw->power.stats));
+ dw->power.start_time = ktime_get_boottime_ns();
+ dw->power.stats[cstate].count = 1;
+ if (dw->power.cur_state > DW3000_PWR_RUN)
+ dw->power.stats[dw->power.cur_state].count = 1;
+ atomic64_set(&dw->power.interrupts, 0);
+ }
+ return length;
+}
+
+static struct kobj_type dw3000_kobj_type = {
+ .sysfs_ops = &kobj_sysfs_ops,
+};
+
+void dw3000_sysfs_init(struct dw3000 *dw)
+{
+ int rc;
+ dev_dbg(dw->dev, "creating sysfs\n");
+ rc = kobject_init_and_add(&dw->sysfs_power_dir, &dw3000_kobj_type,
+ &dw->dev->kobj, "uwb");
+ if (rc)
+ return;
+ rc = sysfs_create_file(&dw->sysfs_power_dir, &dw3000_attribute.attr);
+ if (rc)
+ kobject_del(&dw->sysfs_power_dir);
+}
+
+void dw3000_sysfs_remove(struct dw3000 *dw)
+{
+ if (dw->sysfs_power_dir.state_in_sysfs) {
+ sysfs_remove_file(&dw->sysfs_power_dir, &dw3000_attribute.attr);
+ kobject_del(&dw->sysfs_power_dir);
+ }
+}
+
+/**
+ * dw3000_nfcc_coex_prepare_config() - Prepare the configuration before nfcc
+ * coex.
+ * @dw: The DW device.
+ *
+ * Return: Zero on success, else a negative error code.
+ */
+int dw3000_nfcc_coex_prepare_config(struct dw3000 *dw)
+{
+ int rc;
+
+ trace_dw3000_nfcc_coex_prepare_config(dw);
+
+ /* Disable any rx or tx command in progress. */
+ rc = dw3000_rx_disable(dw);
+ if (rc)
+ return rc;
+
+ /* May need to resync to avoid drift. */
+ rc = dw3000_may_resync(dw);
+ if (rc)
+ return rc;
+
+ /* Reset Wait-for-Response Time. */
+ rc = dw3000_setrxaftertxdelay(dw, 0);
+ if (rc)
+ return rc;
+
+ /* Reset the reception timeout. */
+ rc = dw3000_setrxtimeout(dw, 0);
+ if (rc)
+ return rc;
+
+ /*
+ * Disable RXPTO behavior for two reasons:
+ * - CCC firmware doesn't manage it. As the RXPTO_EN is false,
+ * then ccc is blocked.
+ * - Update cached value in dw3000 context for next use.
+ */
+ return dw3000_setpreambledetecttimeout(dw, 0);
+}
+
+/**
+ * dw3000_nfcc_coex_restore_config() - Restore the configuration after nfcc
+ * coex.
+ * @dw: The DW device.
+ *
+ * Some cache is reset to force the reconfiguration.
+ * Some RF parameters are reconfigured.
+ *
+ * Return: Zero on success, else a negative error code.
+ */
+int dw3000_nfcc_coex_restore_config(struct dw3000 *dw)
+{
+ struct dw3000_local_data *local = &dw->data;
+ struct dw3000_config *config = &dw->config;
+ int rc;
+
+ trace_dw3000_nfcc_coex_restore_config(dw);
+
+ /*
+ * We want to restore the configuration after nfcc slot.
+ */
+
+ /*
+ * Clear security registers related cache.
+ * The STS parameters will be reset during "set_sts_params" call.
+ */
+ memset(local->sts_key, 0, AES_KEYSIZE_128);
+ memset(local->sts_iv, 0, AES_BLOCK_SIZE);
+ dw->config.stsLength = 0;
+
+ /* Clear all cache variables to force the reconfiguration. */
+ local->rx_timeout_pac = 0;
+ local->w4r_time = 0;
+ local->ack_time = 0;
+ local->tx_fctrl = 0;
+ local->rx_frame_timeout_dly = 0;
+ local->ack_time = 0;
+
+ /* Configure the SYS_CFG register. */
+ rc = dw3000_configure_sys_cfg(dw, config);
+ if (rc)
+ return rc;
+
+ /* WiFi coexistence initialisation. */
+ rc = dw3000_coex_init(dw);
+ if (rc)
+ return rc;
+
+ /* Configure antenna selection GPIO if any. */
+ rc = dw3000_config_antenna_gpios(dw);
+ if (rc)
+ return rc;
+
+ /*
+ * Reset cached antenna config to ensure GPIO are reconfigured
+ * correctly.
+ */
+ dw->config.ant[0] = -1;
+ dw->config.ant[1] = -1;
+
+ /* Select the events that will trigger an interrupt. */
+ rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX,
+ DW3000_ENABLE_INT_ONLY);
+ if (rc)
+ return rc;
+
+ /*
+ * PLL already is locked but some RF parameters could be changed.
+ * So we reprogram the Xtal, the DGC, the ADC, ...
+ */
+
+ /* Xtal trim could be changed. */
+ rc = dw3000_prog_xtrim(dw);
+ if (rc)
+ return rc;
+
+ /* Configure PLL coarse code, if needed. */
+ if (dw->chip_ops->prog_pll_coarse_code) {
+ rc = dw->chip_ops->prog_pll_coarse_code(dw);
+ if (rc) {
+ dev_err(dw->dev, "device coarse code setup has failed (%d)\n", rc);
+ return rc;
+ }
+ }
+
+ /* Configure delays. */
+ rc = dw3000_set_antenna_delay(dw, 0);
+ if (rc)
+ return rc;
+
+ rc = dw3000_reconfigure_hw_addr_filt(dw);
+ if (rc)
+ return rc;
+
+ /* Do some device specific initialisation if any. */
+ rc = dw->chip_ops->init(dw);
+ if (rc) {
+ dev_err(dw->dev, "device chip specific init has failed (%d)\n",
+ rc);
+ return rc;
+ }
+
+ /* Reconfigure all dependent channels. */
+ rc = dw3000_configure_chan(dw);
+ if (rc)
+ return rc;
+
+ rc = dw3000_pgf_cal(dw, 1);
+ if (rc)
+ return rc;
+
+ /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock. */
+ if (dw->chip_ops->adc_offset_calibration) {
+ rc = dw->chip_ops->adc_offset_calibration(dw);
+ if (rc)
+ return rc;
+ }
+
+ /* Setup TX preamble size, data rate and SDF timeout count. */
+ rc = dw3000_configure_preamble_length_and_datarate(dw, false);
+ if (rc)
+ return rc;
+
+ /* PHR rate. */
+ rc = dw3000_configure_phr_rate(dw);
+ if (rc)
+ return rc;
+
+ /*
+ * Ensure STS fields are double-buffered if enabled, also enable stats
+ * if configured in module parameters.
+ */
+ rc = dw3000_configure_ciadiag(dw, dw->stats.enabled,
+ dw->data.dblbuffon ?
+ DW3000_CIA_DIAG_LOG_DBL_MID :
+ DW3000_CIA_DIAG_LOG_DBL_OFF);
+ if (rc) {
+ dev_err(dw->dev, "device CIA DIAG setup has failed (%d)\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_core.h b/kernel/drivers/net/ieee802154/dw3000_core.h
new file mode 100644
index 0000000..aa7c4ed
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_core.h
@@ -0,0 +1,743 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CORE_H
+#define __DW3000_CORE_H
+
+#include "dw3000.h"
+
+/* Maximum SPI bus speed when PLL is not yet locked */
+#define DW3000_SPI_SLOW_HZ 3000000
+
+#define DW3000_GPIO_COUNT 9 /* GPIO0 to GPIO8 */
+
+/* DW3000 wake-up latency. At least 2ms is required. */
+#define DW3000_WAKEUP_LATENCY_US 15000
+
+/* Define DW3000 PDOA modes */
+#define DW3000_PDOA_M0 0x0 /* PDOA mode is off */
+#define DW3000_PDOA_M1 0x1 /* PDOA mode 1 */
+#define DW3000_PDOA_M2 0x2 /* PDOA mode 2 (reserved or not supported) */
+#define DW3000_PDOA_M3 0x3 /* PDOA mode 3 */
+#define DW3000_PDOA_CONFIG_MASK 0x3
+
+/* Define DW3000 STS modes */
+#define DW3000_STS_MODE_OFF 0x0 /* STS is off */
+#define DW3000_STS_MODE_1 0x1 /* STS mode 1 */
+#define DW3000_STS_MODE_2 0x2 /* STS mode 2 */
+#define DW3000_STS_MODE_ND 0x3 /* STS with no data */
+#define DW3000_STS_BASIC_MODES_MASK 0x3 /* STS basic modes */
+#define DW3000_STS_MODE_SDC 0x8 /* Enable Super Deterministic Codes */
+#define DW3000_STS_CONFIG_MASK 0xB
+
+/* Offset of each area in CIR accumulator memory */
+#define DW3000_ACC_MEM_IPATOV_OFFSET 0x0 /* Base address */
+#define DW3000_ACC_MEM_PRF64_SIZE 1016 /* Bank size when PRF=64 MHz */
+#define DW3000_ACC_MEM_PRF16_SIZE 992 /* Bank size when PRF=16 MHz */
+#define DW3000_ACC_MEM_STS_OFFSET \
+ 1024 /* Base address when STS enabled and pdoa != 3 */
+#define DW3000_ACC_MEM_STS_SIZE \
+ 512 /* Bank size when STS enabled and pdoa != 3 */
+#define DW3000_ACC_MEM_STS_PDOA3_OFFSET \
+ (DW3000_ACC_MEM_STS_OFFSET + \
+ DW3000_ACC_MEM_STS_SIZE) /* Base address when sts enabled and pdoa mode 3 */
+#define DW3000_ACC_MEM_STS_PDOA3_SZ \
+ 512 /* Bank size when sts enabled and pdoa mode 3 */
+
+/**
+ * DW3000_GET_STS_LEN_UNIT_VALUE() - Convert STS length enum into unit value
+ * @x: value from enum dw3000_sts_lengths
+ *
+ * Return: the STS length in unit of 8-symbols block.
+ */
+#define DW3000_GET_STS_LEN_UNIT_VALUE(x) ((u16)(1 << (x)))
+
+/* Constants for specifying TX Preamble length in symbols */
+#define DW3000_PLEN_4096 0x03 /* Standard preamble length 4096 symbols */
+#define DW3000_PLEN_2048 0x0A /* Non-standard preamble length 2048 symbols */
+#define DW3000_PLEN_1536 0x06 /* Non-standard preamble length 1536 symbols */
+#define DW3000_PLEN_1024 0x02 /* Standard preamble length 1024 symbols */
+#define DW3000_PLEN_512 0x0d /* Non-standard preamble length 512 symbols */
+#define DW3000_PLEN_256 0x09 /* Non-standard preamble length 256 symbols */
+#define DW3000_PLEN_128 0x05 /* Non-standard preamble length 128 symbols */
+#define DW3000_PLEN_72 0x07 /* Non-standard length 72 */
+#define DW3000_PLEN_32 0x04 /* Non-standard length 32 */
+#define DW3000_PLEN_64 0x01 /* Standard preamble length 64 symbols */
+
+/* Constants for specifying the (Nominal) mean Pulse Repetition Frequency
+ These are defined for direct write (with a shift if necessary) to CHAN_CTRL
+ and TX_FCTRL regs */
+#define DW3000_PRF_16M 1 /* UWB PRF 16 MHz */
+#define DW3000_PRF_64M 2 /* UWB PRF 64 MHz */
+#define DW3000_PRF_SCP 3 /* SCP UWB PRF ~100 MHz */
+
+/* Constants for specifying Start of Frame Delimiter (SFD) type */
+#define DW3000_SFD_TYPE_STD 0 /* Standard short IEEE802154 SFD (8 symbols) */
+#define DW3000_SFD_TYPE_DW_8 1 /* Decawave-defined 8 symbols SFD */
+#define DW3000_SFD_TYPE_DW_16 2 /* Decawave-defined 16 symbols SFD */
+#define DW3000_SFD_TYPE_4Z 3 /* IEEE802154z SFD (8 symbols) */
+
+/* Constants for selecting the bit rate for data TX (and RX)
+ These are defined for write (with just a shift) the TX_FCTRL register */
+#define DW3000_BR_850K 0 /* UWB bit rate 850 kbits/s */
+#define DW3000_BR_6M8 1 /* UWB bit rate 6.8 Mbits/s */
+
+/* Constants for selecting the PHR mode */
+#define DW3000_PHRMODE_STD 0x0 /* standard PHR mode */
+#define DW3000_PHRMODE_EXT 0x1 /* DW proprietary extended frames PHR mode */
+
+/* Constants for selecting the bit rate for the PHR */
+#define DW3000_PHRRATE_STD 0x0 /* standard PHR rate, 850 kbits/s */
+#define DW3000_PHRRATE_DTA 0x1 /* PHR at data rate 6.8 Mbits/s */
+
+/* Constants for specifying Preamble Acquisition Chunk (PAC) Size in symbols */
+#define DW3000_PAC8 0 /* recommended for RX of preamble length 128 and below */
+#define DW3000_PAC16 1 /* recommended for RX of preamble length 256 */
+#define DW3000_PAC32 2 /* recommended for RX of preamble length 512 */
+#define DW3000_PAC4 3 /* recommended for RX of preamble length < 127 */
+
+enum spi_modes {
+ DW3000_SPI_RD_BIT = 0x0000U,
+ DW3000_SPI_WR_BIT = 0x8000U,
+ DW3000_SPI_AND_OR_8 = 0x8001U,
+ DW3000_SPI_AND_OR_16 = 0x8002U,
+ DW3000_SPI_AND_OR_32 = 0x8003U,
+ DW3000_SPI_AND_OR_MSK = 0x0003U,
+};
+
+/* Frame filtering configuration options */
+#define DW3000_FF_ENABLE_802_15_4 0x2 /* use 802.15.4 filtering rules */
+#define DW3000_FF_DISABLE 0x0 /* disable FF */
+#define DW3000_FF_BEACON_EN 0x001 /* beacon frames allowed */
+#define DW3000_FF_DATA_EN 0x002 /* data frames allowed */
+#define DW3000_FF_ACK_EN 0x004 /* ack frames allowed */
+#define DW3000_FF_MAC_EN 0x008 /* mac control frames allowed */
+#define DW3000_FF_RSVD_EN 0x010 /* reserved frame types allowed */
+#define DW3000_FF_MULTI_EN 0x020 /* multipurpose frames allowed */
+#define DW3000_FF_FRAG_EN 0x040 /* fragmented frame types allowed */
+#define DW3000_FF_EXTEND_EN 0x080 /* extended frame types allowed */
+#define DW3000_FF_COORD_EN \
+ 0x100 /* behave as coordinator (can receive frames
+ with no dest address (need PAN ID match)) */
+#define DW3000_FF_IMPBRCAST_EN 0x200 /* allow MAC implicit broadcast */
+
+/* DW3000 soft reset options */
+#define DW3000_RESET_ALL 0x00
+#define DW3000_RESET_CTRX 0x0f
+#define DW3000_RESET_RX 0xef
+#define DW3000_RESET_CLEAR 0xff
+
+/* SYS_STATE_LO register information */
+/* TSE is in IDLE (IDLE_PLL) */
+#define DW3000_SYS_STATE_IDLE 0x3
+/* TSE is in TX but TX is in IDLE */
+#define DW3000_SYS_STATE_TXERR 0xD0000
+
+/* Fast commands */
+/* Turn off TX or RX, clear any TX/RX events and put DW3000 into IDLE */
+#define DW3000_CMD_TXRXOFF 0x0
+/* Start TX */
+#define DW3000_CMD_TX 0x1
+/* Enable RX */
+#define DW3000_CMD_RX 0x2
+/* Start delayed TX (RMARKER will be @ time set in DX_TIME register) */
+#define DW3000_CMD_DTX 0x3
+/* Enable RX @ time specified in DX_TIME register */
+#define DW3000_CMD_DRX 0x4
+/* Start delayed TX (RMARKER will be @ time = TX_TIME + DX_TIME) */
+#define DW3000_CMD_DTX_TS 0x5
+/* Enable RX @ time = TX_TIME + DX_TIME */
+#define DW3000_CMD_DRX_TS 0x6
+/* Start delayed TX (RMARKER will be @ time = RX_TIME + DX_TIME) */
+#define DW3000_CMD_DTX_RS 0x7
+/* Enable RX @ time = RX_TIME + DX_TIME */
+#define DW3000_CMD_DRX_RS 0x8
+/* Start delayed TX (RMARKER will be @ time = DREF_TIME + DX_TIME) */
+#define DW3000_CMD_DTX_REF 0x9
+/* Enable RX @ time = DREF_TIME + DX_TIME */
+#define DW3000_CMD_DRX_REF 0xa
+/* Start delayed TX (as DTX below), enable RX when TX done */
+#define DW3000_CMD_DTX_W4R 0xd
+/* Start TX (as below), enable RX when TX done */
+#define DW3000_CMD_TX_W4R 0xc
+/* Toggle double buffer pointer */
+#define DW3000_CMD_DB_TOGGLE 0x13
+/* Write to the Semaphore and try to reserve access (if it hasn't already been
+ reserved by the other master) */
+#define DW3000_CMD_SEMA_REQ 0x14
+/* Release the semaphore if it is currently reserved by this master. */
+#define DW3000_CMD_SEMA_REL 0x15
+/* Only SPI 2 can issue this command. Force access regardless of current
+ semaphore value. */
+#define DW3000_CMD_SEMA_FORCE 0x16
+/* Global digital reset including of the semaphore */
+#define DW3000_CMD_SEMA_RESET 0x18
+/* Global digital reset without reset of the semaphore */
+#define DW3000_CMD_SEMA_RESET_NO_SEM 0x19
+/* Enters sleep/deep sleep according to ANA_CFG - DEEPSLEEP_EN */
+#define DW3000_CMD_ENTER_SLEEP 0x1A
+
+/* Size of RX LUT configuration tables */
+#define DW3000_CONFIGMRXLUT_MAX 7
+#define DW3000_DGC_CFG 0x38
+#define DW3000_DGC_CFG0 0x00000240
+#define DW3000_DGC_CFG1 0x1a491248
+#define DW3000_DGC_CFG2 0x2db248db
+
+/* DW3000 SLEEP and WAKEUP configuration parameters */
+#define DW3000_PGFCAL 0x0800
+#define DW3000_GOTORX 0x0200
+#define DW3000_GOTOIDLE 0x0100
+#define DW3000_SEL_GEAR3 0x00C0
+#define DW3000_SEL_GEAR2 0x0080 /* Short gear table */
+#define DW3000_SEL_GEAR1 0x0040 /* SCP */
+#define DW3000_SEL_GEAR0 0x0000 /* Long gear table */
+#define DW3000_ALT_GEAR 0x0020
+#define DW3000_LOADLDO 0x0010
+#define DW3000_LOADDGC 0x0008
+#define DW3000_LOADBIAS 0x0004
+#define DW3000_RUNSAR 0x0002
+
+/* OTP addresses definitions */
+#define DW3000_LDOTUNELO_ADDRESS (0x04)
+#define DW3000_LDOTUNEHI_ADDRESS (0x05)
+#define DW3000_PARTID_ADDRESS (0x06)
+#define DW3000_LOTID_ADDRESS (0x07)
+#define DW3000_VBAT_ADDRESS (0x08)
+#define DW3000_VTEMP_ADDRESS (0x09)
+#define DW3000_XTRIM_ADDRESS (0x1E)
+#define DW3000_OTPREV_ADDRESS (0x1F)
+#define DW3000_BIAS_TUNE_ADDRESS (0xA)
+#define DW3000_DGC_TUNE_ADDRESS (0x20)
+#define DW3000_PLL_CC_ADDRESS (0x35)
+
+/* Bit fields to select information to retrieve from OTP memory */
+#define DW3000_READ_OTP_PID 0x10 /* read part ID from OTP */
+#define DW3000_READ_OTP_LID 0x20 /* read lot ID from OTP */
+#define DW3000_READ_OTP_BAT 0x40 /* read ref voltage from OTP */
+#define DW3000_READ_OTP_TMP 0x80 /* read ref temperature from OTP */
+
+/* The mean XTAL TRIM value measured on multiple E0 samples.
+ * During the initialization the XTAL TRIM value can be read from the OTP and
+ * in case it is not present, the default would be used instead. */
+#define DW3000_DEFAULT_XTAL_TRIM 0x1f
+
+/* The XTAL TRIM BIAS value for +/- 5PPM offset */
+#define DW3000_XTAL_BIAS 0
+
+/* Clock offset value under which the PDoA value is assumed bad. */
+#define DW3000_CFO_THRESHOLD ((s16)(4 * (1 << 26) / 1000000))
+
+/* All RX errors mask */
+#define DW3000_SYS_STATUS_ALL_RX_ERR \
+ (DW3000_SYS_STATUS_RXPHE_BIT_MASK | DW3000_SYS_STATUS_RXFCE_BIT_MASK | \
+ DW3000_SYS_STATUS_RXFSL_BIT_MASK | DW3000_SYS_STATUS_RXSTO_BIT_MASK | \
+ DW3000_SYS_STATUS_ARFE_BIT_MASK | DW3000_SYS_STATUS_CIAERR_BIT_MASK | \
+ DW3000_SYS_STATUS_CPERR_BIT_MASK | \
+ DW3000_SYS_STATUS_LCSSERR_BIT_MASK)
+
+/* User defined RX timeouts (frame wait timeout and preamble detect timeout)
+ mask. */
+#define DW3000_SYS_STATUS_ALL_RX_TO \
+ (DW3000_SYS_STATUS_RXFTO_BIT_MASK | DW3000_SYS_STATUS_RXPTO_BIT_MASK)
+
+/* All RX events after a correct packet reception mask */
+#define DW3000_SYS_STATUS_ALL_RX_GOOD \
+ (DW3000_SYS_STATUS_RXFR_BIT_MASK | DW3000_SYS_STATUS_RXFCG_BIT_MASK | \
+ DW3000_SYS_STATUS_RXPRD_BIT_MASK | \
+ DW3000_SYS_STATUS_RXSFDD_BIT_MASK | \
+ DW3000_SYS_STATUS_RXPHD_BIT_MASK | \
+ DW3000_SYS_STATUS_CIA_DONE_BIT_MASK)
+
+/* All TX events mask */
+#define DW3000_SYS_STATUS_ALL_TX \
+ (DW3000_SYS_STATUS_AAT_BIT_MASK | DW3000_SYS_STATUS_TXFRB_BIT_MASK | \
+ DW3000_SYS_STATUS_TXPRS_BIT_MASK | DW3000_SYS_STATUS_TXPHS_BIT_MASK | \
+ DW3000_SYS_STATUS_TXFRS_BIT_MASK)
+
+void dw3000_init_config(struct dw3000 *dw);
+
+int dw3000_init(struct dw3000 *dw, bool check_idlerc);
+void dw3000_remove(struct dw3000 *dw);
+
+int dw3000_transfers_init(struct dw3000 *dw);
+void dw3000_transfers_free(struct dw3000 *dw);
+
+void dw3000_spitests(struct dw3000 *dw);
+bool dw3000_spitests_enabled(struct dw3000 *dw);
+
+int dw3000_wait_idle_state(struct dw3000 *dw);
+int dw3000_poweron(struct dw3000 *dw);
+int dw3000_poweroff(struct dw3000 *dw);
+int dw3000_hardreset(struct dw3000 *dw);
+
+int dw3000_softreset(struct dw3000 *dw);
+int dw3000_check_devid(struct dw3000 *dw);
+
+void dw3000_setup_regulators(struct dw3000 *dw);
+int dw3000_setup_reset_gpio(struct dw3000 *dw);
+int dw3000_setup_irq(struct dw3000 *dw);
+int dw3000_setup_wifi_coex(struct dw3000 *dw);
+int dw3000_setup_thread_cpu(struct dw3000 *dw, int *dw3000_thread_cpu);
+int dw3000_setup_qos_latency(struct dw3000 *dw);
+int dw3000_setup_regulator_delay(struct dw3000 *dw);
+
+void dw3000_spi_queue_start(struct dw3000 *dw);
+int dw3000_spi_queue_flush(struct dw3000 *dw);
+int dw3000_spi_queue_reset(struct dw3000 *dw, int rc);
+
+int dw3000_reg_read_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 length, void *buffer);
+int dw3000_reg_read32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 *val);
+int dw3000_reg_read16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 *val);
+int dw3000_reg_read8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u8 *val);
+int dw3000_reg_write_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 length, const void *buffer, enum spi_modes mode);
+int dw3000_reg_write32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 val);
+int dw3000_reg_write16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 val);
+int dw3000_reg_write8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u8 val);
+int dw3000_reg_modify32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u32 _and, u32 _or);
+int dw3000_reg_modify16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u16 _and, u16 _or);
+int dw3000_reg_modify8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset,
+ u8 _and, u8 _or);
+
+int dw3000_write_fastcmd(struct dw3000 *dw, u8 cmd);
+
+int dw3000_xfer(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u16 length,
+ void *buffer, enum spi_modes mode);
+#define dw3000_reg_or8(dw, addr, offset, or_val) \
+ dw3000_reg_modify8(dw, addr, offset, -1, or_val)
+#define dw3000_reg_and8(dw, addr, offset, and_val) \
+ dw3000_reg_modify8(dw, addr, offset, and_val, 0)
+
+#define dw3000_reg_or16(dw, addr, offset, or_val) \
+ dw3000_reg_modify16(dw, addr, offset, -1, or_val)
+#define dw3000_reg_and16(dw, addr, offset, and_val) \
+ dw3000_reg_modify16(dw, addr, offset, and_val, 0)
+
+#define dw3000_reg_or32(dw, addr, offset, or_val) \
+ dw3000_reg_modify32(dw, addr, offset, -1, or_val)
+#define dw3000_reg_and32(dw, addr, offset, and_val) \
+ dw3000_reg_modify32(dw, addr, offset, and_val, 0)
+
+#define sts_to_pdoa(s, pdoa_enabled) \
+ ((s) && (pdoa_enabled) ? DW3000_PDOA_M1 : DW3000_PDOA_M0)
+
+#define dw3000_get_chip_name(dw) (dw3000_chip_versions[dw->chip_idx].name)
+
+int dw3000_enable(struct dw3000 *dw);
+int dw3000_disable(struct dw3000 *dw);
+
+int dw3000_configure_chan(struct dw3000 *dw);
+int dw3000_configure_pcode(struct dw3000 *dw);
+int dw3000_configure_sfd_type(struct dw3000 *dw);
+int dw3000_configure_phr_rate(struct dw3000 *dw);
+int dw3000_configure_preamble_length_and_datarate(struct dw3000 *dw,
+ bool update_sfd_toc_pac);
+
+int dw3000_set_eui64(struct dw3000 *dw, __le64 val);
+int dw3000_set_panid(struct dw3000 *dw, __le16 val);
+int dw3000_set_shortaddr(struct dw3000 *dw, __le16 val);
+int dw3000_set_pancoord(struct dw3000 *dw, bool active);
+int dw3000_set_promiscuous(struct dw3000 *dw, bool on);
+int dw3000_set_sts_pdoa(struct dw3000 *dw, u8 sts_mode, u8 pdoa_mode);
+int dw3000_set_sts_length(struct dw3000 *dw, enum dw3000_sts_lengths len);
+int dw3000_configure_sts_key(struct dw3000 *dw, const u8 *key);
+int dw3000_configure_sts_iv(struct dw3000 *dw, const u8 *iv);
+int dw3000_load_sts_iv(struct dw3000 *dw);
+int dw3000_configure_sys_cfg(struct dw3000 *dw, struct dw3000_config *config);
+int dw3000_configure_hw_addr_filt(struct dw3000 *dw, unsigned long changed);
+int dw3000_enable_auto_fcs(struct dw3000 *dw, bool on);
+
+int dw3000_clear_sys_status(struct dw3000 *dw, u32 clear_bits);
+int dw3000_clear_dss_status(struct dw3000 *dw, u8 clear_bits);
+int dw3000_clear_spi_collision_status(struct dw3000 *dw, u8 clear_bits);
+int dw3000_read_rx_timestamp(struct dw3000 *dw, u64 *rx_ts);
+int dw3000_read_rdb_status(struct dw3000 *dw, u8 *status);
+int dw3000_read_sys_status(struct dw3000 *dw, u32 *status);
+int dw3000_read_sys_time(struct dw3000 *dw, u32 *sys_time);
+
+int dw3000_rx_store_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ u8 pkt_sts);
+int dw3000_rx_calc_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi,
+ struct mcps802154_rx_frame_info *info, u8 pkt_sts);
+int dw3000_rx_stats_inc(struct dw3000 *dw, const enum dw3000_stats_items item,
+ struct dw3000_rssi *rssi);
+
+u32 dw3000_get_dtu_time(struct dw3000 *dw);
+
+int dw3000_forcetrxoff(struct dw3000 *dw);
+
+int dw3000_do_rx_enable(struct dw3000 *dw,
+ const struct mcps802154_rx_frame_config *config,
+ int frame_idx);
+int dw3000_rx_enable(struct dw3000 *dw, bool rx_delayed, u32 date_dtu,
+ u32 timeout_pac);
+int dw3000_rx_disable(struct dw3000 *dw);
+bool dw3000_rx_busy(struct dw3000 *dw, bool busy);
+
+int dw3000_rx_stats_enable(struct dw3000 *dw, bool on);
+void dw3000_rx_stats_clear(struct dw3000 *dw);
+
+int dw3000_enable_autoack(struct dw3000 *dw, bool force);
+int dw3000_disable_autoack(struct dw3000 *dw, bool force);
+
+struct mcps802154_tx_frame_config;
+int dw3000_do_tx_frame(struct dw3000 *dw,
+ const struct mcps802154_tx_frame_config *config,
+ struct sk_buff *skb, int frame_idx);
+
+int dw3000_tx_setcwtone(struct dw3000 *dw, bool on);
+
+int dw3000_config_antenna_gpios(struct dw3000 *dw);
+int dw3000_set_tx_antenna(struct dw3000 *dw, int ant_set_id);
+int dw3000_set_rx_antennas(struct dw3000 *dw, int ant_set_id,
+ bool pdoa_enabled, int frame_idx);
+
+s16 dw3000_read_pdoa(struct dw3000 *dw);
+s16 dw3000_pdoa_to_aoa_lut(struct dw3000 *dw, s16 pdoa_rad_q11);
+int dw3000_read_sts_timestamp(struct dw3000 *dw, u64 *sts_ts);
+int dw3000_read_sts_quality(struct dw3000 *dw, s16 *acc_qual);
+int dw3000_read_clockoffset(struct dw3000 *dw, s16 *cfo);
+int dw3000_prog_xtrim(struct dw3000 *dw);
+
+int dw3000_set_gpio_mode(struct dw3000 *dw, u32 mask, u32 mode);
+int dw3000_set_gpio_dir(struct dw3000 *dw, u16 mask, u16 dir);
+int dw3000_set_gpio_out(struct dw3000 *dw, u16 reset, u16 set);
+
+int dw3000_otp_read32(struct dw3000 *dw, u16 addr, u32 *val);
+int dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data);
+int dw3000_read_otp(struct dw3000 *dw, int mode);
+
+int dw3000_read_frame_cir_data(struct dw3000 *dw,
+ struct mcps802154_rx_frame_info *info,
+ u64 utime);
+int dw3000_cir_data_alloc_count(struct dw3000 *dw, u16 nrecord);
+
+void dw3000_sysfs_init(struct dw3000 *dw);
+void dw3000_sysfs_remove(struct dw3000 *dw);
+
+void dw3000_isr(struct dw3000 *dw);
+enum hrtimer_restart dw3000_idle_timeout(struct hrtimer *timer);
+int dw3000_idle_cancel_timer(struct dw3000 *dw);
+void dw3000_wakeup_timer_start(struct dw3000 *dw, int delay_us);
+void dw3000_wakeup_and_wait(struct dw3000 *dw);
+int dw3000_deep_sleep_and_wakeup(struct dw3000 *dw, int delay_us);
+int dw3000_idle(struct dw3000 *dw, bool timestamp, u32 timestamp_dtu,
+ dw3000_idle_timeout_cb idle_timeout_cb,
+ enum operational_state next_operational_state);
+int dw3000_deepsleep_wakeup_now(struct dw3000 *dw,
+ dw3000_idle_timeout_cb idle_timeout_cb,
+ u32 timestamp_dtu,
+ enum operational_state next_operational_state);
+int dw3000_can_deep_sleep(struct dw3000 *dw, int delay_us);
+int dw3000_trace_rssi_info(struct dw3000 *dw, u32 regid, char *chipver);
+
+int dw3000_testmode_continuous_tx_start(struct dw3000 *dw, u32 frame_length,
+ u32 rate);
+int dw3000_testmode_continuous_tx_stop(struct dw3000 *dw);
+int dw3000_nfcc_coex_prepare_config(struct dw3000 *dw);
+int dw3000_nfcc_coex_restore_config(struct dw3000 *dw);
+
+/* Preamble length related information. */
+struct dw3000_plen_info {
+ /* Preamble length in symbols. */
+ int symb;
+ /* PAC size in symbols. */
+ int pac_symb;
+ /* Register value for this preamble length. */
+ uint8_t dw_reg;
+ /* Register value for PAC size. */
+ uint8_t dw_pac_reg;
+};
+extern const struct dw3000_plen_info _plen_info[];
+
+/* Chip per symbol information. */
+extern const int _chip_per_symbol_info[];
+
+/* PRF related information. */
+struct dw3000_prf_info {
+ /* Number of chips per symbol. */
+ int chip_per_symb;
+};
+extern const struct dw3000_prf_info _prf_info[];
+
+static inline int dw3000_get_sfd_symb(struct dw3000 *dw)
+{
+ /*
+ * SFD_TYPE_STD, SFD_TYPE_DW_8, SFD_TYPE_4Z : 8 symbols
+ * SFD_TYPE_DW_16 : 16 symbols
+ */
+ return dw->config.sfdType == DW3000_SFD_TYPE_DW_16 ? 16 : 8;
+}
+
+static inline int dw3000_compute_shr_dtu(struct dw3000 *dw)
+{
+ const struct dw3000_plen_info *plen_info =
+ &_plen_info[dw->config.txPreambLength - 1];
+ const int chip_per_symb =
+ _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M :
+ DW3000_PRF_16M]
+ .chip_per_symb;
+ const int shr_symb = plen_info->symb + dw3000_get_sfd_symb(dw);
+ return shr_symb * chip_per_symb / DW3000_CHIP_PER_DTU;
+}
+
+static inline int compute_shr_dtu_from_conf(
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params)
+{
+ const int preamble_symb = hrp_uwb_params->psr;
+ const int chip_per_symb =
+ _prf_info[hrp_uwb_params->prf == MCPS802154_PRF_64 ?
+ DW3000_PRF_64M :
+ DW3000_PRF_16M]
+ .chip_per_symb;
+ /* The only possible sfd number of symbols is 8. */
+ const int sfd_symb = 8;
+ const int shr_symb = preamble_symb + sfd_symb;
+ return shr_symb * chip_per_symb / DW3000_CHIP_PER_DTU;
+}
+
+static inline int dw3000_compute_symbol_dtu(struct dw3000 *dw)
+{
+ const int chip_per_symb =
+ _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M :
+ DW3000_PRF_16M]
+ .chip_per_symb;
+ return chip_per_symb / DW3000_CHIP_PER_DTU;
+}
+
+static inline int dw3000_compute_chips_per_pac(struct dw3000 *dw)
+{
+ const int pac_symb = _plen_info[dw->config.txPreambLength - 1].pac_symb;
+ const int chip_per_symb =
+ _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M :
+ DW3000_PRF_16M]
+ .chip_per_symb;
+ return chip_per_symb * pac_symb;
+}
+
+static inline int dw3000_compute_pre_timeout_pac(struct dw3000 *dw)
+{
+ /* Must be called AFTER dw->chips_per_pac initialisation */
+ const int symb = _plen_info[dw->config.txPreambLength - 1].symb;
+ const int pac_symb = _plen_info[dw->config.txPreambLength - 1].pac_symb;
+
+ return (DW3000_RX_ENABLE_STARTUP_DLY * DW3000_CHIP_PER_DLY +
+ dw->chips_per_pac - 1) /
+ dw->chips_per_pac +
+ symb / pac_symb + 2;
+}
+
+static inline int dw3000_frame_duration_dtu(struct dw3000 *dw,
+ int payload_bytes, bool with_shr)
+{
+ const struct dw3000_prf_info *prf_info =
+ &_prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M :
+ DW3000_PRF_16M];
+ int chip_per_symbol_phr = _chip_per_symbol_info[dw->config.phrRate];
+ int chip_per_symbol_data = _chip_per_symbol_info[dw->config.dataRate];
+ /* STS part */
+ const u8 sts_mode = dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK;
+ const int sts_symb =
+ sts_mode == DW3000_STS_MODE_OFF ? 0 : 8 << dw->config.stsLength;
+ const int sts_chips = sts_symb * prf_info->chip_per_symb;
+ /* PHR part. */
+ const int phr_tail_bits = sts_mode == DW3000_STS_MODE_ND ? 0 : 19 + 2;
+ const int phr_chips = phr_tail_bits /* 1 bit/symbol */
+ * chip_per_symbol_phr;
+ /* Data part, 48 Reed-Solomon bits per 330 bits. */
+ const int data_bits = sts_mode == DW3000_STS_MODE_ND ?
+ 0 :
+ (payload_bytes + IEEE802154_FCS_LEN) * 8;
+
+ const int data_rs_bits = data_bits + (data_bits + 329) / 330 * 48;
+ const int data_chips = data_rs_bits /* 1 bit/symbol */
+ * chip_per_symbol_data;
+ /* Done, convert to dtu. */
+ return ((sts_chips + phr_chips + data_chips) / DW3000_CHIP_PER_DTU) +
+ (with_shr ? dw->llhw->shr_dtu : 0);
+}
+
+static inline void dw3000_update_timings(struct dw3000 *dw)
+{
+ struct mcps802154_llhw *llhw = dw->llhw;
+ /* Update configuration dependent timings */
+ llhw->shr_dtu = dw3000_compute_shr_dtu(dw);
+ llhw->symbol_dtu = dw3000_compute_symbol_dtu(dw);
+ /* The CCA detection time shall be equivalent to 40 data symbol periods,
+ Tdsym, for a nominal 850 kb/s, or equivalently, at least 8 (multiplexed)
+ preamble symbols should be captured in the CCA detection time. */
+ llhw->cca_dtu = 8 * llhw->symbol_dtu;
+ dw->chips_per_pac = dw3000_compute_chips_per_pac(dw);
+ dw->pre_timeout_pac = dw3000_compute_pre_timeout_pac(dw);
+}
+
+/**
+ * dw3000_dtu_to_ktime() - compute absolute ktime for the specified DTU time
+ * @dw: the DW device
+ * @ts_dtu: timestamp in DTU unit to convert
+ *
+ * Formula:
+ * ktime = (ts_dtu - dtu0) * D / N + ktime0
+ * Where:
+ * N = DW3000_DTU_FREQ = 15600000
+ * D = 1000000000
+ *
+ * D/N = 1000000000/15600000 = 10000/156
+ * dtu0 always 0.
+ *
+ * Return: the computed kernel time in ns
+ */
+static inline s64 dw3000_dtu_to_ktime(struct dw3000 *dw, u32 ts_dtu)
+{
+ return dw->time_zero_ns +
+ (10000ll * ts_dtu / (DW3000_DTU_FREQ / 100000));
+}
+
+/**
+ * dw3000_ktime_to_dtu() - compute current DTU time
+ * @dw: the DW device
+ * @timestamp_ns: kernel time in ns
+ *
+ * Formula:
+ * dtu = (ktime - ktime0) * N / D + dtu0
+ * Where:
+ * N = DW3000_DTU_FREQ = 15600000
+ * D = 1000000000
+ *
+ * N/D = 15600000/1000000000 = 156/10000
+ * dtu0 always 0.
+ *
+ * Return: driver DTU time
+ */
+static inline u32 dw3000_ktime_to_dtu(struct dw3000 *dw, s64 timestamp_ns)
+{
+ timestamp_ns -= dw->time_zero_ns;
+ return (u32)(timestamp_ns * (DW3000_DTU_FREQ / 100000) / 10000);
+}
+
+/**
+ * dw3000_dtu_to_sys_time() - compute DW SYS_TIME from DTU time
+ * @dw: the DW device
+ * @dtu: the DTU timestamp to convert to SYS_TIME
+ *
+ * Return: the value to write to SYS_TIME register
+ */
+static inline u32 dw3000_dtu_to_sys_time(struct dw3000 *dw, u32 dtu)
+{
+ const int N = DW3000_DTU_PER_SYS_POWER;
+ u32 dtu_sync = dw->dtu_sync;
+ u32 sys_time_sync = dw->sys_time_sync;
+ return ((dtu - dtu_sync) << N) + sys_time_sync;
+}
+
+/**
+ * dw3000_sys_time_to_dtu() - compute current DTU time from SYS_TIME
+ * @dw: the DW device
+ * @sys_time: the DW device SYS_TIME register value to convert to DTU
+ * @dtu_near: a DTU time which must be in the past relative to sys_time, at less
+ * than half the SYS_TIME rollover period
+ *
+ * Return: the corresponding DTU time
+ */
+static inline u32 dw3000_sys_time_to_dtu(struct dw3000 *dw, u32 sys_time,
+ u32 dtu_near)
+{
+ const int N = DW3000_DTU_PER_SYS_POWER;
+ u32 dtu_sync = dw->dtu_sync;
+ u32 sys_time_sync = dw->sys_time_sync;
+ u32 dtu_lsb = (sys_time - (sys_time_sync - (dtu_sync << N))) >> N;
+ u32 dtu_add = ((~dtu_lsb & dtu_near) & (1 << (31 - N))) << 1;
+ u32 mask = (1 << (32 - N)) - 1;
+ return ((dtu_near & ~mask) | dtu_lsb) + dtu_add;
+}
+
+/**
+ * dw3000_sys_time_rctu_to_dtu() - compute current DTU time from RCTU.
+ * @dw: the DW device.
+ * @timestamp_rctu: The DW device RX_STAMP register value in RCTU to convert to DTU.
+ * The RCTU, Ranging Counter Time Unit, is approximately 15.65 picoseconds long.
+ *
+ * Return: The corresponding DTU time.
+ */
+static inline u32 dw3000_sys_time_rctu_to_dtu(struct dw3000 *dw,
+ u64 timestamp_rctu)
+{
+ u32 sys_time = (u32)(timestamp_rctu / DW3000_RCTU_PER_SYS);
+ u32 dtu_near = dw3000_get_dtu_time(dw) - DW3000_DTU_FREQ;
+
+ return dw3000_sys_time_to_dtu(dw, sys_time, dtu_near);
+}
+
+/**
+ * dw3000_reset_rctu_conv_state() - reset RCTU converter
+ * @dw: the DW device
+ *
+ * Called during a stop, rx_disable, reset and idle.
+ */
+static inline void dw3000_reset_rctu_conv_state(struct dw3000 *dw)
+{
+ dw->rctu_conv.state = UNALIGNED;
+}
+
+/**
+ * dw3000_resync_rctu_conv_state() - resync RCTU converter
+ * @dw: the DW device
+ *
+ * Called during a wake-up from deep sleep.
+ */
+static inline void dw3000_resync_rctu_conv_state(struct dw3000 *dw)
+{
+ if (dw->rctu_conv.state == ALIGNED_SYNCED)
+ dw->rctu_conv.state = ALIGNED;
+}
+
+static inline int pac_to_dly(struct mcps802154_llhw *llhw, int pac)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ return (pac * dw->chips_per_pac / DW3000_CHIP_PER_DLY);
+}
+
+static inline int dtu_to_pac(struct mcps802154_llhw *llhw, int timeout_dtu)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ return (timeout_dtu * DW3000_CHIP_PER_DTU + dw->chips_per_pac - 1) /
+ dw->chips_per_pac;
+}
+
+static inline int dtu_to_dly(struct mcps802154_llhw *llhw, int dtu)
+{
+ return (dtu * DW3000_CHIP_PER_DTU / DW3000_CHIP_PER_DLY);
+}
+
+#endif /* __DW3000_CORE_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_core_reg.h b/kernel/drivers/net/ieee802154/dw3000_core_reg.h
new file mode 100644
index 0000000..592f582
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_core_reg.h
@@ -0,0 +1,2219 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_CORE_REG_H
+#define __DW3000_CORE_REG_H
+
+#include "dw3000_compat_reg.h"
+
+/*
+ * Please, keep registers defines ordered by address.
+ * This will ease finding duplicate or renaming.
+ */
+
+/* register DEV_ID */
+#define DW3000_DEV_ID_ID 0x0
+
+/* register EUI_64 */
+#define DW3000_EUI_64_ID 0x4
+#define DW3000_EUI_64_LEN (8U)
+
+/* register SYS_CFG */
+#define DW3000_SYS_CFG_ID 0x10
+#define DW3000_SYS_CFG_LEN (4U)
+#define DW3000_SYS_CFG_MASK 0xFFFFFFFFUL
+#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_OFFSET (23U)
+#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_LEN (2U)
+#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK 0x1800000UL
+#define DW3000_SYS_CFG_DS_IE2_BIT_OFFSET (20U)
+#define DW3000_SYS_CFG_DS_IE2_BIT_LEN (1U)
+#define DW3000_SYS_CFG_DS_IE2_BIT_MASK 0x100000UL
+#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_OFFSET (18U)
+#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_LEN (1U)
+#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK 0x40000UL
+#define DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET (16U)
+#define DW3000_SYS_CFG_PDOA_MODE_BIT_LEN (2U)
+#define DW3000_SYS_CFG_PDOA_MODE_BIT_MASK 0x30000UL
+#define DW3000_SYS_CFG_CP_SDC_BIT_OFFSET (15U)
+#define DW3000_SYS_CFG_CP_SDC_BIT_LEN (1U)
+#define DW3000_SYS_CFG_CP_SDC_BIT_MASK 0x8000U
+#define DW3000_SYS_CFG_CP_TYPE_BIT_OFFSET (14U)
+#define DW3000_SYS_CFG_CP_TYPE_BIT_LEN (1U)
+#define DW3000_SYS_CFG_CP_TYPE_BIT_MASK 0x4000U
+#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET (12U)
+#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_LEN (2U)
+#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK 0x3000U
+#define DW3000_SYS_CFG_AUTO_ACK_BIT_OFFSET (11U)
+#define DW3000_SYS_CFG_AUTO_ACK_BIT_LEN (1U)
+#define DW3000_SYS_CFG_AUTO_ACK_BIT_MASK 0x800U
+#define DW3000_SYS_CFG_RXAUTR_BIT_OFFSET (10U)
+#define DW3000_SYS_CFG_RXAUTR_BIT_LEN (1U)
+#define DW3000_SYS_CFG_RXAUTR_BIT_MASK 0x400U
+#define DW3000_SYS_CFG_RXWTOE_BIT_OFFSET (9U)
+#define DW3000_SYS_CFG_RXWTOE_BIT_LEN (1U)
+#define DW3000_SYS_CFG_RXWTOE_BIT_MASK 0x200U
+#define DW3000_SYS_CFG_CIA_STS_BIT_OFFSET (8U)
+#define DW3000_SYS_CFG_CIA_STS_BIT_LEN (1U)
+#define DW3000_SYS_CFG_CIA_STS_BIT_MASK 0x100U
+#define DW3000_SYS_CFG_CIA_IPATOV_BIT_OFFSET (7U)
+#define DW3000_SYS_CFG_CIA_IPATOV_BIT_LEN (1U)
+#define DW3000_SYS_CFG_CIA_IPATOV_BIT_MASK 0x80U
+#define DW3000_SYS_CFG_SPI_CRC_BIT_OFFSET (6U)
+#define DW3000_SYS_CFG_SPI_CRC_BIT_LEN (1U)
+#define DW3000_SYS_CFG_SPI_CRC_BIT_MASK 0x40U
+#define DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET (5U)
+#define DW3000_SYS_CFG_PHR_6M8_BIT_LEN (1U)
+#define DW3000_SYS_CFG_PHR_6M8_BIT_MASK 0x20U
+#define DW3000_SYS_CFG_PHR_MODE_BIT_OFFSET (4U)
+#define DW3000_SYS_CFG_PHR_MODE_BIT_LEN (1U)
+#define DW3000_SYS_CFG_PHR_MODE_BIT_MASK 0x10U
+#define DW3000_SYS_CFG_EN_DRXB_BIT_OFFSET (3U)
+#define DW3000_SYS_CFG_EN_DRXB_BIT_LEN (1U)
+#define DW3000_SYS_CFG_EN_DRXB_BIT_MASK 0x8U
+#define DW3000_SYS_CFG_DIS_FCE_BIT_OFFSET (2U)
+#define DW3000_SYS_CFG_DIS_FCE_BIT_LEN (1U)
+#define DW3000_SYS_CFG_DIS_FCE_BIT_MASK 0x4U
+#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_OFFSET (1U)
+#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_LEN (1U)
+#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK 0x2U
+#define DW3000_SYS_CFG_FFEN_BIT_OFFSET (0U)
+#define DW3000_SYS_CFG_FFEN_BIT_LEN (1U)
+#define DW3000_SYS_CFG_FFEN_BIT_MASK 0x1U
+
+/* register PANADR */
+#define DW3000_PANADR_ID 0xc
+#define DW3000_PANADR_SHORT_ADDR_BIT_OFFSET (0U)
+#define DW3000_PANADR_SHORT_ADDR_BIT_LEN (2U)
+#define DW3000_PANADR_PAN_ID_BIT_OFFSET (16U)
+#define DW3000_PANADR_PAN_ID_BIT_LEN (2U)
+
+/* register ADR_FILT_CFG */
+#define DW3000_ADR_FILT_CFG_ID 0x14
+#define DW3000_ADR_FILT_CFG_FFBC_BIT_LEN (1U)
+/* DW3000 as pan coordinator */
+#define DW3000_AS_PANCOORD 0x03
+
+/* register SPICRC_CFG */
+#define DW3000_SPICRC_CFG_ID 0x18
+
+/* register SYS_TIME */
+#define DW3000_SYS_TIME_ID 0x1c
+
+/* register TX_FCTRL_HI 0x24, 0x20 */
+#define DW3000_TX_FCTRL_LEN (4U)
+#define DW3000_TX_FCTRL_MASK 0xFFFFFFFFUL
+#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_OFFSET (16U)
+#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_LEN (10U)
+#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_MASK 0x3ff0000UL
+#define DW3000_TX_FCTRL_TXPSR_PE_BIT_OFFSET (12U)
+#define DW3000_TX_FCTRL_TXPSR_PE_BIT_LEN (4U)
+#define DW3000_TX_FCTRL_TXPSR_PE_BIT_MASK 0xf000U
+#define DW3000_TX_FCTRL_TR_BIT_OFFSET (11U)
+#define DW3000_TX_FCTRL_TR_BIT_LEN (1U)
+#define DW3000_TX_FCTRL_TR_BIT_MASK 0x800U
+#define DW3000_TX_FCTRL_TXBR_BIT_OFFSET (10U)
+#define DW3000_TX_FCTRL_TXBR_BIT_LEN (1U)
+#define DW3000_TX_FCTRL_TXBR_BIT_MASK 0x400U
+#define DW3000_TX_FCTRL_TXFLEN_BIT_OFFSET (0U)
+#define DW3000_TX_FCTRL_TXFLEN_BIT_LEN (10U)
+#define DW3000_TX_FCTRL_TXFLEN_BIT_MASK 0x3ffU
+
+/* register RX_FWTO */
+#define DW3000_RX_FWTO_ID 0x34
+
+/* register SYS_ENABLE_LO */
+#define DW3000_SYS_ENABLE_LO_ID 0x3c
+#define DW3000_SYS_ENABLE_LO_LEN (4U)
+#define DW3000_SYS_ENABLE_LO_MASK 0xFFFFFFFFUL
+#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_OFFSET (29U)
+#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_MASK 0x20000000UL
+#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_OFFSET (28U)
+#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_MASK 0x10000000UL
+#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_OFFSET (27U)
+#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_MASK 0x8000000UL
+#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_OFFSET (26U)
+#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_MASK 0x4000000UL
+#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_OFFSET (25U)
+#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_MASK 0x2000000UL
+#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_OFFSET (24U)
+#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_MASK 0x1000000UL
+#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_OFFSET (23U)
+#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK 0x800000UL
+#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_OFFSET (22U)
+#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_MASK 0x400000UL
+#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_OFFSET (21U)
+#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_MASK 0x200000UL
+#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_OFFSET (20U)
+#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_MASK 0x100000UL
+#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_OFFSET (19U)
+#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_MASK 0x80000UL
+#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_OFFSET (18U)
+#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_MASK 0x40000UL
+#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_OFFSET (17U)
+#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_MASK 0x20000UL
+#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_OFFSET (16U)
+#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_MASK 0x10000UL
+#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_OFFSET (15U)
+#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_MASK 0x8000U
+#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_OFFSET (14U)
+#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK 0x4000U
+#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_OFFSET (13U)
+#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_MASK 0x2000U
+#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_OFFSET (12U)
+#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_MASK 0x1000U
+#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_OFFSET (11U)
+#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_MASK 0x800U
+#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_OFFSET (10U)
+#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_MASK 0x400U
+#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_OFFSET (9U)
+#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_MASK 0x200U
+#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_OFFSET (8U)
+#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_MASK 0x100U
+#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_OFFSET (7U)
+#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK 0x80U
+#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_OFFSET (6U)
+#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_MASK 0x40U
+#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_OFFSET (5U)
+#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_MASK 0x20U
+#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_OFFSET (4U)
+#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_MASK 0x10U
+#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_OFFSET (3U)
+#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_MASK 0x8U
+#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_OFFSET (2U)
+#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_MASK 0x4U
+#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_OFFSET (1U)
+#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_MASK 0x2U
+
+/* register SYS_ENABLE_HI */
+#define DW3000_SYS_ENABLE_HI_ID 0x40
+#define DW3000_SYS_ENABLE_HI_LEN (4U)
+#define DW3000_SYS_ENABLE_HI_MASK 0xFFFFFFFFUL
+#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_OFFSET (17U)
+#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_MASK 0x20000UL
+#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_OFFSET (16U)
+#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_MASK 0x10000UL
+#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_OFFSET (15U)
+#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_MASK 0x8000U
+#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_OFFSET (14U)
+#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_MASK 0x4000U
+#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_OFFSET (13U)
+#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_MASK 0x2000U
+#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_OFFSET (12U)
+#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_MASK 0x1000U
+#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_OFFSET (11U)
+#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_MASK 0x800U
+#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_OFFSET (10U)
+#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_MASK 0x400U
+#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_OFFSET (9U)
+#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_MASK 0x200U
+#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_OFFSET (8U)
+#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_MASK 0x100U
+#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_OFFSET (7U)
+#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_MASK 0x80U
+#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_OFFSET (6U)
+#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_MASK 0x40U
+#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_OFFSET (5U)
+#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_MASK 0x20U
+#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_OFFSET (4U)
+#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_MASK 0x10U
+#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_OFFSET (2U)
+#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_MASK 0x4U
+#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_OFFSET (1U)
+#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_LEN (1U)
+#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_MASK 0x2U
+
+/* register SYS_STATUS */
+#define DW3000_SYS_STATUS_ID 0x44
+#define DW3000_SYS_STATUS_LEN (4U)
+#define DW3000_SYS_STATUS_MASK 0xFFFFFFFFUL
+#define DW3000_SYS_STATUS_TIMER1_BIT_OFFSET (31U)
+#define DW3000_SYS_STATUS_TIMER1_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TIMER1_BIT_MASK 0x80000000UL
+#define DW3000_SYS_STATUS_TIMER0_BIT_OFFSET (30U)
+#define DW3000_SYS_STATUS_TIMER0_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TIMER0_BIT_MASK 0x40000000UL
+#define DW3000_SYS_STATUS_ARFE_BIT_OFFSET (29U)
+#define DW3000_SYS_STATUS_ARFE_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_ARFE_BIT_MASK 0x20000000UL
+#define DW3000_SYS_STATUS_CPERR_BIT_OFFSET (28U)
+#define DW3000_SYS_STATUS_CPERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_CPERR_BIT_MASK 0x10000000UL
+#define DW3000_SYS_STATUS_HPDWARN_BIT_OFFSET (27U)
+#define DW3000_SYS_STATUS_HPDWARN_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HPDWARN_BIT_MASK 0x8000000UL
+#define DW3000_SYS_STATUS_RXSTO_BIT_OFFSET (26U)
+#define DW3000_SYS_STATUS_RXSTO_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXSTO_BIT_MASK 0x4000000UL
+#define DW3000_SYS_STATUS_PLL_HILO_BIT_OFFSET (25U)
+#define DW3000_SYS_STATUS_PLL_HILO_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_PLL_HILO_BIT_MASK 0x2000000UL
+#define DW3000_SYS_STATUS_RCINIT_BIT_OFFSET (24U)
+#define DW3000_SYS_STATUS_RCINIT_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RCINIT_BIT_MASK 0x1000000UL
+#define DW3000_SYS_STATUS_SPIRDY_BIT_OFFSET (23U)
+#define DW3000_SYS_STATUS_SPIRDY_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_SPIRDY_BIT_MASK 0x800000UL
+#define DW3000_SYS_STATUS_LCSSERR_BIT_OFFSET (22U)
+#define DW3000_SYS_STATUS_LCSSERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_LCSSERR_BIT_MASK 0x400000UL
+#define DW3000_SYS_STATUS_RXPTO_BIT_OFFSET (21U)
+#define DW3000_SYS_STATUS_RXPTO_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXPTO_BIT_MASK 0x200000UL
+#define DW3000_SYS_STATUS_RXOVRR_BIT_OFFSET (20U)
+#define DW3000_SYS_STATUS_RXOVRR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXOVRR_BIT_MASK 0x100000UL
+#define DW3000_SYS_STATUS_VWARN_BIT_OFFSET (19U)
+#define DW3000_SYS_STATUS_VWARN_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_VWARN_BIT_MASK 0x80000UL
+#define DW3000_SYS_STATUS_CIAERR_BIT_OFFSET (18U)
+#define DW3000_SYS_STATUS_CIAERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_CIAERR_BIT_MASK 0x40000UL
+#define DW3000_SYS_STATUS_RXFTO_BIT_OFFSET (17U)
+#define DW3000_SYS_STATUS_RXFTO_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXFTO_BIT_MASK 0x20000UL
+#define DW3000_SYS_STATUS_RXFSL_BIT_OFFSET (16U)
+#define DW3000_SYS_STATUS_RXFSL_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXFSL_BIT_MASK 0x10000UL
+#define DW3000_SYS_STATUS_RXFCE_BIT_OFFSET (15U)
+#define DW3000_SYS_STATUS_RXFCE_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXFCE_BIT_MASK 0x8000U
+#define DW3000_SYS_STATUS_RXFCG_BIT_OFFSET (14U)
+#define DW3000_SYS_STATUS_RXFCG_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXFCG_BIT_MASK 0x4000U
+#define DW3000_SYS_STATUS_RXFR_BIT_OFFSET (13U)
+#define DW3000_SYS_STATUS_RXFR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXFR_BIT_MASK 0x2000U
+#define DW3000_SYS_STATUS_RXPHE_BIT_OFFSET (12U)
+#define DW3000_SYS_STATUS_RXPHE_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXPHE_BIT_MASK 0x1000U
+#define DW3000_SYS_STATUS_RXPHD_BIT_OFFSET (11U)
+#define DW3000_SYS_STATUS_RXPHD_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXPHD_BIT_MASK 0x800U
+#define DW3000_SYS_STATUS_CIA_DONE_BIT_OFFSET (10U)
+#define DW3000_SYS_STATUS_CIA_DONE_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_CIA_DONE_BIT_MASK 0x400U
+#define DW3000_SYS_STATUS_RXSFDD_BIT_OFFSET (9U)
+#define DW3000_SYS_STATUS_RXSFDD_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXSFDD_BIT_MASK 0x200U
+#define DW3000_SYS_STATUS_RXPRD_BIT_OFFSET (8U)
+#define DW3000_SYS_STATUS_RXPRD_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_RXPRD_BIT_MASK 0x100U
+#define DW3000_SYS_STATUS_TXFRS_BIT_OFFSET (7U)
+#define DW3000_SYS_STATUS_TXFRS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TXFRS_BIT_MASK 0x80U
+#define DW3000_SYS_STATUS_TXPHS_BIT_OFFSET (6U)
+#define DW3000_SYS_STATUS_TXPHS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TXPHS_BIT_MASK 0x40U
+#define DW3000_SYS_STATUS_TXPRS_BIT_OFFSET (5U)
+#define DW3000_SYS_STATUS_TXPRS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TXPRS_BIT_MASK 0x20U
+#define DW3000_SYS_STATUS_TXFRB_BIT_OFFSET (4U)
+#define DW3000_SYS_STATUS_TXFRB_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_TXFRB_BIT_MASK 0x10U
+#define DW3000_SYS_STATUS_AAT_BIT_OFFSET (3U)
+#define DW3000_SYS_STATUS_AAT_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_AAT_BIT_MASK 0x8U
+#define DW3000_SYS_STATUS_SPICRCERR_BIT_OFFSET (2U)
+#define DW3000_SYS_STATUS_SPICRCERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_SPICRCERR_BIT_MASK 0x4U
+#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_OFFSET (1U)
+#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK 0x2U
+#define DW3000_SYS_STATUS_IRQS_BIT_OFFSET (0U)
+#define DW3000_SYS_STATUS_IRQS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_IRQS_BIT_MASK 0x1U
+
+/* register SYS_STATUS_HI */
+#define DW3000_SYS_STATUS_HI_ID 0x48
+#define DW3000_SYS_STATUS_HI_LEN (4U)
+#define DW3000_SYS_STATUS_HI_MASK 0xFFFFFFFFUL
+#define DW3000_SYS_STATUS_HI_RXSTS_BIT_OFFSET (17U)
+#define DW3000_SYS_STATUS_HI_RXSTS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_RXSTS_BIT_MASK 0x20000UL
+#define DW3000_SYS_STATUS_HI_TXSTS_BIT_OFFSET (16U)
+#define DW3000_SYS_STATUS_HI_TXSTS_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_TXSTS_BIT_MASK 0x10000UL
+#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_OFFSET (15U)
+#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_MASK 0x8000U
+#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_OFFSET (14U)
+#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_MASK 0x4000U
+#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_OFFSET (13U)
+#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_MASK 0x2000U
+#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_OFFSET (12U)
+#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_MASK 0x1000U
+#define DW3000_SYS_STATUS_HI_SPIERR_BIT_OFFSET (11U)
+#define DW3000_SYS_STATUS_HI_SPIERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_SPIERR_BIT_MASK 0x800U
+#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_OFFSET (10U)
+#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_MASK 0x400U
+#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_OFFSET (9U)
+#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_MASK 0x200U
+#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_OFFSET (8U)
+#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_MASK 0x100U
+#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_OFFSET (7U)
+#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_MASK 0x80U
+#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_OFFSET (6U)
+#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_MASK 0x40U
+#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_OFFSET (5U)
+#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_MASK 0x20U
+#define DW3000_SYS_STATUS_HI_VT_DET_BIT_OFFSET (4U)
+#define DW3000_SYS_STATUS_HI_VT_DET_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_VT_DET_BIT_MASK 0x10U
+#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_OFFSET (2U)
+#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_MASK 0x4U
+#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_OFFSET (1U)
+#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_LEN (1U)
+#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_MASK 0x2U
+
+/* register RX_FINFO */
+#define DW3000_RX_FINFO_ID 0x4c
+#define DW3000_RX_FINFO_LEN (4U)
+#define DW3000_RX_FINFO_MASK 0xFFFFFFFFUL
+#define DW3000_RX_FINFO_RXP2SS_BIT_OFFSET (20U)
+#define DW3000_RX_FINFO_RXP2SS_BIT_LEN (12U)
+#define DW3000_RX_FINFO_RXP2SS_BIT_MASK 0xfff00000UL
+#define DW3000_RX_FINFO_RXPSR_BIT_OFFSET (18U)
+#define DW3000_RX_FINFO_RXPSR_BIT_LEN (2U)
+#define DW3000_RX_FINFO_RXPSR_BIT_MASK 0xc0000UL
+#define DW3000_RX_FINFO_RXPRF_BIT_OFFSET (16U)
+#define DW3000_RX_FINFO_RXPRF_BIT_LEN (2U)
+#define DW3000_RX_FINFO_RXPRF_BIT_MASK 0x30000UL
+#define DW3000_RX_FINFO_RNG_BIT_OFFSET (15U)
+#define DW3000_RX_FINFO_RNG_BIT_LEN (1U)
+#define DW3000_RX_FINFO_RNG_BIT_MASK 0x8000U
+#define DW3000_RX_FINFO_RXBR_BIT_OFFSET (13U)
+#define DW3000_RX_FINFO_RXBR_BIT_LEN (1U)
+#define DW3000_RX_FINFO_RXBR_BIT_MASK 0x2000U
+#define DW3000_RX_FINFO_RXNSPL_BIT_OFFSET (11U)
+#define DW3000_RX_FINFO_RXNSPL_BIT_LEN (2U)
+#define DW3000_RX_FINFO_RXNSPL_BIT_MASK 0x1800U
+#define DW3000_RX_FINFO_RXFLEN_BIT_OFFSET (0U)
+#define DW3000_RX_FINFO_RXFLEN_BIT_LEN (10U)
+#define DW3000_RX_FINFO_RXFLEN_BIT_MASK 0x3ffU
+
+/* register RX_TIME_0 0x64/060 */
+#define DW3000_RX_TIME_RX_STAMP_LEN 5
+
+/* Register ACK_RESP. 0x10008/0x10000 */
+#define DW3000_ACK_RESP_LEN (4U)
+#define DW3000_ACK_RESP_MASK 0xFFFFFFFFUL
+#define DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET (24U)
+#define DW3000_ACK_RESP_ACK_TIM_BIT_LEN (8U)
+#define DW3000_ACK_RESP_ACK_TIM_BIT_MASK 0xff000000UL
+#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_OFFSET (0U)
+#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_LEN (20U)
+#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_MASK 0xfffffUL
+
+/* register CHAN_CTRL 0x10014/0x10008 */
+#define DW3000_CHAN_CTRL_LEN (4U)
+#define DW3000_CHAN_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_CHAN_CTRL_RX_PCODE_BIT_OFFSET (8U)
+#define DW3000_CHAN_CTRL_RX_PCODE_BIT_LEN (5U)
+#define DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK 0x1f00U
+#define DW3000_CHAN_CTRL_TX_PCODE_BIT_OFFSET (3U)
+#define DW3000_CHAN_CTRL_TX_PCODE_BIT_LEN (5U)
+#define DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK 0xf8U
+#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_OFFSET (1U)
+#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_LEN (2U)
+#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK 0x6U
+#define DW3000_CHAN_CTRL_RF_CHAN_BIT_OFFSET (0U)
+#define DW3000_CHAN_CTRL_RF_CHAN_BIT_LEN (1U)
+#define DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK 0x1U
+
+/* register SPI_COLLISION 0x10020/0x10014 */
+#define DW3000_SPI_COLLISION_STATUS_BIT_MASK 0x1fU
+
+/* register RDB_STATUS 0x10024/0x10018 */
+#define DW3000_RDB_STATUS_LEN (4U)
+#define DW3000_RDB_STATUS_MASK 0xFFFFFFFFUL
+#define DW3000_RDB_STATUS_CP_ERR1_BIT_OFFSET (7U)
+#define DW3000_RDB_STATUS_CP_ERR1_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_CP_ERR1_BIT_MASK 0x80U
+#define DW3000_RDB_STATUS_CIADONE1_BIT_OFFSET (6U)
+#define DW3000_RDB_STATUS_CIADONE1_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_CIADONE1_BIT_MASK 0x40U
+#define DW3000_RDB_STATUS_RXFR1_BIT_OFFSET (5U)
+#define DW3000_RDB_STATUS_RXFR1_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_RXFR1_BIT_MASK 0x20U
+#define DW3000_RDB_STATUS_RXFCG1_BIT_OFFSET (4U)
+#define DW3000_RDB_STATUS_RXFCG1_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_RXFCG1_BIT_MASK 0x10U
+#define DW3000_RDB_STATUS_CP_ERR0_BIT_OFFSET (3U)
+#define DW3000_RDB_STATUS_CP_ERR0_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_CP_ERR0_BIT_MASK 0x8U
+#define DW3000_RDB_STATUS_CIADONE0_BIT_OFFSET (2U)
+#define DW3000_RDB_STATUS_CIADONE0_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_CIADONE0_BIT_MASK 0x4U
+#define DW3000_RDB_STATUS_RXFR0_BIT_OFFSET (1U)
+#define DW3000_RDB_STATUS_RXFR0_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_RXFR0_BIT_MASK 0x2U
+#define DW3000_RDB_STATUS_RXFCG0_BIT_OFFSET (0U)
+#define DW3000_RDB_STATUS_RXFCG0_BIT_LEN (1U)
+#define DW3000_RDB_STATUS_RXFCG0_BIT_MASK 0x1U
+
+/* register RDB_DIAG_MODE 0x10028/0x10020 */
+#define DW3000_RDB_DIAG_MODE_LEN (4U)
+
+/* register AES_START */
+#define DW3000_AES_START_ID 0x1004c
+#define DW3000_AES_START_LEN (4U)
+#define DW3000_AES_START_MASK 0xFFFFFFFFUL
+#define DW3000_AES_START_AES_START_BIT_OFFSET (0U)
+#define DW3000_AES_START_AES_START_BIT_LEN (1U)
+#define DW3000_AES_START_AES_START_BIT_MASK 0x1U
+
+/* register AES_STS */
+#define DW3000_AES_STS_ID 0x10050
+#define DW3000_AES_STS_LEN (4U)
+#define DW3000_AES_STS_MASK 0xFFFFFFFFUL
+#define DW3000_AES_STS_RAM_FULL_BIT_OFFSET (5U)
+#define DW3000_AES_STS_RAM_FULL_BIT_LEN (1U)
+#define DW3000_AES_STS_RAM_FULL_BIT_MASK 0x20U
+#define DW3000_AES_STS_RAM_EMPTY_BIT_OFFSET (4U)
+#define DW3000_AES_STS_RAM_EMPTY_BIT_LEN (1U)
+#define DW3000_AES_STS_RAM_EMPTY_BIT_MASK 0x10U
+#define DW3000_AES_STS_MEM_CONF_BIT_OFFSET (3U)
+#define DW3000_AES_STS_MEM_CONF_BIT_LEN (1U)
+#define DW3000_AES_STS_MEM_CONF_BIT_MASK 0x8U
+#define DW3000_AES_STS_TRANS_ERR_BIT_OFFSET (2U)
+#define DW3000_AES_STS_TRANS_ERR_BIT_LEN (1U)
+#define DW3000_AES_STS_TRANS_ERR_BIT_MASK 0x4U
+#define DW3000_AES_STS_AUTH_ERR_BIT_OFFSET (1U)
+#define DW3000_AES_STS_AUTH_ERR_BIT_LEN (1U)
+#define DW3000_AES_STS_AUTH_ERR_BIT_MASK 0x2U
+#define DW3000_AES_STS_AES_DONE_BIT_OFFSET (0U)
+#define DW3000_AES_STS_AES_DONE_BIT_LEN (1U)
+#define DW3000_AES_STS_AES_DONE_BIT_MASK 0x1U
+
+/* register STS_CFG0/CP_CFG0 */
+#define DW3000_CP_CFG0_ID 0x20000
+#define DW3000_STS_CFG0_ID DW3000_CP_CFG0_ID
+#define DW3000_STS_CFG0_LEN (4U)
+#define DW3000_STS_CFG0_MASK 0xFFFFFFFFUL
+#define DW3000_STS_CFG0_CPS_LEN_BIT_OFFSET (0U)
+#define DW3000_STS_CFG0_CPS_LEN_BIT_LEN (8U)
+#define DW3000_STS_CFG0_CPS_LEN_BIT_MASK 0xffU
+
+/* register STS_CTRL */
+#define DW3000_STS_CTRL_ID 0x20004
+#define DW3000_STS_CTRL_LEN (4U)
+#define DW3000_STS_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_STS_CTRL_RST_LAST_BIT_OFFSET (1U)
+#define DW3000_STS_CTRL_RST_LAST_BIT_LEN (1U)
+#define DW3000_STS_CTRL_RST_LAST_BIT_MASK 0x2U
+#define DW3000_STS_CTRL_LOAD_IV_BIT_OFFSET (0U)
+#define DW3000_STS_CTRL_LOAD_IV_BIT_LEN (1U)
+#define DW3000_STS_CTRL_LOAD_IV_BIT_MASK 0x1U
+
+/* register STS_STS */
+#define DW3000_STS_STS_ID 0x20008
+#define DW3000_STS_STS_LEN (4U)
+#define DW3000_STS_STS_MASK 0xFFFFFFFFUL
+#define DW3000_STS_STS_ACC_QUAL_BIT_OFFSET (0U)
+#define DW3000_STS_STS_ACC_QUAL_BIT_LEN (12U)
+#define DW3000_STS_STS_ACC_QUAL_BIT_MASK 0xfffU
+
+/* register STS_KEY0 */
+#define DW3000_STS_KEY0_ID 0x2000c
+#define DW3000_STS_KEY0_LEN (4U)
+#define DW3000_STS_KEY0_MASK 0xFFFFFFFFUL
+
+/* register STS_KEY1 */
+#define DW3000_STS_KEY1_ID 0x20010
+#define DW3000_STS_KEY1_LEN (4U)
+#define DW3000_STS_KEY1_MASK 0xFFFFFFFFUL
+
+/* register STS_KEY2 */
+#define DW3000_STS_KEY2_ID 0x20014
+#define DW3000_STS_KEY2_LEN (4U)
+#define DW3000_STS_KEY2_MASK 0xFFFFFFFFUL
+
+/* register STS_KEY3 */
+#define DW3000_STS_KEY3_ID 0x20018
+#define DW3000_STS_KEY3_LEN (4U)
+#define DW3000_STS_KEY3_MASK 0xFFFFFFFFUL
+
+/* register STS_KEY */
+#define DW3000_STS_KEY_ID DW3000_STS_KEY0_ID
+#define DW3000_STS_KEY_LEN (16U)
+
+/* register STS_IV0 */
+#define DW3000_STS_IV0_ID 0x2001c
+#define DW3000_STS_IV0_LEN (4U)
+#define DW3000_STS_IV0_MASK 0xFFFFFFFFUL
+
+/* register STS_IV1 */
+#define DW3000_STS_IV1_ID 0x20020
+#define DW3000_STS_IV1_LEN (4U)
+#define DW3000_STS_IV1_MASK 0xFFFFFFFFUL
+
+/* register STS_IV2 */
+#define DW3000_STS_IV2_ID 0x20024
+#define DW3000_STS_IV2_LEN (4U)
+#define DW3000_STS_IV2_MASK 0xFFFFFFFFUL
+
+/* register STS_IV3 */
+#define DW3000_STS_IV3_ID 0x20028
+#define DW3000_STS_IV3_LEN (4U)
+#define DW3000_STS_IV3_MASK 0xFFFFFFFFUL
+
+/* register STS_IV0-STS_IV3 */
+#define DW3000_STS_IV_ID DW3000_STS_IV0_ID
+#define DW3000_STS_IV_LEN (16U)
+
+/* register MRX_CFG */
+#define DW3000_MRX_CFG_ID 0x30000
+#define DW3000_MRX_CFG_LEN (2U)
+#define DW3000_MRX_CFG_MASK 0x1FFFUL
+
+/* register ADC_THRESH_CFG */
+#define DW3000_ADC_THRESH_CFG_ID 0x30010
+#define DW3000_ADC_THRESH_CFG_LEN (4U)
+#define DW3000_ADC_THRESH_CFG_MASK 0xFFFFFFFFUL
+
+/* register AGC_CFG */
+#define DW3000_AGC_CFG_ID 0x30014
+#define DW3000_AGC_CFG_LEN (4U)
+#define DW3000_AGC_CFG_MASK 0xFFFFFFFFUL
+#define DW3000_AGC_DIS_MASK 0xFFFFFFFEUL
+
+/* Register DGC_CFG. */
+#define DW3000_DGC_CFG_ID 0x30018
+#define DW3000_DGC_CFG_LEN (4U)
+#define DW3000_DGC_CFG_MASK 0xffffffffUL
+#define DW3000_DGC_CFG_THR_64_BIT_OFFSET (9U)
+#define DW3000_DGC_CFG_THR_64_BIT_LEN (6U)
+#define DW3000_DGC_CFG_THR_64_BIT_MASK 0x7e00U
+#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_OFFSET (0U)
+#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_LEN (1U)
+#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK 0x1U
+
+/* register DGC_CFG0 */
+#define DW3000_DGC_CFG0_ID 0x3001c
+
+/* register DGC_CFG1 */
+#define DW3000_DGC_CFG1_ID 0x30020
+
+/* register DGC_CFG2 */
+#define DW3000_DGC_CFG2_ID 0x30024
+
+/* register ADC_THRESH_DBG */
+#define DW3000_ADC_THRESH_DBG_ID 0x3004C
+#define DW3000_ADC_THRESH_DBG_LEN (4U)
+#define DW3000_ADC_THRESH_DBG_MASK 0xFFFFFFFFUL
+
+/* register PGF_CAL_CFG */
+#define DW3000_PGF_CAL_CFG_ID 0x4000c
+#define DW3000_PGF_CAL_CFG_LEN (4U)
+#define DW3000_PGF_CAL_CFG_MASK 0xFFFFFFFFUL
+#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_OFFSET (16U)
+#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_LEN (4U)
+#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_MASK 0xf0000UL
+#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_OFFSET (8U)
+#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_LEN (5U)
+#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_MASK 0x1f00U
+#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_OFFSET (4U)
+#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_LEN (1U)
+#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_MASK 0x10U
+#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_OFFSET (0U)
+#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_LEN (2U)
+#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_MASK 0x3U
+
+/* register RX_CAL_CFG (PGF_CAL_CFG renamed in E0) */
+#define DW3000_RX_CAL_CFG_ID DW3000_PGF_CAL_CFG_ID
+#define DW3000_RX_CAL_CFG_LEN (4U)
+#define DW3000_RX_CAL_CFG_MASK 0xFFFFFFFFUL
+#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_OFFSET (16U)
+#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_LEN (4U)
+#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_MASK 0xf0000UL
+#define DW3000_RX_CAL_CFG_CAL_EN_BIT_OFFSET (4U)
+#define DW3000_RX_CAL_CFG_CAL_EN_BIT_LEN (1U)
+#define DW3000_RX_CAL_CFG_CAL_EN_BIT_MASK 0x10U
+#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_OFFSET (0U)
+#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_LEN (2U)
+#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_MASK 0x3U
+
+/* register PGF_I_CTRL0 */
+#define DW3000_PGF_I_CTRL0_ID 0x40010
+
+/* register PGF_I_CTRL1 */
+#define DW3000_PGF_I_CTRL1_ID 0x40014
+
+/* register RX_CAL_RESI (PGF_I_CTRL1 renamed in E0) */
+#define DW3000_RX_CAL_RESI_ID 0x40014
+#define DW3000_RX_CAL_RESI_LEN (4U)
+#define DW3000_RX_CAL_RESI_MASK 0xFFFFFFFFUL
+
+/* register PGF_Q_CTRL1 */
+#define DW3000_PGF_Q_CTRL1_ID 0x4001c
+
+/* register RX_CAL_RESQ (PGF_Q_CTRL1_ID renamed in E0) */
+#define DW3000_RX_CAL_RESQ_ID DW3000_PGF_Q_CTRL1_ID
+#define DW3000_RX_CAL_RESQ_LEN (4U)
+#define DW3000_RX_CAL_RESQ_MASK 0xFFFFFFFFUL
+
+/* register PGF_CAL_STS */
+#define DW3000_PGF_CAL_STS_ID 0x40020
+#define DW3000_PGF_CAL_STS_LEN (4U)
+#define DW3000_PGF_CAL_STS_MASK 0xFFFFFFFFUL
+#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_OFFSET (0U)
+#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_LEN (1U)
+#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_MASK 0x1U
+
+/* register RX_CAL_STS (PGF_CAL_STS renamed in E0) */
+#define RX_CAL_STS_ID DW3000_PGF_CAL_STS_ID
+#define RX_CAL_STS_LEN (4U)
+#define RX_CAL_STS_MASK 0xFFFFFFFFUL
+
+/* register GPIO_MODE */
+#define DW3000_GPIO_MODE_ID 0x50000
+#define DW3000_GPIO_MODE_LEN (4U)
+#define DW3000_GPIO_MODE_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_OFFSET (27U)
+#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_LEN (1U)
+#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK 0x8000000UL
+#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_OFFSET (24U)
+#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_MASK 0x7000000UL
+#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_OFFSET (21U)
+#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_MASK 0xe00000UL
+#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_OFFSET (18U)
+#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK 0x1c0000UL
+#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET (15U)
+#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK 0x38000UL
+#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET (12U)
+#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK 0x7000U
+#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_OFFSET (9U)
+#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_MASK 0xe00U
+#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_OFFSET (6U)
+#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_MASK 0x1c0U
+#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_OFFSET (3U)
+#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_MASK 0x38U
+#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_OFFSET (0U)
+#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN (3U)
+#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK 0x7U
+
+/* register GPIO_DIR */
+#define DW3000_GPIO_DIR_ID 0x50008
+#define DW3000_GPIO_DIR_LEN (4U)
+#define DW3000_GPIO_DIR_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_DIR_GDP8_BIT_OFFSET (8U)
+#define DW3000_GPIO_DIR_GDP8_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP8_BIT_MASK 0x100U
+#define DW3000_GPIO_DIR_GDP7_BIT_OFFSET (7U)
+#define DW3000_GPIO_DIR_GDP7_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP7_BIT_MASK 0x80U
+#define DW3000_GPIO_DIR_GDP6_BIT_OFFSET (6U)
+#define DW3000_GPIO_DIR_GDP6_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP6_BIT_MASK 0x40U
+#define DW3000_GPIO_DIR_GDP5_BIT_OFFSET (5U)
+#define DW3000_GPIO_DIR_GDP5_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP5_BIT_MASK 0x20U
+#define DW3000_GPIO_DIR_GDP4_BIT_OFFSET (4U)
+#define DW3000_GPIO_DIR_GDP4_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP4_BIT_MASK 0x10U
+#define DW3000_GPIO_DIR_GDP3_BIT_OFFSET (3U)
+#define DW3000_GPIO_DIR_GDP3_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP3_BIT_MASK 0x8U
+#define DW3000_GPIO_DIR_GDP2_BIT_OFFSET (2U)
+#define DW3000_GPIO_DIR_GDP2_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP2_BIT_MASK 0x4U
+#define DW3000_GPIO_DIR_GDP1_BIT_OFFSET (1U)
+#define DW3000_GPIO_DIR_GDP1_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP1_BIT_MASK 0x2U
+#define DW3000_GPIO_DIR_GDP0_BIT_OFFSET (0U)
+#define DW3000_GPIO_DIR_GDP0_BIT_LEN (1U)
+#define DW3000_GPIO_DIR_GDP0_BIT_MASK 0x1U
+
+/* register GPIO_OUT */
+#define DW3000_GPIO_OUT_ID 0x5000c
+#define DW3000_GPIO_OUT_LEN (4U)
+#define DW3000_GPIO_OUT_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_OUT_GOP8_BIT_OFFSET (8U)
+#define DW3000_GPIO_OUT_GOP8_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP8_BIT_MASK 0x100U
+#define DW3000_GPIO_OUT_GOP7_BIT_OFFSET (7U)
+#define DW3000_GPIO_OUT_GOP7_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP7_BIT_MASK 0x80U
+#define DW3000_GPIO_OUT_GOP6_BIT_OFFSET (6U)
+#define DW3000_GPIO_OUT_GOP6_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP6_BIT_MASK 0x40U
+#define DW3000_GPIO_OUT_GOP5_BIT_OFFSET (5U)
+#define DW3000_GPIO_OUT_GOP5_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP5_BIT_MASK 0x20U
+#define DW3000_GPIO_OUT_GOP4_BIT_OFFSET (4U)
+#define DW3000_GPIO_OUT_GOP4_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP4_BIT_MASK 0x10U
+#define DW3000_GPIO_OUT_GOP3_BIT_OFFSET (3U)
+#define DW3000_GPIO_OUT_GOP3_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP3_BIT_MASK 0x8U
+#define DW3000_GPIO_OUT_GOP2_BIT_OFFSET (2U)
+#define DW3000_GPIO_OUT_GOP2_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP2_BIT_MASK 0x4U
+#define DW3000_GPIO_OUT_GOP1_BIT_OFFSET (1U)
+#define DW3000_GPIO_OUT_GOP1_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP1_BIT_MASK 0x2U
+#define DW3000_GPIO_OUT_GOP0_BIT_OFFSET (0U)
+#define DW3000_GPIO_OUT_GOP0_BIT_LEN (1U)
+#define DW3000_GPIO_OUT_GOP0_BIT_MASK 0x1U
+
+/* register GPIO_IRQE - GPIO IRQ enable */
+#define DW3000_GPIO_IRQE_ID 0x50010
+#define DW3000_GPIO_IRQE_LEN (4U)
+#define DW3000_GPIO_IRQE_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_IRQE_GIRQE8_BIT_OFFSET (8U)
+#define DW3000_GPIO_IRQE_GIRQE8_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE8_BIT_MASK 0x100U
+#define DW3000_GPIO_IRQE_GIRQE7_BIT_OFFSET (7U)
+#define DW3000_GPIO_IRQE_GIRQE7_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE7_BIT_MASK 0x80U
+#define DW3000_GPIO_IRQE_GIRQE6_BIT_OFFSET (6U)
+#define DW3000_GPIO_IRQE_GIRQE6_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE6_BIT_MASK 0x40U
+#define DW3000_GPIO_IRQE_GIRQE5_BIT_OFFSET (5U)
+#define DW3000_GPIO_IRQE_GIRQE5_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE5_BIT_MASK 0x20U
+#define DW3000_GPIO_IRQE_GIRQE4_BIT_OFFSET (4U)
+#define DW3000_GPIO_IRQE_GIRQE4_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE4_BIT_MASK 0x10U
+#define DW3000_GPIO_IRQE_GIRQE3_BIT_OFFSET (3U)
+#define DW3000_GPIO_IRQE_GIRQE3_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE3_BIT_MASK 0x8U
+#define DW3000_GPIO_IRQE_GIRQE2_BIT_OFFSET (2U)
+#define DW3000_GPIO_IRQE_GIRQE2_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE2_BIT_MASK 0x4U
+#define DW3000_GPIO_IRQE_GIRQE1_BIT_OFFSET (1U)
+#define DW3000_GPIO_IRQE_GIRQE1_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE1_BIT_MASK 0x2U
+#define DW3000_GPIO_IRQE_GIRQE0_BIT_OFFSET (0U)
+#define DW3000_GPIO_IRQE_GIRQE0_BIT_LEN (1U)
+#define DW3000_GPIO_IRQE_GIRQE0_BIT_MASK 0x1U
+
+/* register GPIO_ISTS - GPIO IRQ status */
+#define DW3000_GPIO_ISTS_ID 0x50014
+#define DW3000_GPIO_ISTS_LEN (4U)
+#define DW3000_GPIO_ISTS_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_ISTS_GISTS8_BIT_OFFSET (8U)
+#define DW3000_GPIO_ISTS_GISTS8_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS8_BIT_MASK 0x100U
+#define DW3000_GPIO_ISTS_GISTS7_BIT_OFFSET (7U)
+#define DW3000_GPIO_ISTS_GISTS7_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS7_BIT_MASK 0x80U
+#define DW3000_GPIO_ISTS_GISTS6_BIT_OFFSET (6U)
+#define DW3000_GPIO_ISTS_GISTS6_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS6_BIT_MASK 0x40U
+#define DW3000_GPIO_ISTS_GISTS5_BIT_OFFSET (5U)
+#define DW3000_GPIO_ISTS_GISTS5_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS5_BIT_MASK 0x20U
+#define DW3000_GPIO_ISTS_GISTS4_BIT_OFFSET (4U)
+#define DW3000_GPIO_ISTS_GISTS4_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS4_BIT_MASK 0x10U
+#define DW3000_GPIO_ISTS_GISTS3_BIT_OFFSET (3U)
+#define DW3000_GPIO_ISTS_GISTS3_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS3_BIT_MASK 0x8U
+#define DW3000_GPIO_ISTS_GISTS2_BIT_OFFSET (2U)
+#define DW3000_GPIO_ISTS_GISTS2_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS2_BIT_MASK 0x4U
+#define DW3000_GPIO_ISTS_GISTS1_BIT_OFFSET (1U)
+#define DW3000_GPIO_ISTS_GISTS1_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS1_BIT_MASK 0x2U
+#define DW3000_GPIO_ISTS_GISTS0_BIT_OFFSET (0U)
+#define DW3000_GPIO_ISTS_GISTS0_BIT_LEN (1U)
+#define DW3000_GPIO_ISTS_GISTS0_BIT_MASK 0x1U
+
+/* register GPIO_ISEN */
+#define DW3000_GPIO_ISEN_ID 0x50018
+#define DW3000_GPIO_ISEN_LEN (4U)
+#define DW3000_GPIO_ISEN_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_ISEN_GISEN8_BIT_OFFSET (8U)
+#define DW3000_GPIO_ISEN_GISEN8_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN8_BIT_MASK 0x100U
+#define DW3000_GPIO_ISEN_GISEN7_BIT_OFFSET (7U)
+#define DW3000_GPIO_ISEN_GISEN7_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN7_BIT_MASK 0x80U
+#define DW3000_GPIO_ISEN_GISEN6_BIT_OFFSET (6U)
+#define DW3000_GPIO_ISEN_GISEN6_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN6_BIT_MASK 0x40U
+#define DW3000_GPIO_ISEN_GISEN5_BIT_OFFSET (5U)
+#define DW3000_GPIO_ISEN_GISEN5_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN5_BIT_MASK 0x20U
+#define DW3000_GPIO_ISEN_GISEN4_BIT_OFFSET (4U)
+#define DW3000_GPIO_ISEN_GISEN4_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN4_BIT_MASK 0x10U
+#define DW3000_GPIO_ISEN_GISEN3_BIT_OFFSET (3U)
+#define DW3000_GPIO_ISEN_GISEN3_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN3_BIT_MASK 0x8U
+#define DW3000_GPIO_ISEN_GISEN2_BIT_OFFSET (2U)
+#define DW3000_GPIO_ISEN_GISEN2_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN2_BIT_MASK 0x4U
+#define DW3000_GPIO_ISEN_GISEN1_BIT_OFFSET (1U)
+#define DW3000_GPIO_ISEN_GISEN1_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN1_BIT_MASK 0x2U
+#define DW3000_GPIO_ISEN_GISEN0_BIT_OFFSET (0U)
+#define DW3000_GPIO_ISEN_GISEN0_BIT_LEN (1U)
+#define DW3000_GPIO_ISEN_GISEN0_BIT_MASK 0x1U
+
+/* register GPIO_IMODE */
+#define DW3000_GPIO_IMODE_ID 0x5001c
+#define DW3000_GPIO_IMODE_LEN (4U)
+#define DW3000_GPIO_IMODE_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_IMODE_GIMOD8_BIT_OFFSET (8U)
+#define DW3000_GPIO_IMODE_GIMOD8_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD8_BIT_MASK 0x100U
+#define DW3000_GPIO_IMODE_GIMOD7_BIT_OFFSET (7U)
+#define DW3000_GPIO_IMODE_GIMOD7_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD7_BIT_MASK 0x80U
+#define DW3000_GPIO_IMODE_GIMOD6_BIT_OFFSET (6U)
+#define DW3000_GPIO_IMODE_GIMOD6_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD6_BIT_MASK 0x40U
+#define DW3000_GPIO_IMODE_GIMOD5_BIT_OFFSET (5U)
+#define DW3000_GPIO_IMODE_GIMOD5_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD5_BIT_MASK 0x20U
+#define DW3000_GPIO_IMODE_GIMOD4_BIT_OFFSET (4U)
+#define DW3000_GPIO_IMODE_GIMOD4_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD4_BIT_MASK 0x10U
+#define DW3000_GPIO_IMODE_GIMOD3_BIT_OFFSET (3U)
+#define DW3000_GPIO_IMODE_GIMOD3_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD3_BIT_MASK 0x8U
+#define DW3000_GPIO_IMODE_GIMOD2_BIT_OFFSET (2U)
+#define DW3000_GPIO_IMODE_GIMOD2_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD2_BIT_MASK 0x4U
+#define DW3000_GPIO_IMODE_GIMOD1_BIT_OFFSET (1U)
+#define DW3000_GPIO_IMODE_GIMOD1_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD1_BIT_MASK 0x2U
+#define DW3000_GPIO_IMODE_GIMOD0_BIT_OFFSET (0U)
+#define DW3000_GPIO_IMODE_GIMOD0_BIT_LEN (1U)
+#define DW3000_GPIO_IMODE_GIMOD0_BIT_MASK 0x1U
+
+/* register GPIO_IBES */
+#define DW3000_GPIO_IBES_ID 0x50020
+#define DW3000_GPIO_IBES_LEN (4U)
+#define DW3000_GPIO_IBES_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_IBES_GIBES8_BIT_OFFSET (8U)
+#define DW3000_GPIO_IBES_GIBES8_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES8_BIT_MASK 0x100U
+#define DW3000_GPIO_IBES_GIBES7_BIT_OFFSET (7U)
+#define DW3000_GPIO_IBES_GIBES7_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES7_BIT_MASK 0x80U
+#define DW3000_GPIO_IBES_GIBES6_BIT_OFFSET (6U)
+#define DW3000_GPIO_IBES_GIBES6_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES6_BIT_MASK 0x40U
+#define DW3000_GPIO_IBES_GIBES5_BIT_OFFSET (5U)
+#define DW3000_GPIO_IBES_GIBES5_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES5_BIT_MASK 0x20U
+#define DW3000_GPIO_IBES_GIBES4_BIT_OFFSET (4U)
+#define DW3000_GPIO_IBES_GIBES4_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES4_BIT_MASK 0x10U
+#define DW3000_GPIO_IBES_GIBES3_BIT_OFFSET (3U)
+#define DW3000_GPIO_IBES_GIBES3_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES3_BIT_MASK 0x8U
+#define DW3000_GPIO_IBES_GIBES2_BIT_OFFSET (2U)
+#define DW3000_GPIO_IBES_GIBES2_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES2_BIT_MASK 0x4U
+#define DW3000_GPIO_IBES_GIBES1_BIT_OFFSET (1U)
+#define DW3000_GPIO_IBES_GIBES1_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES1_BIT_MASK 0x2U
+#define DW3000_GPIO_IBES_GIBES0_BIT_OFFSET (0U)
+#define DW3000_GPIO_IBES_GIBES0_BIT_LEN (1U)
+#define DW3000_GPIO_IBES_GIBES0_BIT_MASK 0x1U
+
+/* register GPIO_ICLR - GPIO IRQ clear */
+#define DW3000_GPIO_ICLR_ID 0x50024
+#define DW3000_GPIO_ICLR_LEN (4U)
+#define DW3000_GPIO_ICLR_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_ICLR_GICLR8_BIT_OFFSET (8U)
+#define DW3000_GPIO_ICLR_GICLR8_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR8_BIT_MASK 0x100U
+#define DW3000_GPIO_ICLR_GICLR7_BIT_OFFSET (7U)
+#define DW3000_GPIO_ICLR_GICLR7_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR7_BIT_MASK 0x80U
+#define DW3000_GPIO_ICLR_GICLR6_BIT_OFFSET (6U)
+#define DW3000_GPIO_ICLR_GICLR6_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR6_BIT_MASK 0x40U
+#define DW3000_GPIO_ICLR_GICLR5_BIT_OFFSET (5U)
+#define DW3000_GPIO_ICLR_GICLR5_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR5_BIT_MASK 0x20U
+#define DW3000_GPIO_ICLR_GICLR4_BIT_OFFSET (4U)
+#define DW3000_GPIO_ICLR_GICLR4_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR4_BIT_MASK 0x10U
+#define DW3000_GPIO_ICLR_GICLR3_BIT_OFFSET (3U)
+#define DW3000_GPIO_ICLR_GICLR3_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR3_BIT_MASK 0x8U
+#define DW3000_GPIO_ICLR_GICLR2_BIT_OFFSET (2U)
+#define DW3000_GPIO_ICLR_GICLR2_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR2_BIT_MASK 0x4U
+#define DW3000_GPIO_ICLR_GICLR1_BIT_OFFSET (1U)
+#define DW3000_GPIO_ICLR_GICLR1_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR1_BIT_MASK 0x2U
+#define DW3000_GPIO_ICLR_GICLR0_BIT_OFFSET (0U)
+#define DW3000_GPIO_ICLR_GICLR0_BIT_LEN (1U)
+#define DW3000_GPIO_ICLR_GICLR0_BIT_MASK 0x1U
+
+/* register GPIO_IDBE */
+#define DW3000_GPIO_IDBE_ID 0x50028
+#define DW3000_GPIO_IDBE_LEN (4U)
+#define DW3000_GPIO_IDBE_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_IDBE_GIDBE8_BIT_OFFSET (8U)
+#define DW3000_GPIO_IDBE_GIDBE8_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE8_BIT_MASK 0x100U
+#define DW3000_GPIO_IDBE_GIDBE7_BIT_OFFSET (7U)
+#define DW3000_GPIO_IDBE_GIDBE7_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE7_BIT_MASK 0x80U
+#define DW3000_GPIO_IDBE_GIDBE6_BIT_OFFSET (6U)
+#define DW3000_GPIO_IDBE_GIDBE6_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE6_BIT_MASK 0x40U
+#define DW3000_GPIO_IDBE_GIDBE5_BIT_OFFSET (5U)
+#define DW3000_GPIO_IDBE_GIDBE5_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE5_BIT_MASK 0x20U
+#define DW3000_GPIO_IDBE_GIDBE4_BIT_OFFSET (4U)
+#define DW3000_GPIO_IDBE_GIDBE4_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE4_BIT_MASK 0x10U
+#define DW3000_GPIO_IDBE_GIDBE3_BIT_OFFSET (3U)
+#define DW3000_GPIO_IDBE_GIDBE3_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE3_BIT_MASK 0x8U
+#define DW3000_GPIO_IDBE_GIDBE2_BIT_OFFSET (2U)
+#define DW3000_GPIO_IDBE_GIDBE2_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE2_BIT_MASK 0x4U
+#define DW3000_GPIO_IDBE_GIDBE1_BIT_OFFSET (1U)
+#define DW3000_GPIO_IDBE_GIDBE1_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE1_BIT_MASK 0x2U
+#define DW3000_GPIO_IDBE_GIDBE0_BIT_OFFSET (0U)
+#define DW3000_GPIO_IDBE_GIDBE0_BIT_LEN (1U)
+#define DW3000_GPIO_IDBE_GIDBE0_BIT_MASK 0x1U
+
+/* register GPIO_RAW */
+#define DW3000_GPIO_RAW_ID 0x5002c
+#define DW3000_GPIO_RAW_LEN (4U)
+#define DW3000_GPIO_RAW_MASK 0xFFFFFFFFUL
+#define DW3000_GPIO_RAW_GRAWP8_BIT_OFFSET (8U)
+#define DW3000_GPIO_RAW_GRAWP8_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP8_BIT_MASK 0x100U
+#define DW3000_GPIO_RAW_GRAWP7_BIT_OFFSET (7U)
+#define DW3000_GPIO_RAW_GRAWP7_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP7_BIT_MASK 0x80U
+#define DW3000_GPIO_RAW_GRAWP6_BIT_OFFSET (6U)
+#define DW3000_GPIO_RAW_GRAWP6_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP6_BIT_MASK 0x40U
+#define DW3000_GPIO_RAW_GRAWP5_BIT_OFFSET (5U)
+#define DW3000_GPIO_RAW_GRAWP5_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP5_BIT_MASK 0x20U
+#define DW3000_GPIO_RAW_GRAWP4_BIT_OFFSET (4U)
+#define DW3000_GPIO_RAW_GRAWP4_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP4_BIT_MASK 0x10U
+#define DW3000_GPIO_RAW_GRAWP3_BIT_OFFSET (3U)
+#define DW3000_GPIO_RAW_GRAWP3_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP3_BIT_MASK 0x8U
+#define DW3000_GPIO_RAW_GRAWP2_BIT_OFFSET (2U)
+#define DW3000_GPIO_RAW_GRAWP2_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP2_BIT_MASK 0x4U
+#define DW3000_GPIO_RAW_GRAWP1_BIT_OFFSET (1U)
+#define DW3000_GPIO_RAW_GRAWP1_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP1_BIT_MASK 0x2U
+#define DW3000_GPIO_RAW_GRAWP0_BIT_OFFSET (0U)
+#define DW3000_GPIO_RAW_GRAWP0_BIT_LEN (1U)
+#define DW3000_GPIO_RAW_GRAWP0_BIT_MASK 0x1U
+
+/* register DRX_TUNE0 */
+#define DW3000_DRX_TUNE0_ID 0x60000
+#define DW3000_DRX_TUNE0_LEN (2U)
+#define DW3000_DRX_TUNE0_MASK 0xFFFFUL
+#define DW3000_DRX_TUNE0_DT0B4_BIT_OFFSET (4U)
+#define DW3000_DRX_TUNE0_DT0B4_BIT_LEN (1U)
+#define DW3000_DRX_TUNE0_DT0B4_BIT_MASK 0x10U
+#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_OFFSET (0U)
+#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_LEN (2U)
+#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK 0x3U
+
+/* register DRX_SFDTOC */
+#define DW3000_DRX_SFDTOC_ID 0x60002
+#define DW3000_DRX_SFDTOC_LEN (2U)
+#define DW3000_DRX_SFDTOC_BIT_MASK 0xFFFFUL
+
+/* register DRX_PRETOC */
+#define DW3000_DRX_PRETOC_ID 0x60004
+#define DW3000_DRX_PRETOC_LEN (2U)
+#define DW3000_DRX_PRETOC_BIT_MASK 0xFFFFUL
+
+/* register DRX_TUNE3 */
+#define DW3000_DRX_TUNE3_ID 0x6000c
+
+/* register DTUNE4 */
+#define DW3000_DTUNE4_ID 0x60010
+#define DW3000_DTUNE4_LEN (4U)
+#define DW3000_DTUNE4_MASK 0xFFFFFFFFUL
+#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_OFFSET (24U)
+#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_LEN (8U)
+#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_MASK 0xff000000UL
+
+/* register RF_ENABLE */
+#define DW3000_RF_ENABLE_ID 0x70000
+#define DW3000_RF_ENABLE_LEN (4U)
+#define DW3000_RF_ENABLE_MASK 0xFFFFFFFFUL
+#define DW3000_RF_ENABLE_TX_SW_EN_BIT_OFFSET (25U)
+#define DW3000_RF_ENABLE_TX_SW_EN_BIT_LEN (1U)
+#define DW3000_RF_ENABLE_TX_SW_EN_BIT_MASK 0x2000000UL
+#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_OFFSET (13U)
+#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_LEN (1U)
+#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_MASK 0x2000U
+#define DW3000_RF_ENABLE_TX_EN_BIT_OFFSET (12U)
+#define DW3000_RF_ENABLE_TX_EN_BIT_LEN (1U)
+#define DW3000_RF_ENABLE_TX_EN_BIT_MASK 0x1000U
+#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_OFFSET (11U)
+#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_LEN (1U)
+#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_MASK 0x800U
+#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_OFFSET (10U)
+#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_LEN (1U)
+#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_MASK 0x400U
+
+/* register RF_CTRL_MASK */
+#define DW3000_RF_CTRL_MASK_ID 0x70004
+#define DW3000_RF_CTRL_MASK_LEN (4U)
+#define DW3000_RF_CTRL_MASK_MASK 0xFFFFFFFFUL
+
+/* register RX_CTRL_LO */
+#define DW3000_RX_CTRL_LO_ID 0x70008
+
+/* register RX_CTRL_HI */
+#define DW3000_RX_CTRL_HI_ID 0x70010
+
+/* register RX_SWITCH_CTRL */
+#define DW3000_RF_SWITCH_CTRL_ID 0x70014
+#define DW3000_RF_SWITCH_CTRL_LEN (4U)
+#define DW3000_RF_SWITCH_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_OFFSET (24U)
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_LEN (6U)
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_MASK 0x3F000000
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_OFFSET (16U)
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_LEN (1U)
+#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_MASK 0x10000
+#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_OFFSET (12U)
+#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_LEN (3U)
+#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_MASK 0x7000U
+#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_OFFSET (8U)
+#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_LEN (1U)
+#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_MASK 0x100U
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_OFFSET (6U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_LEN (1U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_MASK 0x40U
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_OFFSET (5U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_LEN (1U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_MASK 0x20U
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET (4U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_LEN (1U)
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_MASK 0x10U
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_NOTOGGLE_BIT_MASK 0x1U
+#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_NOTOGGLE_LEN (0U)
+
+/* register TX_CTRL_LO */
+#define DW3000_TX_CTRL_LO_ID 0x70018
+#define DW3000_TX_CTRL_LO_LEN (4U)
+#define DW3000_TX_CTRL_LO_MASK 0xFFFFFFFFUL
+#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_OFFSET (25U)
+#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_LEN (3U)
+#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_MASK 0xe000000UL
+#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_OFFSET (20U)
+#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_LEN (5U)
+#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_MASK 0x1f00000UL
+#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_OFFSET (18U)
+#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_LEN (2U)
+#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_MASK 0xc0000UL
+#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_OFFSET (16U)
+#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_LEN (2U)
+#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_MASK 0x30000UL
+#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_OFFSET (8U)
+#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_LEN (8U)
+#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_MASK 0xff00U
+#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_OFFSET (6U)
+#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_LEN (2U)
+#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_MASK 0xc0U
+#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_OFFSET (1U)
+#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_LEN (5U)
+#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_MASK 0x3eU
+#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_OFFSET (0U)
+#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_LEN (1U)
+#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_MASK 0x1U
+
+/* register TX_CTRL_HI */
+#define DW3000_TX_CTRL_HI_ID 0x7001c
+#define DW3000_TX_CTRL_HI_LEN (4U)
+#define DW3000_TX_CTRL_HI_MASK 0xFFFFFFFFUL
+#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_OFFSET (31U)
+#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_LEN (1U)
+#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK 0x80000000UL
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_OFFSET (23U)
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_LEN (6U)
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_MASK 0x1f800000UL
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_OFFSET (21U)
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_LEN (2U)
+#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_MASK 0x600000UL
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_OFFSET (16U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_LEN (4U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_MASK 0xf0000UL
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_OFFSET (12U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_LEN (4U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_MASK 0xf000U
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_OFFSET (8U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_LEN (4U)
+#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_MASK 0xf00U
+#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_OFFSET (6U)
+#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_LEN (2U)
+#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_MASK 0xc0U
+#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_OFFSET (0U)
+#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_LEN (6U)
+#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK 0x3fU
+
+/* register TX_TEST */
+#define DW3000_TX_TEST_ID 0x70028
+#define DW3000_TX_TEST_LEN (4U)
+#define DW3000_TX_TEST_MASK 0xFFFFFFFFUL
+#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_OFFSET (27U)
+#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_LEN (1U)
+#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_MASK 0x8000000UL
+#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_OFFSET (26U)
+#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_LEN (1U)
+#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_MASK 0x4000000UL
+#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_OFFSET (25U)
+#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_LEN (1U)
+#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_MASK 0x2000000UL
+#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_OFFSET (24U)
+#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_LEN (1U)
+#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_MASK 0x1000000UL
+#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_OFFSET (18U)
+#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_LEN (1U)
+#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_MASK 0x40000UL
+#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_OFFSET (15U)
+#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_LEN (3U)
+#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_MASK 0x38000UL
+#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_OFFSET (13U)
+#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_LEN (2U)
+#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_MASK 0x6000U
+#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_OFFSET (9U)
+#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_LEN (4U)
+#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_MASK 0x1e00U
+#define DW3000_TX_TEST_TX_DC_TEST_BIT_OFFSET (5U)
+#define DW3000_TX_TEST_TX_DC_TEST_BIT_LEN (4U)
+#define DW3000_TX_TEST_TX_DC_TEST_BIT_MASK 0x1e0U
+#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_OFFSET (4U)
+#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_LEN (1U)
+#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_MASK 0x10U
+#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_OFFSET (3U)
+#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_LEN (1U)
+#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_MASK 0x8U
+#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_OFFSET (2U)
+#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_LEN (1U)
+#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_MASK 0x4U
+#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_OFFSET (1U)
+#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_LEN (1U)
+#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_MASK 0x2U
+#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_OFFSET (0U)
+#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_LEN (1U)
+#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_MASK 0x1U
+
+/* register LDO_TUNE_HI */
+#define DW3000_LDO_TUNE_HI_ID 0x70044
+#define DW3000_LDO_TUNE_HI_LDO_HVAUX_TUNE_BIT_MASK 0xf000U
+
+/* register LDO_CTRL */
+#define DW3000_LDO_CTRL_ID 0x70048
+#define DW3000_LDO_CTRL_LEN (4U)
+#define DW3000_LDO_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_OFFSET (27U)
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_MASK 0x8000000UL
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_OFFSET (26U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_MASK 0x4000000UL
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_OFFSET (25U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_MASK 0x2000000UL
+#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_OFFSET (24U)
+#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_MASK 0x1000000UL
+#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_OFFSET (23U)
+#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_MASK 0x800000UL
+#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_OFFSET (22U)
+#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_MASK 0x400000UL
+#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_OFFSET (21U)
+#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_MASK 0x200000UL
+#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_OFFSET (20U)
+#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_MASK 0x100000UL
+#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_OFFSET (19U)
+#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_MASK 0x80000UL
+#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_OFFSET (18U)
+#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_MASK 0x40000UL
+#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_OFFSET (17U)
+#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_MASK 0x20000UL
+#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_OFFSET (16U)
+#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_MASK 0x10000UL
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_OFFSET (11U)
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_MASK 0x800U
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_OFFSET (10U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_MASK 0x400U
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_OFFSET (9U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_MASK 0x200U
+#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_OFFSET (8U)
+#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_MASK 0x100U
+#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_OFFSET (7U)
+#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_MASK 0x80U
+#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_OFFSET (6U)
+#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_MASK 0x40U
+#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_OFFSET (5U)
+#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_MASK 0x20U
+#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_OFFSET (4U)
+#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_MASK 0x10U
+#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_OFFSET (3U)
+#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_MASK 0x8U
+#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_OFFSET (2U)
+#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_MASK 0x4U
+#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_OFFSET (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_MASK 0x2U
+#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_OFFSET (0U)
+#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_LEN (1U)
+#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_MASK 0x1U
+
+/* register LDO_VOUT*/
+#define DW3000_LDO_VOUT_ID 0x7004C
+
+/* register LDO_RLOAD */
+#define DW3000_LDO_RLOAD_ID 0x70050
+
+/* register PG_TEST */
+#define DW3000_PG_TEST_ID 0x80018
+#define DW3000_PG_TEST_LEN (4U)
+#define DW3000_PG_TEST_MASK 0xFFFFFFFFUL
+#define DW3000_PG_TEST_TX_TEST_CH4_BIT_OFFSET (12U)
+#define DW3000_PG_TEST_TX_TEST_CH4_BIT_LEN (4U)
+#define DW3000_PG_TEST_TX_TEST_CH4_BIT_MASK 0xf000U
+#define DW3000_PG_TEST_TX_TEST_CH3_BIT_OFFSET (8U)
+#define DW3000_PG_TEST_TX_TEST_CH3_BIT_LEN (4U)
+#define DW3000_PG_TEST_TX_TEST_CH3_BIT_MASK 0xf00U
+#define DW3000_PG_TEST_TX_TEST_CH2_BIT_OFFSET (4U)
+#define DW3000_PG_TEST_TX_TEST_CH2_BIT_LEN (4U)
+#define DW3000_PG_TEST_TX_TEST_CH2_BIT_MASK 0xf0U
+#define DW3000_PG_TEST_TX_TEST_CH1_BIT_OFFSET (0U)
+#define DW3000_PG_TEST_TX_TEST_CH1_BIT_LEN (4U)
+#define DW3000_PG_TEST_TX_TEST_CH1_BIT_MASK 0xfU
+
+/* register PLL_CAL */
+#define DW3000_PLL_CAL_ID 0x90008
+#define DW3000_PLL_CAL_LEN (4U)
+#define DW3000_PLL_CAL_MASK 0xFFFFFFFFUL
+#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_OFFSET (8U)
+#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_LEN (1U)
+#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK 0x100U
+#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_OFFSET (1U)
+#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_LEN (1U)
+#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK 0x2U
+
+/* register PLL_CFG */
+#define DW3000_PLL_CFG_ID 0x90000
+
+/* register PLL_COMMON */
+#define DW3000_PLL_COMMON_ID 0x90010
+#define DW3000_PLL_COMMON_LEN (2U)
+#define DW3000_PLL_COMMON_MASK 0x0000FFFFUL
+
+/* register XTAL */
+#define DW3000_XTAL_ID 0x90014
+#define DW3000_XTAL_TRIM_BIT_OFFSET (0U)
+#define DW3000_XTAL_TRIM_BIT_LEN (6U)
+#define DW3000_XTAL_TRIM_BIT_MASK 0x3FU
+
+/* register AON_DIG_CFG */
+#define DW3000_AON_DIG_CFG_ID 0xa0000
+#define DW3000_AON_DIG_CFG_LEN (3U)
+#define DW3000_AON_DIG_CFG_MASK 0xFFFFFFUL
+#define DW3000_AON_DIG_CFG_ONW_AONDLD_OFFSET (0U)
+#define DW3000_AON_DIG_CFG_ONW_AONDLD_LEN (1U)
+#define DW3000_AON_DIG_CFG_ONW_AONDLD_MASK 0x1U
+#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_OFFSET (8U)
+#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_LEN (1U)
+#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_MASK 0x100U
+
+/* register AON_CTRL */
+#define DW3000_AON_CTRL_ID 0xa0004
+#define DW3000_AON_CTRL_LEN (1U)
+#define DW3000_AON_CTRL_MASK 0xFFUL
+#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_OFFSET (7U)
+#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_LEN (1U)
+#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_MASK 0x80U
+#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_OFFSET (6U)
+#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_LEN (1U)
+#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_MASK 0x40U
+#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_OFFSET (5U)
+#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_LEN (1U)
+#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_MASK 0x20U
+#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_OFFSET (4U)
+#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_LEN (1U)
+#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_MASK 0x10U
+#define DW3000_AON_CTRL_OVR_READ_EN_BIT_OFFSET (3U)
+#define DW3000_AON_CTRL_OVR_READ_EN_BIT_LEN (1U)
+#define DW3000_AON_CTRL_OVR_READ_EN_BIT_MASK 0x8U
+#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_OFFSET (2U)
+#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_LEN (1U)
+#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_MASK 0x4U
+#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_OFFSET (1U)
+#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_LEN (1U)
+#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK 0x2U
+#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_OFFSET (0U)
+#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_LEN (1U)
+#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_MASK 0x1U
+
+/* register AON_CFG: AON configuration register */
+#define DW3000_AON_CFG_ID 0xa0014
+#define DW3000_AON_CFG_LEN (1U)
+#define DW3000_AON_CFG_MASK 0xFFUL
+#define DW3000_AON_WAKE_CSN_OFFSET (3U)
+#define DW3000_AON_WAKE_CSN_LEN (1U)
+#define DW3000_AON_WAKE_CSN_MASK 0x8U
+#define DW3000_AON_SLEEP_EN_OFFSET (0U)
+#define DW3000_AON_SLEEP_EN_LEN (1U)
+#define DW3000_AON_SLEEP_EN_MASK 0x1U
+
+/* register NVM_WDATA */
+#define DW3000_NVM_WDATA_ID 0xb0000
+
+/* register NVM_ADDR */
+#define DW3000_NVM_ADDR_ID 0xb0004
+
+/* register NVM_CFG */
+#define DW3000_NVM_CFG_ID 0xb0008
+#define DW3000_NVM_CFG_LEN (4U)
+#define DW3000_NVM_CFG_MASK 0xFFFFFFFFUL
+#define DW3000_NVM_CFG_DGC_SEL_BIT_LEN (1U)
+#define DW3000_NVM_CFG_GEAR_ID_BIT_LEN (2U)
+#define DW3000_NVM_CFG_GEAR_KICK_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_PD_BIT_LEN (1U)
+#define DW3000_NVM_CFG_LDO_KICK_BIT_LEN (1U)
+#define DW3000_NVM_CFG_DGC_KICK_BIT_LEN (1U)
+#define DW3000_NVM_CFG_ADDR_INC_BIT_OFFSET (5U)
+#define DW3000_NVM_CFG_ADDR_INC_BIT_LEN (1U)
+#define DW3000_NVM_CFG_ADDR_INC_BIT_MASK 0x20U
+#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_OFFSET (4U)
+#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_MASK 0x10U
+#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_OFFSET (3U)
+#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_MASK 0x8U
+#define DW3000_NVM_CFG_NVM_WRITE_BIT_OFFSET (2U)
+#define DW3000_NVM_CFG_NVM_WRITE_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_WRITE_BIT_MASK 0x4U
+#define DW3000_NVM_CFG_NVM_READ_BIT_OFFSET (1U)
+#define DW3000_NVM_CFG_NVM_READ_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_READ_BIT_MASK 0x2U
+#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_OFFSET (0U)
+#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_LEN (1U)
+#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_MASK 0x1U
+
+/* register NVM_STATUS */
+#define DW3000_NVM_STATUS_ID 0xb000c
+#define DW3000_NVM_STATUS_NVM_PROG_DONE_BIT_MASK 0x1U
+
+/* register NVM_RDATA */
+#define DW3000_NVM_RDATA_ID 0xb0010
+
+/* register STS_TOA_LO */
+#define DW3000_STS_TOA_LO_ID 0xc0008
+#define DW3000_STS_TOA_LO_LEN (4U)
+#define DW3000_STS_TOA_LO_MASK 0xFFFFFFFFUL
+#define DW3000_STS_TOA_LO_STS_TOA_BIT_OFFSET (0U)
+#define DW3000_STS_TOA_LO_STS_TOA_BIT_LEN (32U)
+#define DW3000_STS_TOA_LO_STS_TOA_BIT_MASK 0xffffffffUL
+
+/* register STS_TOA_HI */
+#define DW3000_STS_TOA_HI_ID 0xc000c
+#define DW3000_STS_TOA_HI_LEN (4U)
+#define DW3000_STS_TOA_HI_MASK 0xFFFFFFFFUL
+#define DW3000_STS_TOA_HI_STS_TOAST_BIT_OFFSET (23U)
+#define DW3000_STS_TOA_HI_STS_TOAST_BIT_LEN (9U)
+#define DW3000_STS_TOA_HI_STS_TOAST_BIT_MASK 0xff800000UL
+#define DW3000_STS_TOA_HI_STS_POA_BIT_OFFSET (8U)
+#define DW3000_STS_TOA_HI_STS_POA_BIT_LEN (14U)
+#define DW3000_STS_TOA_HI_STS_POA_BIT_MASK 0x3fff00UL
+#define DW3000_STS_TOA_HI_STS_TOA_BIT_OFFSET (0U)
+#define DW3000_STS_TOA_HI_STS_TOA_BIT_LEN (8U)
+#define DW3000_STS_TOA_HI_STS_TOA_BIT_MASK 0xffU
+
+/* register IP_TS_LO */
+#define DW3000_IP_TS_LO_ID 0xc0000
+#define DW3000_IP_TS_LO_LEN (4U)
+/* register IP_TS_HI */
+#define DW3000_IP_TS_HI_ID 0xc0004
+#define DW3000_IP_TS_HI_LEN (4U)
+/* register STS_TS_LO */
+#define DW3000_STS_TS_LO_ID 0xc0008
+#define DW3000_STS_TS_LO_LEN (4U)
+/* register STS_TS_HI */
+#define DW3000_STS_TS_HI_ID 0xc000C
+#define DW3000_STS_TS_HI_LEN (4U)
+/* register STS1_TS_LO */
+#define DW3000_STS1_TS_LO_ID 0xc0010
+#define DW3000_STS1_TS_LO_LEN (4U)
+/* register STS1_TS_HI */
+#define DW3000_STS1_TS_HI_ID 0xc0014
+#define DW3000_STS1_TS_HI_LEN (4U)
+
+/* register STS1_TOA_LO */
+#define DW3000_STS1_TOA_LO_ID 0xc0010
+#define DW3000_STS1_TOA_LO_LEN (4U)
+#define DW3000_STS1_TOA_LO_MASK 0xFFFFFFFFUL
+#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_OFFSET (0U)
+#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_LEN (32U)
+#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_MASK 0xffffffffUL
+
+/* register STS1_TOA_HI */
+#define DW3000_STS1_TOA_HI_ID 0xc0014
+#define DW3000_STS1_TOA_HI_LEN (4U)
+#define DW3000_STS1_TOA_HI_MASK 0xFFFFFFFFUL
+#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_OFFSET (23U)
+#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_LEN (9U)
+#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_MASK 0xff800000UL
+#define DW3000_STS1_TOA_HI_STS1_POA_BIT_OFFSET (8U)
+#define DW3000_STS1_TOA_HI_STS1_POA_BIT_LEN (14U)
+#define DW3000_STS1_TOA_HI_STS1_POA_BIT_MASK 0x3fff00UL
+#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_OFFSET (0U)
+#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_LEN (8U)
+#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_MASK 0xffU
+
+/* Register CIA_TDOA_1_PDOA */
+#define DW3000_CIA_TDOA_1_PDOA_ID 0xc001c
+#define DW3000_CIA_TDOA_1_PDOA_LEN (4U)
+#define DW3000_CIA_TDOA_1_PDOA_MASK 0xffffffffUL
+#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_OFFSET (30U)
+#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_LEN (1U)
+#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_MASK 0x40000000UL
+#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_OFFSET (16U)
+#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_LEN (14U)
+#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK 0x3fff0000UL
+#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_OFFSET (0U)
+#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_LEN (9U)
+#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_MASK 0x1ffU
+
+/* register CIA_DIAG0 */
+#define DW3000_CIA_DIAG0_ID 0xc0020
+#define DW3000_CIA_DIAG0_LEN (4U)
+#define DW3000_CIA_DIAG0_COE_PPM_BIT_OFFSET (0U)
+#define DW3000_CIA_DIAG0_COE_PPM_BIT_LEN (12U)
+#define DW3000_CIA_DIAG0_COE_PPM_BIT_MASK 0xfffU
+
+/* register IP_DIAG0 */
+#define DW3000_IP_DIAG0_ID 0xc0028
+#define DW3000_IP_DIAG0_LEN (4U)
+
+/* register IP_DIAG1 */
+#define DW3000_IP_DIAG1_ID 0xc002c
+#define DW3000_IP_DIAG1_LEN (4U)
+
+/* register IP_DIAG2 */
+#define DW3000_IP_DIAG2_ID 0xc0030
+#define DW3000_IP_DIAG2_LEN (4U)
+
+/* register IP_DIAG8 */
+#define DW3000_IP_DIAG8_ID 0xc0048
+#define DW3000_IP_DIAG8_LEN (4U)
+
+/* register IP_DIAG12 */
+#define DW3000_IP_DIAG12_ID 0xc0058
+#define DW3000_IP_DIAG12_LEN (4U)
+#define DW3000_IP_DIAG12_MASK (0xfff)
+
+/* register CP0_DIAG12 */
+#define DW3000_CP0_DIAG1_ID 0xc0060
+#define DW3000_CP0_DIAG1_LEN (4U)
+
+/* register CP0_DIAG12 */
+#define DW3000_CP0_DIAG12_ID 0xd0020
+#define DW3000_CP0_DIAG12_LEN (4U)
+
+/* register CP1_DIAG1 */
+#define DW3000_CP1_DIAG1_ID 0xd003c
+#define DW3000_CP1_DIAG1_LEN (4U)
+
+/* register CP1_DIAG1 */
+#define DW3000_CP1_DIAG12_ID 0xd0068
+#define DW3000_CP1_DIAG12_LEN (4U)
+
+/* register STS_DIAG_0 */
+#define DW3000_STS_DIAG_0_ID 0xc005c
+#define DW3000_STS_DIAG_0_LEN (4U)
+#define DW3000_STS_DIAG_0_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_1 */
+#define DW3000_STS_DIAG_1_ID 0xc0060
+#define DW3000_STS_DIAG_1_LEN (4U)
+#define DW3000_STS_DIAG_1_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_2 */
+#define DW3000_STS_DIAG_2_ID 0xc0064
+#define DW3000_STS_DIAG_2_LEN (4U)
+#define DW3000_STS_DIAG_2_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_3 */
+#define DW3000_STS_DIAG_3_ID 0xc0068
+#define DW3000_STS_DIAG_3_LEN (4U)
+#define DW3000_STS_DIAG_3_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_4 */
+#define DW3000_STS_DIAG_4_ID 0xd0000
+#define DW3000_STS_DIAG_4_LEN (4U)
+#define DW3000_STS_DIAG_4_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_5 */
+#define DW3000_STS_DIAG_5_ID 0xd0004
+#define DW3000_STS_DIAG_5_LEN (4U)
+#define DW3000_STS_DIAG_5_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_6 */
+#define DW3000_STS_DIAG_6_ID 0xd0008
+#define DW3000_STS_DIAG_6_LEN (4U)
+#define DW3000_STS_DIAG_6_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_7 */
+#define DW3000_STS_DIAG_7_ID 0xd000c
+#define DW3000_STS_DIAG_7_LEN (4U)
+#define DW3000_STS_DIAG_7_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_8 */
+#define DW3000_STS_DIAG_8_ID 0xd0010
+#define DW3000_STS_DIAG_8_LEN (4U)
+#define DW3000_STS_DIAG_8_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_9 */
+#define DW3000_STS_DIAG_9_ID 0xd0014
+#define DW3000_STS_DIAG_9_LEN (4U)
+#define DW3000_STS_DIAG_9_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_10 */
+#define DW3000_STS_DIAG_10_ID 0xd0018
+#define DW3000_STS_DIAG_10_LEN (4U)
+#define DW3000_STS_DIAG_10_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_11 */
+#define DW3000_STS_DIAG_11_ID 0xd001c
+#define DW3000_STS_DIAG_11_LEN (4U)
+#define DW3000_STS_DIAG_11_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_12 */
+#define DW3000_STS_DIAG_12_ID 0xd0020
+#define DW3000_STS_DIAG_12_LEN (4U)
+#define DW3000_STS_DIAG_12_MASK 0x7FFUL
+
+/* register STS_DIAG_13 */
+#define DW3000_STS_DIAG_13_ID 0xd0024
+#define DW3000_STS_DIAG_13_LEN (4U)
+#define DW3000_STS_DIAG_13_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_14 */
+#define DW3000_STS_DIAG_14_ID 0xd0028
+#define DW3000_STS_DIAG_14_LEN (4U)
+#define DW3000_STS_DIAG_14_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_15 */
+#define DW3000_STS_DIAG_15_ID 0xd002c
+#define DW3000_STS_DIAG_15_LEN (4U)
+#define DW3000_STS_DIAG_15_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_16 */
+#define DW3000_STS_DIAG_16_ID 0xd0030
+#define DW3000_STS_DIAG_16_LEN (4U)
+#define DW3000_STS_DIAG_16_MASK 0xFFFFFFFFUL
+
+/* register STS_DIAG_17 */
+#define DW3000_STS_DIAG_17_ID 0xd0034
+#define DW3000_STS_DIAG_17_LEN (4U)
+#define DW3000_STS_DIAG_17_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_0 */
+#define DW3000_STS1_DIAG_0_ID 0xd0038
+#define DW3000_STS1_DIAG_0_LEN (4U)
+#define DW3000_STS1_DIAG_0_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_1 */
+#define DW3000_STS1_DIAG_1_ID 0xd003c
+#define DW3000_STS1_DIAG_1_LEN (4U)
+#define DW3000_STS1_DIAG_1_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_2 */
+#define DW3000_STS1_DIAG_2_ID 0xd0040
+#define DW3000_STS1_DIAG_2_LEN (4U)
+#define DW3000_STS1_DIAG_2_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_3 */
+#define DW3000_STS1_DIAG_3_ID 0xd0044
+#define DW3000_STS1_DIAG_3_LEN (4U)
+#define DW3000_STS1_DIAG_3_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_4 */
+#define DW3000_STS1_DIAG_4_ID 0xd0048
+#define DW3000_STS1_DIAG_4_LEN (4U)
+#define DW3000_STS1_DIAG_4_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_5 */
+#define DW3000_STS1_DIAG_5_ID 0xd004c
+#define DW3000_STS1_DIAG_5_LEN (4U)
+#define DW3000_STS1_DIAG_5_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_6 */
+#define DW3000_STS1_DIAG_6_ID 0xd0050
+#define DW3000_STS1_DIAG_6_LEN (4U)
+#define DW3000_STS1_DIAG_6_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_7 */
+#define DW3000_STS1_DIAG_7_ID 0xd0054
+#define DW3000_STS1_DIAG_7_LEN (4U)
+#define DW3000_STS1_DIAG_7_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_8 */
+#define DW3000_STS1_DIAG_8_ID 0xd0058
+#define DW3000_STS1_DIAG_8_LEN (4U)
+#define DW3000_STS1_DIAG_8_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_9 */
+#define DW3000_STS1_DIAG_9_ID 0xd005c
+#define DW3000_STS1_DIAG_9_LEN (4U)
+#define DW3000_STS1_DIAG_9_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_10 */
+#define DW3000_STS1_DIAG_10_ID 0xd0060
+#define DW3000_STS1_DIAG_10_LEN (4U)
+#define DW3000_STS1_DIAG_10_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_11 */
+#define DW3000_STS1_DIAG_11_ID 0xd0064
+#define DW3000_STS1_DIAG_11_LEN (4U)
+#define DW3000_STS1_DIAG_11_MASK 0xFFFFFFFFUL
+
+/* register STS1_DIAG_12 */
+#define DW3000_STS1_DIAG_12_ID 0xd0068
+#define DW3000_STS1_DIAG_12_LEN (4U)
+#define DW3000_STS1_DIAG_12_MASK 0x7FFUL
+
+/* register CIA_CONF */
+#define DW3000_CIA_CONF_ID 0xe0000
+#define DW3000_CIA_CONF_LEN (4U)
+#define DW3000_CIA_CONF_MINDIAG_BIT_MASK (0x100000)
+
+/* register RX_ANTENNA_DELAY */
+#define DW3000_RX_ANTENNA_DELAY_ID 0xe0000
+
+/* register CY_CONFIG_LO_ID 0xe0012/0xe0014 */
+#define DW3000_CY_CONFIG_LO_MANUALLOWERBOUND_BIT_MASK 0x7f0000UL
+
+/* register STS_CONFIG_LO (CY_CONFIG_LO_ID renamed in E0) */
+#define DW3000_STS_CONFIG_LO_ID DW3000_CY_CONFIG_LO_ID
+#define DW3000_STS_CONFIG_LO_LEN (4U)
+#define DW3000_STS_CONFIG_LO_MASK 0xFFFFFFFFUL
+#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_OFFSET (16U)
+#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_LEN (7U)
+#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_MASK 0x7f0000UL
+#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_OFFSET (5U)
+#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_LEN (2U)
+#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_MASK 0x60U
+#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_OFFSET (0U)
+#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_LEN (5U)
+#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_MASK 0x1fU
+
+/* register STS_CONFIG_HI (CY_CONFIG_HI_ID renamed in E0) */
+#define DW3000_STS_CONFIG_HI_ID DW3000_CY_CONFIG_HI_ID
+#define DW3000_STS_CONFIG_HI_LEN (4U)
+#define DW3000_STS_CONFIG_HI_MASK 0xFFFFFFFFUL
+#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_OFFSET (31U)
+#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_LEN (1U)
+#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_MASK 0x80000000UL
+#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_OFFSET (30U)
+#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_LEN (1U)
+#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_MASK 0x40000000UL
+#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_OFFSET (29U)
+#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_LEN (1U)
+#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_MASK 0x20000000UL
+#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_OFFSET (28U)
+#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_LEN (1U)
+#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_MASK 0x10000000UL
+#define DW3000_STS_CONFIG_HI_RES_B0_BIT_OFFSET (4U)
+#define DW3000_STS_CONFIG_HI_RES_B0_BIT_LEN (4U)
+#define DW3000_STS_CONFIG_HI_RES_B0_BIT_MASK 0xf0U
+
+/* register TEST_CTRL0 0xf0024/0xf0028 */
+#define DW3000_TEST_CTRL0_LEN (4U)
+#define DW3000_TEST_CTRL0_MASK 0xFFFFFFFFUL
+#define DW3000_TEST_CTRL0_FIXED_STS_BIT_OFFSET (29U)
+#define DW3000_TEST_CTRL0_FIXED_STS_BIT_LEN (1U)
+#define DW3000_TEST_CTRL0_FIXED_STS_BIT_MASK 0x20000000UL
+#define DW3000_TEST_CTRL0_CIA_RUN_BIT_OFFSET (26U)
+#define DW3000_TEST_CTRL0_CIA_RUN_BIT_LEN (1U)
+#define DW3000_TEST_CTRL0_CIA_RUN_BIT_MASK 0x4000000UL
+#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_OFFSET (24U)
+#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_LEN (1U)
+#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_MASK 0x1000000UL
+#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_OFFSET (21U)
+#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_LEN (1U)
+#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_MASK 0x200000UL
+#define DW3000_TEST_CTRL0_TX_PSTM_BIT_OFFSET (4U)
+#define DW3000_TEST_CTRL0_TX_PSTM_BIT_LEN (1U)
+#define DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK 0x10U
+
+/* register SYS_STATE_LO */
+#define DW3000_SYS_STATE_LO_ID 0xf0030
+#define DW3000_SYS_STATE_LO_LEN (4U)
+#define DW3000_SYS_STATE_LO_MASK 0xFFFFFFFFUL
+
+/* register SOFT_RST */
+#define DW3000_SOFT_RST_ID 0x110000
+#define DW3000_SOFT_RST_LEN (4U)
+#define DW3000_SOFT_RST_MASK 0xFFFFFFFFUL
+#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_OFFSET (11U)
+#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_LEN (1U)
+#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_MASK 0x800U
+#define DW3000_SOFT_RST_TIM_RST_N_BIT_OFFSET (10U)
+#define DW3000_SOFT_RST_TIM_RST_N_BIT_LEN (1U)
+#define DW3000_SOFT_RST_TIM_RST_N_BIT_MASK 0x400U
+
+/* register CLK_CTRL */
+#define DW3000_CLK_CTRL_ID 0x110004
+#define DW3000_CLK_CTRL_LEN (4U)
+#define DW3000_CLK_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_OFFSET (26U)
+#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_MASK 0x4000000UL
+#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_OFFSET (25U)
+#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_MASK 0x2000000UL
+#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_OFFSET (24U)
+#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_MASK 0x1000000UL
+#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_OFFSET (23U)
+#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_MASK 0x800000UL
+#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_OFFSET (21U)
+#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_MASK 0x200000UL
+#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_OFFSET (20U)
+#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_MASK 0x100000UL
+#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_OFFSET (19U)
+#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_MASK 0x80000UL
+#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_OFFSET (18U)
+#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_MASK 0x40000UL
+#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_OFFSET (16U)
+#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK 0x10000UL
+#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_OFFSET (15U)
+#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_MASK 0x8000U
+#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_OFFSET (14U)
+#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_MASK 0x4000U
+#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_OFFSET (13U)
+#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_MASK 0x2000U
+#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_OFFSET (12U)
+#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_MASK 0x1000U
+#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_OFFSET (11U)
+#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_MASK 0x800U
+#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_OFFSET (9U)
+#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_MASK 0x200U
+#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_OFFSET (8U)
+#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_MASK 0x100U
+#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_OFFSET (7U)
+#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_MASK 0x80U
+#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_OFFSET (6U)
+#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_LEN (1U)
+#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_MASK 0x40U
+#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_OFFSET (4U)
+#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_LEN (2U)
+#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_MASK 0x30U
+#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_OFFSET (2U)
+#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_LEN (2U)
+#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_MASK 0xcU
+#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_OFFSET (0U)
+#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_LEN (2U)
+#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_MASK 0x3U
+
+/* register SEQ_CTRL */
+#define DW3000_SEQ_CTRL_ID 0x110008
+#define DW3000_SEQ_CTRL_LEN (4U)
+#define DW3000_SEQ_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_OFFSET (26U)
+#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_LEN (6U)
+#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_MASK 0xfc000000UL
+#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_OFFSET (25U)
+#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_MASK 0x2000000UL
+#define DW3000_SEQ_CTRL_FORCE2RC_BIT_OFFSET (24U)
+#define DW3000_SEQ_CTRL_FORCE2RC_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE2RC_BIT_MASK 0x1000000UL
+#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_OFFSET (23U)
+#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK 0x800000UL
+#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_OFFSET (22U)
+#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_MASK 0x400000UL
+#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_OFFSET (21U)
+#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_MASK 0x200000UL
+#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_OFFSET (20U)
+#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_MASK 0x100000UL
+#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_OFFSET (19U)
+#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_MASK 0x80000UL
+#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_OFFSET (18U)
+#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_MASK 0x40000UL
+#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_OFFSET (17U)
+#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_MASK 0x20000UL
+#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_OFFSET (16U)
+#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_MASK 0x10000UL
+#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_OFFSET (15U)
+#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_MASK 0x8000U
+#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_OFFSET (14U)
+#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_MASK 0x4000U
+#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_OFFSET (13U)
+#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_MASK 0x2000U
+#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_OFFSET (12U)
+#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_MASK 0x1000U
+#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_OFFSET (11U)
+#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_MASK 0x800U
+#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_OFFSET (10U)
+#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_MASK 0x400U
+#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_OFFSET (9U)
+#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_MASK 0x200U
+#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_OFFSET (8U)
+#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK 0x100U
+#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_OFFSET (7U)
+#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_MASK 0x80U
+#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_OFFSET (5U)
+#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_MASK 0x20U
+#define DW3000_SEQ_CTRL_RF_CTRL_BIT_OFFSET (3U)
+#define DW3000_SEQ_CTRL_RF_CTRL_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_RF_CTRL_BIT_MASK 0x8U
+#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_OFFSET (2U)
+#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_MASK 0x4U
+#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_OFFSET (1U)
+#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_MASK 0x2U
+#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_OFFSET (0U)
+#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_LEN (1U)
+#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_MASK 0x1U
+
+/* register PWR_UP_TIMES_LO */
+#define DW3000_PWR_UP_TIMES_LO_ID 0x110010
+#define DW3000_PWR_UP_TIMES_LO_LEN (4U)
+#define DW3000_PWR_UP_TIMES_LO_MASK 0xFFFFFFFFUL
+#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_OFFSET (0U)
+#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_LEN (32U)
+#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_MASK 0xFFFFFFFFUL
+
+/* register LED_CTRL 0x110016/0x110018 */
+#define DW3000_LED_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_OFFSET (16U)
+#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_LEN (4U)
+#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK 0xf0000UL
+#define DW3000_LED_CTRL_BLINK_EN_BIT_OFFSET (8U)
+#define DW3000_LED_CTRL_BLINK_EN_BIT_LEN (1U)
+#define DW3000_LED_CTRL_BLINK_EN_BIT_MASK 0x100U
+#define DW3000_LED_CTRL_BLINK_CNT_BIT_OFFSET (0U)
+#define DW3000_LED_CTRL_BLINK_CNT_BIT_LEN (8U)
+#define DW3000_LED_CTRL_BLINK_CNT_BIT_MASK 0xffU
+
+/* register BIAS_CTRL 0x11001f/0x110030 */
+#define DW3000_BIAS_CTRL_DIG_BIAS_DAC_ULV_BIT_MASK 0x1fU
+
+/* Dual SPI Semaphore events register DSS_STAT */
+#define DW3000_DSS_STAT_ID 0x110038
+#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_OFFSET (0U)
+#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_LEN (1U)
+#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK 0x1U
+#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_OFFSET (1U)
+#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_LEN (1U)
+#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK 0x2U
+
+/* register TIMER_CTRL */
+#define DW3000_TIMER_CTRL_ID 0x11003c
+#define DW3000_TIMER_CTRL_LEN (4U)
+#define DW3000_TIMER_CTRL_MASK 0xFFFFFFFFUL
+#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_OFFSET (22U)
+#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_MASK 0x400000UL
+#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_OFFSET (21U)
+#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_MASK 0x200000UL
+#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_OFFSET (20U)
+#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_MASK 0x100000UL
+#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_OFFSET (19U)
+#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_MASK 0x80000UL
+#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_OFFSET (16U)
+#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_LEN (3U)
+#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_MASK 0x70000UL
+#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_OFFSET (14U)
+#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_MASK 0x4000U
+#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_OFFSET (13U)
+#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_MASK 0x2000U
+#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_OFFSET (12U)
+#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_MASK 0x1000U
+#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_OFFSET (11U)
+#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_MASK 0x800U
+#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_OFFSET (8U)
+#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_LEN (3U)
+#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_MASK 0x700U
+#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_OFFSET (3U)
+#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_MASK 0x8U
+#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_OFFSET (2U)
+#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_MASK 0x4U
+#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_OFFSET (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_MASK 0x2U
+#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_OFFSET (0U)
+#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_LEN (1U)
+#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_MASK 0x1U
+
+/* register TIMER0_CNT_SET */
+#define DW3000_TIMER0_CNT_SET_ID 0x110040
+#define DW3000_TIMER0_CNT_SET_LEN (4U)
+#define DW3000_TIMER0_CNT_SET_MASK 0xFFFFFFFFUL
+#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_OFFSET (0U)
+#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_LEN (22U)
+#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_MASK 0x3fffffUL
+
+/* register TIMER1_CNT_SET */
+#define DW3000_TIMER1_CNT_SET_ID 0x110044
+#define DW3000_TIMER1_CNT_SET_LEN (4U)
+#define DW3000_TIMER1_CNT_SET_MASK 0xFFFFFFFFUL
+#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_OFFSET (0U)
+#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_LEN (22U)
+#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_MASK 0x3fffffUL
+
+/* register TIMER_STATUS */
+#define DW3000_TIMER_STATUS_ID 0x110048
+#define DW3000_TIMER_STATUS_LEN (4U)
+#define DW3000_TIMER_STATUS_MASK 0xFFFFFFFFUL
+#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_OFFSET (8U)
+#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_LEN (8U)
+#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_MASK 0xff00U
+#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_OFFSET (0U)
+#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_LEN (8U)
+#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_MASK 0xffU
+
+/* DW3000 CIR memory */
+#define DW3000_CIR_RAM_ID 0x150000
+/* 2048 records of 6 byte each */
+#define DW3000_CIR_RAM_LEN (2048 * 6)
+
+/* DW3000 scratch ram */
+#define DW3000_SCRATCH_RAM_ID 0x160000
+#define DW3000_SCRATCH_RAM_LEN 127
+
+/* Double buffer diagnostic register set */
+#define DW3000_DB_DIAG_SET_1 0x180000
+#define DW3000_DB_DIAG_SET_2 0x1800e8
+#define DW3000_DB_DIAG_SET_LEN 0xe8
+/* Double buffer diagnostic register offsets */
+/* RDB_DMODE = 1 || 2 || 4 */
+#define DW3000_DB_DIAG_RX_FINFO 0x0
+#define DW3000_DB_DIAG_RX_TIME 0x4
+#define DW3000_DB_DIAG_CIA_DIAG0 0xc
+#define DW3000_DB_DIAG_TDOA 0x10
+#define DW3000_DB_DIAG_PDOA 0x14
+#define DW3000_DB_DIAG_Reserved1 0x18
+#define DW3000_DB_DIAG_IP_DIAG_12 0x1c
+/* RDB_DMODE = 2 || 4 */
+#define DW3000_DB_DIAG_IP_TS 0x20
+#define DW3000_DB_DIAG_Reserved2 0x24
+#define DW3000_DB_DIAG_STS_TS 0x28
+#define DW3000_DB_DIAG_Reserved3 0x2c
+#define DW3000_DB_DIAG_STS1_TS 0x30
+#define DW3000_DB_DIAG_Reserved4 0x34
+/* RDB_DMODE = 4 */
+#define DW3000_DB_DIAG_CIA_DIAG1 0x38
+#define DW3000_DB_DIAG_IP_DIAG0 0x3c
+#define DW3000_DB_DIAG_IP_DIAG1 0x40
+#define DW3000_DB_DIAG_IP_DIAG2 0x44
+#define DW3000_DB_DIAG_IP_DIAG3 0x48
+#define DW3000_DB_DIAG_IP_DIAG4 0x4c
+#define DW3000_DB_DIAG_IP_DIAG8 0x5c
+#define DW3000_DB_DIAG_STS_DIAG0 0x6c
+#define DW3000_DB_DIAG_STS_DIAG1 0x70
+#define DW3000_DB_DIAG_STS_DIAG2 0x74
+#define DW3000_DB_DIAG_STS_DIAG3 0x78
+#define DW3000_DB_DIAG_STS_DIAG4 0x7c
+#define DW3000_DB_DIAG_STS_DIAG8 0x8c
+#define DW3000_DB_DIAG_STS_DIAG12 0x9c
+#define DW3000_DB_DIAG_STS1_DIAG0 0xb4
+#define DW3000_DB_DIAG_STS1_DIAG1 0xb8
+#define DW3000_DB_DIAG_STS1_DIAG2 0xbc
+#define DW3000_DB_DIAG_STS1_DIAG3 0xc0
+#define DW3000_DB_DIAG_STS1_DIAG4 0xc4
+#define DW3000_DB_DIAG_STS1_DIAG8 0xd4
+#define DW3000_DB_DIAG_STS1_DIAG12 0xe4
+
+/* Dual SPI Semaphore register SPI_SEM */
+#define DW3000_SPI_SEM_ID 0x1a0000
+#define DW3000_SPI_SEM_SPI1_RG_BIT_OFFSET (0U)
+#define DW3000_SPI_SEM_SPI1_RG_BIT_LEN (1U)
+#define DW3000_SPI_SEM_SPI1_RG_BIT_MASK 0x1U
+#define DW3000_SPI_SEM_SPI2_RG_BIT_OFFSET (1U)
+#define DW3000_SPI_SEM_SPI2_RG_BIT_LEN (1U)
+#define DW3000_SPI_SEM_SPI2_RG_BIT_MASK 0x2U
+#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_OFFSET (9U)
+#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_LEN (1U)
+#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK 0x200U
+#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_OFFSET (10U)
+#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_LEN (1U)
+#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK 0x400U
+
+/* Receive Data Buffer (in double buffer set) */
+#define DW3000_RX_BUFFER_A_ID 0x120000
+
+/* Receive Data Buffer (in double buffer set) */
+#define DW3000_RX_BUFFER_B_ID 0x130000
+
+/* Transmit Data Buffer */
+#define DW3000_TX_BUFFER_ID 0x140000
+
+/* pointer to access indirect access buffer A */
+#define DW3000_INDIRECT_POINTER_A_ID 0x1D0000
+
+/* pointer to access indirect access buffer B */
+#define DW3000_INDIRECT_POINTER_B_ID 0x1E0000
+
+/* register INDIRECT_ADDR_A */
+#define DW3000_INDIRECT_ADDR_A_ID 0x1f0004
+
+/* register ADDR_OFFSET_A */
+#define DW3000_ADDR_OFFSET_A_ID 0x1f0008
+
+/* register INDIRECT_ADDR_B */
+#define DW3000_INDIRECT_ADDR_B_ID 0x1f000c
+
+/* register ADDR_OFFSET_B */
+#define DW3000_ADDR_OFFSET_B_ID 0x1f0010
+
+#endif /* __DW3000_CORE_REG_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_core_tests.c b/kernel/drivers/net/ieee802154/dw3000_core_tests.c
new file mode 100644
index 0000000..ed69b63
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_core_tests.c
@@ -0,0 +1,516 @@
+#include <kunit/test.h>
+
+/* Forward declaration of replacement functions. */
+static u64 kunit_get_boottime_ns(void);
+
+/* Replace ktime_get_boottime_ns calls to kunit_get_boottime_ns calls. */
+#define ktime_get_boottime_ns kunit_get_boottime_ns
+#define dw3000_get_dtu_time kunit_dw3000_get_dtu_time
+#define trace_dw3000_power_stats(dw, state, boot_time_ns, len_or_date) \
+ do { \
+ } while (0)
+
+/* Include tested functions. */
+#include "dw3000_core.h"
+#include "dw3000_power_stats.h"
+
+/* Keep sync with the same tables in dw3000_core.c */
+const struct dw3000_plen_info _plen_info[] = {
+ {
+ .symb = 64,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_64,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 1024,
+ .pac_symb = 32,
+ .dw_reg = DW3000_PLEN_1024,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 4096,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_4096,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 32,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_32,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 128,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_128,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ .symb = 1536,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_1536,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ .symb = 72,
+ .pac_symb = 8,
+ .dw_reg = DW3000_PLEN_72,
+ .dw_pac_reg = DW3000_PAC8,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ .symb = 256,
+ .pac_symb = 16,
+ .dw_reg = DW3000_PLEN_256,
+ .dw_pac_reg = DW3000_PAC16,
+ },
+ {
+ .symb = 2048,
+ .pac_symb = 64,
+ .dw_reg = DW3000_PLEN_2048,
+ .dw_pac_reg = DW3000_PAC32,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ /* Invalid */
+ .symb = 0,
+ .pac_symb = 0,
+ .dw_reg = 0,
+ .dw_pac_reg = 0,
+ },
+ {
+ .symb = 512,
+ .pac_symb = 16,
+ .dw_reg = DW3000_PLEN_512,
+ .dw_pac_reg = DW3000_PAC16,
+ },
+};
+
+/* Chip per symbol for 850kbps (512) and 6.8Mbps (64) */
+const int _chip_per_symbol_info[2] = { 512, 64 };
+
+const struct dw3000_prf_info _prf_info[] = {
+ {
+ /* Invalid PRF */
+ 0,
+ },
+ {
+ /* 16 MHz */
+ .chip_per_symb = 496,
+ },
+ {
+ /* 64 MHz */
+ .chip_per_symb = 508,
+ },
+};
+
+/* Static variable declarations */
+static u64 kunit_boot_time_ns;
+static u32 kunit_dtu_time;
+
+/**
+ * kunit_get_boottime_ns() - ktime_get_boottime_ns replacement for tests
+ *
+ * Returns: boot time value in ns which increment by 1e6 for each call.
+ */
+static u64 kunit_get_boottime_ns(void)
+{
+ kunit_boot_time_ns += 1000000;
+ return kunit_boot_time_ns;
+}
+
+/**
+ * kunit_dw3000_get_dtu_time() - dw3000_get_dtu_time kunit wrapper
+ * @dw: the DW device
+ *
+ * Return: The current simulated DTU time.
+ */
+u32 kunit_dw3000_get_dtu_time(struct dw3000 *dw)
+{
+ return kunit_dtu_time;
+}
+
+/* Define the test cases. */
+
+static void dw3000_ktime_to_dtu_test_basic(struct kunit *test)
+{
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ /* Ensure allocation succeeded. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ /* Tests with time_zero_ns == 0 */
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_ktime_to_dtu(dw, 0));
+ KUNIT_EXPECT_EQ(test, 15u, dw3000_ktime_to_dtu(dw, 1000));
+ KUNIT_EXPECT_EQ(test, 156u, dw3000_ktime_to_dtu(dw, 10000));
+ KUNIT_EXPECT_EQ(test, 312u, dw3000_ktime_to_dtu(dw, 20000));
+ /* Tests with time_zero_ns == 1000000 */
+ dw->time_zero_ns = 1000000;
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_ktime_to_dtu(dw, 1000000));
+ KUNIT_EXPECT_EQ(test, 15u, dw3000_ktime_to_dtu(dw, 1001000));
+ KUNIT_EXPECT_EQ(test, (u32)-15600, dw3000_ktime_to_dtu(dw, 0));
+ KUNIT_EXPECT_EQ(test, (u32)-15584, dw3000_ktime_to_dtu(dw, 1000));
+}
+
+static void dw3000_dtu_to_ktime_test_basic(struct kunit *test)
+{
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ /* Ensure allocation succeeded. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ /* Tests with time_zero_ns == 0 */
+ KUNIT_EXPECT_EQ(test, 0ll, dw3000_dtu_to_ktime(dw, 0));
+ KUNIT_EXPECT_EQ(test, 64102ll, dw3000_dtu_to_ktime(dw, 1000));
+ /* Tests with time_zero_ns == 1000000 */
+ dw->time_zero_ns = 1000000;
+ KUNIT_EXPECT_EQ(test, 1000000ll, dw3000_dtu_to_ktime(dw, 0));
+ KUNIT_EXPECT_EQ(test, 1064102ll, dw3000_dtu_to_ktime(dw, 1000));
+}
+
+static void dw3000_dtu_to_sys_time_test_basic(struct kunit *test)
+{
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ /* Ensure allocation succeeded. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ /* Tests with dtu_sync == 0 & sys_time_sync == 0 */
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_dtu_to_sys_time(dw, 0));
+ KUNIT_EXPECT_EQ(test, 1u << 4, dw3000_dtu_to_sys_time(dw, 1));
+ /* Tests with dtu_sync == 10000 & sys_time_sync == 0 */
+ dw->dtu_sync = 10000;
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_dtu_to_sys_time(dw, 10000));
+ KUNIT_EXPECT_EQ(test, 1u << 4, dw3000_dtu_to_sys_time(dw, 10001));
+ /* Tests with dtu_sync == 0 & sys_time_sync == 0 */
+ dw->sys_time_sync = 1000000;
+ KUNIT_EXPECT_EQ(test, 1000000u, dw3000_dtu_to_sys_time(dw, 10000));
+ KUNIT_EXPECT_EQ(test, 1000016u, dw3000_dtu_to_sys_time(dw, 10001));
+}
+
+static void dw3000_sys_time_to_dtu_test_basic(struct kunit *test)
+{
+ u32 dtu_near = 0;
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ /* Ensure allocation succeeded. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ /* Tests with dtu_sync == 0 & sys_time_sync == 0 */
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_sys_time_to_dtu(dw, 0, dtu_near));
+ KUNIT_EXPECT_EQ(test, 1u, dw3000_sys_time_to_dtu(dw, 1 << 4, dtu_near));
+ /* Tests with dtu_near == 10000 */
+ dtu_near = 10000;
+ KUNIT_EXPECT_EQ(test, 1005u,
+ dw3000_sys_time_to_dtu(dw, 1005 << 4, dtu_near));
+ /* Tests with dtu_sync == 1000000/16 & sys_time_sync == 1000000 */
+ dw->sys_time_sync = 1000000;
+ dw->dtu_sync = 1000000 >> 4;
+ KUNIT_EXPECT_EQ(test, 10000u,
+ dw3000_sys_time_to_dtu(dw, 10000 << 4, dtu_near));
+ /* Tests with real values from traces
+ wakeup: dtu_near: 0x773a2f1, dtu_sync: 0x852a9ff, sys_time_sync: 0x293a6
+ rx_enable: timestamp_dtu=0x085776e8
+ read_rx_timestamp: value 1336512593 -> 0x4fa99051 -> sys_time 0x13ea64
+ get_rx_frame: timestamp_dtu=0x185776e6 timestamp_rctu=0xffffffffc3fee418
+ */
+ dtu_near = 0x773a2f1u;
+ dw->dtu_sync = 0x852a9ffu;
+ dw->sys_time_sync = 0x293a6u;
+ KUNIT_EXPECT_EQ(test, 0x853bf6au,
+ dw3000_sys_time_to_dtu(dw, 0x13ea64u, dtu_near));
+}
+
+static void dw3000_sys_time_rctu_to_dtu_test_basic(struct kunit *test)
+{
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+
+ /* Ensure allocation succeeded. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+
+ /* Tests with dtu_sync == 0, sys_time_sync == 0 and dtu_near == 0.
+ * Set kunit_dtu_time to DW3000_DTU_FREQ to get dtu_near == 0 in
+ * dw3000_sys_time_rctu_to_dtu(). */
+ kunit_dtu_time = DW3000_DTU_FREQ;
+ KUNIT_EXPECT_EQ(test, 0u, dw3000_sys_time_rctu_to_dtu(dw, 0));
+ KUNIT_EXPECT_EQ(test, 1u,
+ dw3000_sys_time_rctu_to_dtu(dw, DW3000_RCTU_PER_DTU));
+
+ /* Tests with dtu_near == 10000.
+ * Set kunit_dtu_time to DW3000_DTU_FREQ + 10000 to get dtu_near == 10000
+ * in dw3000_sys_time_rctu_to_dtu(). */
+ kunit_dtu_time = DW3000_DTU_FREQ + 10000;
+ KUNIT_EXPECT_EQ(
+ test, 1005u,
+ dw3000_sys_time_rctu_to_dtu(dw, 1005 * DW3000_RCTU_PER_DTU));
+
+ /* Tests with dtu_sync == 1000000/16 & sys_time_sync == 1000000 */
+ dw->sys_time_sync = 1000000;
+ dw->dtu_sync = 1000000 >> 4;
+ KUNIT_EXPECT_EQ(
+ test, 10000u,
+ dw3000_sys_time_rctu_to_dtu(dw, 10000 * DW3000_RCTU_PER_DTU));
+
+ /* Tests with real values from traces:
+ * timestamp_rctu: 63852263355
+ * dtu_sync: 13025
+ * sys_time_sync: 5414
+ * dtu_near: 4349
+ * dw3000_sys_time_rctu_to_dtu() return: 15601618
+ *
+ * Set kunit_dtu_time to DW3000_DTU_FREQ + 4349 to get dtu_near == 4349
+ * in dw3000_sys_time_rctu_to_dtu(). */
+ kunit_dtu_time = DW3000_DTU_FREQ + 4349;
+ dw->dtu_sync = 13025;
+ dw->sys_time_sync = 5414;
+ KUNIT_EXPECT_EQ(test, 15601618u,
+ dw3000_sys_time_rctu_to_dtu(dw, 63852263355));
+}
+
+static void power_stats_test_setup(struct dw3000 *dw)
+{
+ struct dw3000_power *pwr = &dw->power;
+
+ kunit_boot_time_ns = 0;
+ pwr->stats[DW3000_PWR_OFF].count = 1;
+ pwr->start_time = ktime_get_boottime_ns();
+ dw->config.txPreambLength = DW3000_PLEN_64;
+ dw->config.txCode = 9;
+ dw->config.sfdType = DW3000_SFD_TYPE_4Z;
+ dw3000_update_timings(dw);
+}
+
+static void dw3000_power_stats_test_basic(struct kunit *test)
+{
+ struct mcps802154_llhw *llhw =
+ kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL);
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ struct dw3000_power *pwr = &dw->power;
+ u64 incr = 1000000;
+ /* Ensure allocation succeeded and good state. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ if (!dw || !llhw)
+ return;
+ dw->llhw = llhw;
+ power_stats_test_setup(dw);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr, pwr->start_time);
+
+ /* Simple changes
+ * - RUN - DEEPSLEEP - RUN - DEEPSLEEP - RUN - OFF - RUN - OFF
+ * - cur_state change to new state immediately
+ * - stats[X].count change immediately
+ * - stats[X].dur change when another new state Y apply
+ * - each call update time by 1e6 increment thank to ktime_get_boottime_ns()
+ * replacement function.
+ */
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr * 1, pwr->stats[DW3000_PWR_OFF].dur);
+ dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_DEEPSLEEP, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_DEEPSLEEP, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr * 2, pwr->stats[DW3000_PWR_OFF].dur);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ KUNIT_EXPECT_EQ(test, incr * 3, pwr->stats[DW3000_PWR_OFF].dur);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ /* Final state checks */
+ KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_OFF].count);
+ KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_DEEPSLEEP].count);
+ KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_RUN].count);
+ KUNIT_EXPECT_EQ(test, incr * 4, pwr->stats[DW3000_PWR_OFF].dur);
+ KUNIT_EXPECT_EQ(test, incr * 2, pwr->stats[DW3000_PWR_DEEPSLEEP].dur);
+ KUNIT_EXPECT_EQ(test, incr * 4, pwr->stats[DW3000_PWR_RUN].dur);
+}
+
+static void dw3000_power_stats_test_tx(struct kunit *test)
+{
+ struct mcps802154_llhw *llhw =
+ kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL);
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ struct dw3000_power *pwr = &dw->power;
+ u64 incr = 1000000;
+ u64 txdur = 0;
+
+ /* Ensure allocation succeeded and good state. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ if (!dw || !llhw)
+ return;
+ dw->llhw = llhw;
+ power_stats_test_setup(dw);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr, pwr->start_time);
+
+ /* TX case
+ * - OFF - RUN - TX - IDLE - TX - IDLE - TX - IDLE - TX - IDLE - OFF
+ * - stats[TX].dur is in DTU, which is a 15.6MHz clock
+ * - since purpose is to test d3000_power_stats() and not
+ * dw3000_frame_duration_dtu(), tx_adjust is summed to test
+ * resulting stats[TX].dur.
+ */
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].count);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].dur);
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count);
+
+ dw3000_power_stats(dw, DW3000_PWR_TX, 0);
+ txdur += pwr->tx_adjust; /* sum calculated frame duration */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_TX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_TX, 16);
+ txdur += pwr->tx_adjust; /* sum calculated frame duration */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_TX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_TX, 32);
+ txdur += pwr->tx_adjust; /* sum calculated frame duration */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_TX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_TX, 127);
+ txdur += pwr->tx_adjust; /* sum calculated frame duration */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_TX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ /* Final state checks */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count);
+ KUNIT_EXPECT_EQ(test, 5ull, pwr->stats[DW3000_PWR_IDLE].count);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].count);
+ KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_OFF].count);
+ KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_OFF].dur);
+ KUNIT_EXPECT_EQ(test, 9 * incr, pwr->stats[DW3000_PWR_RUN].dur);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_IDLE].dur);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].dur);
+}
+
+static void dw3000_power_stats_test_rx(struct kunit *test)
+{
+ struct mcps802154_llhw *llhw =
+ kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL);
+ struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL);
+ struct dw3000_power *pwr = &dw->power;
+ u64 incr = 1000000; /* in ns first */
+ u64 rxdur = 0;
+
+ /* Ensure allocation succeeded and good state. */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw);
+ if (!dw || !llhw)
+ return;
+ dw->llhw = llhw;
+ power_stats_test_setup(dw);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr, pwr->start_time);
+
+ /* RX case
+ * - OFF - RUN - RX - IDLE - RX - IDLE - RX - IDLE - RX - IDLE - OFF
+ * - stats[RX].dur is in DTU, which is a 15.6MHz clock
+ * - we check here stats[RX].dur is well updated according give DTU dates
+ */
+ incr = 15600; /* those checks are in DTU */
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].count);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].dur);
+ dw3000_power_stats(dw, DW3000_PWR_RUN, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count);
+
+ dw3000_power_stats(dw, DW3000_PWR_RX, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, incr, pwr->stats[DW3000_PWR_RX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_RX, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_RX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, 0);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_RX].dur);
+
+ rxdur = pwr->stats[DW3000_PWR_RX].dur;
+ dw3000_power_stats(dw, DW3000_PWR_RX, (int)0x10000);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_RX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, (int)0x20000u);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, rxdur + 0x10000ull,
+ pwr->stats[DW3000_PWR_RX].dur);
+
+ rxdur = pwr->stats[DW3000_PWR_RX].dur;
+ dw3000_power_stats(dw, DW3000_PWR_RX, (int)-128);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_RX].count);
+ dw3000_power_stats(dw, DW3000_PWR_IDLE, (int)128);
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, rxdur + 256ull, pwr->stats[DW3000_PWR_RX].dur);
+
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ dw3000_power_stats(dw, DW3000_PWR_OFF, 0);
+ /* Final state checks */
+ incr = 1000000; /* those checks are in ns */
+ KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state);
+ KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count);
+ KUNIT_EXPECT_EQ(test, 5ull, pwr->stats[DW3000_PWR_IDLE].count);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].count);
+ KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_OFF].count);
+ KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_OFF].dur);
+ KUNIT_EXPECT_EQ(test, 9 * incr, pwr->stats[DW3000_PWR_RUN].dur);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_IDLE].dur);
+ KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].dur);
+}
+
+static struct kunit_case dw3000_core_test_cases[] = {
+ KUNIT_CASE(dw3000_ktime_to_dtu_test_basic),
+ KUNIT_CASE(dw3000_dtu_to_ktime_test_basic),
+ KUNIT_CASE(dw3000_dtu_to_sys_time_test_basic),
+ KUNIT_CASE(dw3000_sys_time_to_dtu_test_basic),
+ KUNIT_CASE(dw3000_sys_time_rctu_to_dtu_test_basic),
+ KUNIT_CASE(dw3000_power_stats_test_basic),
+ KUNIT_CASE(dw3000_power_stats_test_tx),
+ KUNIT_CASE(dw3000_power_stats_test_rx),
+ {}
+};
+
+static struct kunit_suite dw3000_core_test_suite = {
+ .name = "dw3000-core",
+ .test_cases = dw3000_core_test_cases,
+};
+kunit_test_suite(dw3000_core_test_suite);
+
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/net/ieee802154/dw3000_debugfs.c b/kernel/drivers/net/ieee802154/dw3000_debugfs.c
new file mode 100644
index 0000000..33b88c1
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_debugfs.c
@@ -0,0 +1,775 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/string.h>
+
+#include "dw3000.h"
+#include "dw3000_core.h"
+#include "dw3000_debugfs.h"
+#include "dw3000_chip.h"
+#include "dw3000_cir.h"
+
+#define MAX_CHARS_DISPLAY_DEC_UINT32 10
+#define MAX_CHARS_DISPLAY_DEC_INT32 11
+#define MAX_CHARS_DISPLAY_HEX_UINT32 8
+
+#define dw3000_dbgfs_regop_write(sz, _dw, _addr, _mask, data) \
+ (_mask) ? dw3000_reg_modify##sz(_dw, _addr, 0, ~_mask, data) : \
+ dw3000_reg_write##sz(_dw, _addr, 0, data)
+
+/**
+ * dw3000_dbgfs_power() - Chip agnostic function to handle poweron/off
+ * @filp: debugfs file pointer associated to the virtual register
+ * @write: false means dump current power status, true means: modify it
+ * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means
+ * poweron. Other values are rejected
+ * @size: buffer size
+ * @ppos: offset in opened file
+ *
+ * Return: a negative error code or the size written or readed from buffer
+ */
+static int dw3000_dbgfs_power(struct file *filp, bool write, void *buffer,
+ size_t size, loff_t *ppos)
+{
+ struct dw3000_debugfs_file *dbgfs_file =
+ (struct dw3000_debugfs_file *)filp->private_data;
+ struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv;
+ struct dw3000 *dw = crp->dw;
+ char cbuf[3];
+ int rc;
+ u8 on;
+
+ if (*ppos > 0)
+ return 0;
+
+ if (write) {
+ if (kstrtou8_from_user(buffer, size, 0, &on)) {
+ dev_err(dw->dev, "no valid value provided\n");
+ return -EINVAL;
+ }
+
+ if (on == 1) {
+ rc = dw3000_poweron(dw);
+ if (rc)
+ dev_warn(dw->dev, "poweron returns %d\n", rc);
+ } else if (on == 0) {
+ rc = dw3000_poweroff(dw);
+ if (rc)
+ dev_warn(dw->dev, "poweroff returns %d\n", rc);
+ } else {
+ dev_err(dw->dev, "%u value not handled\n", on);
+ return -EINVAL;
+ }
+ if (!rc)
+ rc = size;
+ } else {
+ on = dw->current_operational_state != DW3000_OP_STATE_OFF;
+ rc = scnprintf(cbuf, 3, "%u\n", on);
+ if (copy_to_user(buffer, cbuf, rc)) {
+ dev_err(dw->dev, "impossible to copy data to userland");
+ return -EFAULT;
+ }
+ }
+ *ppos += rc;
+ return rc;
+}
+
+/**
+ * dw3000_dbgfs_cir_data() - Consummer function to dump CIR data to userspace
+ * @filp: debugfs file pointer associated to the virtual register
+ * @write: false means dump current power status, true means: modify it
+ * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means
+ * poweron. Other values are rejected
+ * @size: buffer size
+ * @ppos: offset in opened file
+ *
+ * Return: a negative error code or the size written or readed from buffer
+ */
+
+static int dw3000_dbgfs_cir_data(struct file *filp, bool write, void *buffer,
+ size_t size, loff_t *ppos)
+{
+ struct dw3000_debugfs_file *dbgfs_file = filp->private_data;
+ struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv;
+ struct dw3000 *dw = crp->dw;
+ struct dw3000_cir_data *cir = dw->cir_data;
+ size_t bufsz;
+
+ /* Try to read cir data but no memory have been allocated for */
+ if (unlikely(!cir))
+ return 0;
+
+ /* size is memory footprint of dw->cir_data without header members */
+ bufsz = sizeof(*cir) +
+ sizeof(struct dw3000_cir_record) * (cir->count - 1) -
+ offsetof(struct dw3000_cir_data, count);
+
+ /* Reset before waiting. Allowing memory change detection */
+ dw->cir_data_changed = false;
+
+ /* Wait for producer */
+ if (wait_for_completion_interruptible(&cir->complete)) {
+ return -ERESTARTSYS;
+ }
+
+ /* During wait, cir_data can be reallocated. If there is any change
+ * current caller have to be closed gently */
+ smp_rmb();
+ if (dw->cir_data_changed)
+ return 0;
+
+ /* Wait for release of shared memory */
+ if (mutex_lock_interruptible(&cir->mutex)) {
+ return -EINTR;
+ }
+
+ if (copy_to_user(buffer, &cir->count, bufsz)) {
+ mutex_unlock(&cir->mutex);
+ dev_err(dw->dev, "impossible to copy data to userland");
+ return -EFAULT;
+ }
+
+ mutex_unlock(&cir->mutex);
+ /* Output is 16 bytes aligned */
+ bufsz = ALIGN(bufsz, 16);
+ *ppos += bufsz;
+
+ return bufsz;
+}
+
+/**
+ * dw3000_dbgfs_cir_config() - Runtime modification of record count and filter
+ * @filp: debugfs file pointer associated to the virtual register
+ * @write: false means dump current power status, true means: modify it
+ * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means
+ * poweron. Other values are rejected
+ * @size: buffer size
+ * @ppos: offset in opened file
+ *
+ * Return: a negative error code or the size written or readed from buffer
+ */
+static int dw3000_dbgfs_cir_config(struct file *filp, bool write, void *buffer,
+ size_t size, loff_t *ppos)
+{
+ struct dw3000_debugfs_file *dbgfs_file =
+ (struct dw3000_debugfs_file *)filp->private_data;
+ struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv;
+ struct dw3000 *dw = crp->dw;
+ struct dw3000_cir_data *cir = dw->cir_data;
+ static const char fmt[] = "count %u filter 0x%x offset %d\n";
+ /* fit format string with max values for each (U)32 and sign */
+ char cbuf[sizeof(fmt) +
+ MAX_CHARS_DISPLAY_DEC_UINT32 +
+ MAX_CHARS_DISPLAY_DEC_UINT32 +
+ MAX_CHARS_DISPLAY_HEX_UINT32];
+ unsigned int newcount;
+ int newoff;
+ u32 newfilter;
+ int r = 0;
+
+ if (unlikely(!cir))
+ return 0;
+
+ if (*ppos > 0)
+ return 0;
+
+ if (!write) {
+ r = scnprintf(cbuf, sizeof(cbuf), fmt, cir->count, cir->filter,
+ cir->offset);
+ if (copy_to_user(buffer, cbuf, r)) {
+ dev_err(dw->dev, "impossible to copy data to userland");
+ *ppos += size;
+ return -EFAULT;
+ }
+ *ppos += r;
+ return r;
+ }
+
+ if (size >= sizeof(cbuf))
+ return -EINVAL;
+ if (copy_from_user(cbuf, buffer, size)) {
+ dev_err(dw->dev, "copy failed\n");
+ return -EFAULT;
+ }
+
+ r = sscanf(cbuf, fmt, &newcount, &newfilter, &newoff);
+ if (r != 3) {
+ dev_err(dw->dev, "input format error (%d)\n", r);
+ return -EINVAL;
+ }
+
+ if (cir->count != newcount && newcount >= 1) {
+ r = dw3000_cir_data_alloc_count(dw, newcount);
+ if (r < 0)
+ return -ENOMEM;
+ } else if (newcount < 1) {
+ return -EINVAL;
+ }
+
+ dw->cir_data->filter = newfilter;
+ dw->cir_data->offset = newoff;
+
+ *ppos += size;
+ return size;
+}
+
+static const struct dw3000_chip_register virtual_registers[] = {
+ { "power", 0x0, 0x0, 0x0, DW3000_CHIPREG_PERM, dw3000_dbgfs_power },
+ { "cir_data", 0x0, 0x0, 0x0,
+ DW3000_CHIPREG_RO | DW3000_CHIPREG_OPENONCE, dw3000_dbgfs_cir_data },
+ { "cir_config", 0x0, 0x0, 0x0, DW3000_CHIPREG_PERM,
+ dw3000_dbgfs_cir_config },
+};
+
+/** struct do_reg_xfer_params - parameters for spi register access
+ * @reg: pointer to the register to reach
+ * @operation: set operation to do on reg according to enum dw3000_reg_operation
+ * @value: pointer on the data for write and modify operations
+ */
+struct do_reg_xfer_params {
+ const struct dw3000_chip_register *reg;
+ int operation;
+ void *value;
+};
+
+static int do_dump_xfer(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct dw3000_debugfs_dump_private *priv = in;
+ int rc;
+
+ rc = dw3000_xfer(dw, priv->current_reg->address << 16, 0,
+ priv->current_reg->size, out, DW3000_SPI_RD_BIT);
+
+ return rc;
+}
+
+static int do_reg_write(struct dw3000 *dw,
+ const struct dw3000_chip_register *reg, u64 *value)
+{
+ u8 shift = (reg->mask) ? ffs(reg->mask) - 1 : 0;
+ int rc;
+
+ *value <<= shift;
+
+ switch (reg->size) {
+ case 1:
+ rc = dw3000_dbgfs_regop_write(8, dw, reg->address, reg->mask,
+ *value);
+ break;
+ case 2:
+ rc = dw3000_dbgfs_regop_write(16, dw, reg->address, reg->mask,
+ *value);
+ break;
+ case 4:
+ rc = dw3000_dbgfs_regop_write(32, dw, reg->address, reg->mask,
+ *value);
+ break;
+ default:
+ if (unlikely(reg->mask)) {
+ dev_warn(
+ dw->dev,
+ "mask defined but modify not supported for this size\n");
+ rc = -ENOSYS;
+ break;
+ }
+ rc = dw3000_reg_write_fast(dw, reg->address, 0, reg->size,
+ value, DW3000_SPI_WR_BIT);
+ }
+ return rc;
+}
+
+static int do_reg_read(struct dw3000 *dw,
+ const struct dw3000_chip_register *reg, void *value)
+{
+ u8 shift = (reg->mask) ? ffs(reg->mask) - 1 : 0;
+ int rc;
+
+ rc = dw3000_reg_read_fast(dw, reg->address, 0, reg->size, value);
+
+ if (reg->mask && !rc) {
+ *(u32 *)value &= reg->mask;
+ *(u32 *)value >>= shift;
+ }
+
+ return rc;
+}
+
+static int do_reg_xfer(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_reg_xfer_params *xfer = in;
+ int rc = 0;
+
+ if (xfer->operation == DW_REG_READ)
+ rc = do_reg_read(dw, xfer->reg, out);
+ else
+ rc = do_reg_write(dw, xfer->reg, (u64 *)xfer->value);
+
+ return rc;
+}
+
+static int dw3000_file_release(struct inode *inode, struct file *file)
+{
+ if (file->private_data) {
+ kfree(file->private_data);
+ file->private_data = NULL;
+ }
+ return 0;
+}
+
+static int dw3000_dump_open(struct inode *inode, struct file *file)
+{
+ struct dw3000_debugfs_dump_private *dev_priv;
+ struct dw3000 *dw;
+ size_t sz = sizeof(struct dw3000_debugfs_dump_private);
+
+ if (unlikely(!inode->i_private))
+ return -ENXIO;
+
+ file->private_data = kzalloc(sz, GFP_KERNEL);
+ if (!file->private_data) {
+ dev_priv =
+ (struct dw3000_debugfs_dump_private *)inode->i_private;
+ dw = dev_priv->dbgfile.chip_reg_priv.dw;
+ dev_err(dw->dev, "Allocation failed. Cannot open file.\n");
+ return -ENOMEM;
+ }
+ memcpy(file->private_data, inode->i_private, sz);
+ return 0;
+}
+
+static ssize_t format_reg_output(struct dw3000 *dw, void *binbuf,
+ ssize_t reg_sz, char __user *userbuf)
+{
+ char cbuf[36]; /* 0x + 16 bytes + \n \0 */
+ ssize_t r = 0;
+
+ if (reg_sz > 8) {
+ if (copy_to_user(userbuf, binbuf, reg_sz)) {
+ dev_err(dw->dev, "copy failed\n");
+ return -EFAULT;
+ }
+ return reg_sz;
+ } else {
+ r = scnprintf(cbuf, 36, "0x%llx\n", *(u64 *)binbuf);
+ if (copy_to_user(userbuf, cbuf, r)) {
+ dev_err(dw->dev, "impossible to copy data to userland");
+ return -EFAULT;
+ }
+ }
+ return r;
+}
+
+static ssize_t format_dump_output(struct dw3000 *dw, void *buffer,
+ char *reg_name, ssize_t buffer_sz,
+ char __user *userbuf)
+{
+ char *cbuf;
+ char *cbufpos;
+ size_t cbuflen;
+ char *buffer_idx;
+ ssize_t line_len, remain;
+ int r = 0;
+ size_t bufpos = 0;
+
+ /* Buffer for all registers of the fileid
+ * Size is computed for a byte display, this is more than
+ * required for word (4bytes) thus we keep this formula to be
+ * able to securely print data whatever the format selected */
+ cbuflen = buffer_sz * 3 + 14 + 1; /* N regs max ; reg name */
+ cbuf = (char *)kzalloc(cbuflen * sizeof(char), GFP_KERNEL);
+ if (!cbuf) {
+ dev_err(dw->dev, "dbgfs: impossible to alloc buffers mem\n");
+ return -ENOMEM;
+ }
+
+ /* Output generation */
+ buffer_idx = buffer;
+ bufpos += scnprintf(cbuf, cbuflen, "%.12s:\n", reg_name);
+
+ remain = cbuflen - bufpos;
+ cbufpos = cbuf + bufpos;
+
+ while (buffer_sz) {
+ line_len = hex_dump_to_buffer(buffer_idx, buffer_sz, 16, 4,
+ cbufpos, remain, false);
+ if (line_len >= remain) {
+ dev_err(dw->dev, "buffer too small");
+ break;
+ }
+ cbufpos[line_len++] = '\n';
+ cbufpos += line_len;
+ remain -= line_len;
+ buffer_idx += 16;
+ buffer_sz -= min(16, (int)buffer_sz);
+ buffer_sz = (buffer_sz + 3) & ~3;
+ };
+
+ r = cbufpos - cbuf;
+ if (copy_to_user(userbuf, cbuf, r)) {
+ dev_err(dw->dev, "impossible to copy data to userland");
+ r = -EFAULT;
+ goto format_dump_output_err;
+ }
+
+format_dump_output_err:
+ if (cbuf)
+ kfree(cbuf);
+ return r;
+}
+
+static ssize_t dw3000_dump_read(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct dw3000_debugfs_dump_private *priv = filp->private_data;
+ struct dw3000_stm_command cmd = { do_dump_xfer, priv, NULL };
+ struct dw3000_chip_register_priv *crp = &priv->dbgfile.chip_reg_priv;
+ struct dw3000 *dw;
+ void *buffer;
+ int r = 0;
+
+ dw = priv->dbgfile.chip_reg_priv.dw;
+
+ /* A runlevel under DW3000_OP_STATE_IDLE_RC doesn't allow SPI xfer */
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) {
+ dev_err(dw->dev,
+ "unable to dump registers: device not ready\n");
+ return -EIO;
+ }
+
+ /* Reset feature */
+ if (*ppos == 0)
+ priv->current_reg = crp->reg;
+
+ /* If table's end reached reset current reg */
+ if ((priv->current_reg == (crp->reg + crp->count)) ||
+ !(priv->current_reg->flags & DW3000_CHIPREG_DUMP))
+ return 0;
+
+ /* Buffer for all registers of the fileid */
+ buffer = kzalloc(priv->current_reg->size, GFP_KERNEL);
+ if (!buffer) {
+ dev_err(dw->dev, "dbgfs: impossible to alloc buffers mem\n");
+ r = -ENOMEM;
+ goto dw3000_dump_read_err;
+ }
+
+ /* Retrieve all DW's registers content */
+ cmd.out = (void *)buffer;
+ r = dw3000_enqueue_generic(dw, &cmd);
+ if (r) {
+ dev_err(dw->dev, "Fail to read registers in fileId %d (%d)\n",
+ priv->current_reg->address, r);
+ goto dw3000_dump_read_err;
+ }
+
+ /* Output generation */
+ r = format_dump_output(dw, buffer, (char *)priv->current_reg->name,
+ priv->current_reg->size, userbuf);
+
+ /* Update current_reg: jump to the next file_id */
+ priv->current_reg++;
+
+ /* Update position for sequential read */
+ *ppos = *ppos + r;
+
+dw3000_dump_read_err:
+ if (buffer)
+ kfree(buffer);
+ return r;
+}
+
+static ssize_t dw3000_dbgfs_reg_op(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos, bool write)
+{
+ struct dw3000_debugfs_file *dbgfs_file =
+ (struct dw3000_debugfs_file *)filp->private_data;
+ struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv;
+ struct do_reg_xfer_params xfer;
+ struct dw3000 *dw = crp->dw;
+ unsigned long long binbuf[DW3000_REG_MAX_SZ / sizeof(long long)];
+ struct dw3000_stm_command cmd = { do_reg_xfer, &xfer, binbuf };
+ const struct dw3000_chip_register *reg = crp->reg;
+ ssize_t outsize, buflen;
+ ssize_t r = 0;
+ xfer.operation = DW_REG_READ;
+ memset(binbuf, 0, DW3000_REG_MAX_SZ);
+
+ /* A runlevel under DW3000_OP_STATE_IDLE_RC doesn't allow SPI xfer
+ * except if permanent access is specially granted to this register */
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC &&
+ !(reg->flags & DW3000_CHIPREG_PERM)) {
+ dev_err(dw->dev,
+ "unable to reach registers: device not ready\n");
+ return -EIO;
+ }
+
+ if (reg->callback)
+ return reg->callback(filp, write, userbuf, count, ppos);
+
+ if (*ppos > 0)
+ return 0;
+
+ if (write) {
+ /* Check write-protection of the register */
+ if ((reg->flags & DW3000_CHIPREG_WP) && dw3000_is_active(dw)) {
+ dev_err(dw->dev,
+ "register %s is protected. Write refused "
+ "because device is active\n",
+ reg->name);
+ r = -EACCES;
+ goto dw3000_reg_op_err;
+ }
+
+ /* Get value from userspace */
+ if (reg->size <= 8) {
+ if (kstrtoull_from_user(userbuf, count, 0, binbuf)) {
+ r = -EINVAL;
+ dev_err(dw->dev, "%s is not valid value\n",
+ userbuf);
+ goto dw3000_reg_op_err;
+ }
+ } else {
+ /* input is binary stream. truncated to reg.size or
+ * filled with 0 if provided is shorter */
+ buflen = min(count, sizeof(binbuf));
+ if (copy_from_user(binbuf, userbuf, buflen)) {
+ r = -EFAULT;
+ dev_err(dw->dev, "copy failed\n");
+ goto dw3000_reg_op_err;
+ }
+ }
+ xfer.operation = DW_REG_WRITE;
+ xfer.value = binbuf;
+ }
+
+ /* do spi operation */
+ xfer.reg = reg;
+ r = dw3000_enqueue_generic(dw, &cmd);
+ if (r) {
+ dev_err(dw->dev, "fail to access register %s (%ld)\n",
+ reg->name, r);
+ goto dw3000_reg_op_err;
+ }
+
+ if (!write) {
+ if (reg->mask) {
+ /* size of mask, 0 inside it included */
+ outsize = fls(reg->mask >> (ffs(reg->mask) - 1)) - 1;
+ outsize = (outsize >> 3) + 1;
+ } else
+ outsize = reg->size;
+
+ r = format_reg_output(dw, binbuf, outsize, userbuf);
+
+ if (r < 0)
+ goto dw3000_reg_op_err;
+ else
+ count = r;
+ }
+ *ppos += count;
+ r = count;
+
+dw3000_reg_op_err:
+ return r;
+}
+
+static int dw3000_dbgfs_reg_release(struct inode *inode, struct file *file)
+{
+ struct dw3000_debugfs_file *dbgfs_file = file->private_data;
+
+ atomic_dec(&dbgfs_file->fileopened);
+
+ return 0;
+}
+
+static int dw3000_dbgfs_reg_open(struct inode *inode, struct file *file)
+{
+ struct dw3000_debugfs_file *dbgfs_file =
+ (struct dw3000_debugfs_file *)inode->i_private;
+ struct dw3000_chip_register_priv *crp;
+
+ if (unlikely(!dbgfs_file))
+ return -ENXIO;
+
+ file->private_data = dbgfs_file;
+ crp = &dbgfs_file->chip_reg_priv;
+
+ if (crp->reg->flags & DW3000_CHIPREG_OPENONCE) {
+ if (!atomic_add_unless(&dbgfs_file->fileopened, 1, 1))
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static ssize_t dw3000_dbgfs_reg_read(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return dw3000_dbgfs_reg_op(filp, userbuf, count, ppos, false);
+}
+
+static ssize_t dw3000_dbgfs_reg_write(struct file *filp,
+ const char __user *userbuf, size_t count,
+ loff_t *ppos)
+{
+ return dw3000_dbgfs_reg_op(filp, (char *)userbuf, count, ppos, true);
+}
+
+static struct file_operations dw3000_dump_fops = {
+ .read = dw3000_dump_read,
+ .open = dw3000_dump_open,
+ .release = dw3000_file_release,
+};
+
+static struct file_operations dw3000_reg_fops = {
+ .read = dw3000_dbgfs_reg_read,
+ .write = dw3000_dbgfs_reg_write,
+ .open = dw3000_dbgfs_reg_open,
+ .release = dw3000_dbgfs_reg_release,
+};
+
+static int dw3000_debugsfs_regs_init(struct dw3000 *dw,
+ const struct dw3000_chip_register *regs,
+ size_t reg_count)
+{
+ struct dw3000_debugfs_file *cur;
+ int i;
+
+ for (i = 0; i < reg_count; i++) {
+ if (regs[i].flags & DW3000_CHIPREG_DUMP)
+ continue;
+
+ cur = kzalloc(sizeof(struct dw3000_debugfs_file), GFP_KERNEL);
+ if (!cur) {
+ dw3000_debugfs_remove(dw);
+ return -ENOMEM;
+ }
+
+ cur->chip_reg_priv.reg = &regs[i];
+ cur->chip_reg_priv.dw = dw;
+ INIT_LIST_HEAD(&cur->ll);
+ list_add_tail(&cur->ll, &dw->debugfs.dbgfile_list);
+
+ cur->file = debugfs_create_file(
+ regs[i].name,
+ S_IRUGO |
+ ((regs[i].flags & DW3000_CHIPREG_RO) ? 0 :
+ S_IWUGO),
+ dw->debugfs.parent_dir, &cur->chip_reg_priv,
+ &dw3000_reg_fops);
+ }
+
+ return 0;
+}
+
+/**
+ * dw3000_debugsfs_init() - Debugfs interface for DW's registers
+ * @dw: The DW device.
+ *
+ * Debugfs exposition of DW's registers
+ *
+ * Return: 0 if success, -EINVAL if dw have no registers configured and
+ * ENODEV if files are impossible to create (resulting of a debugfs
+ * missing support)
+ */
+int dw3000_debugsfs_init(struct dw3000 *dw)
+{
+ struct dw3000_debugfs_dump_private *dump;
+ size_t reg_count;
+ const struct dw3000_chip_register *regs =
+ dw->chip_ops->get_registers(dw, &reg_count);
+ int rc;
+
+ if (!regs)
+ return -EINVAL;
+ if (!reg_count) {
+ dev_dbg(dw->dev, "empty registers list returned. init abort\n");
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&dw->debugfs.dbgfile_list);
+ dw->debugfs.parent_dir = debugfs_create_dir("dw3000", NULL);
+ if (IS_ERR(dw->debugfs.parent_dir)) {
+ dw->debugfs.parent_dir = NULL;
+ dev_err(dw->dev, "unable to create parent dir %ld\n",
+ PTR_ERR(dw->debugfs.parent_dir));
+ return -EINVAL;
+ }
+ if (!dw->debugfs.parent_dir) { /* err even if debugfs activated */
+ dev_err(dw->dev, "debugfs directory creation failed\n");
+ return -EINVAL;
+ }
+
+ /* Creation of "registers" file to dump whole file ids */
+ dump = kzalloc(sizeof(struct dw3000_debugfs_dump_private), GFP_KERNEL);
+ if (!dump) {
+ dw3000_debugfs_remove(dw);
+ return -ENOMEM;
+ }
+
+ dump->dbgfile.file = debugfs_create_file("registers", S_IRUGO,
+ dw->debugfs.parent_dir, dump,
+ &dw3000_dump_fops);
+
+ dump->dbgfile.chip_reg_priv.count = reg_count;
+ dump->dbgfile.chip_reg_priv.reg = regs;
+ dump->dbgfile.chip_reg_priv.dw = dw;
+ INIT_LIST_HEAD(&dump->dbgfile.ll);
+ list_add_tail(&dump->dbgfile.ll, &dw->debugfs.dbgfile_list);
+
+ /* Creation of a file for each non-dumpable register */
+ rc = dw3000_debugsfs_regs_init(dw, regs, reg_count);
+ if (rc) {
+ dev_err(dw->dev, "error when init of chip's registers\n");
+ return rc;
+ }
+ rc = dw3000_debugsfs_regs_init(dw, virtual_registers,
+ ARRAY_SIZE(virtual_registers));
+ if (rc) {
+ dev_err(dw->dev, "error when init of virtual registers\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * dw3000_debugfs_remove() - Remove all files and directory created by _init
+ * @dw: The DW Device
+ *
+ * Return: nothing
+ */
+void dw3000_debugfs_remove(struct dw3000 *dw)
+{
+ while (!list_empty(&dw->debugfs.dbgfile_list)) {
+ struct dw3000_debugfs_file *cur =
+ list_first_entry(&dw->debugfs.dbgfile_list,
+ struct dw3000_debugfs_file, ll);
+ debugfs_remove(cur->file);
+ list_del(&cur->ll);
+ kfree(cur);
+ }
+
+ debugfs_remove_recursive(dw->debugfs.parent_dir);
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_debugfs.h b/kernel/drivers/net/ieee802154/dw3000_debugfs.h
new file mode 100644
index 0000000..4f440fb
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_debugfs.h
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_DEBUGFS_H
+#define __DW3000_DEBUGFS_H
+
+#define DW3000_REGCONTENT_MAX_SZ (12) /* 4 bytes + 0x + \n + \0 */
+#define DW3000_REG_MAX_SZ (16)
+
+/**
+ * enum dw3000_reg_operation - operation done on a register
+ * @DW_REG_READ: read whole register. If mask is set the output is shrinked
+ * @DW_REG_WRITE: write whole register. include modify operation if mask!=0
+ */
+enum dw3000_reg_operation {
+ DW_REG_READ = 0,
+ DW_REG_WRITE,
+};
+
+/** struct dw3000_debugfs - debugfs informations in device struct dw
+ * @parent_dir: dw parent directory in debugfs
+ * @dbgfile_list: linked list of each files in debugfs
+ */
+struct dw3000_debugfs {
+ struct dentry *parent_dir;
+ struct list_head dbgfile_list;
+};
+
+/** struct dw3000_debugfs_file - debugfs file related structure
+ * @chip_reg_priv: register
+ * @file: filesystem representation
+ * @fileopened: different from 0 if file already opened
+ * @ll: linked list for ressources release
+ */
+struct dw3000_debugfs_file {
+ struct dw3000_chip_register_priv chip_reg_priv;
+ struct dentry *file;
+ atomic_t fileopened;
+ struct list_head ll;
+};
+
+/**
+ * struct dw3000_debugfs_dump_private - private data associated to dbgfs file
+ * @dbgfile: structure associated to the dbgfs file
+ * @current_reg: current fileid reg to dump through 'registers' file
+ */
+struct dw3000_debugfs_dump_private {
+ struct dw3000_debugfs_file dbgfile;
+ const struct dw3000_chip_register *current_reg;
+};
+
+int dw3000_debugsfs_init(struct dw3000 *dw);
+void dw3000_debugfs_remove(struct dw3000 *dw);
+
+#endif
diff --git a/kernel/drivers/net/ieee802154/dw3000_mcps.c b/kernel/drivers/net/ieee802154/dw3000_mcps.c
new file mode 100644
index 0000000..c9365b2
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_mcps.c
@@ -0,0 +1,1517 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <net/mcps802154.h>
+
+#include "dw3000.h"
+#include "dw3000_pm.h"
+#include "dw3000_core.h"
+#include "dw3000_mcps.h"
+#include "dw3000_testmode.h"
+#include "dw3000_trc.h"
+#include "dw3000_nfcc_coex_mcps.h"
+#include "dw3000_pctt_mcps.h"
+#include "dw3000_coex.h"
+#include "dw3000_cir.h"
+#include "dw3000_power_stats.h"
+
+static int completion_active(struct completion *completion)
+{
+#if (KERNEL_VERSION(5, 7, 0) > LINUX_VERSION_CODE)
+ return waitqueue_active(&completion->wait);
+#else
+ return swait_active(&completion->wait);
+#endif
+}
+
+static inline u64 timestamp_rctu_to_rmarker_rctu(struct dw3000 *dw,
+ u64 timestamp_rctu,
+ u32 rmarker_dtu);
+
+static inline u32
+tx_rmarker_offset(struct dw3000 *dw,
+ const struct mcps802154_channel *channel_params,
+ int ant_set_id)
+{
+ struct dw3000_config *config = &dw->config;
+ const struct dw3000_antenna_calib *ant_calib;
+ const struct dw3000_antenna_calib_prf *ant_calib_prf;
+ int chanidx;
+ int prfidx;
+ s8 ant_idx1, ant_idx2;
+ int chan = channel_params ? channel_params->channel : config->chan;
+ int pcode = channel_params ? channel_params->preamble_code :
+ config->txCode;
+
+ if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX) {
+ dev_err(dw->dev,
+ "antennas set id %d is out of range, max is %d\n",
+ ant_set_id, ANTSET_ID_MAX);
+ return 0;
+ }
+
+ /* Retrieve TX antenna from antenna set */
+ dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2);
+ if (ant_idx1 < 0 && ant_idx2 >= 0)
+ ant_idx1 = ant_idx2; /* use ant_idx2 if ant_idx1 undefined */
+
+ if (ant_idx1 < 0) {
+ /* Specified TX antenna must be valid */
+ dev_err(dw->dev, "Bad antennas set id selected (%d)\n",
+ ant_set_id);
+ return 0;
+ }
+
+ ant_calib = &dw->calib_data.ant[ant_idx1];
+ /* Current configured ant_id. */
+ if (ant_idx1 == config->ant[ant_calib->port])
+ return config->rmarkerOffset;
+
+
+ chanidx = chan == 9 ? DW3000_CALIBRATION_CHANNEL_9 :
+ DW3000_CALIBRATION_CHANNEL_5;
+ prfidx = pcode >= 9 ? DW3000_CALIBRATION_PRF_64MHZ :
+ DW3000_CALIBRATION_PRF_16MHZ;
+
+ ant_calib_prf = &ant_calib->ch[chanidx].prf[prfidx];
+
+ return ant_calib_prf->ant_delay;
+}
+
+static int do_set_hw_addr_filt(struct dw3000 *dw, const void *in, void *out);
+static int do_set_promiscuous_mode(struct dw3000 *dw, const void *in,
+ void *out);
+
+static int do_start(struct dw3000 *dw, const void *in, void *out)
+{
+#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE)
+ struct spi_controller *ctlr = dw->spi->controller;
+#else
+ struct spi_master *ctlr = dw->spi->master;
+#endif
+ const unsigned long changed = (unsigned long)-1;
+ int rc;
+
+ trace_dw3000_mcps_start(dw);
+
+ /* Enforce required CPU latency */
+ dw3000_pm_qos_update_request(dw, dw3000_qos_latency);
+ /* Lock power management of SPI controller */
+ rc = pm_runtime_get_sync(ctlr->dev.parent);
+ if (rc < 0) {
+ pm_runtime_put_noidle(ctlr->dev.parent);
+ dev_err(&ctlr->dev, "Failed to power device: %d\n", rc);
+ }
+ dw->has_lock_pm = !rc;
+ /* Soft reset */
+ rc = dw3000_softreset(dw);
+ if (rc) {
+ dev_err(dw->dev, "device reset failed: %d\n", rc);
+ goto fail;
+ }
+ /* Initialize & configure the device */
+ rc = dw3000_init(dw, true);
+ if (rc) {
+ dev_err(dw->dev, "device init failed: %d\n", rc);
+ goto fail;
+ }
+ /* Apply other configuration not done by dw3000_init() */
+ rc = do_set_hw_addr_filt(dw, &changed, NULL);
+ if (rc)
+ goto fail;
+ rc = do_set_promiscuous_mode(dw, NULL, NULL);
+ if (rc)
+ goto fail;
+ /* Reset ranging clock requirement */
+ dw->need_ranging_clock = false;
+ /* Enable the device */
+ rc = dw3000_enable(dw);
+fail:
+ trace_dw3000_return_int(dw, rc);
+ return rc;
+}
+
+/**
+ * start() - Start the device and configure it
+ * @llhw: Low-level hardware without MCPS.
+ *
+ * This callback starts the device and put it in right operational state.
+ * First, device is power-on from the caller context, then it is configured
+ * using the the hi-priority device thread.
+ *
+ * Return: 0 on success else a negative error
+ */
+static int start(struct mcps802154_llhw *llhw)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_start, NULL, NULL };
+ int rc;
+
+ /* Turn on power */
+ rc = dw3000_poweron(dw);
+ if (rc) {
+ dev_err(dw->dev, "device power on failed: %d\n", rc);
+ return rc;
+ }
+ /* Ensure RESET GPIO for enough time */
+ rc = dw3000_hardreset(dw);
+ if (rc) {
+ dev_err(dw->dev, "hard reset failed: %d\n", rc);
+ return rc;
+ }
+ /* and wait SPI ready IRQ */
+ rc = dw3000_wait_idle_state(dw);
+ if (rc) {
+ dev_err(dw->dev, "wait device power on failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Do soft reset and all initialization from the high-prio thread */
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+static int do_stop(struct dw3000 *dw, const void *in, void *out)
+{
+ int rc;
+
+ trace_dw3000_mcps_stop(dw);
+
+ /* Disable the device */
+ rc = dw3000_disable(dw);
+ if (rc)
+ dev_warn(dw->dev, "device disable failed: %d\n", rc);
+ /* Power-off */
+ rc = dw3000_poweroff(dw);
+ if (rc)
+ dev_err(dw->dev, "device power-off failed: %d\n", rc);
+ /* Unlock power management of SPI controller */
+ if (dw->has_lock_pm) {
+#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE)
+ struct spi_controller *ctlr = dw->spi->controller;
+#else
+ struct spi_master *ctlr = dw->spi->master;
+#endif
+ pm_runtime_put(ctlr->dev.parent);
+ dw->has_lock_pm = false;
+ }
+ /* Reset ranging clock requirement */
+ dw->need_ranging_clock = false;
+ dw3000_reset_rctu_conv_state(dw);
+ /* Reset cached antenna config to ensure GPIO are well reconfigured */
+ dw->config.ant[0] = -1;
+ dw->config.ant[1] = -1;
+ /* Relax CPU latency requirement */
+ dw3000_pm_qos_update_request(dw, PM_QOS_RESUME_LATENCY_NO_CONSTRAINT);
+ trace_dw3000_return_void(dw);
+ return 0;
+}
+
+/**
+ * stop() - Stop the device and power-down it
+ * @llhw: Low-level hardware without MCPS.
+ *
+ * This callback stops the device and power-down it.
+ */
+static void stop(struct mcps802154_llhw *llhw)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_stop, NULL, NULL };
+
+ dw3000_enqueue_generic(dw, &cmd);
+}
+
+struct do_tx_frame_params {
+ struct sk_buff *skb;
+ const struct mcps802154_tx_frame_config *config;
+ int frame_idx;
+};
+
+static int do_tx_frame(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tx_frame_params *params =
+ (const struct do_tx_frame_params *)in;
+
+ return dw3000_do_tx_frame(dw, params->config, params->skb,
+ params->frame_idx);
+}
+
+static int tx_frame(struct mcps802154_llhw *llhw, struct sk_buff *skb,
+ const struct mcps802154_tx_frame_config *config,
+ int frame_idx, int next_delay_dtu)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct do_tx_frame_params params = { .skb = skb,
+ .config = config,
+ .frame_idx = frame_idx };
+ struct dw3000_stm_command cmd = { do_tx_frame, &params, NULL };
+
+ /* Check data : no data if SP3, must have data otherwise */
+ if (((config->flags & MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK) ==
+ MCPS802154_TX_FRAME_CONFIG_SP3) != !skb)
+ return -EINVAL;
+
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+struct do_rx_frame_params {
+ const struct mcps802154_rx_frame_config *config;
+ int frame_idx;
+};
+
+static int do_rx_enable(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_rx_frame_params *params =
+ (const struct do_rx_frame_params *)in;
+
+ return dw3000_do_rx_enable(dw, params->config, params->frame_idx);
+}
+
+static int rx_enable(struct mcps802154_llhw *llhw,
+ const struct mcps802154_rx_frame_config *config,
+ int frame_idx, int next_delay_dtu)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct do_rx_frame_params params = { .config = config,
+ .frame_idx = frame_idx };
+ struct dw3000_stm_command cmd = { do_rx_enable, &params, NULL };
+
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+static int do_rx_disable(struct dw3000 *dw, const void *in, void *out)
+{
+ int ret;
+
+ trace_dw3000_mcps_rx_disable(dw);
+ ret = dw3000_rx_disable(dw);
+ /* Reset ranging clock requirement */
+ dw->need_ranging_clock = false;
+ dw3000_reset_rctu_conv_state(dw);
+ trace_dw3000_return_int(dw, ret);
+ return ret;
+}
+
+static int rx_disable(struct mcps802154_llhw *llhw)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_rx_disable, NULL, NULL };
+ int ret;
+
+ if (dw3000_rx_busy(dw, true))
+ return -EBUSY;
+ ret = dw3000_enqueue_generic(dw, &cmd);
+ WARN_ON_ONCE(dw3000_rx_busy(dw, false));
+ return ret;
+}
+
+/**
+ * get_ranging_pdoa_fom() - compute the figure of merit of the PDoA.
+ * @sts_fom: STS FoM on a received frame.
+ * @cfo: Clock-offset of a received frame.
+ *
+ * If CFO is inside the [-CFO_THRESHOLD;CFO_THRESHOLD] range or if the STS FoM
+ * is less than sts_fom_threshold, PDoA FoM is 1, the worst.
+ *
+ * If the STS FoM is greater or equal than sts_fom_threshold,
+ * sts_fom_threshold to 255 values are mapped to 2 to 255.
+ *
+ * Return: the PDoA FoM value.
+ */
+static u8 get_ranging_pdoa_fom(u8 sts_fom, s16 cfo)
+{
+ /* For a normalized STS FoM in 0 to 255, the STS is not reliable if
+ * the STS FoM is less than 60 percents of its maximum value.
+ */
+ static const int sts_fom_threshold = 153;
+
+ /* sts_fom_threshold .. sts_fom_max values are mapped to pdoa_fom_min .. pdoa_fom_max.
+ * The relation is pdoa_fom = a * sts_fom + b, with
+ * pdoa_fom_min = sts_fom_threshold * a + b
+ * pdoa_fom_max = sts_fom_max * a + b
+ * So:
+ * a = (pdoa_fom_max - pdoa_fom_min) / (sts_fom_max - sts_fom_threshold)
+ * b = pdoa_fom_min - sts_fom_threshold * a
+ */
+ static const int sts_fom_max = 255;
+ static const int pdoa_fom_min = 2;
+ static const int pdoa_fom_max = 255;
+ static const int a_numerator = pdoa_fom_max - pdoa_fom_min;
+ static const int a_denominator = sts_fom_max - sts_fom_threshold;
+ static const int b = pdoa_fom_min - ((sts_fom_threshold * a_numerator) /
+ a_denominator);
+
+ if (DW3000_XTAL_BIAS && (cfo > -DW3000_CFO_THRESHOLD) &&
+ (cfo < DW3000_CFO_THRESHOLD))
+ return 1;
+ if (sts_fom < sts_fom_threshold)
+ return 1;
+ return ((a_numerator * sts_fom) / a_denominator) + b;
+}
+
+static int get_ranging_sts_fom(struct mcps802154_llhw *llhw,
+ struct mcps802154_rx_frame_info *info)
+{
+ int ret;
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_config *config = &dw->config;
+ /* Max sts_acc_qual value depend on STS length */
+ int sts_acc_max = DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength) * 8;
+ s16 sts_acc_qual;
+
+ /* TODO: Reading TOAST disabled. According to hardware team,
+ * this needs more tuning. They suggest to use quality only for
+ * now. See UWB-940 and commit "disable TOAST quality checking
+ * for STS". */
+
+ ret = dw3000_read_sts_quality(dw, &sts_acc_qual);
+ if (ret)
+ return ret;
+ /* DW3000 only support one STS segment. */
+ info->ranging_sts_fom[0] =
+ clamp(1 + sts_acc_qual * 254 / sts_acc_max, 1, 255);
+ /* Set FoM of all other segments to maximum value so that they do not
+ * cause quality check failure. */
+ memset(&info->ranging_sts_fom[1], 0xFF, MCPS802154_STS_N_SEGS_MAX - 1);
+ return ret;
+}
+
+static int rx_get_rssi(struct dw3000 *dw, struct mcps802154_rx_frame_info *info,
+ const enum dw3000_stats_items item)
+{
+ struct dw3000_config *config = &dw->config;
+ int ret = 0;
+
+ if (dw->stats.enabled || info->flags & MCPS802154_RX_FRAME_INFO_RSSI) {
+ struct dw3000_rssi rssi;
+ u8 sts = config->stsMode & DW3000_STS_BASIC_MODES_MASK;
+ ret = dw3000_rx_store_rssi(dw, &rssi, sts);
+ if (ret) {
+ info->flags &= ~MCPS802154_RX_FRAME_INFO_RSSI;
+ return ret;
+ }
+ if (dw->stats.enabled)
+ dw3000_rx_stats_inc(dw, item, &rssi);
+ ret = dw3000_rx_calc_rssi(dw, &rssi, info, sts);
+ }
+ return ret;
+}
+
+static int rx_get_frame(struct mcps802154_llhw *llhw, struct sk_buff **skb,
+ struct mcps802154_rx_frame_info *info)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_rx *rx = &dw->rx;
+ unsigned long flags;
+ u64 timestamp_rctu;
+ u64 pkt_ts = 0;
+ int ret = 0;
+ s16 cfo = S16_MAX;
+ u8 rx_flags;
+
+ trace_dw3000_mcps_rx_get_frame(dw, info->flags);
+
+ /* Sanity check parameters */
+ if (unlikely(!info || !skb)) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Acquire RX lock */
+ spin_lock_irqsave(&rx->lock, flags);
+ /* Check buffer available */
+ if (unlikely(!rx->skb && !(rx->flags & DW3000_RX_FLAG_ND))) {
+ spin_unlock_irqrestore(&rx->lock, flags);
+ ret = -EAGAIN;
+ goto error;
+ }
+ /* Give the last received frame we stored */
+ *skb = rx->skb;
+ rx->skb = NULL;
+ rx_flags = rx->flags;
+ timestamp_rctu = rx->ts_rctu;
+ pkt_ts = timestamp_rctu;
+ /* Release RX lock */
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ if (info->flags & (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) {
+ if (!(rx_flags & DW3000_RX_FLAG_TS))
+ info->flags &= ~(
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU |
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU);
+ else {
+ u32 rmarker_dtu =
+ dw3000_sys_time_rctu_to_dtu(dw, timestamp_rctu);
+ u64 rmarker_rctu = timestamp_rctu_to_rmarker_rctu(
+ dw, timestamp_rctu, rmarker_dtu);
+ info->timestamp_rctu =
+ rmarker_rctu - config->rmarkerOffset;
+ info->timestamp_dtu = rmarker_dtu - llhw->shr_dtu;
+ }
+ }
+ /* In case of auto-ack send. */
+ if (rx_flags & DW3000_RX_FLAG_AACK)
+ info->flags |= MCPS802154_RX_FRAME_INFO_AACK;
+ /* Read CFO and adjust XTAL trim if need */
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA ||
+ dw->data.check_cfo) {
+ ret = dw3000_read_clockoffset(dw, &cfo);
+ if (ret)
+ goto error;
+ }
+ if (DW3000_XTAL_BIAS && dw->data.check_cfo &&
+ (cfo > -DW3000_CFO_THRESHOLD) && (cfo < DW3000_CFO_THRESHOLD)) {
+ /* CFO inside bad interval, adjust it for next round. */
+ if (cfo < 0) {
+ /* Decrease xtal_bias */
+ dw->data.xtal_bias -= DW3000_XTAL_BIAS;
+ } else {
+ /* Increase xtal_bias */
+ dw->data.xtal_bias += DW3000_XTAL_BIAS;
+ }
+ /* Reprogram XTAL_TRIM if needed. */
+ ret = dw3000_prog_xtrim(dw);
+ if (unlikely(ret))
+ goto error;
+ }
+ /* In case of STS */
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU) {
+ u64 sts_ts_rctu;
+
+ ret = dw3000_read_sts_timestamp(dw, &sts_ts_rctu);
+ if (ret)
+ goto error;
+ pkt_ts = sts_ts_rctu;
+ /* DW3000 only support one STS segment. */
+ info->ranging_sts_timestamp_diffs_rctu[0] = 0;
+ info->ranging_sts_timestamp_diffs_rctu[1] =
+ sts_ts_rctu - timestamp_rctu;
+ if ((config->stsMode & DW3000_STS_BASIC_MODES_MASK) ==
+ DW3000_STS_MODE_2) {
+ /* TODO: calc SRMARKER0 */
+ }
+ }
+ info->ranging_sts_fom[0] = 0;
+ if (info->flags & (MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM |
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM)) {
+ ret = get_ranging_sts_fom(llhw, info);
+ if (ret)
+ goto error;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM) {
+ if (info->ranging_sts_fom[0])
+ info->ranging_pdoa_fom = get_ranging_pdoa_fom(
+ info->ranging_sts_fom[0], cfo);
+ else
+ info->ranging_pdoa_fom = 0;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_OFFSET) {
+ if (cfo == S16_MAX) {
+ ret = dw3000_read_clockoffset(dw, &cfo);
+ if (ret)
+ goto error;
+ }
+ info->ranging_offset_rctu = cfo;
+ /* DW3000 provide directly the ratio (as Q26),
+ * so set arbitrarily the ranging interval (denominator) to 1 */
+ info->ranging_tracking_interval_rctu = 1 << 26;
+ }
+
+ /* If dbgfs file is opened & waiting for data, fill structure and wake-up reading process */
+ if (dw->cir_data && completion_active(&dw->cir_data->complete)) {
+ ret = dw3000_read_frame_cir_data(dw, info, pkt_ts);
+ if (ret)
+ goto error;
+ }
+ /* Report statistics and if required process RSSI */
+ ret = rx_get_rssi(dw, info, DW3000_STATS_RX_GOOD);
+ if (ret)
+ goto error;
+
+ /* Keep only implemented. */
+ info->flags &= (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU |
+ MCPS802154_RX_FRAME_INFO_AACK |
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA |
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM |
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM |
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_RANGING_OFFSET |
+ MCPS802154_RX_FRAME_INFO_RSSI);
+ trace_dw3000_return_int_u32(dw, info->flags, *skb ? (*skb)->len : 0);
+ return 0;
+
+error:
+ trace_dw3000_return_int_u32(dw, ret, 0);
+ return ret;
+}
+
+static int rx_get_error_frame(struct mcps802154_llhw *llhw,
+ struct mcps802154_rx_frame_info *info)
+{
+ struct dw3000 *dw = llhw->priv;
+ int ret = 0;
+
+ trace_dw3000_mcps_rx_get_error_frame(dw, info->flags);
+ /* Sanity check */
+ if (unlikely(!info)) {
+ ret = -EINVAL;
+ goto error;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU) {
+ if (dw3000_read_rx_timestamp(dw, &info->timestamp_rctu))
+ info->flags &= ~MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU;
+ }
+ /* Report statistics and if required process RSSI */
+ ret = rx_get_rssi(dw, info, DW3000_STATS_RX_ERROR);
+ if (ret)
+ goto error;
+ /* Keep only implemented. */
+ info->flags &= (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_RSSI);
+
+error:
+ trace_dw3000_return_int_u32(dw, ret, info->flags);
+ return ret;
+}
+
+/**
+ * rx_get_measurement - Update measurement.
+ * @llhw: Low-level device pointer.
+ * @rx_ctx: Custom rx context (can be NULL).
+ * @info: Measurement to update.
+ *
+ * Return: 0 or error.
+ */
+static int rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx,
+ struct mcps802154_rx_measurement_info *info)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ if (info->flags & MCPS802154_RX_MEASUREMENTS_AOAS) {
+ info->aoas[0].pdoa_rad_q11 = dw3000_read_pdoa(dw);
+ info->aoas[0].aoa_rad_q11 =
+ dw3000_pdoa_to_aoa_lut(dw, info->aoas[0].pdoa_rad_q11);
+ info->n_aoas = 1;
+ }
+
+ /* TODO: UWB-4961 Usage of a mcps802154_rx_frame_info is a
+ * workaround used until rx_get_rssi() can be fully removed
+ * from rx_get_frame(). */
+ if (info->flags & MCPS802154_RX_MEASUREMENTS_RSSIS) {
+ struct mcps802154_rx_frame_info frame_info;
+ int ret;
+
+ frame_info.flags = MCPS802154_RX_FRAME_INFO_RSSI;
+ ret = rx_get_rssi(dw, &frame_info, DW3000_STATS_RX_GOOD);
+ if (ret) {
+ info->n_rssis = 0;
+ } else {
+ info->rssis_q1[0] = frame_info.rssi;
+ /* Only one RSSI computed per frame */
+ info->n_rssis = 1;
+ }
+ }
+
+ /* Keep only implemented. */
+ info->flags &= MCPS802154_RX_MEASUREMENTS_AOAS |
+ MCPS802154_RX_MEASUREMENTS_RSSIS;
+
+ return 0;
+}
+
+static int dw3000_handle_idle_timeout(struct dw3000 *dw)
+{
+ /* MCPS feeback must be done outside driver kthread. */
+ schedule_work(&dw->timer_expired_work);
+ return 0;
+}
+
+static int do_idle(struct dw3000 *dw, const void *in, void *out)
+{
+ bool timestamp = !!in;
+ u32 timestamp_dtu = timestamp ? *(const u32 *)in : 0;
+
+ int r = dw3000_idle(dw, timestamp, timestamp_dtu,
+ dw3000_handle_idle_timeout,
+ DW3000_OP_STATE_IDLE_PLL);
+ trace_dw3000_return_int(dw, r);
+ return r;
+}
+
+static int idle(struct mcps802154_llhw *llhw, bool timestamp, u32 timestamp_dtu)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_idle, NULL, NULL };
+
+ if (timestamp)
+ cmd.in = &timestamp_dtu;
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+static int do_reset(struct dw3000 *dw, const void *in, void *out)
+{
+ int rc;
+
+ trace_dw3000_mcps_reset(dw);
+ /* Disable the device before resetting it */
+ rc = dw3000_disable(dw);
+ if (rc) {
+ dev_err(dw->dev, "device disable failed: %d\n", rc);
+ goto fail;
+ }
+ /* Soft reset */
+ rc = dw3000_softreset(dw);
+ if (rc != 0) {
+ dev_err(dw->dev, "device reset failed: %d\n", rc);
+ goto fail;
+ }
+ /* Initialize & configure the device */
+ rc = dw3000_init(dw, true);
+ if (rc != 0) {
+ dev_err(dw->dev, "device reset failed: %d\n", rc);
+ goto fail;
+ }
+ /* Enable the device */
+ rc = dw3000_enable(dw);
+ if (rc) {
+ dev_err(dw->dev, "device enable failed: %d\n", rc);
+ goto fail;
+ }
+fail:
+ dw3000_reset_rctu_conv_state(dw);
+ trace_dw3000_return_int(dw, rc);
+ return rc;
+}
+
+static int reset(struct mcps802154_llhw *llhw)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_reset, NULL, NULL };
+
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+static int get_current_timestamp_dtu(struct mcps802154_llhw *llhw,
+ u32 *timestamp_dtu)
+{
+ struct dw3000 *dw = llhw->priv;
+ int ret = 0;
+
+ trace_dw3000_mcps_get_timestamp(dw);
+ /* Must be called after start() */
+ if (dw3000_is_active(dw)) {
+ u32 margin = 0;
+ *timestamp_dtu = dw3000_get_dtu_time(dw);
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_PLL)
+ margin = US_TO_DTU(DW3000_WAKEUP_LATENCY_US);
+ *timestamp_dtu += margin;
+ } else
+ ret = -EBUSY;
+ trace_dw3000_return_int_u32(dw, ret, *timestamp_dtu);
+ return ret;
+}
+
+static inline u64 timestamp_rctu_to_rmarker_rctu(struct dw3000 *dw,
+ u64 timestamp_rctu,
+ u32 rmarker_dtu)
+{
+ struct dw3000_rctu_conv *rctu = &dw->rctu_conv;
+ static const u64 rctu_mask = (1ll << 40) - 1;
+ u64 rmarker_rctu;
+
+ if (rctu->state == UNALIGNED) {
+ rctu->alignment_rmarker_dtu = rmarker_dtu;
+ rctu->state = ALIGNED;
+ trace_dw3000_rctu_convert_align(dw, rmarker_dtu);
+ }
+ if (rctu->state == ALIGNED) {
+ u32 alignment_rmarker_sys_time =
+ dw3000_dtu_to_sys_time(dw, rctu->alignment_rmarker_dtu);
+ u64 alignment_rmarker_rctu =
+ (u64)alignment_rmarker_sys_time * DW3000_RCTU_PER_SYS;
+ rctu->synced_rmarker_rctu = alignment_rmarker_rctu;
+ rctu->state = ALIGNED_SYNCED;
+ trace_dw3000_rctu_convert_synced(dw, alignment_rmarker_rctu);
+ }
+ rmarker_rctu = (timestamp_rctu - rctu->synced_rmarker_rctu) & rctu_mask;
+ trace_dw3000_rctu_convert_rx(dw, rmarker_dtu, timestamp_rctu,
+ rmarker_rctu);
+ return rmarker_rctu;
+}
+
+static u64 tx_timestamp_dtu_to_rmarker_rctu(
+ struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params, int ant_set_id)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_rctu_conv *rctu = &dw->rctu_conv;
+
+ const u32 shr_dtu = hrp_uwb_params ?
+ compute_shr_dtu_from_conf(hrp_uwb_params) :
+ llhw->shr_dtu;
+ const u32 rmarker_dtu = tx_timestamp_dtu + shr_dtu;
+ const u32 ant_offset =
+ tx_rmarker_offset(dw, channel_params, ant_set_id);
+ u64 rmarker_rctu;
+ s64 rmarker_diff_dtu;
+
+ if (rctu->state == UNALIGNED) {
+ rctu->alignment_rmarker_dtu = rmarker_dtu;
+ rctu->state = ALIGNED;
+ trace_dw3000_rctu_convert_align(dw, rmarker_dtu);
+ }
+ rmarker_diff_dtu = rmarker_dtu - rctu->alignment_rmarker_dtu;
+ rmarker_rctu = rmarker_diff_dtu * DW3000_RCTU_PER_DTU + ant_offset;
+ trace_dw3000_rctu_convert_tx(dw, tx_timestamp_dtu, ant_offset,
+ rmarker_rctu);
+ return rmarker_rctu;
+}
+
+static s64 difference_timestamp_rctu(struct mcps802154_llhw *llhw,
+ u64 timestamp_a_rctu, u64 timestamp_b_rctu)
+{
+ /* RCTU time is an unsigned encoded over 40 bytes. This function
+ calculate the signed difference between two unsigned 40 bytes */
+ static const u64 rctu_rollover = 1ll << 40;
+ static const u64 rctu_mask = rctu_rollover - 1;
+ s64 diff_rctu = (timestamp_a_rctu - timestamp_b_rctu) & rctu_mask;
+
+ if (diff_rctu & (rctu_rollover >> 1))
+ diff_rctu = diff_rctu | ~rctu_mask;
+ return diff_rctu;
+}
+
+static int compute_frame_duration_dtu(struct mcps802154_llhw *llhw,
+ int payload_bytes)
+{
+ struct dw3000 *dw = llhw->priv;
+ return dw3000_frame_duration_dtu(dw, payload_bytes, true);
+}
+
+static int do_set_channel(struct dw3000 *dw, const void *in, void *out)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ unsigned long changed = *(const unsigned long *)in;
+
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) {
+ /* Cannot configure device, save info and ensure it will be
+ configured on wakeup */
+ dss->config_changed |= changed;
+ return 0;
+ }
+ if (changed & DW3000_CHANNEL_CHANGED)
+ /* Reconfigure all channel dependent */
+ return dw3000_configure_chan(dw);
+ else if (changed & DW3000_PCODE_CHANGED)
+ /* Only change CHAN_CTRL with new code */
+ return dw3000_configure_pcode(dw);
+ return 0;
+}
+
+int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel,
+ u8 preamble_code)
+{
+ unsigned long changed = 0;
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_stm_command cmd = { do_set_channel, &changed, NULL };
+ int ret = 0;
+
+ trace_dw3000_mcps_set_channel(dw, page, channel, preamble_code);
+ /* Check parameters early */
+ if (page != 4 || (channel != 5 && channel != 9) ||
+ (dw->restricted_channels & (1 << (channel % 16)))) {
+ ret = -EINVAL;
+ goto trace;
+ }
+ switch (preamble_code) {
+ /* DW3000_PRF_16M */
+ case 3:
+ case 4:
+ /* DW3000_PRF_64M */
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ break;
+ default:
+ ret = -EINVAL;
+ goto trace;
+ }
+ /* Detect configuration change(s) */
+ if (config->chan != channel)
+ changed |= DW3000_CHANNEL_CHANGED;
+ if ((config->txCode != preamble_code) ||
+ (config->rxCode != preamble_code))
+ changed |= DW3000_PCODE_CHANGED;
+ if (!changed)
+ goto trace;
+ /* Update configuration structure */
+ config->chan = channel;
+ config->txCode = preamble_code;
+ config->rxCode = preamble_code;
+ /* Reconfigure the chip with it if needed */
+ ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0;
+trace:
+ trace_dw3000_return_int(dw, ret);
+ return ret;
+}
+
+static int do_set_hrp_uwb_params(struct dw3000 *dw, const void *in, void *out)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ unsigned long changed = *(const unsigned long *)in;
+ int rc;
+
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) {
+ /* Cannot configure device, save info and ensure it will be
+ configured on wakeup */
+ dss->config_changed |= changed;
+ return 0;
+ }
+ if (changed &
+ (DW3000_PREAMBLE_LENGTH_CHANGED | DW3000_DATA_RATE_CHANGED)) {
+ /* reconfigure data rate and preamble size if needed. */
+ rc = dw3000_configure_preamble_length_and_datarate(
+ dw, !(changed & DW3000_PREAMBLE_LENGTH_CHANGED));
+ if (rc)
+ return rc;
+ }
+ if (changed & DW3000_SFD_CHANGED) {
+ /* Only change CHAN_CTRL with new code */
+ rc = dw3000_configure_sfd_type(dw);
+ if (rc)
+ return rc;
+ }
+ if (changed & DW3000_PHR_RATE_CHANGED) {
+ rc = dw3000_configure_phr_rate(dw);
+ if (rc)
+ return rc;
+ }
+
+ if (changed & (DW3000_SFD_CHANGED | DW3000_PREAMBLE_LENGTH_CHANGED))
+ dw3000_update_timings(dw);
+
+ return rc;
+}
+
+static int check_hrp_uwb_params(struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *params)
+{
+ switch (params->prf) {
+ case MCPS802154_PRF_16:
+ case MCPS802154_PRF_64:
+ break;
+ case MCPS802154_PRF_125:
+ case MCPS802154_PRF_250:
+ return -ENOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ switch (params->psr) {
+ case MCPS802154_PSR_32:
+ case MCPS802154_PSR_64:
+ case MCPS802154_PSR_128:
+ case MCPS802154_PSR_256:
+ case MCPS802154_PSR_1024:
+ case MCPS802154_PSR_4096:
+ break;
+ case MCPS802154_PSR_16:
+ case MCPS802154_PSR_24:
+ case MCPS802154_PSR_48:
+ case MCPS802154_PSR_96:
+ return -ENOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ switch (params->sfd_selector) {
+ case MCPS802154_SFD_4A:
+ case MCPS802154_SFD_4Z_8:
+ break;
+ case MCPS802154_SFD_4Z_4:
+ case MCPS802154_SFD_4Z_16:
+ case MCPS802154_SFD_4Z_32:
+ return -ENOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ switch (params->data_rate) {
+ case MCPS802154_DATA_RATE_6M81:
+ case MCPS802154_DATA_RATE_850K:
+ break;
+ case MCPS802154_DATA_RATE_7M80:
+ case MCPS802154_DATA_RATE_27M2:
+ case MCPS802154_DATA_RATE_31M2:
+ return -ENOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int set_hrp_uwb_params(struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *params)
+{
+ unsigned long changed = 0;
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_config *config = &dw->config;
+ struct dw3000_stm_command cmd = { do_set_hrp_uwb_params, &changed,
+ NULL };
+ int ret;
+ int psr, sfd_selector, phr_hi_rate, data_rate;
+
+ /* The prf parameter is not used. This is due to the specificity of
+ * the DW3000 chip where the prf is not programmed explicitly,
+ * but implicitly when the preamble code is configured.
+ * Please see the DW3000 User Manual V0.3 P116.
+ */
+
+ /* Check parameters early */
+ ret = check_hrp_uwb_params(llhw, params);
+ if (ret)
+ return ret;
+
+ switch (params->psr) {
+ case MCPS802154_PSR_32:
+ psr = DW3000_PLEN_32;
+ break;
+ case MCPS802154_PSR_128:
+ psr = DW3000_PLEN_128;
+ break;
+ case MCPS802154_PSR_256:
+ psr = DW3000_PLEN_256;
+ break;
+ case MCPS802154_PSR_1024:
+ psr = DW3000_PLEN_1024;
+ break;
+ case MCPS802154_PSR_4096:
+ psr = DW3000_PLEN_4096;
+ break;
+ default:
+ psr = DW3000_PLEN_64;
+ break;
+ }
+
+ switch (params->sfd_selector) {
+ case MCPS802154_SFD_4A:
+ sfd_selector = DW3000_SFD_TYPE_STD;
+ break;
+ default:
+ sfd_selector = DW3000_SFD_TYPE_4Z;
+ break;
+ }
+
+ switch (params->data_rate) {
+ case MCPS802154_DATA_RATE_850K:
+ data_rate = DW3000_BR_850K;
+ break;
+ default:
+ data_rate = DW3000_BR_6M8;
+ break;
+ }
+
+ phr_hi_rate = params->phr_hi_rate ? DW3000_PHRRATE_DTA :
+ DW3000_PHRRATE_STD;
+
+ /* Detect configuration change(s) */
+ if (config->txPreambLength != psr)
+ changed |= DW3000_PREAMBLE_LENGTH_CHANGED;
+ if (config->sfdType != sfd_selector)
+ changed |= DW3000_SFD_CHANGED;
+ if (config->phrRate != phr_hi_rate)
+ changed |= DW3000_PHR_RATE_CHANGED;
+ if (config->dataRate != data_rate)
+ changed |= DW3000_DATA_RATE_CHANGED;
+ if (!changed)
+ return 0;
+
+ /* Update configuration structure */
+ config->txPreambLength = psr;
+ config->sfdType = sfd_selector;
+ config->phrRate = phr_hi_rate;
+ config->dataRate = data_rate;
+
+ /* Reconfigure the chip with it if needed */
+ ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0;
+ return ret;
+}
+
+static int do_set_sts_params(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct mcps802154_sts_params *params =
+ (const struct mcps802154_sts_params *)in;
+ enum dw3000_sts_lengths len;
+ int rc;
+ trace_dw3000_mcps_set_sts_params(dw, params);
+ /* Set STS segment(s) length */
+ /* ffs(x) return 1 for bit0, 2 for bit1... */
+ len = (enum dw3000_sts_lengths)(ffs(params->seg_len) - 4);
+ rc = dw3000_set_sts_length(dw, len);
+ if (rc)
+ goto fail;
+ /* Send KEY & IV */
+ rc = dw3000_configure_sts_key(dw, params->key);
+ if (rc)
+ goto fail;
+ rc = dw3000_configure_sts_iv(dw, params->v);
+ if (rc)
+ goto fail;
+ rc = dw3000_load_sts_iv(dw);
+fail:
+ trace_dw3000_return_int(dw, rc);
+ return rc;
+}
+
+static int set_sts_params(struct mcps802154_llhw *llhw,
+ const struct mcps802154_sts_params *params)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_set_sts_params, params, NULL };
+
+ /* Must be called after start() */
+ if (!dw3000_is_active(dw))
+ return -EBUSY;
+ /* Check parameters */
+ if (params->n_segs > 1)
+ return -EINVAL;
+ /* TODO: HACK TO REMOVE: store parameters and setup change on wakeup */
+ dw3000_wakeup_and_wait(dw);
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+struct do_set_hw_addr_filt_params {
+ struct ieee802154_hw_addr_filt *filt;
+ unsigned long changed;
+};
+
+static int do_set_hw_addr_filt(struct dw3000 *dw, const void *in, void *out)
+{
+ struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state;
+ unsigned long changed = *(const unsigned long *)in;
+
+ if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) {
+ /* Cannot configure device, save info and ensure it will be
+ configured on wakeup */
+ dss->config_changed |= changed;
+ return 0;
+ }
+
+ return dw3000_configure_hw_addr_filt(dw, changed);
+}
+
+static int set_hw_addr_filt(struct mcps802154_llhw *llhw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_config *config = &dw->config;
+ struct ieee802154_hw_addr_filt *cfilt = &config->hw_addr_filt;
+ struct dw3000_stm_command cmd = { do_set_hw_addr_filt, &changed, NULL };
+ int ret;
+
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED)
+ cfilt->short_addr = filt->short_addr;
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED)
+ cfilt->ieee_addr = filt->ieee_addr;
+ if (changed & IEEE802154_AFILT_PANID_CHANGED)
+ cfilt->pan_id = filt->pan_id;
+ if (changed & IEEE802154_AFILT_PANC_CHANGED)
+ cfilt->pan_coord = filt->pan_coord;
+
+ trace_dw3000_mcps_set_hw_addr_filt(dw, (u8)changed);
+ ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0;
+ trace_dw3000_return_int(dw, ret);
+ return ret;
+}
+
+static int set_txpower(struct mcps802154_llhw *llhw, s32 mbm)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int set_cca_mode(struct mcps802154_llhw *llhw,
+ const struct wpan_phy_cca *cca)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int set_cca_ed_level(struct mcps802154_llhw *llhw, s32 mbm)
+{
+ struct dw3000 *dw = llhw->priv;
+
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int do_set_promiscuous_mode(struct dw3000 *dw, const void *in, void *out)
+{
+ return dw3000_set_promiscuous(dw, dw->config.promisc);
+}
+
+static int set_promiscuous_mode(struct mcps802154_llhw *llhw, bool on)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct dw3000_stm_command cmd = { do_set_promiscuous_mode, NULL, NULL };
+
+ dev_dbg(dw->dev, "%s called, (mode: %sabled)\n", __func__,
+ (on) ? "en" : "dis");
+ dw->config.promisc = on;
+ return dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0;
+}
+
+static int check_calibration_value(struct mcps802154_llhw *llhw,
+ const char *key, void *value)
+{
+ struct dw3000 *dw = llhw->priv;
+ if (!strcmp(key, "restricted_channels")) {
+ /* Prevent every channels from being restricted. */
+ if (!(DW3000_SUPPORTED_CHANNELS & ~*(u16 *)value))
+ return -EINVAL;
+ /* Prevent current channel from being restricted if dw3000 is active. */
+ if (((1 << llhw->hw->phy->current_channel) & *(u16 *)value) &&
+ dw3000_is_active(dw))
+ return -EBUSY;
+ }
+ if ((strlen(key) > 22) && !strcmp(key + 13, ".pdoa_lut")) {
+ int i;
+ dw3000_pdoa_lut_t *lut = value;
+ for (i = 1; i < DW3000_CALIBRATION_PDOA_LUT_MAX; i++)
+ if ((*lut)[i][0] <= (*lut)[i - 1][0])
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int set_calibration(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length)
+{
+ struct dw3000 *dw = llhw->priv;
+ void *param;
+ int len;
+ int r;
+ /* Sanity checks */
+ if (!key || !value || !length)
+ return -EINVAL;
+ /* Search parameter */
+ len = dw3000_calib_parse_key(dw, key, &param);
+ if (len < 0)
+ return len;
+ if (len > length)
+ return -EINVAL;
+ r = check_calibration_value(llhw, key, value);
+ if (r)
+ return r;
+ /* FIXME: This copy isn't big-endian compatible. */
+ memcpy(param, value, len);
+
+ /* One parameter has changed. */
+ dw3000_calib_update_config(dw);
+ /* TODO: need reconfiguration? */
+ return 0;
+}
+
+static int get_calibration(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length)
+{
+ struct dw3000 *dw = llhw->priv;
+ void *param;
+ int len;
+ /* Sanity checks */
+ if (!key)
+ return -EINVAL;
+ /* Calibration parameters */
+ len = dw3000_calib_parse_key(dw, key, &param);
+ if (len < 0)
+ return len;
+ if (len <= length)
+ memcpy(value, param, len);
+ else if (value && length)
+ /* Provided buffer size isn't enough, return an error */
+ return -ENOSPC;
+ /* Return selected parameter length or error */
+ return len;
+}
+
+static const char *const *list_calibration(struct mcps802154_llhw *llhw)
+{
+ return dw3000_calib_list_keys(llhw->priv);
+}
+
+struct do_vendor_params {
+ u32 vendor_id;
+ u32 subcmd;
+ void *data;
+ size_t data_len;
+};
+
+static int do_vendor_cmd(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_vendor_params *params = in;
+
+ switch (params->subcmd) {
+ case LLHW_VENDOR_CMD_PCTT_SETUP_HW:
+ return dw3000_pctt_vendor_cmd(dw, params->vendor_id,
+ params->subcmd, params->data,
+ params->data_len);
+ case LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS:
+ case LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION:
+ case LLHW_VENDOR_CMD_NFCC_COEX_STOP:
+ return dw3000_nfcc_coex_vendor_cmd(dw, params->vendor_id,
+ params->subcmd, params->data,
+ params->data_len);
+ }
+ return -EINVAL;
+}
+
+/**
+ * vendor_cmd() - Forward vendor commands processing to dw3000 function.
+ * @llhw: Low-level hardware without MCPS.
+ * @vendor_id: Vendor Identifier on 3 bytes.
+ * @subcmd: Sub-command identifier.
+ * @data: Null or data related with the sub-command.
+ * @data_len: Length of the data
+ *
+ * Return: 0 on success, 1 to request a stop, error on other value.
+ */
+static int vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct do_vendor_params params = {
+ .vendor_id = vendor_id,
+ .subcmd = subcmd,
+ .data = data,
+ .data_len = data_len,
+ };
+ struct dw3000_stm_command cmd = { do_vendor_cmd, &params, NULL };
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+static int get_antenna_caps(struct mcps802154_llhw *llhw, int ant_idx,
+ uint32_t *caps)
+{
+ struct dw3000 *dw = llhw->priv;
+ const struct dw3000_antenna_calib *ant_calib;
+
+ if (ant_idx < 0 || ant_idx >= ANTMAX) {
+ dev_err(dw->dev, "Bad antenna number (%d)\n", ant_idx);
+ return -EINVAL;
+ }
+
+ ant_calib = &dw->calib_data.ant[ant_idx];
+ *caps = ant_calib->caps;
+ return 0;
+}
+
+/**
+ * get_power_stats() - Forward vendor commands processing to dw3000 function.
+ * @llhw: Low-level hardware without MCPS.
+ * @pwr_stats: mcps802154_power_stats structure to be filled.
+ *
+ * Return: 0 on success or negative error code.
+ */
+static int get_power_stats(struct mcps802154_llhw *llhw,
+ struct mcps802154_power_stats *pwr_stats)
+{
+ struct dw3000 *dw = llhw->priv;
+ u64 idle_dur, rx_ns, tx_ns;
+
+ /* Update the power statistics if needed. */
+ if (dw->power.cur_state <= DW3000_PWR_IDLE)
+ dw3000_power_stats(dw, dw->power.cur_state, 0);
+ /* TX/RX are kept in DTU unit. Convert it here to limit conversion error */
+ rx_ns = dw->power.stats[DW3000_PWR_RX].dur * 10000 /
+ (DW3000_DTU_FREQ / 100000);
+ tx_ns = dw->power.stats[DW3000_PWR_TX].dur * 10000 /
+ (DW3000_DTU_FREQ / 100000);
+ idle_dur = dw->power.stats[DW3000_PWR_RUN].dur - tx_ns - rx_ns;
+
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_OFF].dur =
+ dw->power.stats[DW3000_PWR_OFF].dur;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_OFF].count =
+ dw->power.stats[DW3000_PWR_OFF].count;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_SLEEP].dur =
+ dw->power.stats[DW3000_PWR_DEEPSLEEP].dur;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_SLEEP].count =
+ dw->power.stats[DW3000_PWR_DEEPSLEEP].count;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_IDLE].dur = idle_dur;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_IDLE].count =
+ dw->power.stats[DW3000_PWR_IDLE].count;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_RX].dur = rx_ns;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_RX].count =
+ dw->power.stats[DW3000_PWR_RX].count;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_TX].dur = tx_ns;
+ pwr_stats->power_state_stats[MCPS802154_PWR_STATE_TX].count =
+ dw->power.stats[DW3000_PWR_TX].count;
+ pwr_stats->interrupts = atomic64_read(&dw->power.interrupts);
+ return 0;
+}
+
+static const struct mcps802154_ops dw3000_mcps_ops = {
+ .start = start,
+ .stop = stop,
+ .tx_frame = tx_frame,
+ .rx_enable = rx_enable,
+ .rx_disable = rx_disable,
+ .rx_get_frame = rx_get_frame,
+ .rx_get_error_frame = rx_get_error_frame,
+ .rx_get_measurement = rx_get_measurement,
+ .idle = idle,
+ .reset = reset,
+ .get_current_timestamp_dtu = get_current_timestamp_dtu,
+ .tx_timestamp_dtu_to_rmarker_rctu = tx_timestamp_dtu_to_rmarker_rctu,
+ .difference_timestamp_rctu = difference_timestamp_rctu,
+ .compute_frame_duration_dtu = compute_frame_duration_dtu,
+ .set_channel = set_channel,
+ .set_hrp_uwb_params = set_hrp_uwb_params,
+ .check_hrp_uwb_params = check_hrp_uwb_params,
+ .set_sts_params = set_sts_params,
+ .set_hw_addr_filt = set_hw_addr_filt,
+ .set_txpower = set_txpower,
+ .set_cca_mode = set_cca_mode,
+ .set_cca_ed_level = set_cca_ed_level,
+ .set_promiscuous_mode = set_promiscuous_mode,
+ .set_calibration = set_calibration,
+ .get_calibration = get_calibration,
+ .list_calibration = list_calibration,
+ .vendor_cmd = vendor_cmd,
+ .get_antenna_caps = get_antenna_caps,
+ .get_power_stats = get_power_stats,
+ MCPS802154_TESTMODE_CMD(dw3000_tm_cmd)
+};
+
+/**
+ * dw3000_mcps_alloc() - Allocate low-level MCPS driver
+ * @dev: SPI device to associate with
+ *
+ * Return: Allocated struct dw3000 or NULL on error
+ */
+struct dw3000 *dw3000_mcps_alloc(struct device *dev)
+{
+ struct mcps802154_llhw *llhw;
+ struct dw3000 *dw;
+
+ dev_dbg(dev, "%s called\n", __func__);
+ llhw = mcps802154_alloc_llhw(sizeof(*dw), &dw3000_mcps_ops);
+ if (llhw == NULL)
+ return NULL;
+ dw = llhw->priv;
+ dw->llhw = llhw;
+ dw->dev = dev;
+ dw3000_init_config(dw);
+
+ /* Configure IEEE802154 HW capabilities */
+ llhw->hw->flags =
+ (IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_RX_OMIT_CKSUM);
+ llhw->flags =
+ (MCPS802154_LLHW_BPRF | MCPS802154_LLHW_DATA_RATE_850K |
+ MCPS802154_LLHW_DATA_RATE_6M81 |
+ MCPS802154_LLHW_PHR_DATA_RATE_850K |
+ MCPS802154_LLHW_PHR_DATA_RATE_6M81 | MCPS802154_LLHW_PRF_16 |
+ MCPS802154_LLHW_PRF_64 | MCPS802154_LLHW_PSR_32 |
+ MCPS802154_LLHW_PSR_64 | MCPS802154_LLHW_PSR_128 |
+ MCPS802154_LLHW_PSR_256 | MCPS802154_LLHW_PSR_1024 |
+ MCPS802154_LLHW_PSR_4096 | MCPS802154_LLHW_SFD_4A |
+ MCPS802154_LLHW_SFD_4Z_8 | MCPS802154_LLHW_STS_SEGMENT_1 |
+ MCPS802154_LLHW_AOA_AZIMUTH | MCPS802154_LLHW_AOA_ELEVATION |
+ MCPS802154_LLHW_AOA_FOM);
+
+ llhw->hw->phy->supported.channels[4] = DW3000_SUPPORTED_CHANNELS;
+
+ /* Set time related fields */
+ llhw->dtu_freq_hz = DW3000_DTU_FREQ;
+ llhw->dtu_rctu = DW3000_RCTU_PER_DTU;
+ llhw->rstu_dtu = DW3000_DTU_PER_RSTU;
+ llhw->anticip_dtu = DW3000_ANTICIP_DTU;
+ llhw->idle_dtu = DW3000_DTU_FREQ;
+ /* Set antennas related fields */
+ llhw->rx_antenna_pairs = ANTPAIR_MAX;
+ llhw->tx_antennas = DW3000_CALIBRATION_ANTENNA_MAX;
+ /* Set time related field that are configuration dependent */
+ dw3000_update_timings(dw);
+ /* Symbol is ~0.994us @ PRF16 or ~1.018us @ PRF64. Use 1. */
+ llhw->hw->phy->symbol_duration = 1;
+
+ /* Set extended address. */
+ llhw->hw->phy->perm_extended_addr = 0xd6552cd6e41ceb57;
+
+ /* Driver phy page 4 as default, channel is copied from init config. */
+ llhw->hw->phy->current_channel = dw->config.chan;
+ llhw->hw->phy->current_page = 4;
+ llhw->current_preamble_code = dw->config.txCode;
+ /* AoA/PDoA filtering. */
+ llhw->rx_ctx_size = sizeof(struct dw3000_rx_ctx);
+
+ return dw;
+}
+
+/**
+ * dw3000_mcps_free() - Free low-level MCPS driver
+ * @dw: the DW device to free
+ */
+void dw3000_mcps_free(struct dw3000 *dw)
+{
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ if (dw->llhw) {
+ struct mcps802154_llhw *llhw = dw->llhw;
+ dw->llhw = NULL;
+ mcps802154_free_llhw(llhw);
+ }
+}
+
+/**
+ * dw3000_mcps_register() - Register low-level MCPS driver
+ * @dw: the allocated DW device to register in MCPS
+ *
+ * Return: zero on success, else a negative error code.
+ */
+int dw3000_mcps_register(struct dw3000 *dw)
+{
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ return mcps802154_register_llhw(dw->llhw);
+}
+
+/**
+ * dw3000_mcps_unregister() - Unregister low-level MCPS driver
+ * @dw: the DW device to unregister from MCPS
+ */
+void dw3000_mcps_unregister(struct dw3000 *dw)
+{
+ dev_dbg(dw->dev, "%s called\n", __func__);
+ mcps802154_unregister_llhw(dw->llhw);
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_mcps.h b/kernel/drivers/net/ieee802154/dw3000_mcps.h
new file mode 100644
index 0000000..4eb6aa7
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_mcps.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_MCPS_H
+#define __DW3000_MCPS_H
+
+struct dw3000 *dw3000_mcps_alloc(struct device *dev);
+int dw3000_mcps_register(struct dw3000 *dw);
+void dw3000_mcps_unregister(struct dw3000 *dw);
+void dw3000_mcps_free(struct dw3000 *dw);
+
+#endif /* __DW3000_MCPS_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h
new file mode 100644
index 0000000..0bffafc
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h
@@ -0,0 +1,188 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_NFCC_COEX_H
+#define __DW3000_NFCC_COEX_H
+
+#include <linux/module.h>
+#include <net/vendor_cmd.h>
+
+/* Main defines */
+#define DW3000_NFCC_COEX_SIGNATURE_STR "QORVO"
+#define DW3000_NFCC_COEX_SIGNATURE_LEN 5
+#define DW3000_NFCC_COEX_MAX_NB_TLV 12
+
+/* For TLV messages written by AP / read by NFCC,
+ * the scratch memory region is SCRATCH_RAM[0-63]. */
+#define DW3000_NFCC_COEX_MSG_OUT_OFFSET 0
+#define DW3000_NFCC_COEX_MSG_OUT_SIZE 64
+/* For TLV messages read by AP / written by NFCC,
+ * the scratch memory region is SCRATCH_RAM[64-127]. */
+#define DW3000_NFCC_COEX_MSG_IN_OFFSET 64
+#define DW3000_NFCC_COEX_MSG_IN_SIZE 63
+
+#define DW3000_NFCC_COEX_MSG_MAX_SIZE DW3000_NFCC_COEX_MSG_OUT_SIZE
+
+/* MSG_HEADER_LEN is also the sizeof of dw3000_nfcc_coex_msg structure. */
+#define MSG_HEADER_LEN (DW3000_NFCC_COEX_SIGNATURE_LEN + 3)
+#define MSG_LEN(x) (MSG_HEADER_LEN + (x).tlvs_len)
+
+#define DW3000_NFCC_COEX_UUS_PER_SYS_POWER 8 /* To use with right shift. */
+#define DW3000_NFCC_COEX_DTU_PER_UUS_POWER 4 /* To use with left shift. */
+
+/**
+ * enum dw3000_nfcc_coex_send - Type of message to send.
+ *
+ * @DW3000_NFCC_COEX_SEND_CLK_SYNC: Clock sync message.
+ * @DW3000_NFCC_COEX_SEND_CLK_OFFSET: Clock offset message.
+ * @DW3000_NFCC_COEX_SEND_STOP: Stop message.
+ */
+enum dw3000_nfcc_coex_send {
+ DW3000_NFCC_COEX_SEND_CLK_SYNC,
+ DW3000_NFCC_COEX_SEND_CLK_OFFSET,
+ DW3000_NFCC_COEX_SEND_STOP,
+};
+
+/**
+ * struct dw3000_nfcc_coex_msg - Message read/write from/to scratch memory.
+ */
+struct dw3000_nfcc_coex_msg {
+ /**
+ * @signature: String signature, example "QORVO".
+ */
+ u8 signature[DW3000_NFCC_COEX_SIGNATURE_LEN];
+ /**
+ * @ver_id: Version identifier.
+ */
+ u8 ver_id;
+ /**
+ * @seqnum: Sequence number.
+ */
+ u8 seqnum;
+ /**
+ * @nb_tlv: Number of TLV object in the body message.
+ */
+ u8 nb_tlv;
+ /**
+ * @tlvs: Body message. Its addr points to TLVs start.
+ */
+ u8 tlvs[];
+} __attribute__((packed));
+
+/**
+ * struct dw3000_nfcc_coex_buffer - Memory buffer to read/write a NFCC message.
+ */
+struct dw3000_nfcc_coex_buffer {
+ /* Unamed union for structured access or raw access. */
+ union {
+ /**
+ * @raw: Byte memory.
+ */
+ u8 raw[DW3000_NFCC_COEX_MSG_MAX_SIZE];
+ /**
+ * @msg: nfcc_coex message.
+ */
+ struct dw3000_nfcc_coex_msg msg;
+ };
+ /**
+ * @tlvs_len: Len of TLVs in bytes.
+ */
+ u8 tlvs_len;
+} __attribute__((packed));
+
+/**
+ * struct dw3000_nfcc_coex_rx_msg_info - Result of message parsed.
+ */
+struct dw3000_nfcc_coex_rx_msg_info {
+ /**
+ * @next_timestamp_dtu: Next NFCC access requested.
+ */
+ u32 next_timestamp_dtu;
+ /**
+ * @next_duration_dtu: Next NFCC duration access.
+ */
+ int next_duration_dtu;
+ /**
+ * @next_slot_found: True when first next slot is found.
+ */
+ bool next_slot_found;
+};
+
+/**
+ * struct dw3000_nfcc_coex - NFCC coexistence context.
+ */
+struct dw3000_nfcc_coex {
+ /**
+ * @access_info: Access information to provide to upper layer.
+ */
+ struct llhw_vendor_cmd_nfcc_coex_get_access_info access_info;
+ /**
+ * @session_time0_dtu: Timestamp used as reference between NFCC and AP.
+ */
+ u32 session_time0_dtu;
+ /**
+ * @access_start_dtu: start date of nfcc session in DTU.
+ */
+ u32 access_start_dtu;
+ /**
+ * @prev_offset_sys_time: Previous offset between local and decawave time.
+ */
+ u32 prev_offset_sys_time;
+ /**
+ * @original_channel: channel number to be restored after NFCC session.
+ */
+ u8 original_channel;
+ /**
+ * @rx_seq_num: Sequence number of last valid message check.
+ */
+ u8 rx_seq_num;
+ /**
+ * @tx_seq_num: Sequence message counter increased on outgoing message.
+ */
+ u8 tx_seq_num;
+ /**
+ * @enabled: True when nfcc coex is handling an access.
+ */
+ bool enabled;
+ /**
+ * @configured: True when nfcc coex is configured.
+ */
+ bool configured;
+ /**
+ * @send: Type of message to send.
+ */
+ enum dw3000_nfcc_coex_send send;
+ /**
+ * @first_rx_message: False after the first valid msg received.
+ */
+ bool first_rx_message;
+ /**
+ * @watchdog_timer: Watchdog timer to detect spi not bring back.
+ */
+ struct timer_list watchdog_timer;
+ /**
+ * @version: Protocol version to use.
+ */
+ u8 version;
+};
+
+#endif /* __DW3000_NFCC_COEX_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c
new file mode 100644
index 0000000..594df06
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c
@@ -0,0 +1,201 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000_nfcc_coex_buffer.h"
+#include "dw3000_core.h"
+#include "dw3000_core_reg.h"
+#include "dw3000_trc.h"
+
+/**
+ * dw3000_nfcc_coex_read_scratch_ram() - Copy from scratch ram memory to buffer.
+ * @dw: Driver context.
+ * @buffer: Buffer to write.
+ * @len: Number of byte to copy.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_read_scratch_ram(
+ struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer, u16 len)
+{
+ static const int offset = DW3000_NFCC_COEX_MSG_IN_OFFSET;
+ int end_offset = len + offset;
+
+ if (end_offset > DW3000_SCRATCH_RAM_LEN) {
+ trace_dw3000_nfcc_coex_err(dw, "read scratch ram bad address");
+ return -EINVAL;
+ }
+ return dw3000_xfer(dw, DW3000_SCRATCH_RAM_ID, offset, len, buffer->raw,
+ DW3000_SPI_RD_BIT);
+}
+
+/**
+ * dw3000_nfcc_coex_write_scratch_ram() - Copy from buffer to scratch ram memory.
+ * @dw: Driver context.
+ * @buffer: Buffer to read.
+ * @len: Number of byte to copy.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int
+dw3000_nfcc_coex_write_scratch_ram(struct dw3000 *dw,
+ const struct dw3000_nfcc_coex_buffer *buffer,
+ u16 len)
+{
+ static const int offset = DW3000_NFCC_COEX_MSG_OUT_OFFSET;
+ int end_offset = len + offset;
+ /* TODO: Avoid the cast, which remove the const with better code.
+ * Idea to dig: define dw3000_xfer with 2 pointers(read/write).
+ * And develop the idea of full duplex API. */
+ void *raw = (void *)buffer->raw;
+
+ if (end_offset > DW3000_SCRATCH_RAM_LEN) {
+ trace_dw3000_nfcc_coex_err(dw, "write scratch ram bad address");
+ return -EINVAL;
+ }
+ return dw3000_xfer(dw, DW3000_SCRATCH_RAM_ID, offset, len, raw,
+ DW3000_SPI_WR_BIT);
+}
+
+/**
+ * dw3000_nfcc_coex_is_spi1_reserved() - Get the status of SPI1 reserved status.
+ * @dw: Driver context.
+ * @val: Boolean updated on success.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_is_spi1_reserved(struct dw3000 *dw, bool *val)
+{
+ u8 reg;
+ int rc;
+
+ /* Check if SPI1 is reserved by reading SPI_SEM register. */
+ rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 0, &reg);
+ if (rc)
+ return rc;
+
+ *val = reg & DW3000_SPI_SEM_SPI1_RG_BIT_MASK;
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_release_spi1() - Release the SPI1.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_release_spi1(struct dw3000 *dw)
+{
+ int rc;
+ bool is_spi1_enabled;
+
+ rc = dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_REL);
+ if (rc)
+ return rc;
+ rc = dw3000_nfcc_coex_is_spi1_reserved(dw, &is_spi1_enabled);
+ if (rc)
+ return rc;
+
+ return is_spi1_enabled ? -EBUSY : 0;
+}
+
+/**
+ * dw3000_nfcc_coex_reserve_spi1() - Reserve the SPI1.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_reserve_spi1(struct dw3000 *dw)
+{
+ int rc;
+ bool is_spi1_reserved;
+
+ rc = dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_REQ);
+ if (rc)
+ return rc;
+ /* Check if the SPI1 is really reserved.
+ * Indeed, if SPI2 is already reserved, SPI1 could not be reserved. */
+ rc = dw3000_nfcc_coex_is_spi1_reserved(dw, &is_spi1_reserved);
+ if (rc)
+ return rc;
+
+ return is_spi1_reserved ? 0 : -EBUSY;
+}
+
+/**
+ * dw3000_nfcc_coex_write_buffer() - Write buffer into scratch memory.
+ * @dw: Driver context.
+ * @buffer: buffer to write in scratch memory.
+ * @len: byte length of the buffer.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_write_buffer(struct dw3000 *dw,
+ const struct dw3000_nfcc_coex_buffer *buffer,
+ u16 len)
+{
+ int rc;
+
+ if (!dw->nfcc_coex.enabled)
+ return -EOPNOTSUPP;
+ if (len > DW3000_NFCC_COEX_MSG_OUT_SIZE) {
+ trace_dw3000_nfcc_coex_err(
+ dw, "writing to nfcc exceed SCRATCH_AP_SIZE");
+ return -EINVAL;
+ }
+ rc = dw3000_nfcc_coex_reserve_spi1(dw);
+ if (rc)
+ return rc;
+ rc = dw3000_nfcc_coex_write_scratch_ram(dw, buffer, len);
+ if (rc)
+ return rc;
+ dw->nfcc_coex.tx_seq_num++;
+ /* Trigger IRQ2 to inform NFCC. */
+ return dw3000_nfcc_coex_release_spi1(dw);
+}
+
+/**
+ * dw3000_nfcc_coex_read_buffer() - Read buffer from scratch memory.
+ * @dw: Driver context.
+ * @buffer: buffer fill with content of the scratch memory.
+ * @len: byte length of the buffer.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_read_buffer(struct dw3000 *dw,
+ struct dw3000_nfcc_coex_buffer *buffer,
+ u16 len)
+{
+ int rc;
+
+ if (!dw->nfcc_coex.enabled)
+ return -EOPNOTSUPP;
+ if (len > DW3000_NFCC_COEX_MSG_IN_SIZE) {
+ trace_dw3000_nfcc_coex_err(
+ dw, "Reading from NFCC exceed SCRATCH_AP_SIZE");
+ return -EINVAL;
+ }
+ rc = dw3000_nfcc_coex_read_scratch_ram(dw, buffer, len);
+ if (rc)
+ trace_dw3000_nfcc_coex_err(
+ dw, "Error while reading NFCC scratch RAM");
+ return rc;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h
new file mode 100644
index 0000000..61a3d66
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef __DW3000_NFCC_COEX_BUFFER_H
+#define __DW3000_NFCC_COEX_BUFFER_H
+
+#include <linux/module.h>
+#include "dw3000_nfcc_coex.h"
+#include "dw3000.h"
+
+int dw3000_nfcc_coex_write_buffer(struct dw3000 *dw,
+ const struct dw3000_nfcc_coex_buffer *buffer,
+ u16 len);
+int dw3000_nfcc_coex_read_buffer(struct dw3000 *dw,
+ struct dw3000_nfcc_coex_buffer *buffer,
+ u16 len);
+
+#endif /* __DW3000_NFCC_COEX_BUFFER_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c
new file mode 100644
index 0000000..9d8aef9
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c
@@ -0,0 +1,398 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000_nfcc_coex_core.h"
+#include "dw3000_nfcc_coex_buffer.h"
+#include "dw3000_nfcc_coex_msg.h"
+#include "dw3000_nfcc_coex.h"
+#include "dw3000_core.h"
+#include "dw3000_trc.h"
+#include "dw3000_mcps.h"
+
+#include <linux/module.h>
+
+/* dw3000_nfcc_coex_margin_dtu:
+ * - Can't be bigger than ANTICIP_DTU (trouble with CLOCK_SYNC).
+ * - Lower than 4ms is really dangerous. */
+unsigned dw3000_nfcc_coex_margin_dtu = US_TO_DTU(16000);
+module_param_named(nfcc_coex_margin_dtu, dw3000_nfcc_coex_margin_dtu, uint,
+ 0444);
+MODULE_PARM_DESC(
+ nfcc_coex_margin_dtu,
+ "Margin in dtu needed to give access to the NFCC controller and for the NFCC controller"
+ " to wake up (default is anticip_dtu)");
+
+/**
+ * dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts() - Enable all SPI available interrupts.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts(struct dw3000 *dw)
+{
+ int rc;
+ u8 reg;
+
+ /* Clear SPI1MAVAIL and SPI2MAVAIL interrupt status. */
+ rc = dw3000_clear_dss_status(
+ dw, DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK |
+ DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK);
+ if (rc)
+ return rc;
+ /* Disable SPIRDY in SYS_MASK. If it is enabled, the IRQ2 will not work.
+ * It is an undocumented feature.
+ */
+ rc = dw3000_reg_modify32(
+ dw, DW3000_SYS_ENABLE_LO_ID, 0,
+ (u32)~DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK, 0);
+ if (rc)
+ return rc;
+ /* Enable the dual SPI interrupt for SPI */
+ rc = dw3000_reg_modify32(dw, DW3000_SYS_CFG_ID, 0, U32_MAX,
+ (u32)DW3000_SYS_CFG_DS_IE2_BIT_MASK);
+ if (rc)
+ return rc;
+ /* The masked write transactions do not work on the SPI_SEM register.
+ * So, a read, modify, write sequence is mandatory on this register.
+ *
+ * The 16 bits SPI_SEM register can be accessed as two 8 bits registers.
+ * So, only read the upper 8 bits for performance.
+ */
+ rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 1, &reg);
+ if (rc)
+ return rc;
+ /* Set SPI1MAVAIL and SPI2MAVAIL masks */
+ reg |= (DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK >> 8) |
+ (DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK >> 8);
+ return dw3000_reg_write8(dw, DW3000_SPI_SEM_ID, 1, reg);
+}
+
+/**
+ * dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts() - Disable all SPI available interrupts.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts(struct dw3000 *dw)
+{
+ int rc;
+ u8 reg;
+
+ /* Please read the comment in enable_SPIxMAVAIL_interrupts() for SPI_SEM access. */
+ rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 1, &reg);
+ if (rc)
+ return rc;
+ /* Reset SPI1MAVAIL and SPI2MAVAIL masks. */
+ reg &= ~(DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK >> 8) &
+ ~(DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK >> 8);
+ rc = dw3000_reg_write8(dw, DW3000_SPI_SEM_ID, 1, reg);
+ if (rc)
+ return rc;
+ /* Disable the dual SPI interrupt for SPI. */
+ return dw3000_reg_modify32(dw, DW3000_SYS_CFG_ID, 0,
+ (u32)~DW3000_SYS_CFG_DS_IE2_BIT_MASK, 0);
+}
+
+/**
+ * dw3000_nfcc_coex_update_access_info() - Update access info cache.
+ * @dw: Driver context.
+ * @buffer: buffer to read.
+ */
+static void dw3000_nfcc_coex_update_access_info(
+ struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer)
+{
+ struct llhw_vendor_cmd_nfcc_coex_get_access_info *access_info =
+ &dw->nfcc_coex.access_info;
+ struct dw3000_nfcc_coex_rx_msg_info rx_msg_info = {};
+ int r;
+
+ r = dw3000_nfcc_coex_message_check(dw, buffer, &rx_msg_info);
+ if (r) {
+ trace_dw3000_nfcc_coex_warn(dw, "message check failure");
+ goto failure;
+ }
+
+ /* Update access_info. */
+ access_info->stop = !rx_msg_info.next_slot_found;
+ access_info->watchdog_timeout = false;
+ if (rx_msg_info.next_slot_found) {
+ /* Request the handle earlier to the mac layer. */
+ access_info->next_timestamp_dtu =
+ rx_msg_info.next_timestamp_dtu -
+ dw3000_nfcc_coex_margin_dtu;
+ access_info->next_duration_dtu = rx_msg_info.next_duration_dtu +
+ dw3000_nfcc_coex_margin_dtu;
+ }
+ return;
+
+failure:
+ /* When buffer content is unexpected then request a stop. */
+ memset(access_info, 0, sizeof(*access_info));
+ access_info->stop = true;
+}
+
+/**
+ * dw3000_nfcc_coex_configure() - Configure the nfcc_coex.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_configure(struct dw3000 *dw)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+ bool channel_changed = nfcc_coex->original_channel != dw->config.chan;
+ int r;
+
+ if (nfcc_coex->configured)
+ return 0;
+
+ trace_dw3000_nfcc_coex_configure(dw);
+ if (channel_changed) {
+ r = dw3000_configure_chan(dw);
+ if (r) {
+ trace_dw3000_nfcc_coex_err(dw,
+ "configure channel fails");
+ return r;
+ }
+ }
+ r = dw3000_nfcc_coex_prepare_config(dw);
+ if (r) {
+ trace_dw3000_nfcc_coex_warn(dw,
+ "prepare the configuration fails");
+ return r;
+ }
+
+ r = dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts(dw);
+ if (r) {
+ trace_dw3000_nfcc_coex_err(
+ dw, "SPIxMAVAIL interrupts enable failed");
+ return r;
+ }
+
+ nfcc_coex->configured = true;
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_do_watchdog_timeout() - Do watchdog timeout event in workqueue.
+ * @dw: Driver context.
+ * @in: Data to read.
+ * @out: Data to write.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_do_watchdog_timeout(struct dw3000 *dw,
+ const void *in, void *out)
+{
+ /* Update status for GET_ACCESS_INFORMATION vendor. */
+ dw->nfcc_coex.access_info.watchdog_timeout = true;
+ /* Do same as dw3000_nfcc_coex_disable function without decawave
+ * register access which it probably locked by NFCC. */
+ dw->nfcc_coex.enabled = false;
+ dw->config.chan = dw->nfcc_coex.original_channel;
+ mcps802154_broken(dw->llhw);
+
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_do_spi1_avail() - Do SPI1 available irq event in workqueue.
+ * @dw: Driver context.
+ * @in: Data to read.
+ * @out: Data to write.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_do_spi1_avail(struct dw3000 *dw, const void *in,
+ void *out)
+{
+ struct dw3000_nfcc_coex_buffer buffer;
+ int r;
+
+ /* Is watchdog timer running? */
+ r = dw3000_nfcc_coex_cancel_watchdog(dw);
+ if (r) {
+ trace_dw3000_nfcc_coex_warn(
+ dw, "spi available without timer pending");
+ goto spi1_avail_failure;
+ }
+
+ r = dw3000_nfcc_coex_read_buffer(dw, &buffer,
+ DW3000_NFCC_COEX_MSG_IN_SIZE);
+ if (r)
+ goto spi1_avail_failure;
+
+ dw3000_nfcc_coex_update_access_info(dw, &buffer);
+ dw3000_nfcc_coex_disable(dw);
+ mcps802154_tx_done(dw->llhw);
+ return 0;
+
+spi1_avail_failure:
+ dw3000_nfcc_coex_disable(dw);
+ mcps802154_broken(dw->llhw);
+ return r;
+}
+
+/**
+ * dw3000_nfcc_coex_watchdog_timeout() - Watchdog timeout event.
+ * @timer: Timer context.
+ */
+static void dw3000_nfcc_coex_watchdog_timeout(struct timer_list *timer)
+{
+ struct dw3000_nfcc_coex *nfcc_coex =
+ from_timer(nfcc_coex, timer, watchdog_timer);
+ struct dw3000 *dw = nfcc_coex_to_dw(nfcc_coex);
+ struct dw3000_stm_command cmd = { dw3000_nfcc_coex_do_watchdog_timeout,
+ NULL, NULL };
+
+ trace_dw3000_nfcc_coex_watchdog_timeout(dw);
+ dw3000_enqueue_timer(dw, &cmd);
+}
+
+/**
+ * dw3000_nfcc_coex_cancel_watchdog() - Cancel the watchdog timer.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, otherwise -errno on error.
+ */
+int dw3000_nfcc_coex_cancel_watchdog(struct dw3000 *dw)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+
+ trace_dw3000_nfcc_coex_cancel_watchdog(dw);
+ /* Is watchdog timer running? */
+ if (!del_timer(&nfcc_coex->watchdog_timer))
+ return -ENOENT;
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_spi1_avail() - Handle SPI1 available interrupt.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_spi1_avail(struct dw3000 *dw)
+{
+ struct dw3000_stm_command cmd = { dw3000_nfcc_coex_do_spi1_avail, NULL,
+ NULL };
+
+ trace_dw3000_nfcc_coex_spi1_avail(dw);
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+/**
+ * dw3000_nfcc_coex_idle_timeout() - Idle expired.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_idle_timeout(struct dw3000 *dw)
+{
+ int r;
+
+ trace_dw3000_nfcc_coex_idle_timeout(dw);
+ r = dw3000_nfcc_coex_configure(dw);
+ if (r)
+ goto idle_timeout_failure;
+ r = dw3000_nfcc_coex_message_send(dw);
+ if (r)
+ goto idle_timeout_failure;
+ return 0;
+
+idle_timeout_failure:
+ dw3000_nfcc_coex_disable(dw);
+ mcps802154_broken(dw->llhw);
+ return r;
+}
+
+/**
+ * dw3000_nfcc_coex_init() - Initialize NFCC coexistence context.
+ * @dw: Driver context.
+ */
+void dw3000_nfcc_coex_init(struct dw3000 *dw)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+
+ memset(nfcc_coex, 0, sizeof(*nfcc_coex));
+ timer_setup(&nfcc_coex->watchdog_timer,
+ dw3000_nfcc_coex_watchdog_timeout, 0);
+}
+
+/**
+ * dw3000_nfcc_coex_enable() - Enable NFCC coexistence.
+ * @dw: Driver context.
+ * @channel: Channel number (5 or 9).
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_enable(struct dw3000 *dw, u8 channel)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+
+ trace_dw3000_nfcc_coex_enable(dw, channel);
+ if (nfcc_coex->enabled)
+ return -EBUSY;
+
+ /* Save current channel. */
+ nfcc_coex->original_channel = dw->config.chan;
+ nfcc_coex->configured = false;
+ nfcc_coex->enabled = true;
+ /* Set the new channel. */
+ dw->config.chan = channel;
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_disable() - Disable NFCC coexistence.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_disable(struct dw3000 *dw)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+ bool channel_changed = nfcc_coex->original_channel != dw->config.chan;
+ int r;
+
+ trace_dw3000_nfcc_coex_disable(dw);
+ if (!dw->nfcc_coex.enabled)
+ return 0;
+
+ dw->config.chan = dw->nfcc_coex.original_channel;
+ dw->nfcc_coex.enabled = false;
+
+ if (dw->nfcc_coex.configured) {
+ r = dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts(dw);
+ if (r)
+ return r;
+ if (channel_changed) {
+ r = dw3000_configure_chan(dw);
+ if (r)
+ return r;
+ }
+ r = dw3000_nfcc_coex_restore_config(dw);
+ if (r)
+ return r;
+ }
+ return 0;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h
new file mode 100644
index 0000000..26bfa64
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef __DW3000_NFCC_COEX_CORE_H
+#define __DW3000_NFCC_COEX_CORE_H
+
+#include <linux/module.h>
+#include "dw3000_nfcc_coex.h"
+#include "dw3000.h"
+
+extern unsigned dw3000_nfcc_coex_margin_dtu;
+
+int dw3000_nfcc_coex_cancel_watchdog(struct dw3000 *dw);
+int dw3000_nfcc_coex_spi1_avail(struct dw3000 *dw);
+int dw3000_nfcc_coex_idle_timeout(struct dw3000 *dw);
+void dw3000_nfcc_coex_init(struct dw3000 *dw);
+int dw3000_nfcc_coex_enable(struct dw3000 *dw, u8 channel);
+int dw3000_nfcc_coex_disable(struct dw3000 *dw);
+int dw3000_nfcc_coex_configure(struct dw3000 *dw);
+
+#endif /* __DW3000_NFCC_COEX_CORE_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c
new file mode 100644
index 0000000..2214859
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c
@@ -0,0 +1,243 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000_nfcc_coex_mcps.h"
+#include "dw3000_nfcc_coex_msg.h"
+#include "dw3000_nfcc_coex_buffer.h"
+#include "dw3000_nfcc_coex_core.h"
+#include "dw3000.h"
+#include "dw3000_trc.h"
+#include "dw3000_core.h"
+
+#define DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS 24000
+
+static int dw3000_nfcc_coex_wakeup_and_send(struct dw3000 *dw,
+ s32 idle_duration_dtu,
+ u32 send_timestamp_dtu)
+{
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+ int r;
+
+ trace_dw3000_nfcc_coex_wakeup_and_send(
+ dw, nfcc_coex->send, idle_duration_dtu, send_timestamp_dtu);
+
+ if (idle_duration_dtu > dw->llhw->anticip_dtu) {
+ r = dw3000_idle(dw, true, send_timestamp_dtu,
+ dw3000_nfcc_coex_idle_timeout,
+ DW3000_OP_STATE_MAX);
+ goto wakeup_and_send_end;
+ } else if (dw->current_operational_state ==
+ DW3000_OP_STATE_DEEP_SLEEP) {
+ r = dw3000_deepsleep_wakeup_now(dw,
+ dw3000_nfcc_coex_idle_timeout,
+ send_timestamp_dtu,
+ DW3000_OP_STATE_MAX);
+ goto wakeup_and_send_end;
+ }
+
+ r = dw3000_nfcc_coex_configure(dw);
+ if (r)
+ goto wakeup_and_send_end;
+ r = dw3000_nfcc_coex_message_send(dw);
+ if (r)
+ goto wakeup_and_send_end;
+ return 0;
+
+wakeup_and_send_end:
+ if (r)
+ dw3000_nfcc_coex_disable(dw);
+ return r;
+}
+
+/**
+ * dw3000_nfcc_coex_handle_access() - handle access to provide to NFCC.
+ * @dw: Driver context.
+ * @data: Address of handle access information.
+ * @data_len: Number of byte of the data object.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_handle_access(struct dw3000 *dw, void *data,
+ int data_len)
+{
+ const struct llhw_vendor_cmd_nfcc_coex_handle_access *info = data;
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+ const u32 dtu_per_ms = dw->llhw->dtu_freq_hz / 1000;
+ u32 now_dtu;
+ s32 idle_duration_dtu;
+ int r;
+
+ if (!info || data_len != sizeof(*info))
+ return -EINVAL;
+
+ if (timer_pending(&nfcc_coex->watchdog_timer)) {
+ trace_dw3000_nfcc_coex_err(dw, "watchdog timer is pending");
+ return -EBUSY;
+ }
+
+ r = dw3000_nfcc_coex_enable(dw, info->chan);
+ if (r)
+ return r;
+
+ now_dtu = dw3000_get_dtu_time(dw);
+ idle_duration_dtu = info->timestamp_dtu - now_dtu;
+
+ trace_dw3000_nfcc_coex_handle_access(dw, info, idle_duration_dtu);
+ nfcc_coex->send = info->start ? DW3000_NFCC_COEX_SEND_CLK_SYNC :
+ DW3000_NFCC_COEX_SEND_CLK_OFFSET;
+ if (info->start) {
+ nfcc_coex->version = info->version;
+ /*
+ * Save first start session date, to retrieve MSB bits lost
+ * for next received timestamp through session_time0_dtu.
+ * It's saved because between session_time0_dtu and
+ * access_start_dtu, a deep sleep can occur, and so
+ * `dtu_to_sys_time` must be call after the wake up.
+ * Between access_start_dtu and now, the duration can be less
+ * than 2 ms (fira slot) in multi-region feature.
+ * So the margin is like a second anticip dtu to add to provide
+ * time for NFC handling.
+ */
+ nfcc_coex->access_start_dtu =
+ info->timestamp_dtu + dw3000_nfcc_coex_margin_dtu;
+ }
+ nfcc_coex->watchdog_timer.expires =
+ jiffies +
+ msecs_to_jiffies((info->timestamp_dtu - now_dtu) / dtu_per_ms +
+ DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS);
+ add_timer(&nfcc_coex->watchdog_timer);
+
+ /* Send message and so release the SPI close to the nfc_coex_margin. */
+ return dw3000_nfcc_coex_wakeup_and_send(dw, idle_duration_dtu,
+ info->timestamp_dtu);
+}
+
+/**
+ * dw3000_nfcc_coex_get_access_information() - Forward access info cached.
+ * @dw: Driver context.
+ * @data: Address where to write access information.
+ * @data_len: Number of byte of the data object.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_get_access_information(struct dw3000 *dw,
+ void *data, int data_len)
+{
+ const struct llhw_vendor_cmd_nfcc_coex_get_access_info *access_info =
+ &dw->nfcc_coex.access_info;
+
+ if (!data || data_len != sizeof(*access_info))
+ return -EINVAL;
+
+ memcpy(data, access_info, data_len);
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_stop() - Stop NFCC.
+ * @dw: Driver context.
+ * @data: Address of stop information.
+ * @data_len: Number of byte of the data object.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int dw3000_nfcc_coex_stop(struct dw3000 *dw, void *data, int data_len)
+{
+ const struct llhw_vendor_cmd_nfcc_coex_stop *info = data;
+ struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex;
+ const u32 dtu_per_ms = dw->llhw->dtu_freq_hz / 1000;
+ u32 now_dtu, send_timestamp_dtu;
+ s32 idle_duration_dtu;
+ int r;
+
+ if (data_len && data_len != sizeof(*info))
+ return -EINVAL;
+
+ if (timer_pending(&nfcc_coex->watchdog_timer)) {
+ trace_dw3000_nfcc_coex_err(dw, "watchdog timer is pending");
+ return -EBUSY;
+ }
+
+ r = dw3000_nfcc_coex_enable(dw, dw->config.chan);
+ if (r)
+ return r;
+
+ nfcc_coex->send = DW3000_NFCC_COEX_SEND_STOP;
+
+ if (info) {
+ now_dtu = dw3000_get_dtu_time(dw);
+ send_timestamp_dtu = info->timestamp_dtu;
+ idle_duration_dtu = send_timestamp_dtu - now_dtu;
+ nfcc_coex->watchdog_timer.expires =
+ jiffies +
+ msecs_to_jiffies(
+ (info->timestamp_dtu - now_dtu) / dtu_per_ms +
+ DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS);
+ nfcc_coex->version = info->version;
+ } else {
+ send_timestamp_dtu = 0;
+ idle_duration_dtu = 0;
+ nfcc_coex->watchdog_timer.expires =
+ jiffies +
+ msecs_to_jiffies(
+ DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS);
+ /* Cancel wakeup timer launch by idle() */
+ dw3000_idle_cancel_timer(dw);
+ }
+
+ add_timer(&nfcc_coex->watchdog_timer);
+
+ /* Send message and so release the SPI close to the nfc_coex_margin. */
+ return dw3000_nfcc_coex_wakeup_and_send(dw, idle_duration_dtu,
+ send_timestamp_dtu);
+}
+
+/**
+ * dw3000_nfcc_coex_vendor_cmd() - Vendor NFCC coexistence command processing.
+ *
+ * @dw: Driver context.
+ * @vendor_id: Vendor Identifier on 3 bytes.
+ * @subcmd: Sub-command identifier.
+ * @data: Null or data related with the sub-command.
+ * @data_len: Length of the data
+ *
+ * Return: 0 on success, 1 to request a stop, error on other value.
+ */
+int dw3000_nfcc_coex_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len)
+{
+ /* NFCC needs a D0 chip or above. C0 does not have 2 SPI interfaces. */
+ if (__dw3000_chip_version == DW3000_C0_VERSION)
+ return -EOPNOTSUPP;
+
+ switch (subcmd) {
+ case LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS:
+ return dw3000_nfcc_coex_handle_access(dw, data, data_len);
+ case LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION:
+ return dw3000_nfcc_coex_get_access_information(dw, data,
+ data_len);
+ case LLHW_VENDOR_CMD_NFCC_COEX_STOP:
+ return dw3000_nfcc_coex_stop(dw, data, data_len);
+ default:
+ return -EINVAL;
+ }
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h
new file mode 100644
index 0000000..a2ba707
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_NFCC_COEX_MCPS_H
+#define __DW3000_NFCC_COEX_MCPS_H
+
+#include <linux/module.h>
+#include "dw3000.h"
+
+int dw3000_nfcc_coex_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len);
+
+#endif /* __DW3000_NFCC_COEX_MCPS_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c
new file mode 100644
index 0000000..18b74be
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c
@@ -0,0 +1,330 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "dw3000_nfcc_coex_msg.h"
+#include "dw3000_nfcc_coex_buffer.h"
+#include "dw3000_nfcc_coex_core.h"
+#include "dw3000.h"
+#include "dw3000_trc.h"
+#include "dw3000_core.h"
+
+/* TLVs len helpers. */
+#define TLV_TYPELEN_LEN 2 /* type 1 byte, len 1 byte. */
+#define TLV_U32_LEN (4 + 1) /* u32 + ack/nack. */
+#define TLV_SLOTS_LEN(nbslots) \
+ (1 + (8 * (nbslots)) + 1) /* nslots + slots + ack/nack. */
+#define TLV_SLOTS_LIST_SIZE_MAX (1 + (8 * (TLV_MAX_NB_SLOTS)))
+#define MSG_NEXT_TLV(buffer, offset) \
+ (struct dw3000_nfcc_coex_tlv *)((buffer)->msg.tlvs + (offset))
+
+/**
+ * struct dw3000_nfcc_coex_tlv - Type Length Value.
+ */
+struct dw3000_nfcc_coex_tlv {
+ /**
+ * @type: Identifier of TLV.
+ */
+ u8 type;
+ /**
+ * @len: Number of byte of TLV array.
+ */
+ u8 len;
+ /**
+ * @tlv: Value of the TLV.
+ */
+ u8 tlv[];
+} __attribute__((packed));
+
+/**
+ * dw3000_nfcc_coex_header_put() - Fill NFCC frame header.
+ * @dw: Driver context.
+ * @buffer: Message buffer to fill.
+ */
+void dw3000_nfcc_coex_header_put(struct dw3000 *dw,
+ struct dw3000_nfcc_coex_buffer *buffer)
+{
+ struct dw3000_nfcc_coex_msg *msg = &buffer->msg;
+
+ trace_dw3000_nfcc_coex_header_put(dw, dw->nfcc_coex.version,
+ dw->nfcc_coex.tx_seq_num);
+ memcpy(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR,
+ DW3000_NFCC_COEX_SIGNATURE_LEN);
+ msg->ver_id = dw->nfcc_coex.version;
+ msg->seqnum = dw->nfcc_coex.tx_seq_num;
+ msg->nb_tlv = 0;
+ buffer->tlvs_len = 0;
+}
+
+/**
+ * dw3000_nfcc_coex_tlv_u32_put() - Fill buffer payload for a TLV.
+ * @buffer: Message buffer to fill.
+ * @type: Type id of the TLV.
+ * @value: Value of TLV.
+ */
+static void dw3000_nfcc_coex_tlv_u32_put(struct dw3000_nfcc_coex_buffer *buffer,
+ enum dw3000_nfcc_coex_tlv_type type,
+ u32 value)
+{
+ struct dw3000_nfcc_coex_msg *msg = &buffer->msg;
+ struct dw3000_nfcc_coex_tlv *tlv;
+ u32 *v;
+
+ tlv = MSG_NEXT_TLV(buffer, buffer->tlvs_len);
+ msg->nb_tlv++;
+ tlv->type = type;
+ tlv->len = 4;
+ v = (u32 *)&tlv->tlv;
+ *v = value;
+ buffer->tlvs_len += TLV_TYPELEN_LEN + TLV_U32_LEN;
+}
+
+/**
+ * dw3000_nfcc_coex_clock_sync_payload_put() - Fill clock sync frame payload.
+ * @dw: Driver context.
+ * @buffer: Buffer to set with help of handle_access.
+ */
+static void
+dw3000_nfcc_coex_clock_sync_payload_put(struct dw3000 *dw,
+ struct dw3000_nfcc_coex_buffer *buffer)
+{
+ u32 session_time0_sys_time =
+ dw3000_dtu_to_sys_time(dw, dw->nfcc_coex.access_start_dtu);
+
+ /* Update clock reference. */
+ dw->nfcc_coex.session_time0_dtu = dw->nfcc_coex.access_start_dtu;
+ /* Prepare message. */
+ trace_dw3000_nfcc_coex_clock_sync_payload_put(dw,
+ session_time0_sys_time);
+ dw3000_nfcc_coex_header_put(dw, buffer);
+ dw3000_nfcc_coex_tlv_u32_put(buffer,
+ DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0,
+ session_time0_sys_time);
+}
+
+/**
+ * dw3000_nfcc_coex_clock_offset_payload_put() - Fill clock offset payload.
+ * @dw: Driver context.
+ * @buffer: Buffer to set with help of handle_access.
+ * @clock_offset_sys_time: Offset to add to next nfcc_coex schedule.
+ */
+static void dw3000_nfcc_coex_clock_offset_payload_put(
+ struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer,
+ u32 clock_offset_sys_time)
+{
+ trace_dw3000_nfcc_coex_clock_offset_payload_put(dw,
+ clock_offset_sys_time);
+ dw3000_nfcc_coex_header_put(dw, buffer);
+ dw3000_nfcc_coex_tlv_u32_put(buffer,
+ DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS,
+ clock_offset_sys_time);
+}
+
+/**
+ * dw3000_nfcc_coex_stop_session_payload_put() - Fill stop session payload.
+ * @dw: Driver context.
+ * @buffer: Buffer to set with help of handle_access.
+ * @session_id: Session id to stop.
+ */
+static void dw3000_nfcc_coex_stop_session_payload_put(
+ struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer,
+ u32 session_id)
+{
+ trace_dw3000_nfcc_coex_stop_session_payload_put(dw, session_id);
+
+ dw3000_nfcc_coex_header_put(dw, buffer);
+
+ dw3000_nfcc_coex_tlv_u32_put(
+ buffer, DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION, session_id);
+}
+
+/**
+ * dw3000_nfcc_coex_message_send() - Write message for NFCC and release SPI1.
+ * @dw: Driver context.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_message_send(struct dw3000 *dw)
+{
+ struct dw3000_nfcc_coex_buffer buffer = {};
+ /* Build the absolute sys time offset. */
+ u32 offset_sys_time =
+ (dw->dtu_sync << DW3000_DTU_PER_SYS_POWER) - dw->sys_time_sync;
+ u32 clock_offset_sys_time;
+
+ switch (dw->nfcc_coex.send) {
+ case DW3000_NFCC_COEX_SEND_CLK_SYNC:
+ dw3000_nfcc_coex_clock_sync_payload_put(dw, &buffer);
+ break;
+ default:
+ case DW3000_NFCC_COEX_SEND_CLK_OFFSET:
+ /* Compute the clock correction to forward to NFCC. */
+ clock_offset_sys_time =
+ offset_sys_time - dw->nfcc_coex.prev_offset_sys_time;
+ /* Build the message with the clock update to forward. */
+ dw3000_nfcc_coex_clock_offset_payload_put(
+ dw, &buffer, -clock_offset_sys_time);
+ break;
+ case DW3000_NFCC_COEX_SEND_STOP:
+ dw3000_nfcc_coex_stop_session_payload_put(
+ dw, &buffer, DW3000_NFCC_COEX_SESSION_ID_DEFAULT);
+ break;
+ }
+
+ dw->nfcc_coex.prev_offset_sys_time = offset_sys_time;
+ /* Write message to NFCC and release SP1. */
+ return dw3000_nfcc_coex_write_buffer(dw, &buffer, MSG_LEN(buffer));
+}
+
+/**
+ * dw3000_nfcc_coex_header_check() - Check header message.
+ * @dw: Driver context.
+ * @buffer: Buffer to check.
+ *
+ * Return: 0 when buffer contain a valid message, else an error.
+ */
+static int
+dw3000_nfcc_coex_header_check(struct dw3000 *dw,
+ const struct dw3000_nfcc_coex_buffer *buffer)
+{
+ const struct dw3000_nfcc_coex_msg *msg = &buffer->msg;
+
+ trace_dw3000_nfcc_coex_header_check(dw, msg->signature, msg->ver_id,
+ msg->seqnum, msg->nb_tlv);
+ /* Check signature. */
+ if (memcmp(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR,
+ DW3000_NFCC_COEX_SIGNATURE_LEN)) {
+ return -EINVAL;
+ }
+ /* Check AP_NFCC Interface Version ID. */
+ if (msg->ver_id != dw->nfcc_coex.version) {
+ return -EINVAL;
+ }
+ /* Read number of TLVs. */
+ if (msg->nb_tlv > DW3000_NFCC_COEX_MAX_NB_TLV) {
+ return -EINVAL;
+ }
+ /* Check if message is a new one with the sequence number. */
+ if (!dw->nfcc_coex.first_rx_message &&
+ (msg->seqnum - dw->nfcc_coex.rx_seq_num) > 0) {
+ /* TODO: Reject message with bad seqnum. */
+ }
+
+ dw->nfcc_coex.rx_seq_num = msg->seqnum;
+ dw->nfcc_coex.first_rx_message = false;
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_tlvs_check() - Set information on a received message.
+ * @dw: Driver context.
+ * @buffer: Buffer to read.
+ * @rx_msg_info: information updated on valid message.
+ *
+ * Return: 0 on success, else an error.
+ */
+static int
+dw3000_nfcc_coex_tlvs_check(struct dw3000 *dw,
+ const struct dw3000_nfcc_coex_buffer *buffer,
+ struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info)
+{
+ static const int tlvs_len_max =
+ DW3000_NFCC_COEX_MSG_MAX_SIZE - MSG_HEADER_LEN;
+ const struct dw3000_nfcc_coex_msg *msg = &buffer->msg;
+ const struct dw3000_nfcc_coex_tlv_slot_list *slot_list = NULL;
+ int tlvs_len = 0; /* Start parsing at first TLV. */
+ int i;
+
+ /* Process tlvs. */
+ for (i = 0; i < msg->nb_tlv; i++) {
+ struct dw3000_nfcc_coex_tlv *tlv;
+
+ if ((tlvs_len + sizeof(*tlv)) > tlvs_len_max)
+ return -EINVAL;
+
+ tlv = MSG_NEXT_TLV(buffer, tlvs_len);
+ tlvs_len += tlv->len;
+
+ if (tlvs_len > tlvs_len_max)
+ return -EINVAL;
+
+ trace_dw3000_nfcc_coex_tlv_check(dw, tlv->type, tlv->len,
+ tlv->tlv);
+ if (tlv->type == DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS) {
+ /* Reject a new TLV with same type. Behavior not defined. */
+ if (slot_list)
+ return -EINVAL;
+ /* Check if the tlv size isn't exceeding the list max size */
+ if (tlv->len > TLV_SLOTS_LIST_SIZE_MAX)
+ return -EINVAL;
+ slot_list = (const struct dw3000_nfcc_coex_tlv_slot_list
+ *)&tlv->tlv;
+ /* Update rx_msg_info. */
+ if (slot_list->nb_slots > 0) {
+ const struct dw3000_nfcc_coex_tlv_slot *slot =
+ &slot_list->slots[0];
+ u32 next_in_session_dtu =
+ slot->t_start_uus
+ << DW3000_NFCC_COEX_DTU_PER_UUS_POWER;
+
+ rx_msg_info->next_slot_found = true;
+ rx_msg_info->next_timestamp_dtu =
+ dw->nfcc_coex.session_time0_dtu +
+ next_in_session_dtu;
+ rx_msg_info->next_duration_dtu =
+ (slot->t_end_uus - slot->t_start_uus)
+ << DW3000_NFCC_COEX_DTU_PER_UUS_POWER;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * dw3000_nfcc_coex_message_check() - Check and read message.
+ * @dw: Driver context.
+ * @buffer: Buffer to read.
+ * @rx_msg_info: Result of message parsed updated on success.
+ *
+ * Return: 0 on success, else an error.
+ */
+int dw3000_nfcc_coex_message_check(
+ struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer,
+ struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info)
+{
+ int r;
+
+ r = dw3000_nfcc_coex_header_check(dw, buffer);
+ if (r)
+ return r;
+
+ r = dw3000_nfcc_coex_tlvs_check(dw, buffer, rx_msg_info);
+ if (r)
+ return r;
+
+ if (rx_msg_info->next_slot_found)
+ trace_dw3000_nfcc_coex_rx_msg_info(
+ dw, rx_msg_info->next_timestamp_dtu,
+ rx_msg_info->next_duration_dtu);
+ return 0;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h
new file mode 100644
index 0000000..4214088
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h
@@ -0,0 +1,96 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_NFCC_COEX_MSG_H
+#define __DW3000_NFCC_COEX_MSG_H
+
+#include <linux/module.h>
+#include "dw3000_nfcc_coex.h"
+#include "dw3000.h"
+
+#define TLV_MAX_NB_SLOTS 4
+#define DW3000_NFCC_COEX_SESSION_ID_DEFAULT 0
+
+/**
+ * enum dw3000_nfcc_coex_tlv_type - TLVs types.
+ *
+ * @DW3000_NFCC_COEX_TLV_TYPE_UNSPEC: Invalid command.
+ * @DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0:
+ * Indicate start of UWB session in RCTU time unit.
+ * @DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST:
+ * Indicate the requested next time slots.
+ * @DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS:
+ * Indicate the UWB clock offset in V1 protocol.
+ * @DW3000_NFCC_COEX_TLV_TYPE_ERROR:
+ * Indicate error condition.
+ * @DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS:
+ * Indicate the UWB clock offset in V2 protocol.
+ * @DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION:
+ * Indicate the stop session in V3 protocol.
+ */
+enum dw3000_nfcc_coex_tlv_type {
+ DW3000_NFCC_COEX_TLV_TYPE_UNSPEC,
+
+ DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0,
+ DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST,
+ DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS,
+ DW3000_NFCC_COEX_TLV_TYPE_ERROR,
+ DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS,
+ DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION
+};
+
+/**
+ * struct dw3000_nfcc_coex_tlv_slot - TLV slot definition.
+ */
+struct dw3000_nfcc_coex_tlv_slot {
+ /**
+ * @t_start_uus: Start date in 65536*RCTU (close to usec unit).
+ */
+ u32 t_start_uus;
+ /**
+ * @t_end_uus: End date in 65536*RCTU (close to usec unit).
+ */
+ u32 t_end_uus;
+};
+
+/**
+ * struct dw3000_nfcc_coex_tlv_slot_list - TLV slots.
+ */
+struct dw3000_nfcc_coex_tlv_slot_list {
+ /**
+ * @nb_slots: Number of slots used.
+ */
+ u8 nb_slots;
+ /**
+ * @slots: array of start/end session time.
+ */
+ struct dw3000_nfcc_coex_tlv_slot slots[TLV_MAX_NB_SLOTS];
+} __attribute__((packed));
+
+void dw3000_nfcc_coex_header_put(struct dw3000 *dw,
+ struct dw3000_nfcc_coex_buffer *buffer);
+int dw3000_nfcc_coex_message_send(struct dw3000 *dw);
+int dw3000_nfcc_coex_message_check(
+ struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer,
+ struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info);
+
+#endif /* __DW3000_NFCC_COEX_MSG_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt.h b/kernel/drivers/net/ieee802154/dw3000_pctt.h
new file mode 100644
index 0000000..74a2542
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_pctt.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_PCTT_H
+#define __DW3000_PCTT_H
+
+/**
+ * struct dw3000_pctt - PCTT coexistence context.
+ */
+struct dw3000_pctt {
+ /**
+ * @enabled: True when pctt is enabled.
+ */
+ bool enabled;
+};
+
+#endif /* __DW3000_PCTT_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c
new file mode 100644
index 0000000..400080c
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "dw3000_pctt_mcps.h"
+#include "dw3000.h"
+#include "dw3000_core.h"
+
+#include <net/vendor_cmd.h>
+
+int dw3000_pctt_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len)
+{
+ struct llhw_vendor_cmd_pctt_setup_hw *info = data;
+ struct dw3000_config *config = &dw->config;
+ int rc;
+
+ if (info) {
+ if (sizeof(*info) != data_len)
+ return -EINVAL;
+
+ if (dw->pctt.enabled)
+ return -EBUSY;
+
+ config->chan = info->chan;
+ config->txPreambLength = !info->preamble_duration ?
+ DW3000_PLEN_32 :
+ DW3000_PLEN_64;
+ config->txCode = config->rxCode = info->preamble_code_index;
+ config->sfdType = !info->sfd_id ? DW3000_SFD_TYPE_STD :
+ DW3000_SFD_TYPE_4Z;
+ config->dataRate = !info->psdu_data_rate ? DW3000_BR_6M8 :
+ DW3000_BR_850K;
+ config->stsMode = info->rframe_config;
+
+ rc = dw3000_set_promiscuous(dw, true);
+ if (rc)
+ return rc;
+ rc = dw3000_configure_sys_cfg(dw, config);
+ if (rc)
+ return rc;
+ rc = dw3000_configure_chan(dw);
+ if (rc)
+ return rc;
+ }
+ dw->pctt.enabled = !!info;
+ return dw3000_enable_auto_fcs(dw, !dw->pctt.enabled);
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h
new file mode 100644
index 0000000..6eda5eb
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_PCTT_MCPS_H
+#define __DW3000_PCTT_MCPS_H
+
+#include <linux/module.h>
+
+/* Forward declaration. */
+struct dw3000;
+
+int dw3000_pctt_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len);
+
+#endif /* __DW3000_PCTT_MCPS_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_perf.h b/kernel/drivers/net/ieee802154/dw3000_perf.h
new file mode 100644
index 0000000..98d2f92
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_perf.h
@@ -0,0 +1,144 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_PERF_H
+#define __DW3000_PERF_H
+
+#include <linux/version.h>
+#include <linux/perf_event.h>
+
+static struct perf_event_attr perf_hw_attr[] = {
+ {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES,
+ .size = sizeof(struct perf_event_attr),
+ .pinned = 1,
+ .disabled = 1,
+ },
+ {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_INSTRUCTIONS,
+ .size = sizeof(struct perf_event_attr),
+ .pinned = 1,
+ .disabled = 1,
+ },
+ {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
+ .size = sizeof(struct perf_event_attr),
+ .pinned = 1,
+ .disabled = 1,
+ },
+ {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CACHE_MISSES,
+ .size = sizeof(struct perf_event_attr),
+ .pinned = 1,
+ .disabled = 1,
+ }
+};
+
+#define PERF_EVT_COUNT (ARRAY_SIZE(perf_hw_attr))
+
+static const char *const perf_hw_evt_name[PERF_EVT_COUNT] = {
+ "cpu cycles ", "instructions", "branch insts", "cache misses"
+};
+
+static struct perf_event *perf_hw_evt[PERF_EVT_COUNT];
+
+/* Callback function for perf event subsystem */
+static void overflow_callback(struct perf_event *event,
+ struct perf_sample_data *data,
+ struct pt_regs *regs)
+{
+ struct dw3000 *dw = event->overflow_handler_context;
+
+ event->hw.interrupts = 0;
+ dev_warn(dw->dev, "test mode: counter overflow for event %x:%llx\n",
+ event->attr.type, event->attr.config);
+}
+
+static inline void perf_event_create_all(struct dw3000 *dw)
+{
+ int i;
+
+ for (i = 0; i < PERF_EVT_COUNT; i++) {
+ struct perf_event *evt = perf_event_create_kernel_counter(
+ &perf_hw_attr[i], smp_processor_id(), NULL,
+ overflow_callback, dw);
+ if (IS_ERR(evt)) {
+ dev_warn(
+ dw->dev,
+ "test mode: cannot create perf_hw_evt %d (err %ld)\n",
+ i, PTR_ERR(evt));
+ evt = NULL;
+ }
+ perf_hw_evt[i] = evt;
+ }
+}
+
+static inline void perf_event_release_all(void)
+{
+ int i;
+
+ for (i = 0; i < PERF_EVT_COUNT; i++) {
+ struct perf_event *evt = perf_hw_evt[i];
+
+ if (evt)
+ perf_event_release_kernel(evt);
+ }
+}
+
+static inline void perf_event_start_all(void)
+{
+ int i;
+
+ for (i = 0; i < PERF_EVT_COUNT; i++) {
+ struct perf_event *evt = perf_hw_evt[i];
+
+ if (evt)
+ perf_event_enable(evt);
+ }
+}
+
+static inline void perf_event_stop_all(u64 *vals)
+{
+ u64 dummy[2];
+ int i;
+
+ for (i = 0; i < PERF_EVT_COUNT; i++) {
+ struct perf_event *evt = perf_hw_evt[i];
+
+ if (evt) {
+ vals[i] = perf_event_read_value(evt, &dummy[0],
+ &dummy[1]);
+#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE)
+ perf_event_disable(evt);
+ local64_set(&evt->count, 0);
+#else
+ perf_event_pause(evt, true);
+#endif
+ }
+ }
+}
+
+#endif /* __DW3000_PERF_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_pm.h b/kernel/drivers/net/ieee802154/dw3000_pm.h
new file mode 100644
index 0000000..9baf0c7
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_pm.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_PM_H
+#define __DW3000_PM_H
+
+#include <linux/pm_qos.h>
+#include <linux/version.h>
+
+#include "dw3000.h"
+
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+#define PM_QOS_RESUME_LATENCY_NO_CONSTRAINT S32_MAX
+#endif
+
+extern int dw3000_qos_latency;
+
+static inline void dw3000_pm_qos_add_request(struct dw3000 *dw, int latency)
+{
+#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE)
+ cpu_latency_qos_add_request(&dw->pm_qos_req, latency);
+#endif
+}
+
+static inline void dw3000_pm_qos_update_request(struct dw3000 *dw, int latency)
+{
+#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE)
+ cpu_latency_qos_update_request(&dw->pm_qos_req, latency);
+#endif
+}
+
+static inline void dw3000_pm_qos_remove_request(struct dw3000 *dw)
+{
+#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE)
+ cpu_latency_qos_remove_request(&dw->pm_qos_req);
+#endif
+}
+
+#endif /* __DW3000_PM_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_power_stats.h b/kernel/drivers/net/ieee802154/dw3000_power_stats.h
new file mode 100644
index 0000000..baa857c
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_power_stats.h
@@ -0,0 +1,105 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+/**
+ * dw3000_power_stats() - compute time elapsed in dw3000 states
+ * @dw: the DW device on which state is changed
+ * @state: the new state
+ * @len_or_date: frame length to be transmitted or RX dates in DTU
+ *
+ * Update power statistics according current state leaved and new state.
+ * States > RUN are handled specifically.
+ */
+static inline void dw3000_power_stats(struct dw3000 *dw, int state,
+ int len_or_date)
+{
+ struct dw3000_power *pw = &dw->power;
+ int cstate = min(pw->cur_state, DW3000_PWR_RUN);
+ int nstate = min(state, DW3000_PWR_RUN);
+ u64 boot_time_ns = ktime_get_boottime_ns();
+ s64 duration;
+ u32 adjust;
+ u32 cur_dtu_time;
+
+ /* Trace call */
+ trace_dw3000_power_stats(dw, state, boot_time_ns, len_or_date);
+ /* Sanity checks first */
+ if (state < 0 || state >= DW3000_PWR_MAX)
+ return;
+ /* Calculate duration of current state. */
+ duration = boot_time_ns - pw->start_time;
+ /* Update basic state statistics */
+ pw->stats[cstate].dur += duration;
+ if (nstate != cstate)
+ pw->stats[nstate].count++;
+ /* Handle specific states */
+ cstate = pw->cur_state;
+ switch (state) {
+ case DW3000_PWR_IDLE:
+ adjust = 0;
+ if (cstate == DW3000_PWR_TX) {
+ /* TX duration is just saved pw->tx_adjust */
+ adjust = (u32)pw->tx_adjust;
+ } else if (cstate == DW3000_PWR_RX) {
+ /* RX duration is just cur_dtu_time-pw->rx_start */
+ cur_dtu_time = (u32)len_or_date;
+ if (!cur_dtu_time) {
+ /* RX frame end time is not given, so get current DTU. */
+ cur_dtu_time =
+ dw3000_ktime_to_dtu(dw, boot_time_ns);
+ }
+ adjust = cur_dtu_time - pw->rx_start;
+ }
+ pw->stats[cstate].dur += adjust;
+ if (state != cstate)
+ pw->stats[DW3000_PWR_IDLE].count++;
+ break;
+ case DW3000_PWR_TX:
+ /* TX time is calculated according frame len only */
+ pw->tx_adjust =
+ dw3000_frame_duration_dtu(dw, len_or_date, true);
+ pw->stats[DW3000_PWR_TX].count++;
+ break;
+ case DW3000_PWR_RX:
+ /* RX time is calculated using start time and reception time */
+ cur_dtu_time = (u32)len_or_date;
+ if (!cur_dtu_time) {
+ /* Start time is unknown for immediate RX but we need
+ it, so get current DTU time. */
+ cur_dtu_time = dw3000_ktime_to_dtu(dw, boot_time_ns);
+ }
+ pw->rx_start = cur_dtu_time;
+ pw->stats[DW3000_PWR_RX].count++;
+ break;
+ case DW3000_PWR_RUN:
+ /* Entering RUN state also enter IDLE state */
+ if (state != cstate)
+ pw->stats[DW3000_PWR_IDLE].count++;
+ break;
+ default:
+ break;
+ }
+ /* Update current information */
+ pw->start_time = boot_time_ns;
+ pw->cur_state = state;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_spi.c b/kernel/drivers/net/ieee802154/dw3000_spi.c
new file mode 100644
index 0000000..cadc9b1
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_spi.c
@@ -0,0 +1,296 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+
+#include "dw3000.h"
+#include "dw3000_pm.h"
+#include "dw3000_core.h"
+#include "dw3000_stm.h"
+#include "dw3000_mcps.h"
+#include "dw3000_debugfs.h"
+
+/* Default value for auto_deep_sleep_margin.
+ * Set to -1 (disabled) until we want to have deep-sleep enabled by default. */
+#define DW3000_AUTO_DEEP_SLEEP_MARGIN_US -1
+
+int dw3000_qos_latency = 0;
+
+unsigned dw3000_regulator_delay_us = 1000;
+
+static int dw3000_lna_pa_mode = 0;
+module_param_named(lna_pa_mode, dw3000_lna_pa_mode, int, 0444);
+MODULE_PARM_DESC(
+ lna_pa_mode,
+ "Configure LNA/PA mode. May conflict with WiFi coexistence GPIO number, 0 for disabled (default)");
+
+/**
+ * dw3000_spi_probe() - Probe and initialize DW3000 SPI device
+ * @spi: the SPI device to probe and initialize
+ *
+ * Called when module is loaded and an SPI controller driver exist.
+ * This function allocates private device structure and then initialize
+ *
+ * - SPI, HRTimer, waitqueue,
+ * - sysfs/debugfs,
+ * - regulators, GPIO & IRQ from DT,
+ * - IRQ & events processing thread,
+ *
+ * Then, if no errors, the device is registered to MCPS upper-layer and
+ * the processing thread is started to do device probing.
+ *
+ * Return: 0 if device probed correctly else a negative error.
+ */
+static int dw3000_spi_probe(struct spi_device *spi)
+{
+ struct dw3000 *dw;
+ int rc;
+ int dw3000_thread_cpu;
+
+ /* Allocate MCPS 802.15.4 device */
+ dw = dw3000_mcps_alloc(&spi->dev);
+ if (!dw) {
+ rc = -ENOMEM;
+ goto err_alloc_hw;
+ }
+ dw->llhw->hw->parent = &spi->dev;
+ spi_set_drvdata(spi, dw);
+ dw->spi = spi;
+
+ /* Initialization of the wifi coex parameters */
+ rc = dw3000_setup_wifi_coex(dw);
+ if (rc != 0)
+ goto err_wifi_coex;
+
+ /* Initialization of thread cpu */
+ rc = dw3000_setup_thread_cpu(dw, &dw3000_thread_cpu);
+ if (rc != 0)
+ goto err_thread_cpu;
+
+ /* Initialization of qos latency */
+ rc = dw3000_setup_qos_latency(dw);
+ if (rc != 0)
+ goto err_qos_latency;
+
+ /* Initialization of regulator delay */
+ rc = dw3000_setup_regulator_delay(dw);
+ if (rc != 0)
+ goto err_regulator_delay;
+
+ dw->lna_pa_mode = (s8)dw3000_lna_pa_mode;
+#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE)
+#if (KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE)
+ dw->spi_pid = spi->controller->kworker->task->pid;
+#else
+ dw->spi_pid = spi->controller->kworker.task->pid;
+#endif
+#else
+ dw->spi_pid = spi->master->kworker.task->pid;
+#endif
+ dw->auto_sleep_margin_us = DW3000_AUTO_DEEP_SLEEP_MARGIN_US;
+ dw->current_operational_state = DW3000_OP_STATE_OFF;
+ init_waitqueue_head(&dw->operational_state_wq);
+ /* Initialization of the idle timer for wakeup. */
+ hrtimer_init(&dw->idle_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ dw->idle_timer.function = dw3000_idle_timeout;
+
+ dev_info(dw->dev, "Loading driver...06202023");
+ dw3000_sysfs_init(dw);
+
+ /* Setup SPI parameters */
+ dev_info(dw->dev, "setup mode: %d, %u bits/w, %u Hz max\n",
+ (int)(spi->mode & (SPI_CPOL | SPI_CPHA)), spi->bits_per_word,
+ spi->max_speed_hz);
+ dev_info(dw->dev, "can_dma: %d\n", spi->master->can_dma != NULL);
+ spi->bits_per_word = 8;
+#if (KERNEL_VERSION(5, 3, 0) <= LINUX_VERSION_CODE)
+ spi->rt = 1;
+#endif
+#if (KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE)
+ /* Quirk to force spi_set_cs() in spi_setup() to do something!
+ !!! Not doing this results in CS line stay LOW and SPIRDY IRQ
+ isn't fired later when powering-on the device. Previous kernel
+ don't have this bug as it always apply new CS state. */
+ spi->master->last_cs_enable = true;
+#endif
+ /* Save configured device max speed */
+ dw->of_max_speed_hz = spi->max_speed_hz;
+ /* Setup SPI and put CS line in HIGH state! */
+ rc = spi_setup(spi);
+ if (rc != 0)
+ goto err_spi_setup;
+
+ /* Request and setup regulators if availables*/
+ dw3000_setup_regulators(dw);
+
+ /* Request and setup the reset GPIO pin */
+ /* This leave the DW3000 in reset state until dw3000_hardreset() put
+ the GPIO back in input mode.
+ This also ensure no spurious IRQ fire during the dw3000_setup_irq()
+ call below. (If the GPIO state is well maintained low). */
+ rc = dw3000_setup_reset_gpio(dw);
+ if (rc != 0)
+ goto err_setup_gpios;
+
+ /* Allocate pre-computed SPI messages for fast access some registers */
+ rc = dw3000_transfers_init(dw);
+ if (rc != 0)
+ goto err_transfers_init;
+
+ /* Initialise state descriptor */
+ /* This ensure wait queue exist before IRQ handler is setup in case
+ of spurious IRQ (mainly because hw problem with reset GPIO). */
+ rc = dw3000_state_init(dw, dw3000_thread_cpu);
+ if (rc != 0) {
+ dev_err(dw->dev, "state machine initialisation failed: %d\n",
+ rc);
+ goto err_state_init;
+ }
+
+ dev_info(dw->dev, "SPI pid: %u, dw3000 pid: %u\n", dw->spi_pid,
+ dw->dw3000_pid);
+ /* Request and setup the irq GPIO pin */
+ rc = dw3000_setup_irq(dw);
+ if (rc != 0)
+ goto err_setup_irq;
+
+ /*
+ * Initialize PM QoS. Using the default latency won't change anything
+ * to the QoS list
+ */
+ dw3000_pm_qos_add_request(dw, PM_QOS_DEFAULT_VALUE);
+
+ /* Start state machine & initialise device using high-prio thread */
+ rc = dw3000_state_start(dw);
+ if (rc != 0)
+ goto err_state_start;
+
+ /* Debugfs interface */
+ rc = dw3000_debugsfs_init(dw);
+ if (rc != 0)
+ goto err_debugfs;
+
+ /* Register MCPS 802.15.4 device */
+ rc = dw3000_mcps_register(dw);
+ if (rc != 0) {
+ dev_err(&spi->dev, "could not register: %d\n", rc);
+ goto err_register_hw;
+ }
+
+ /* All is ok */
+ return 0;
+
+err_register_hw:
+ dw3000_debugfs_remove(dw);
+err_debugfs:
+err_state_start:
+ dw3000_pm_qos_remove_request(dw);
+err_setup_irq:
+ dw3000_state_stop(dw);
+err_state_init:
+ dw3000_transfers_free(dw);
+err_transfers_init:
+err_setup_gpios:
+err_spi_setup:
+ dw3000_sysfs_remove(dw);
+err_regulator_delay:
+err_qos_latency:
+err_thread_cpu:
+err_wifi_coex:
+ dw3000_mcps_free(dw);
+ spi_set_drvdata(spi, NULL);
+err_alloc_hw:
+ return rc;
+}
+
+/**
+ * dw3000_spi_remove() - Remove DW3000 SPI device
+ * @spi: the SPI device to remove
+ *
+ * Called when module is unloaded, this function removes all
+ * sysfs/debugfs files, unregister device from the MCPS
+ * module and them free all remaining resources.
+ *
+ * Return: always 0
+ */
+static int dw3000_spi_remove(struct spi_device *spi)
+{
+ struct dw3000 *dw = spi_get_drvdata(spi);
+
+ if (dw == NULL)
+ /* Error during probe, all already freed */
+ return 0;
+
+ dev_dbg(dw->dev, "unloading...");
+
+ /* Remove sysfs files */
+ dw3000_debugfs_remove(dw);
+ dw3000_sysfs_remove(dw);
+ /* Unregister subsystems */
+ dw3000_mcps_unregister(dw);
+ /* Stop state machine */
+ dw3000_state_stop(dw);
+ dw3000_pm_qos_remove_request(dw);
+ /* Free pre-computed SPI messages */
+ dw3000_transfers_free(dw);
+ /* Release the mcps 802.15.4 device */
+ dw3000_cir_data_alloc_count(dw, 0);
+ dw3000_mcps_free(dw);
+
+ return 0;
+}
+
+enum { DW3000,
+};
+
+static const struct of_device_id dw3000_of_ids[] = {
+ { .compatible = "decawave,dw3000", .data = (void *)DW3000 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dw3000_of_ids);
+
+static const struct spi_device_id dw3000_spi_ids[] = {
+ { "dw3000", DW3000 },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, dw3000_spi_ids);
+
+static struct spi_driver dw3000_driver = {
+ .driver = {
+ .name = "dw3000",
+ .of_match_table = of_match_ptr(dw3000_of_ids),
+ },
+ .id_table = dw3000_spi_ids,
+ .probe = dw3000_spi_probe,
+ .remove = dw3000_spi_remove,
+};
+module_spi_driver(dw3000_driver);
+
+#ifdef GITVERSION
+MODULE_VERSION(GITVERSION);
+#endif
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("David Girault <david.girault@qorvo.com>");
+MODULE_DESCRIPTION("DecaWave DW3000 IEEE 802.15.4 driver");
diff --git a/kernel/drivers/net/ieee802154/dw3000_stm.c b/kernel/drivers/net/ieee802154/dw3000_stm.c
new file mode 100644
index 0000000..5b85643
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_stm.c
@@ -0,0 +1,406 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+
+#include "dw3000.h"
+#include "dw3000_core.h"
+
+#define DW3000_MIN_CLAMP_VALUE 460
+
+/* First version with sched_setattr_nocheck: v4.16-rc1~164^2~5 */
+#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE)
+#include <uapi/linux/sched/types.h>
+#endif
+
+static int dw3000_min_clamp_value = 0;
+
+module_param_named(min_clamp_value, dw3000_min_clamp_value, int, 0644);
+MODULE_PARM_DESC(min_clamp_value, "Sets the minimum cpu frequency the dw3000 thread must run at");
+
+
+static void dw3000_get_clamp_from_dt(struct dw3000 *dw) {
+ int dt_clamp = DW3000_MIN_CLAMP_VALUE;
+ int ret;
+
+ if (!dw->dev->of_node)
+ return;
+ /* debug value is priority */
+ if (dw3000_min_clamp_value) {
+ dw->min_clamp_value = dw3000_min_clamp_value;
+ dev_info(dw->dev, "using debug min clamp=%d\n", dw->min_clamp_value);
+ return;
+ }
+
+ ret = of_property_read_u32(dw->dev->of_node, "min_clamp", &dt_clamp);
+ if (ret) {
+ dev_err(dw->dev, "error reading dt_clamp ret=%d\n", ret);
+ }
+ dw->min_clamp_value = dt_clamp;
+ dev_info(dw->dev, "dt_clamp=%d\n", dw->min_clamp_value);
+}
+
+static inline int dw3000_set_sched_attr(struct dw3000 *dw, struct task_struct *p)
+{
+#if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE)
+ struct sched_param sched_par = { .sched_priority = MAX_RT_PRIO - 2 };
+ /* Increase thread priority */
+ return sched_setscheduler(p, SCHED_FIFO, &sched_par);
+#else
+ struct sched_attr attr = { .sched_policy = SCHED_FIFO,
+ .sched_priority = MAX_RT_PRIO - 2,
+ .sched_flags = SCHED_FLAG_UTIL_CLAMP_MIN,
+ .sched_util_min = dw->min_clamp_value };
+ return sched_setattr_nocheck(p, &attr);
+#endif
+}
+
+/* Enqueue work item(s) */
+void dw3000_enqueue(struct dw3000 *dw, unsigned long work)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ stm->pending_work |= work;
+ wake_up_locked(&stm->work_wq);
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+}
+
+/* Enqueue a generic work and wait for execution */
+int dw3000_enqueue_generic(struct dw3000 *dw, struct dw3000_stm_command *cmd)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+ int work = DW3000_COMMAND_WORK;
+
+ if (current == stm->mthread) {
+ /* We can't enqueue a new work from the same context and wait,
+ but it can be executed directly instead. */
+ return cmd->cmd(dw, cmd->in, cmd->out);
+ }
+
+ /* Mutex is used in dw3000_enqueue_generic()
+ * This protection will work with the spinlock in order to allow
+ * the CPU to sleep and avoid ressources wasting during spinning
+ */
+ if (mutex_lock_interruptible(&stm->mtx) == -EINTR) {
+ dev_err(dw->dev, "work enqueuing interrupted by signal");
+ return -EINTR;
+ }
+ /* Slow path if not in STM thread context */
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ stm->pending_work |= work;
+ stm->generic_work = cmd;
+ wake_up_locked(&stm->work_wq);
+ wait_event_interruptible_locked_irq(stm->work_wq,
+ !(stm->pending_work & work));
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+ mutex_unlock(&stm->mtx);
+ return cmd->ret;
+}
+
+/* Enqueue a timer work and don't wait for execution because sleeping in not
+ * possible from a timer callback function.
+ */
+void dw3000_enqueue_timer(struct dw3000 *dw, struct dw3000_stm_command *cmd)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ if (stm->pending_work & DW3000_TIMER_WORK) {
+ /* A timer cmd is already queued. */
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+ dev_err(dw->dev,
+ "A timer cmd is already queued, this cmd will be ignored\n");
+ return;
+ }
+ stm->pending_work |= DW3000_TIMER_WORK;
+ /* The cmd should not be stored on the stack of the calling function. */
+ stm->timer_work = *cmd;
+ wake_up_locked(&stm->work_wq);
+ /* Can't unlock in the event thread, when the cmd is finished, because
+ * the current function is executed in the timer function in atomic context.
+ * If the unlock is made in the event thread, a preempt leak warning
+ * occurs in call_timer_fn().
+ * So, it's less bad to unlock here.
+ */
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+ /* Can't return cmd->ret because it's not yet executed. */
+}
+
+/* Dequeue work item(s) */
+void dw3000_dequeue(struct dw3000 *dw, unsigned long work)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ stm->pending_work &= ~work;
+ wake_up_locked(&stm->work_wq);
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+}
+
+/* Enqueue IRQ work */
+void dw3000_enqueue_irq(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ if (!(stm->pending_work & DW3000_IRQ_WORK)) {
+ stm->pending_work |= DW3000_IRQ_WORK;
+ disable_irq_nosync(dw->spi->irq);
+ }
+ wake_up_locked(&stm->work_wq);
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+}
+
+void dw3000_clear_irq(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ stm->pending_work &= ~DW3000_IRQ_WORK;
+ enable_irq(dw->spi->irq);
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+}
+
+/* Wait for new work in the queue */
+void dw3000_wait_pending_work(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ wait_event_interruptible_locked_irq(
+ stm->work_wq, stm->pending_work || kthread_should_stop());
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+}
+
+/* Read work queue state */
+unsigned long dw3000_get_pending_work(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long work;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ work = stm->pending_work;
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+
+ return work;
+}
+
+/* Chip detect work that run inside the high-priority thread below */
+static int dw3000_detect_work(struct dw3000 *dw, const void *in, void *out)
+{
+ int rc;
+
+ /* Now, read DEV_ID and initialise chip version */
+ dev_notice(dw->dev, "checking device presence\n");
+ rc = dw3000_check_devid(dw);
+ if (rc) {
+ dev_err(dw->dev, "device checking failed: %d\n", rc);
+ dw3000_poweroff(dw); // Force power-off if error.
+ return rc;
+ }
+ dev_notice(dw->dev, "device present\n");
+
+ /* Read OTP data early */
+ rc = dw3000_read_otp(dw, DW3000_READ_OTP_PID | DW3000_READ_OTP_LID);
+ if (unlikely(rc)) {
+ dev_err(dw->dev, "device OTP read has failed (%d)\n", rc);
+ return rc;
+ }
+
+ /* Now, we just power-off the device waiting for it to be used by the
+ * MAC to avoid power consumption. Except if SPI tests are enabled. */
+ rc = dw3000_spitests_enabled(dw) ? 0 : dw3000_poweroff(dw);
+ return rc;
+}
+
+/* Event handling thread function */
+int dw3000_event_thread(void *data)
+{
+ struct dw3000 *dw = data;
+ struct dw3000_state *stm = &dw->stm;
+ unsigned long pending_work = 0;
+
+ /* Run until stopped */
+ while (!kthread_should_stop()) {
+ /* TODO: State independent errors (ex: PLL_HILO) */
+
+ /* Pending work items */
+ pending_work = dw3000_get_pending_work(dw);
+
+ /* TODO: SPI/HW errors.
+ * Every function that uses SPI transmission must enqueue
+ * DW3000_ERROR_WORK item if any error occurs.
+ */
+
+ /* Check IRQ activity */
+ if (pending_work & DW3000_IRQ_WORK) {
+ /* Handle the event in the ISR */
+ dw3000_isr(dw);
+ dw3000_clear_irq(dw);
+ continue;
+ }
+
+ /* In nearly all states, we can execute generic works. */
+ if (pending_work & DW3000_COMMAND_WORK) {
+ struct dw3000_stm_command *cmd = stm->generic_work;
+ bool is_detect_work = cmd->cmd == dw3000_detect_work;
+
+ cmd->ret = cmd->cmd(dw, cmd->in, cmd->out);
+ dw3000_dequeue(dw, DW3000_COMMAND_WORK);
+ if (unlikely(is_detect_work &&
+ dw3000_spitests_enabled(dw))) {
+ /* Run SPI tests if enabled after dw3000_detect_work. */
+ dw3000_spitests(dw);
+ /* Power down the device after SPI tests */
+ dw3000_poweroff(dw);
+ }
+ }
+
+ /* Execute the cmd from a timer handler that can't sleep. */
+ if (pending_work & DW3000_TIMER_WORK) {
+ struct dw3000_stm_command *cmd = &stm->timer_work;
+
+ cmd->ret = cmd->cmd(dw, cmd->in, cmd->out);
+ dw3000_dequeue(dw, DW3000_TIMER_WORK);
+ }
+
+ if (!pending_work) {
+ /* Wait for more work */
+ dw3000_wait_pending_work(dw);
+ }
+ }
+
+ /* Make sure device is off */
+ dw3000_remove(dw);
+ /* Power down the device */
+ dw3000_poweroff(dw);
+
+ dev_dbg(dw->dev, "thread finished\n");
+ return 0;
+}
+
+/* Prepare state machine */
+int dw3000_state_init(struct dw3000 *dw, int cpu)
+{
+ struct dw3000_state *stm = &dw->stm;
+ int rc;
+ /* Clear memory */
+ memset(stm, 0, sizeof(*stm));
+
+ /* Wait queues */
+ init_waitqueue_head(&stm->work_wq);
+
+ mutex_init(&stm->mtx);
+
+ /* SKIP: Setup timers (state timeout and ADC timers) */
+
+ /* Init event handler thread */
+ stm->mthread = kthread_create(dw3000_event_thread, dw, "dw3000-%s",
+ dev_name(dw->dev));
+ if (IS_ERR(stm->mthread)) {
+ int err = PTR_ERR(stm->mthread);
+ stm->mthread = NULL;
+ return err;
+ }
+ get_task_struct(stm->mthread);
+ if (cpu >= 0)
+ kthread_bind(stm->mthread, (unsigned)cpu);
+ dw->dw3000_pid = stm->mthread->pid;
+
+ /* Increase thread priority */
+ dw3000_get_clamp_from_dt(dw);
+ rc = dw3000_set_sched_attr(dw, stm->mthread);
+ if (rc)
+ dev_err(dw->dev, "dw3000_set_sched_attr failed: %d\n", rc);
+ return 0;
+}
+
+/* Start state machine */
+int dw3000_state_start(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+ struct dw3000_stm_command cmd = { dw3000_detect_work, NULL, NULL };
+ unsigned long flags;
+ int rc;
+
+ /* Ensure spurious IRQ that may come during dw3000_setup_irq() (because
+ IRQ pin is already HIGH) isn't handle by the STM thread. */
+ spin_lock_irqsave(&stm->work_wq.lock, flags);
+ stm->pending_work &= ~DW3000_IRQ_WORK;
+ spin_unlock_irqrestore(&stm->work_wq.lock, flags);
+
+ /* Start state machine thread */
+ wake_up_process(stm->mthread);
+ dev_dbg(dw->dev, "state machine started\n");
+
+ /* Turn on power and de-assert reset GPIO */
+ rc = dw3000_poweron(dw);
+ if (rc) {
+ dev_err(dw->dev, "device power on failed: %d\n", rc);
+ return rc;
+ }
+ /* Ensure RESET GPIO for enough time */
+ rc = dw3000_hardreset(dw);
+ if (rc) {
+ dev_err(dw->dev, "hard reset failed: %d\n", rc);
+ return rc;
+ }
+ /* and wait SPI ready IRQ */
+ rc = dw3000_wait_idle_state(dw);
+ if (rc) {
+ dev_err(dw->dev, "wait device power on failed: %d\n", rc);
+ return rc;
+ }
+ /* Do chip detection and return result to caller */
+ return dw3000_enqueue_generic(dw, &cmd);
+}
+
+/* Stop state machine */
+int dw3000_state_stop(struct dw3000 *dw)
+{
+ struct dw3000_state *stm = &dw->stm;
+
+ if (stm->mthread == NULL)
+ return 0; /* already stopped or not created yet */
+
+ /* Stop state machine thread */
+ kthread_stop(stm->mthread);
+ put_task_struct(stm->mthread);
+ stm->mthread = NULL;
+
+ dev_dbg(dw->dev, "state machine stopped\n");
+ return 0;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_stm.h b/kernel/drivers/net/ieee802154/dw3000_stm.h
new file mode 100644
index 0000000..81a67dd
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_stm.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_STM_H
+#define __DW3000_STM_H
+
+struct dw3000;
+
+/* Pending work bits */
+enum { DW3000_IRQ_WORK = BIT(0),
+ DW3000_COMMAND_WORK = BIT(1),
+ DW3000_TIMER_WORK = BIT(2),
+};
+
+/* Custom function for command */
+typedef int (*cmd_func)(struct dw3000 *dw, const void *in, void *out);
+
+/* Generic command descriptor */
+struct dw3000_stm_command {
+ cmd_func cmd;
+ const void *in;
+ void *out;
+ int ret;
+};
+
+/* DW3000 state machine */
+struct dw3000_state {
+ /* Pending work bitmap */
+ unsigned long pending_work;
+ /* Error recovery count */
+ unsigned int recovery_count;
+ /* Generic work argument */
+ struct dw3000_stm_command *generic_work;
+ /* Timer work argument */
+ struct dw3000_stm_command timer_work;
+ /* Event handler thread */
+ struct task_struct *mthread;
+ /* Wait queue */
+ wait_queue_head_t work_wq;
+ /* Enqueue generic mutex */
+ struct mutex mtx;
+};
+
+/* Event handler of the state machine */
+int dw3000_event_thread(void *data);
+
+void dw3000_enqueue(struct dw3000 *dw, unsigned long work);
+void dw3000_enqueue_irq(struct dw3000 *dw);
+int dw3000_enqueue_generic(struct dw3000 *dw, struct dw3000_stm_command *cmd);
+void dw3000_enqueue_timer(struct dw3000 *dw, struct dw3000_stm_command *cmd);
+
+int dw3000_state_init(struct dw3000 *dw, int cpu);
+int dw3000_state_start(struct dw3000 *dw);
+int dw3000_state_stop(struct dw3000 *dw);
+
+#endif /* __DW3000_STM_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode.c b/kernel/drivers/net/ieee802154/dw3000_testmode.c
new file mode 100644
index 0000000..1097d2d
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_testmode.c
@@ -0,0 +1,436 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/slab.h>
+#include <net/netlink.h>
+
+#include "dw3000.h"
+#include "dw3000_core.h"
+#include "dw3000_trc.h"
+#include "dw3000_testmode.h"
+#include "dw3000_testmode_nl.h"
+
+int set_hrp_uwb_params(struct mcps802154_llhw *llhw, int prf, int psr,
+ int sfd_selector, int phr_rate, int data_rate);
+int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel,
+ u8 preamble_code);
+
+static const struct nla_policy dw3000_tm_policy[DW3000_TM_ATTR_MAX + 1] = {
+ [DW3000_TM_ATTR_CMD] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_RX_GOOD_CNT] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_RX_BAD_CNT] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_RSSI_DATA] = { .type = NLA_BINARY,
+ .len = DW3000_TM_RSSI_DATA_MAX_LEN },
+ [DW3000_TM_ATTR_OTP_ADDR] = { .type = NLA_U16 },
+ [DW3000_TM_ATTR_OTP_VAL] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_OTP_DONE] = { .type = NLA_U8 },
+ [DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_CONTTX_FRAME_LENGHT] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_CONTTX_RATE] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_CONTTX_DURATION] = { .type = NLA_S32 },
+ [DW3000_TM_ATTR_PSR] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_SFD] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_PHR_RATE] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_DATA_RATE] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_PAGE] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_CHANNEL] = { .type = NLA_U32 },
+ [DW3000_TM_ATTR_PREAMBLE_CODE] = { .type = NLA_U32 },
+};
+
+struct do_tm_cmd_params {
+ struct mcps802154_llhw *llhw;
+ struct nlattr **nl_attr;
+};
+
+static int do_tm_cmd_start_rx_diag(struct dw3000 *dw, const void *in, void *out)
+{
+ int rc;
+ /**
+ * Since the MCPS enables RX by default on the device at startup before
+ * running any test, enabling it once again manually via testmode
+ * command will cause the device to raise repeated IRQ and flood
+ * this driver's event thread.
+ * As a workaround, we disable RX before performing the testmode
+ * command.
+ */
+ rc = dw3000_rx_disable(dw);
+ if (rc)
+ return rc;
+ /* Enable receiver and promiscuous mode */
+ rc = dw3000_rx_enable(dw, 0, 0, 0);
+ if (rc)
+ return rc;
+ rc = dw3000_set_promiscuous(dw, true);
+ if (rc)
+ return rc;
+ /* Enable statistics */
+ return dw3000_rx_stats_enable(dw, true);
+}
+
+static int do_tm_cmd_stop_rx_diag(struct dw3000 *dw, const void *in, void *out)
+{
+ int rc;
+ /* Disable receiver and promiscuous mode */
+ rc = dw3000_rx_disable(dw);
+ if (rc)
+ return rc;
+ rc = dw3000_set_promiscuous(dw, false);
+ if (rc)
+ return rc;
+ /* Disable statistics */
+ return dw3000_rx_stats_enable(dw, false);
+}
+
+static int do_tm_cmd_get_rx_diag(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ struct dw3000_stats *stats = &dw->stats;
+ struct sk_buff *nl_skb;
+ size_t rssi_len;
+ int count = stats->count[DW3000_STATS_RX_GOOD];
+ int rc;
+
+ /* TODO: we don't send RSSI data for error frames. We should change this. */
+ rssi_len = count < (DW3000_RSSI_REPORTS_MAX << 1) ?
+ count :
+ DW3000_RSSI_REPORTS_MAX << 1;
+ rssi_len *= sizeof(struct dw3000_rssi);
+
+ /**
+ * Allocate netlink message. The approximated size includes
+ * the testmode's command id and data.
+ */
+ nl_skb = mcps802154_testmode_alloc_reply_skb(
+ params->llhw, 3 * sizeof(u32) + rssi_len);
+ if (!nl_skb) {
+ dev_err(dw->dev, "failed to alloc skb reply\n");
+ return -ENOMEM;
+ }
+ /* Append good and bad RX counters to the netlink message */
+ rc = nla_put_u32(nl_skb, DW3000_TM_ATTR_RX_GOOD_CNT,
+ stats->count[DW3000_STATS_RX_GOOD]);
+ if (rc) {
+ dev_err(dw->dev, "failed to put testmode rx counter: %d\n", rc);
+ goto nla_put_failure;
+ }
+ rc = nla_put_u32(nl_skb, DW3000_TM_ATTR_RX_BAD_CNT,
+ stats->count[DW3000_STATS_RX_ERROR] +
+ stats->count[DW3000_STATS_RX_TO]);
+ if (rc) {
+ dev_err(dw->dev, "failed to put testmode rx counter: %d\n", rc);
+ goto nla_put_failure;
+ }
+ /* Append RSSI data to the netlink message */
+ rc = nla_put(nl_skb, DW3000_TM_ATTR_RSSI_DATA, rssi_len, stats->rssi);
+ if (rc) {
+ dev_err(dw->dev, "failed to copy testmode data: %d\n", rc);
+ goto nla_put_failure;
+ }
+ return mcps802154_testmode_reply(params->llhw, nl_skb);
+
+nla_put_failure:
+ nlmsg_free(nl_skb);
+ return rc;
+}
+
+static int do_tm_cmd_clear_rx_diag(struct dw3000 *dw, const void *in, void *out)
+{
+ /* Clear statistics */
+ dw3000_rx_stats_clear(dw);
+ return 0;
+}
+
+static int do_tm_cmd_start_tx_cwtone(struct dw3000 *dw, const void *in,
+ void *out)
+{
+ int rc;
+ /* Disable receiver */
+ rc = dw3000_rx_disable(dw);
+ if (rc)
+ return rc;
+ /* Play repeated CW tone */
+ return dw3000_tx_setcwtone(dw, true);
+}
+
+static int do_tm_cmd_stop_tx_cwtone(struct dw3000 *dw, const void *in,
+ void *out)
+{
+ /* Stop repeated CW tone */
+ return dw3000_tx_setcwtone(dw, false);
+}
+
+static int do_tm_cmd_otp_read(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ struct sk_buff *msg;
+ u32 otp_val;
+ u16 otp_addr;
+ int rc;
+
+ /* Verify the OTP address attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_OTP_ADDR])
+ return -EINVAL;
+
+ otp_addr = nla_get_u16(params->nl_attr[DW3000_TM_ATTR_OTP_ADDR]);
+ /* Verify if the given OTP address exceeds the limit */
+ if (otp_addr > DW3000_OTP_ADDRESS_LIMIT)
+ return -EINVAL;
+ /* Read at OTP address */
+ rc = dw3000_otp_read32(dw, otp_addr, &otp_val);
+ if (rc)
+ return rc;
+ /**
+ * Allocate netlink message. The approximated size includes
+ * the testmode's command id and data.
+ */
+ msg = mcps802154_testmode_alloc_reply_skb(params->llhw,
+ 2 * sizeof(u32));
+ if (!msg) {
+ dev_err(dw->dev, "failed to alloc skb reply\n");
+ return -ENOMEM;
+ }
+ /* Append OTP memory's value to the netlink message */
+ rc = nla_put_u32(msg, DW3000_TM_ATTR_OTP_VAL, otp_val);
+ if (rc) {
+ dev_err(dw->dev, "failed to put testmode otp val: %d\n", rc);
+ goto nla_put_failure;
+ }
+ return mcps802154_testmode_reply(params->llhw, msg);
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return rc;
+}
+
+static int do_tm_cmd_otp_write(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ struct sk_buff *msg;
+ u32 otp_val;
+ u16 otp_addr;
+ u8 otp_done;
+ int rc;
+
+ /* Verify the OTP address attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_OTP_ADDR] ||
+ !params->nl_attr[DW3000_TM_ATTR_OTP_VAL])
+ return -EINVAL;
+
+ otp_addr = nla_get_u16(params->nl_attr[DW3000_TM_ATTR_OTP_ADDR]);
+ /* Verify if the given OTP address exceeds the limit */
+ if (otp_addr > DW3000_OTP_ADDRESS_LIMIT)
+ return -EINVAL;
+ otp_val = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_OTP_VAL]);
+ /* Write at OTP address */
+ rc = dw3000_otp_write32(dw, otp_addr, otp_val);
+ otp_done = (rc) ? 0 : 1;
+ /**
+ * Allocate netlink message. The approximated size includes
+ * the testmode's command id and data.
+ */
+ msg = mcps802154_testmode_alloc_reply_skb(params->llhw,
+ 2 * sizeof(u32));
+ if (!msg) {
+ dev_err(dw->dev, "failed to alloc skb reply\n");
+ return -ENOMEM;
+ }
+ /* Append OTP memory's value to the netlink message */
+ rc = nla_put_u8(msg, DW3000_TM_ATTR_OTP_DONE, otp_done);
+ if (rc) {
+ dev_err(dw->dev, "failed to put testmode otp done: %d\n", rc);
+ goto nla_put_failure;
+ }
+ return mcps802154_testmode_reply(params->llhw, msg);
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return rc;
+}
+
+static int do_tm_cmd_deep_sleep(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ u32 delay;
+
+ /* Verify the delay attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS])
+ return -EINVAL;
+ delay = nla_get_u32(
+ params->nl_attr[DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS]);
+ dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_IDLE_PLL;
+ return dw3000_deep_sleep_and_wakeup(dw, delay * 1000);
+}
+
+static int do_tm_cmd_start_cont_tx(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ u32 frame_length;
+ u32 rate;
+ s32 duration;
+ int rc;
+
+ /* Verify mandatory attributes */
+ if (!params->nl_attr[DW3000_TM_ATTR_CONTTX_FRAME_LENGHT] ||
+ !params->nl_attr[DW3000_TM_ATTR_CONTTX_RATE])
+ return -EINVAL;
+
+ frame_length = nla_get_u32(
+ params->nl_attr[DW3000_TM_ATTR_CONTTX_FRAME_LENGHT]);
+ if (frame_length < 4)
+ return -EINVAL;
+ rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_CONTTX_RATE]);
+
+ /* Disable receiver */
+ rc = dw3000_rx_disable(dw);
+ if (rc)
+ return rc;
+
+ dw->config.stsMode = DW3000_STS_MODE_OFF;
+
+ rc = dw3000_testmode_continuous_tx_start(dw, frame_length, rate);
+ if (rc)
+ return rc;
+
+ if (params->nl_attr[DW3000_TM_ATTR_CONTTX_DURATION]) {
+ duration = nla_get_s32(
+ params->nl_attr[DW3000_TM_ATTR_CONTTX_DURATION]);
+ msleep(duration * 1000);
+ rc = dw3000_testmode_continuous_tx_stop(dw);
+ }
+
+ return rc;
+}
+
+static int do_tm_cmd_stop_cont_tx(struct dw3000 *dw, const void *in, void *out)
+{
+ return dw3000_testmode_continuous_tx_stop(dw);
+}
+
+static int do_tm_cmd_set_hrp_uwb_params(struct dw3000 *dw, const void *in,
+ void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ u32 psr;
+ u32 sfd;
+ u32 phr_rate;
+ u32 data_rate;
+
+ /* Verify the psr attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_PSR])
+ return -EINVAL;
+ psr = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PSR]);
+
+ /* Verify the sfd attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_SFD])
+ return -EINVAL;
+ sfd = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_SFD]);
+
+ /* Verify the phr_rate attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_PHR_RATE])
+ return -EINVAL;
+ phr_rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PHR_RATE]);
+
+ /* Verify the data_rate attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_DATA_RATE])
+ return -EINVAL;
+ data_rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_DATA_RATE]);
+
+ return set_hrp_uwb_params(params->llhw, 0, psr, sfd, phr_rate,
+ data_rate);
+}
+
+static int do_tm_cmd_set_channel(struct dw3000 *dw, const void *in, void *out)
+{
+ const struct do_tm_cmd_params *params = in;
+ u32 page;
+ u32 channel;
+ u32 preamble_code;
+
+ /* Verify the page attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_PAGE])
+ return -EINVAL;
+ page = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PAGE]);
+
+ /* Verify the channel attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_CHANNEL])
+ return -EINVAL;
+ channel = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_CHANNEL]);
+
+ /* Verify the preamble_code attribute exists */
+ if (!params->nl_attr[DW3000_TM_ATTR_PREAMBLE_CODE])
+ return -EINVAL;
+ preamble_code =
+ nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PREAMBLE_CODE]);
+
+ return set_channel(params->llhw, page, channel, preamble_code);
+}
+
+int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data, int len)
+{
+ struct dw3000 *dw = llhw->priv;
+ struct do_tm_cmd_params params;
+ struct dw3000_stm_command cmd = { NULL, &params, NULL };
+ struct nlattr *attr[DW3000_TM_ATTR_MAX + 1];
+
+ static const cmd_func cmds[__DW3000_TM_CMD_AFTER_LAST] = {
+ [DW3000_TM_CMD_START_RX_DIAG] = do_tm_cmd_start_rx_diag,
+ [DW3000_TM_CMD_STOP_RX_DIAG] = do_tm_cmd_stop_rx_diag,
+ [DW3000_TM_CMD_GET_RX_DIAG] = do_tm_cmd_get_rx_diag,
+ [DW3000_TM_CMD_CLEAR_RX_DIAG] = do_tm_cmd_clear_rx_diag,
+ [DW3000_TM_CMD_OTP_READ] = do_tm_cmd_otp_read,
+ [DW3000_TM_CMD_OTP_WRITE] = do_tm_cmd_otp_write,
+ [DW3000_TM_CMD_START_TX_CWTONE] = do_tm_cmd_start_tx_cwtone,
+ [DW3000_TM_CMD_STOP_TX_CWTONE] = do_tm_cmd_stop_tx_cwtone,
+ [DW3000_TM_CMD_START_CONTINUOUS_TX] = do_tm_cmd_start_cont_tx,
+ [DW3000_TM_CMD_STOP_CONTINUOUS_TX] = do_tm_cmd_stop_cont_tx,
+ [DW3000_TM_CMD_DEEP_SLEEP] = do_tm_cmd_deep_sleep,
+ [DW3000_TM_CMD_SET_HRP_PARAMS] = do_tm_cmd_set_hrp_uwb_params,
+ [DW3000_TM_CMD_SET_CHANNEL] = do_tm_cmd_set_channel,
+ };
+ u32 tm_cmd;
+ int ret;
+
+ ret = nla_parse(attr, DW3000_TM_ATTR_MAX, data, len, dw3000_tm_policy,
+ NULL);
+
+ if (ret)
+ return ret;
+
+ if (!attr[DW3000_TM_ATTR_CMD])
+ return -EINVAL;
+
+ params = (struct do_tm_cmd_params){ llhw, attr };
+
+ tm_cmd = nla_get_u32(attr[DW3000_TM_ATTR_CMD]);
+ /* Share the testmode's command with each thread-safe function */
+ trace_dw3000_tm_cmd(dw, tm_cmd);
+
+ if (tm_cmd < __DW3000_TM_CMD_AFTER_LAST && cmds[tm_cmd]) {
+ cmd.cmd = cmds[tm_cmd];
+ ret = dw3000_enqueue_generic(dw, &cmd);
+ } else
+ ret = -EOPNOTSUPP;
+
+ trace_dw3000_return_int(dw, ret);
+ return ret;
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode.h b/kernel/drivers/net/ieee802154/dw3000_testmode.h
new file mode 100644
index 0000000..09b7fcd
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_testmode.h
@@ -0,0 +1,20 @@
+#ifndef DW3000_TESTMODE_H
+#define DW3000_TESTMODE_H
+
+#include <net/mcps802154.h>
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+
+int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data, int len);
+
+#else
+
+static inline int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data,
+ int len)
+{
+ return 0;
+}
+
+#endif /* CONFIG_MCPS802154_TESTMODE */
+
+#endif /* DW3000_TESTMODE_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h b/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h
new file mode 100644
index 0000000..5628aa7
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h
@@ -0,0 +1,128 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef DW3000_TESTMODE_NL_H
+#define DW3000_TESTMODE_NL_H
+
+#include <linux/types.h>
+
+/**
+ * struct dw3000_rssi - Required data for RSSI calculation in userland.
+ * @cir_pwr: value of the channel impulse response power (CIR power)
+ * @pacc_cnt: value of the the preamble accumulation count (PACC count)
+ * @prf_64mhz: value of preamble repetition frequency (0 = 16MHz, 1 = 64MHz)
+ * @dgc_dec: value of dgc decision from DGC_CFG register
+ */
+struct dw3000_rssi {
+ uint32_t cir_pwr : 17;
+ uint16_t pacc_cnt : 11;
+ uint8_t prf_64mhz : 1;
+ uint8_t dgc_dec : 3;
+} __attribute__((__packed__));
+
+/* Since both DW3720 & DW3120 user manuals specify only 11-bits at most for
+ * diagnostic counters, we do the same for RSSI report number. */
+#define DW3000_RSSI_REPORTS_MAX (1 << 11)
+#define DW3000_TM_RSSI_DATA_MAX_LEN \
+ (DW3000_RSSI_REPORTS_MAX * sizeof(struct dw3000_rssi))
+
+/* OTP address limit */
+#define DW3000_OTP_ADDRESS_LIMIT 0x7f
+
+/* All dw3000 testmode interface attributes */
+enum dw3000_tm_attr {
+ __DW3000_TM_ATTR_INVALID = 0,
+ DW3000_TM_ATTR_CMD,
+ DW3000_TM_ATTR_RX_GOOD_CNT,
+ DW3000_TM_ATTR_RX_BAD_CNT,
+ DW3000_TM_ATTR_RSSI_DATA,
+ DW3000_TM_ATTR_OTP_ADDR,
+ DW3000_TM_ATTR_OTP_VAL,
+ DW3000_TM_ATTR_OTP_DONE,
+ DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS,
+ DW3000_TM_ATTR_CONTTX_FRAME_LENGHT,
+ DW3000_TM_ATTR_CONTTX_RATE,
+ DW3000_TM_ATTR_CONTTX_DURATION,
+
+ /* HRP parameters */
+ DW3000_TM_ATTR_PSR,
+ DW3000_TM_ATTR_SFD,
+ DW3000_TM_ATTR_PHR_RATE,
+ DW3000_TM_ATTR_DATA_RATE,
+
+ /* Complex channel parameters */
+ DW3000_TM_ATTR_PAGE,
+ DW3000_TM_ATTR_CHANNEL,
+ DW3000_TM_ATTR_PREAMBLE_CODE,
+
+ /* keep last */
+ __DW3000_TM_ATTR_AFTER_LAST,
+ DW3000_TM_ATTR_MAX = __DW3000_TM_ATTR_AFTER_LAST - 1,
+};
+
+/* All dw3000 testmode interface commands specified in DW3000_TM_ATTR_CMD */
+enum dw3000_tm_cmd {
+ __DW3000_TM_CMD_INVALID = 0,
+ DW3000_TM_CMD_START_RX_DIAG,
+ DW3000_TM_CMD_STOP_RX_DIAG,
+ DW3000_TM_CMD_GET_RX_DIAG,
+ DW3000_TM_CMD_CLEAR_RX_DIAG,
+ DW3000_TM_CMD_OTP_READ,
+ DW3000_TM_CMD_OTP_WRITE,
+
+ /* Start/Stop Continous Wave Tone */
+ DW3000_TM_CMD_START_TX_CWTONE,
+ DW3000_TM_CMD_STOP_TX_CWTONE,
+
+ /* Continuous TX : start/stop sending frame at regular interval */
+ DW3000_TM_CMD_START_CONTINUOUS_TX,
+ DW3000_TM_CMD_STOP_CONTINUOUS_TX,
+
+ /* TODO: CCC enum could be remove, as their are no more used. */
+ DW3000_TM_CMD_CCC_START,
+ DW3000_TM_CMD_CCC_TEST_SCRATCH,
+ DW3000_TM_CMD_CCC_TEST_SPI1,
+ DW3000_TM_CMD_CCC_TEST_SPI2,
+ DW3000_TM_CMD_CCC_READ_TLVS,
+ DW3000_TM_CMD_CCC_WRITE_TLVS,
+ DW3000_TM_CMD_CCC_TEST_DIRECT,
+ DW3000_TM_CMD_CCC_TEST_WAIT,
+ DW3000_TM_CMD_CCC_TEST_LATE,
+ DW3000_TM_CMD_CCC_TEST_CONFLICT,
+ DW3000_TM_CMD_CCC_TEST_OFFSET,
+
+ /* Deep sleep test */
+ DW3000_TM_CMD_DEEP_SLEEP,
+
+ /* Set HRP parameters */
+ DW3000_TM_CMD_SET_HRP_PARAMS,
+
+ /* Set complex channel */
+ DW3000_TM_CMD_SET_CHANNEL,
+
+ /* keep last */
+ __DW3000_TM_CMD_AFTER_LAST,
+ DW3000_TM_CMD_MAX = __DW3000_TM_CMD_AFTER_LAST - 1,
+};
+
+#endif /* DW3000_TESTMODE_NL_H */
diff --git a/kernel/drivers/net/ieee802154/dw3000_trc.c b/kernel/drivers/net/ieee802154/dw3000_trc.c
new file mode 100644
index 0000000..7f8080a
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_trc.c
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/module.h>
+
+#ifndef __CHECKER__
+#define CREATE_TRACE_POINTS
+#include "dw3000_trc.h"
+
+#endif
diff --git a/kernel/drivers/net/ieee802154/dw3000_trc.h b/kernel/drivers/net/ieee802154/dw3000_trc.h
new file mode 100644
index 0000000..29943e8
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_trc.h
@@ -0,0 +1,1535 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM dw3000
+
+#if !defined(__DW3000_TRACE) || defined(TRACE_HEADER_MULTI_READ)
+#define __DW3000_TRACE
+
+#include <linux/tracepoint.h>
+#include <linux/version.h>
+
+#include "dw3000.h"
+#include "dw3000_nfcc_coex.h"
+#include "dw3000_nfcc_coex_msg.h"
+#include "dw3000_testmode_nl.h"
+#include "dw3000_core_reg.h"
+#include "dw3000_core.h"
+
+#define MAXNAME 32
+#define DW_ENTRY __array(char, dw_name, MAXNAME)
+#define DW_ASSIGN strlcpy(__entry->dw_name, dw->dev->kobj.name, MAXNAME)
+#define DW_PR_FMT "%s"
+#define DW_PR_ARG __entry->dw_name
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+#define DW_TM_CMD_ENTRY __field(u32, cmd)
+#define DW_TM_CMD_ASSIGN __entry->cmd = cmd
+#define DW_TM_CMD_PR_FMT "cmd: %s"
+#define dw3000_tm_cmd_name(name) \
+ { \
+ DW3000_TM_CMD_##name, #name \
+ }
+/* clang-format off */
+#define DW_TM_CMD_SYMBOLS \
+ dw3000_tm_cmd_name(START_RX_DIAG), \
+ dw3000_tm_cmd_name(STOP_RX_DIAG), \
+ dw3000_tm_cmd_name(GET_RX_DIAG), \
+ dw3000_tm_cmd_name(CLEAR_RX_DIAG), \
+ dw3000_tm_cmd_name(START_TX_CWTONE), \
+ dw3000_tm_cmd_name(STOP_TX_CWTONE), \
+ dw3000_tm_cmd_name(OTP_READ), \
+ dw3000_tm_cmd_name(OTP_WRITE)
+/* clang-format on */
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_START_RX_DIAG);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_STOP_RX_DIAG);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_GET_RX_DIAG);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_CLEAR_RX_DIAG);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_START_TX_CWTONE);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_STOP_TX_CWTONE);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_OTP_READ);
+TRACE_DEFINE_ENUM(DW3000_TM_CMD_OTP_WRITE);
+#define DW_TM_CMD_PR_ARG __print_symbolic(__entry->cmd, DW_TM_CMD_SYMBOLS)
+#endif
+
+#define DW_PWR_ENTRY __field(int, state)
+#define DW_PWR_ASSIGN __entry->state = state
+#define DW_PWR_PR_FMT "state: %s"
+
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_OFF);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_DEEP_SLEEP);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_SLEEP);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_WAKE_UP);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_INIT_RC);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_IDLE_RC);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_IDLE_PLL);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_TX_WAIT);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_TX);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_RX_WAIT);
+TRACE_DEFINE_ENUM(DW3000_OP_STATE_RX);
+
+#define dw3000_op_state_name(name) \
+ { \
+ DW3000_OP_STATE_##name, #name \
+ }
+
+/* clang-format off */
+#define DW3000_OP_STATE_SYMBOLS \
+ dw3000_op_state_name(OFF), \
+ dw3000_op_state_name(DEEP_SLEEP), \
+ dw3000_op_state_name(SLEEP), \
+ dw3000_op_state_name(WAKE_UP), \
+ dw3000_op_state_name(INIT_RC), \
+ dw3000_op_state_name(IDLE_RC), \
+ dw3000_op_state_name(IDLE_PLL), \
+ dw3000_op_state_name(TX_WAIT), \
+ dw3000_op_state_name(TX), \
+ dw3000_op_state_name(RX_WAIT), \
+ dw3000_op_state_name(RX)
+/* clang-format on */
+
+#define dw3000_pwr_name(name) \
+ { \
+ DW3000_PWR_##name, #name \
+ }
+/* clang-format off */
+#define DW_PWR_SYMBOLS \
+ dw3000_pwr_name(OFF), \
+ dw3000_pwr_name(DEEPSLEEP), \
+ dw3000_pwr_name(RUN), \
+ dw3000_pwr_name(IDLE), \
+ dw3000_pwr_name(RX), \
+ dw3000_pwr_name(TX)
+/* clang-format on */
+TRACE_DEFINE_ENUM(DW3000_PWR_OFF);
+TRACE_DEFINE_ENUM(DW3000_PWR_DEEPSLEEP);
+TRACE_DEFINE_ENUM(DW3000_PWR_RUN);
+TRACE_DEFINE_ENUM(DW3000_PWR_IDLE);
+TRACE_DEFINE_ENUM(DW3000_PWR_RX);
+TRACE_DEFINE_ENUM(DW3000_PWR_TX);
+#define DW_PWR_PR_ARG __print_symbolic(__entry->state, DW_PWR_SYMBOLS)
+
+#define dw3000_sys_status_hi_name(name) \
+ { \
+ (u64)(DW3000_SYS_STATUS_HI_##name##_BIT_MASK) << 32, #name \
+ }
+#define dw3000_sys_status_name(name) \
+ { \
+ DW3000_SYS_STATUS_##name##_BIT_MASK, #name \
+ }
+
+#define DW_SYS_STATUS_FLAGS_ENTRY __field(u64, status)
+#define DW_SYS_STATUS_FLAGS_ASSIGN __entry->status = status
+#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE)
+#define DW_SYS_STATUS_FLAGS_PR_FMT "status: %s"
+/* clang-format off */
+#define DW_SYS_STATUS_FLAGS \
+ dw3000_sys_status_hi_name(RXSTS), \
+ dw3000_sys_status_hi_name(TXSTS), \
+ dw3000_sys_status_hi_name(SEMA_ERR), \
+ dw3000_sys_status_hi_name(COEX_CLR), \
+ dw3000_sys_status_hi_name(COEX_ERR), \
+ dw3000_sys_status_hi_name(CCA_FAIL), \
+ dw3000_sys_status_hi_name(SPIERR), \
+ dw3000_sys_status_hi_name(SPI_UNF), \
+ dw3000_sys_status_hi_name(SPI_OVF), \
+ dw3000_sys_status_hi_name(CMD_ERR), \
+ dw3000_sys_status_hi_name(AES_ERR), \
+ dw3000_sys_status_hi_name(AES_DONE), \
+ dw3000_sys_status_hi_name(GPIO_IRQ), \
+ dw3000_sys_status_hi_name(VT_DET), \
+ dw3000_sys_status_hi_name(PGFCAL_ERR), \
+ dw3000_sys_status_hi_name(RXPREJ), \
+ dw3000_sys_status_name(TIMER1), \
+ dw3000_sys_status_name(TIMER0), \
+ dw3000_sys_status_name(ARFE), \
+ dw3000_sys_status_name(CPERR), \
+ dw3000_sys_status_name(HPDWARN), \
+ dw3000_sys_status_name(RXSTO), \
+ dw3000_sys_status_name(PLL_HILO), \
+ dw3000_sys_status_name(RCINIT), \
+ dw3000_sys_status_name(SPIRDY), \
+ dw3000_sys_status_name(LCSSERR), \
+ dw3000_sys_status_name(RXPTO), \
+ dw3000_sys_status_name(RXOVRR), \
+ dw3000_sys_status_name(VWARN), \
+ dw3000_sys_status_name(CIAERR), \
+ dw3000_sys_status_name(RXFTO), \
+ dw3000_sys_status_name(RXFSL), \
+ dw3000_sys_status_name(RXFCE), \
+ dw3000_sys_status_name(RXFCG), \
+ dw3000_sys_status_name(RXFR), \
+ dw3000_sys_status_name(RXPHE), \
+ dw3000_sys_status_name(RXPHD), \
+ dw3000_sys_status_name(CIA_DONE), \
+ dw3000_sys_status_name(RXSFDD), \
+ dw3000_sys_status_name(RXPRD), \
+ dw3000_sys_status_name(TXFRS), \
+ dw3000_sys_status_name(TXPHS), \
+ dw3000_sys_status_name(TXPRS), \
+ dw3000_sys_status_name(TXFRB), \
+ dw3000_sys_status_name(AAT), \
+ dw3000_sys_status_name(SPICRCERR), \
+ dw3000_sys_status_name(CLK_PLL_LOCK), \
+ dw3000_sys_status_name(IRQS)
+/* clang-format on */
+#define DW_SYS_STATUS_FLAGS_PR_ARG \
+ __print_flags_u64(__entry->status, "|", DW_SYS_STATUS_FLAGS)
+#else
+/* __print_flags_u64 is not supported. */
+#define DW_SYS_STATUS_FLAGS_PR_FMT "status: 0x%0llx"
+#define DW_SYS_STATUS_FLAGS_PR_ARG __entry->status
+#endif
+
+#define RX_FRAME_CONFIG_FLAGS_ENTRY __field(u8, flags)
+#define RX_FRAME_CONFIG_FLAGS_ASSIGN entry->flags = flags
+#define RX_FRAME_CONFIG_FLAGS_PR_FMT "flags: %s"
+
+#define mcps802154_rx_frame_config_name(name) \
+ { \
+ MCPS802154_RX_FRAME_CONFIG_##name, #name \
+ }
+
+/* clang-format off */
+#define RX_FRAME_CONFIG_FLAGS \
+ mcps802154_rx_frame_config_name(TIMESTAMP_DTU), \
+ mcps802154_rx_frame_config_name(AACK), \
+ mcps802154_rx_frame_config_name(RANGING), \
+ mcps802154_rx_frame_config_name(KEEP_RANGING_CLOCK), \
+ mcps802154_rx_frame_config_name(RANGING_PDOA), \
+ mcps802154_rx_frame_config_name(SP3), \
+ mcps802154_rx_frame_config_name(SP2), \
+ mcps802154_rx_frame_config_name(SP1), \
+ mcps802154_rx_frame_config_name(STS_MODE_MASK)
+/* clang-format on */
+
+#define RX_FRAME_CONFIG_FLAGS_PR_ARG \
+ __print_flags(__entry->flags, "|", RX_FRAME_CONFIG_FLAGS)
+
+#define RX_FRAME_INFO_FLAGS_ENTRY __field(u16, flags)
+#define RX_FRAME_INFO_FLAGS_ASSIGN entry->flags = flags
+#define RX_FRAME_INFO_FLAGS_PR_FMT "flags: %s"
+
+#define mcps802154_rx_frame_info_name(name) \
+ { \
+ MCPS802154_RX_FRAME_INFO_##name, #name \
+ }
+
+/* clang-format off */
+#define RX_FRAME_INFO_FLAGS \
+ mcps802154_rx_frame_info_name(TIMESTAMP_DTU), \
+ mcps802154_rx_frame_info_name(TIMESTAMP_RCTU), \
+ mcps802154_rx_frame_info_name(LQI), \
+ mcps802154_rx_frame_info_name(RSSI), \
+ mcps802154_rx_frame_info_name(RANGING_FOM), \
+ mcps802154_rx_frame_info_name(RANGING_OFFSET), \
+ mcps802154_rx_frame_info_name(RANGING_PDOA), \
+ mcps802154_rx_frame_info_name(RANGING_PDOA_FOM), \
+ mcps802154_rx_frame_info_name(RANGING_STS_TIMESTAMP_RCTU), \
+ mcps802154_rx_frame_info_name(RANGING_STS_FOM), \
+ mcps802154_rx_frame_info_name(AACK)
+/* clang-format on */
+
+#define RX_FRAME_INFO_FLAGS_PR_ARG \
+ __print_flags(__entry->flags, "|", RX_FRAME_INFO_FLAGS)
+
+#define TX_FRAME_CONFIG_FLAGS_ENTRY __field(u8, flags)
+#define TX_FRAME_CONFIG_FLAGS_ASSIGN entry->flags = flags
+#define TX_FRAME_CONFIG_FLAGS_PR_FMT "flags: %s"
+
+#define mcps802154_tx_frame_config_name(name) \
+ { \
+ MCPS802154_TX_FRAME_CONFIG_##name, #name \
+ }
+
+/* clang-format off */
+#define TX_FRAME_CONFIG_FLAGS \
+ mcps802154_tx_frame_config_name(TIMESTAMP_DTU), \
+ mcps802154_tx_frame_config_name(CCA), \
+ mcps802154_tx_frame_config_name(RANGING), \
+ mcps802154_tx_frame_config_name(KEEP_RANGING_CLOCK), \
+ mcps802154_tx_frame_config_name(SP3), \
+ mcps802154_tx_frame_config_name(SP2), \
+ mcps802154_tx_frame_config_name(SP1), \
+ mcps802154_tx_frame_config_name(STS_MODE_MASK)
+/* clang-format on */
+
+#define TX_FRAME_CONFIG_FLAGS_PR_ARG \
+ __print_flags(__entry->flags, "|", TX_FRAME_CONFIG_FLAGS)
+
+#define dw3000_nfcc_coex_tlv_type_name(name) \
+ { \
+ DW3000_NFCC_COEX_TLV_TYPE_##name, #name \
+ }
+
+#define DW3000_NFCC_COEX_TLV_TYPE_ENTRY \
+ __field(enum dw3000_nfcc_coex_tlv_type, type)
+#define DW3000_NFCC_COEX_TLV_TYPE_ASSIGN __entry->type = type
+#define DW3000_NFCC_COEX_TLV_TYPE_PR_FMT "type: %s"
+/* clang-format off */
+#define DW3000_NFCC_COEX_TLV_TYPE_SYMBOLS \
+ dw3000_nfcc_coex_tlv_type_name(UNSPEC), \
+ dw3000_nfcc_coex_tlv_type_name(SESSION_TIME0), \
+ dw3000_nfcc_coex_tlv_type_name(SLOT_LIST), \
+ dw3000_nfcc_coex_tlv_type_name(TLV_UWBCNT_OFFS), \
+ dw3000_nfcc_coex_tlv_type_name(ERROR), \
+ dw3000_nfcc_coex_tlv_type_name(SLOT_LIST_UUS)
+/* clang-format on */
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_UNSPEC);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_ERROR);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS);
+#define DW3000_NFCC_COEX_TLV_TYPE_ARG \
+ __print_symbolic(__entry->type, DW3000_NFCC_COEX_TLV_TYPE_SYMBOLS)
+
+#define dw3000_dss_stats_name(name) \
+ { \
+ DW3000_DSS_STAT_##name##_BIT_MASK, #name \
+ }
+/* clang-format off */
+#define DW3000_DSS_STATS_SYMBOLS \
+ dw3000_dss_stats_name(SPI1_AVAIL), \
+ dw3000_dss_stats_name(SPI2_AVAIL)
+/* clang-format on */
+TRACE_DEFINE_ENUM(DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK);
+TRACE_DEFINE_ENUM(DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK);
+
+#define dw3000_nfcc_coex_send_name(name) \
+ { \
+ DW3000_NFCC_COEX_SEND_##name, #name \
+ }
+
+#define DW3000_NFCC_COEX_SEND_ENTRY __field(enum dw3000_nfcc_coex_send, send)
+#define DW3000_NFCC_COEX_SEND_ASSIGN __entry->send = send
+#define DW3000_NFCC_COEX_SEND_PR_FMT "send: %s"
+/* clang-format off */
+#define DW3000_NFCC_COEX_SEND \
+ dw3000_nfcc_coex_send_name(CLK_SYNC), \
+ dw3000_nfcc_coex_send_name(CLK_OFFSET), \
+ dw3000_nfcc_coex_send_name(STOP)
+/* clang-format on */
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_CLK_SYNC);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_CLK_OFFSET);
+TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_STOP);
+#define DW3000_NFCC_COEX_SEND_ARG \
+ __print_symbolic(__entry->send, DW3000_NFCC_COEX_SEND)
+
+/* We don't want clang-format to modify the following events definition!
+ Look at net/wireless/trace.h for the required format. */
+/* clang-format off */
+
+/*************************************************************
+ * dw3000 functions return traces *
+ *************************************************************/
+
+DECLARE_EVENT_CLASS(dw_only_evt,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT, DW_PR_ARG)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_return_void,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_return_int,
+ TP_PROTO(struct dw3000 *dw, int ret),
+ TP_ARGS(dw, ret),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, ret)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ret = ret;
+ ),
+ TP_printk(DW_PR_FMT ", return: %d", DW_PR_ARG, __entry->ret)
+);
+
+TRACE_EVENT(dw3000_return_int_u32,
+ TP_PROTO(struct dw3000 *dw, int ret, u32 val),
+ TP_ARGS(dw, ret, val),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, ret)
+ __field(u32, val)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ret = ret;
+ __entry->val = val;
+ ),
+ TP_printk(DW_PR_FMT ", return: %d, value: 0x%x", DW_PR_ARG,
+ __entry->ret, __entry->val)
+);
+
+TRACE_EVENT(dw3000_return_int_u64,
+ TP_PROTO(struct dw3000 *dw, int ret, u64 val),
+ TP_ARGS(dw, ret, val),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, ret)
+ __field(u64, val)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ret = ret;
+ __entry->val = val;
+ ),
+ TP_printk(DW_PR_FMT ", return: %d, value: 0x%llx", DW_PR_ARG,
+ __entry->ret, __entry->val)
+);
+
+/*************************************************************
+ * dw3000 mcps functions traces *
+ *************************************************************/
+
+DEFINE_EVENT(dw_only_evt, dw3000_mcps_start,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_mcps_stop,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_mcps_tx_frame,
+ TP_PROTO(struct dw3000 *dw, u8 flags, u16 len),
+ TP_ARGS(dw, flags, len),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ TX_FRAME_CONFIG_FLAGS_ENTRY
+ __field(u16, len)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ TX_FRAME_CONFIG_FLAGS_ASSIGN;
+ __entry->len = len;
+ ),
+ TP_printk(DW_PR_FMT ", " TX_FRAME_CONFIG_FLAGS_PR_FMT ", skb->len: %d",
+ DW_PR_ARG, TX_FRAME_CONFIG_FLAGS_PR_ARG,__entry->len)
+);
+
+TRACE_EVENT(dw3000_mcps_tx_frame_too_late,
+ TP_PROTO(struct dw3000 *dw, u32 tx_date_dtu, u32 cur_time_dtu),
+ TP_ARGS(dw, tx_date_dtu, cur_time_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, tx_date_dtu)
+ __field(u32, cur_time_dtu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->tx_date_dtu = tx_date_dtu;
+ __entry->cur_time_dtu = cur_time_dtu;
+ ),
+ TP_printk(DW_PR_FMT ", cannot program delayed, tx_date_dtu: 0x%x"
+ ", cur_time_dtu: 0x%x", DW_PR_ARG, __entry->tx_date_dtu,
+ __entry->cur_time_dtu)
+);
+
+TRACE_EVENT(dw3000_mcps_rx_enable,
+ TP_PROTO(struct dw3000 *dw, u8 flags, int timeout),
+ TP_ARGS(dw, flags, timeout),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ RX_FRAME_CONFIG_FLAGS_ENTRY
+ __field(int, timeout)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ RX_FRAME_CONFIG_FLAGS_ASSIGN;
+ __entry->timeout = timeout;
+ ),
+ TP_printk(DW_PR_FMT ", " RX_FRAME_CONFIG_FLAGS_PR_FMT ", timeout: %d",
+ DW_PR_ARG, RX_FRAME_CONFIG_FLAGS_PR_ARG, __entry->timeout)
+);
+
+TRACE_EVENT(dw3000_mcps_rx_enable_too_late,
+ TP_PROTO(struct dw3000 *dw, u32 rx_date_dtu, u32 cur_time_dtu),
+ TP_ARGS(dw, rx_date_dtu, cur_time_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, rx_date_dtu)
+ __field(u32, cur_time_dtu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->rx_date_dtu = rx_date_dtu;
+ __entry->cur_time_dtu = cur_time_dtu;
+ ),
+ TP_printk(DW_PR_FMT ", too late to program delayed, rx_date_dtu: 0x%x, current_dtu: 0x%x",
+ DW_PR_ARG, __entry->rx_date_dtu, __entry->cur_time_dtu)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_mcps_rx_disable,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_mcps_rx_get_frame,
+ TP_PROTO(struct dw3000 *dw, u16 flags),
+ TP_ARGS(dw, flags),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ RX_FRAME_INFO_FLAGS_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ RX_FRAME_INFO_FLAGS_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT ", " RX_FRAME_INFO_FLAGS_PR_FMT,
+ DW_PR_ARG, RX_FRAME_INFO_FLAGS_PR_ARG)
+);
+
+TRACE_EVENT(dw3000_mcps_rx_get_error_frame,
+ TP_PROTO(struct dw3000 *dw, u16 flags),
+ TP_ARGS(dw, flags),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ RX_FRAME_INFO_FLAGS_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ RX_FRAME_INFO_FLAGS_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT ", " RX_FRAME_INFO_FLAGS_PR_FMT,
+ DW_PR_ARG, RX_FRAME_INFO_FLAGS_PR_ARG)
+ );
+
+DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_tx,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_rx,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_idle,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_idle_late,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_idle,
+ TP_PROTO(struct dw3000 *dw, bool timeout, u32 timeout_dtu,
+ enum operational_state next_operational_state),
+ TP_ARGS(dw, timeout, timeout_dtu, next_operational_state),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(bool, timeout)
+ __field(u32, timeout_dtu)
+ __field(enum operational_state, next_operational_state)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->timeout = timeout;
+ __entry->timeout_dtu = timeout_dtu;
+ __entry->next_operational_state = next_operational_state;
+ ),
+ TP_printk(DW_PR_FMT ", timeout: %s, timeout_dtu: 0x%0x, "
+ "next_operational_state: %s" , DW_PR_ARG,
+ __entry->timeout ? "true" : "false", __entry->timeout_dtu,
+ __print_symbolic(__entry->next_operational_state,
+ DW3000_OP_STATE_SYMBOLS))
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_mcps_reset,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_mcps_get_timestamp,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_mcps_set_channel,
+ TP_PROTO(struct dw3000 *dw, u8 page, u8 channel, u8 pcode),
+ TP_ARGS(dw, page, channel, pcode),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, page)
+ __field(u8, channel)
+ __field(u8, pcode)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->page = page;
+ __entry->channel = channel;
+ __entry->pcode = pcode;
+ ),
+ TP_printk(DW_PR_FMT ", page: %u, channel: %u, preamble_code: %u",
+ DW_PR_ARG, __entry->page, __entry->channel,
+ __entry->pcode)
+);
+
+TRACE_EVENT(dw3000_mcps_set_hw_addr_filt,
+ TP_PROTO(struct dw3000 *dw, u8 changed),
+ TP_ARGS(dw, changed),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, changed)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->changed = changed;
+ ),
+ TP_printk(DW_PR_FMT ", changed: 0x%x", DW_PR_ARG,
+ __entry->changed)
+);
+
+TRACE_EVENT(dw3000_mcps_set_sts_params,
+ TP_PROTO(struct dw3000 *dw, const struct mcps802154_sts_params *p),
+ TP_ARGS(dw, p),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, n_segs)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->n_segs = p->n_segs;
+ ),
+ TP_printk(DW_PR_FMT ", n_segs: %u", DW_PR_ARG,
+ __entry->n_segs)
+);
+
+/*************************************************************
+ * dw3000 RCTU converter functions traces *
+ *************************************************************/
+TRACE_EVENT(dw3000_rctu_convert_align,
+ TP_PROTO(struct dw3000 *dw, u32 rmarker_dtu),
+ TP_ARGS(dw, rmarker_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, rmarker_dtu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->rmarker_dtu = rmarker_dtu;
+ ),
+ TP_printk(DW_PR_FMT ", rmarker_dtu: 0x%x", DW_PR_ARG,
+ __entry->rmarker_dtu)
+);
+
+TRACE_EVENT(dw3000_rctu_convert_synced,
+ TP_PROTO(struct dw3000 *dw, u64 rmarker_rctu),
+ TP_ARGS(dw, rmarker_rctu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u64, rmarker_rctu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->rmarker_rctu = rmarker_rctu;
+ ),
+ TP_printk(DW_PR_FMT ", rmarker_rctu: 0x%llx", DW_PR_ARG,
+ __entry->rmarker_rctu)
+);
+
+TRACE_EVENT(dw3000_rctu_convert_rx,
+ TP_PROTO(struct dw3000 *dw, u32 rmarker_dtu, u64 ts_rctu, u64 rmarker_rctu),
+ TP_ARGS(dw, rmarker_dtu, ts_rctu, rmarker_rctu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, rmarker_dtu)
+ __field(u64, ts_rctu)
+ __field(u64, rmarker_rctu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->rmarker_dtu = rmarker_dtu;
+ __entry->ts_rctu = ts_rctu;
+ __entry->rmarker_rctu = rmarker_rctu;
+ ),
+ TP_printk(DW_PR_FMT ", rmarker_dtu: 0x%x, ts_rctu: 0x%llx, "
+ "rmarker_rctu: 0x%llx",
+ DW_PR_ARG, __entry->rmarker_dtu, __entry->ts_rctu,
+ __entry->rmarker_rctu)
+);
+
+TRACE_EVENT(dw3000_rctu_convert_tx,
+ TP_PROTO(struct dw3000 *dw, u32 ts_dtu, u32 ant_offset, u64 rmarker_rctu),
+ TP_ARGS(dw, ts_dtu, ant_offset, rmarker_rctu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, ts_dtu)
+ __field(u32, ant_offset)
+ __field(u64, rmarker_rctu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ts_dtu = ts_dtu;
+ __entry->ant_offset = ant_offset;
+ __entry->rmarker_rctu = rmarker_rctu;
+ ),
+ TP_printk(DW_PR_FMT ", ts_dtu: 0x%x, ant_offset: 0x%x, rmarker_rctu: 0x%llx",
+ DW_PR_ARG, __entry->ts_dtu, __entry->ant_offset,
+ __entry->rmarker_rctu)
+);
+
+/*************************************************************
+ * dw3000 core functions traces *
+ *************************************************************/
+TRACE_EVENT(dw3000_isr,
+ TP_PROTO(struct dw3000 *dw, u64 status),
+ TP_ARGS(dw, status),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ DW_SYS_STATUS_FLAGS_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ DW_SYS_STATUS_FLAGS_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT ", " DW_SYS_STATUS_FLAGS_PR_FMT,
+ DW_PR_ARG, DW_SYS_STATUS_FLAGS_PR_ARG)
+);
+
+TRACE_EVENT(dw3000_check_idlerc,
+ TP_PROTO(struct dw3000 *dw, u32 low_sys_status),
+ TP_ARGS(dw, low_sys_status),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, low_sys_status)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->low_sys_status = low_sys_status;
+ ),
+ TP_printk(DW_PR_FMT ", low_sys_status: 0x%08x",
+ DW_PR_ARG, __entry->low_sys_status)
+);
+
+TRACE_EVENT(dw3000_wakeup_and_wait,
+ TP_PROTO(struct dw3000 *dw, enum operational_state operational_state),
+ TP_ARGS(dw, operational_state),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(enum operational_state, operational_state)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->operational_state = operational_state;
+ ),
+ TP_printk(DW_PR_FMT ", operational_state: %s", DW_PR_ARG,
+ __print_symbolic(__entry->operational_state,
+ DW3000_OP_STATE_SYMBOLS))
+);
+
+TRACE_EVENT(dw3000_set_operational_state,
+ TP_PROTO(struct dw3000 *dw, enum operational_state operational_state),
+ TP_ARGS(dw, operational_state),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(enum operational_state, operational_state)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->operational_state = operational_state;
+ ),
+ TP_printk(DW_PR_FMT ", operational_state: %s", DW_PR_ARG,
+ __print_symbolic(__entry->operational_state,
+ DW3000_OP_STATE_SYMBOLS))
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_deepsleep_wakeup,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_idle_timeout,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_idle_cancel_timer,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_check_operational_state,
+ TP_PROTO(struct dw3000 *dw, int delay_dtu,
+ enum operational_state current_operational_state,
+ enum operational_state next_operational_state),
+ TP_ARGS(dw, delay_dtu, current_operational_state,
+ next_operational_state),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, delay_dtu)
+ __field(enum operational_state, current_operational_state)
+ __field(enum operational_state, next_operational_state)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->delay_dtu = delay_dtu;
+ __entry->current_operational_state = current_operational_state;
+ __entry->next_operational_state = next_operational_state;
+ ),
+ TP_printk(DW_PR_FMT ", delay_dtu: %d, current_operational_state: %s, "
+ "next_operational_state: %s", DW_PR_ARG, __entry->delay_dtu,
+ __print_symbolic(__entry->current_operational_state,
+ DW3000_OP_STATE_SYMBOLS),
+ __print_symbolic(__entry->next_operational_state,
+ DW3000_OP_STATE_SYMBOLS))
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_read_rx_timestamp,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_resync_dtu_sys_time,
+ TP_PROTO(struct dw3000 *dw, u32 sys_time_sync, u32 dtu_sync),
+ TP_ARGS(dw, sys_time_sync, dtu_sync),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, sys_time_sync)
+ __field(u32, dtu_sync)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->sys_time_sync = sys_time_sync;
+ __entry->dtu_sync = dtu_sync;
+ ),
+ TP_printk(DW_PR_FMT ", sys_time_sync: 0x%08x, dtu_sync: 0x%08x",
+ DW_PR_ARG, __entry->sys_time_sync, __entry->dtu_sync)
+);
+
+TRACE_EVENT(dw3000_deep_sleep,
+ TP_PROTO(struct dw3000 *dw, int rc),
+ TP_ARGS(dw, rc),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, result)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->result = rc;
+ ),
+ TP_printk(DW_PR_FMT ", result: %d", DW_PR_ARG,
+ __entry->result)
+);
+
+TRACE_EVENT(dw3000_deep_sleep_enter,
+ TP_PROTO(struct dw3000 *dw, s64 time_before),
+ TP_ARGS(dw, time_before),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(s64, time_before)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->time_before = time_before;
+ ),
+ TP_printk(DW_PR_FMT ", time_ns: %lld", DW_PR_ARG, __entry->time_before)
+);
+
+TRACE_EVENT(dw3000_wakeup_timer_start,
+ TP_PROTO(struct dw3000 *dw, int delay_us),
+ TP_ARGS(dw, delay_us),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, delay_us)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->delay_us = delay_us;
+ ),
+ TP_printk(DW_PR_FMT ", delay: %dus", DW_PR_ARG, __entry->delay_us)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_wakeup,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_wakeup_done,
+ TP_PROTO(struct dw3000 *dw, s64 sleep_time_us, u32 sleep_enter_dtu,
+ u32 dtu_next_op, enum operational_state next_op),
+ TP_ARGS(dw, sleep_time_us, sleep_enter_dtu, dtu_next_op, next_op),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(s64, sleep_time_us)
+ __field(u32, sleep_enter_dtu)
+ __field(u32, dtu_next_op)
+ __field(enum operational_state, next_op)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->sleep_time_us = sleep_time_us;
+ __entry->sleep_enter_dtu = sleep_enter_dtu;
+ __entry->dtu_next_op = dtu_next_op;
+ __entry->next_op = next_op;
+ ),
+ TP_printk(DW_PR_FMT ", sleep_us: %lld, sleep_enter_dtu: 0x%x, "
+ "next_op_date: 0x%x, next_op: %s", DW_PR_ARG,
+ __entry->sleep_time_us, __entry->sleep_enter_dtu,
+ __entry->dtu_next_op,
+ __print_symbolic(__entry->next_op, DW3000_OP_STATE_SYMBOLS))
+);
+
+TRACE_EVENT(dw3000_power_stats,
+ TP_PROTO(struct dw3000 *dw, int state, u64 boot_time_ns, int len_or_date),
+ TP_ARGS(dw, state, boot_time_ns, len_or_date),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ DW_PWR_ENTRY
+ __field(u64, boot_time_ns)
+ __field(int, len_or_date)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ DW_PWR_ASSIGN;
+ __entry->boot_time_ns = boot_time_ns;
+ __entry->len_or_date = len_or_date;
+ ),
+ TP_printk(DW_PR_FMT ", " DW_PWR_PR_FMT ", boot_time_ns: %llu, len_or_date: %u",
+ DW_PR_ARG, DW_PWR_PR_ARG, __entry->boot_time_ns,
+ (unsigned)__entry->len_or_date)
+);
+
+TRACE_EVENT(dw3000_prog_xtrim,
+ TP_PROTO(struct dw3000 *dw, int res, int value, int bias),
+ TP_ARGS(dw, res, value, bias),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, res)
+ __field(int, value)
+ __field(int, bias)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->res = res;
+ __entry->value = value;
+ __entry->bias = bias;
+ ),
+ TP_printk(DW_PR_FMT ", res: %d, xtrim: %d, bias: %d",
+ DW_PR_ARG, __entry->res, __entry->value, __entry->bias)
+);
+
+TRACE_EVENT(dw3000_set_antenna_gpio,
+ TP_PROTO(struct dw3000 *dw, int res, u8 gpio, u8 value),
+ TP_ARGS(dw, res, gpio, value),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, res)
+ __field(u8, gpio)
+ __field(u8, value)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->res = res;
+ __entry->gpio = gpio;
+ __entry->value = value;
+ ),
+ TP_printk(DW_PR_FMT ", res: %d, gpio: %u, value: %u",
+ DW_PR_ARG, __entry->res, __entry->gpio, __entry->value)
+);
+
+TRACE_EVENT(dw3000_read_frame_cir_data,
+ TP_PROTO(struct dw3000 *dw, u8 prf, u8 stsMode, u64 utime),
+ TP_ARGS(dw, prf, stsMode, utime),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, prf)
+ __field(u8, stsMode)
+ __field(u64, utime)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->prf = prf;
+ __entry->stsMode = stsMode;
+ __entry->utime = utime;
+ ),
+ TP_printk(DW_PR_FMT ", utime: %llu, prf: %u, sts: %u",
+ DW_PR_ARG, __entry->utime, __entry->prf, __entry->stsMode)
+);
+
+TRACE_EVENT(dw3000_read_frame_cir_data_return,
+ TP_PROTO(struct dw3000 *dw, u64 ts, u32 f1, u32 f2, u32 f3,
+ u16 acc, u16 fpidx, s32 off, u32 filter),
+ TP_ARGS(dw, ts, f1, f2, f3, acc, fpidx, off, filter),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u64, ts)
+ __field(u32, f1)
+ __field(u32, f2)
+ __field(u32, f3)
+ __field(u16, acc)
+ __field(u16, fpidx)
+ __field(s32, off)
+ __field(u32, filter)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ts = ts;
+ __entry->f1 = f1;
+ __entry->f2 = f2;
+ __entry->f3 = f3;
+ __entry->acc = acc;
+ __entry->fpidx = fpidx;
+ __entry->off = off;
+ __entry->filter = filter;
+ ),
+ TP_printk(DW_PR_FMT ", ts: %llu, f1: %u, f2: %u, f3: %u, "
+ "acc: %hu, fpidx: %hu, offset: %d, filter: 0x%x",
+ DW_PR_ARG, __entry->ts, __entry->f1, __entry->f2,
+ __entry->f3, __entry->acc, __entry->fpidx, __entry->off,
+ __entry->filter)
+);
+
+TRACE_EVENT(dw3000_coex_gpio,
+ TP_PROTO(struct dw3000 *dw, bool state, int delay_us, u32 expire, bool status),
+ TP_ARGS(dw, state, delay_us, expire, status),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(bool, state)
+ __field(int, delay_us)
+ __field(u32, expire)
+ __field(bool, status)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->state = state;
+ __entry->delay_us = delay_us;
+ __entry->expire = expire;
+ __entry->status = status;
+ ),
+ TP_printk(DW_PR_FMT ", current state: %s, new state: %s, delay_us: %d, expire: %u",
+ DW_PR_ARG, __entry->status ? "ON" : "OFF", __entry->state ? "ON" : "OFF",
+ __entry->delay_us, (unsigned)__entry->expire)
+);
+
+TRACE_EVENT(dw3000_coex_gpio_start,
+ TP_PROTO(struct dw3000 *dw, int delay_us, bool status, int coex_interval_us),
+ TP_ARGS(dw, delay_us, status, coex_interval_us),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, delay_us)
+ __field(bool, status)
+ __field(int, coex_interval_us)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->delay_us = delay_us;
+ __entry->status = status;
+ __entry->coex_interval_us = coex_interval_us;
+ ),
+ TP_printk(DW_PR_FMT ", delay_us: %d, status: %s, coex_interval_us: %d",
+ DW_PR_ARG, __entry->delay_us,
+ __entry->status ? "ON" : "OFF", __entry->coex_interval_us)
+);
+
+TRACE_EVENT(dw3000_coex_gpio_stop,
+ TP_PROTO(struct dw3000 *dw, bool status),
+ TP_ARGS(dw, status),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(bool, status)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->status = status;
+ ),
+ TP_printk(DW_PR_FMT ", status: %s",
+ DW_PR_ARG,
+ __entry->status ? "ON" : "OFF")
+);
+
+TRACE_EVENT(dw3000_adjust_tx_power,
+ TP_PROTO(struct dw3000 *dw, u32 base_power, u32 adjusted_power,
+ u32 frm_dur, u16 pl_len, u8 chan, u16 th_boost,
+ u16 app_boost),
+ TP_ARGS(dw, base_power, adjusted_power, frm_dur, pl_len, chan,
+ th_boost, app_boost),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, base_power)
+ __field(u32, adjusted_power)
+ __field(u32, frm_dur)
+ __field(u16, pl_len)
+ __field(u8, chan)
+ __field(u16, th_boost)
+ __field(u16, app_boost)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->base_power = base_power;
+ __entry->adjusted_power = adjusted_power;
+ __entry->frm_dur = frm_dur;
+ __entry->pl_len = pl_len;
+ __entry->chan = chan;
+ __entry->th_boost = th_boost;
+ __entry->app_boost = app_boost;
+ ),
+ TP_printk(DW_PR_FMT " base pwr: 0x%08x, adjusted: 0x%08x (chan: %u,"
+ "frm_dur: %u, PL_len: %u, th. boost: %u, applied boost: %u)",
+ DW_PR_ARG, __entry->base_power, __entry->adjusted_power,
+ __entry->chan, __entry->frm_dur, __entry->pl_len,
+ __entry->th_boost, __entry->app_boost)
+);
+
+TRACE_EVENT(dw3000_rx_rssi,
+ TP_PROTO(struct dw3000 *dw, const char *chip_name, bool sts, u32 cir_pwr,
+ u16 pacc_cnt, u8 prf_64mhz, u8 dgc_dec),
+ TP_ARGS(dw, chip_name, sts, cir_pwr, pacc_cnt, prf_64mhz, dgc_dec),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(const char *, chip_name)
+ __field(bool, sts)
+ __field(u32, cir_pwr)
+ __field(u16, pacc_cnt)
+ __field(u8, prf_64mhz)
+ __field(u8, dgc_dec)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->chip_name = chip_name;
+ __entry->sts = sts;
+ __entry->cir_pwr = cir_pwr;
+ __entry->pacc_cnt = pacc_cnt;
+ __entry->prf_64mhz = prf_64mhz;
+ __entry->dgc_dec = dgc_dec;
+ ),
+ TP_printk(DW_PR_FMT ", chip: %s, sts: %u, cir_pwr: %u, pacc_cnt: %u"
+ ", prf_64mhz: %u, dgc_dec: %u",
+ DW_PR_ARG, __entry->chip_name, __entry->sts,
+ __entry->cir_pwr, __entry->pacc_cnt, __entry->prf_64mhz,
+ __entry->dgc_dec)
+ );
+
+TRACE_EVENT(dw3000_isr_dss_stat,
+ TP_PROTO(struct dw3000 *dw, u8 dss_stat),
+ TP_ARGS(dw, dss_stat),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, dss_stat)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->dss_stat = dss_stat;
+ ),
+ TP_printk(DW_PR_FMT ", dss_stat: %s", DW_PR_ARG,
+ __print_flags(__entry->dss_stat, "|",
+ DW3000_DSS_STATS_SYMBOLS))
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_header_put,
+ TP_PROTO(struct dw3000 *dw, u8 ver_id, u8 seq_num),
+ TP_ARGS(dw, ver_id, seq_num),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, ver_id)
+ __field(u8, seq_num)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->ver_id = ver_id;
+ __entry->seq_num = seq_num;
+ ),
+ TP_printk(DW_PR_FMT ", ver_id: %u, seq_num: %u", DW_PR_ARG,
+ __entry->ver_id, __entry->seq_num)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_clock_sync_payload_put,
+ TP_PROTO(struct dw3000 *dw, u32 session_time0_sys_time),
+ TP_ARGS(dw, session_time0_sys_time),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, session_time0_sys_time)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->session_time0_sys_time = session_time0_sys_time;
+ ),
+ TP_printk(DW_PR_FMT ", session_time0_sys_time: 0x%08x", DW_PR_ARG,
+ __entry->session_time0_sys_time)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_clock_offset_payload_put,
+ TP_PROTO(struct dw3000 *dw, u32 clock_offset_sys_time),
+ TP_ARGS(dw, clock_offset_sys_time),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, clock_offset_sys_time)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->clock_offset_sys_time = clock_offset_sys_time;
+ ),
+ TP_printk(DW_PR_FMT ", clock_offset_sys_time: 0x%08x", DW_PR_ARG,
+ __entry->clock_offset_sys_time)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_stop_session_payload_put,
+ TP_PROTO(struct dw3000 *dw, u32 session_id),
+ TP_ARGS(dw, session_id),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, session_id)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->session_id = session_id;
+ ),
+ TP_printk(DW_PR_FMT ", session_id %d", DW_PR_ARG,
+ __entry->session_id)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_rx_msg_info,
+ TP_PROTO(struct dw3000 *dw, u32 next_timestamp_dtu,
+ int next_duration_dtu),
+ TP_ARGS(dw, next_timestamp_dtu, next_duration_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, next_timestamp_dtu)
+ __field(int, next_duration_dtu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ __entry->next_duration_dtu = next_duration_dtu;
+ ),
+ TP_printk(DW_PR_FMT ", next_timestamp_dtu: 0x%08x"
+ ", next_duration_dtu: 0x%08x", DW_PR_ARG,
+ __entry->next_timestamp_dtu, __entry->next_duration_dtu)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_header_check,
+ TP_PROTO(struct dw3000 *dw, const char *signature,
+ u8 ver_id, u8 seq_num, u8 nb_tlv),
+ TP_ARGS(dw, signature, ver_id, seq_num, nb_tlv),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __array(u8, signature, DW3000_NFCC_COEX_SIGNATURE_LEN)
+ __field(u8, ver_id)
+ __field(u8, seq_num)
+ __field(u8, nb_tlv)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ memcpy(__entry->signature, signature,
+ DW3000_NFCC_COEX_SIGNATURE_LEN);
+ __entry->ver_id = ver_id;
+ __entry->seq_num = seq_num;
+ __entry->nb_tlv = nb_tlv;
+ ),
+ TP_printk(DW_PR_FMT ", signature: %s, ver_id: %u, seq_num: %u"
+ ", nb_tlv: %u", DW_PR_ARG,
+ __print_hex(__entry->signature,
+ DW3000_NFCC_COEX_SIGNATURE_LEN),
+ __entry->ver_id, __entry->seq_num, __entry->nb_tlv)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_tlv_check,
+ TP_PROTO(struct dw3000 *dw, enum dw3000_nfcc_coex_tlv_type type,
+ u8 len, const u8 *data),
+ TP_ARGS(dw, type, len, data),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ DW3000_NFCC_COEX_TLV_TYPE_ENTRY
+ __dynamic_array(u8, data, len)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ DW3000_NFCC_COEX_TLV_TYPE_ASSIGN;
+ memcpy(__get_dynamic_array(data), data,
+ __get_dynamic_array_len(data));
+ ),
+ TP_printk(DW_PR_FMT ", " DW3000_NFCC_COEX_TLV_TYPE_PR_FMT ", data: %s",
+ DW_PR_ARG, DW3000_NFCC_COEX_TLV_TYPE_ARG,
+ __print_hex(__get_dynamic_array(data), __get_dynamic_array_len(data)))
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_handle_access,
+ TP_PROTO(struct dw3000 *dw, const struct llhw_vendor_cmd_nfcc_coex_handle_access *info,
+ s32 idle_duration_dtu),
+ TP_ARGS(dw, info, idle_duration_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(bool, start)
+ __field(s32, idle_duration_dtu)
+ __field(u32, timestamp_dtu)
+ __field(int, duration_dtu)
+ __field(int, chan)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->start = info->start;
+ __entry->idle_duration_dtu = idle_duration_dtu;
+ __entry->timestamp_dtu = info->timestamp_dtu;
+ __entry->duration_dtu = info->duration_dtu;
+ __entry->chan = info->chan;
+ ),
+ TP_printk(DW_PR_FMT ", start: %s, idle_duration_dtu: 0x%08x"
+ ", timestamp_dtu: 0x%08x, duration_dtu: 0x%08x,"
+ " chan: %d", DW_PR_ARG,
+ __entry->start ? "true" : "false",
+ __entry->idle_duration_dtu,
+ __entry->timestamp_dtu,
+ __entry->duration_dtu, __entry->chan)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_wakeup_and_send,
+ TP_PROTO(struct dw3000 *dw, enum dw3000_nfcc_coex_send send,
+ s32 idle_duration_dtu, u32 send_timestamp_dtu),
+ TP_ARGS(dw, send, idle_duration_dtu, send_timestamp_dtu),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ DW3000_NFCC_COEX_SEND_ENTRY
+ __field(s32, idle_duration_dtu)
+ __field(u32, send_timestamp_dtu)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ DW3000_NFCC_COEX_SEND_ASSIGN,
+ __entry->idle_duration_dtu = idle_duration_dtu;
+ __entry->send_timestamp_dtu = send_timestamp_dtu;
+ ),
+ TP_printk(DW_PR_FMT ", " DW3000_NFCC_COEX_SEND_PR_FMT
+ ", idle_duration_dtu: %d, send_timestamp_dtu: 0x%08x",
+ DW_PR_ARG,
+ DW3000_NFCC_COEX_SEND_ARG,
+ __entry->idle_duration_dtu,
+ __entry->send_timestamp_dtu)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_err,
+ TP_PROTO(struct dw3000 *dw, const char *err),
+ TP_ARGS(dw, err),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __string(err, err)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __assign_str(err, err);
+ ),
+ TP_printk(DW_PR_FMT ", err: \"%s\"", DW_PR_ARG, __get_str(err))
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_warn,
+ TP_PROTO(struct dw3000 *dw, const char *warn),
+ TP_ARGS(dw, warn),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __string(warn, warn)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __assign_str(warn, warn);
+ ),
+ TP_printk(DW_PR_FMT ", warn: \"%s\"", DW_PR_ARG, __get_str(warn))
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_configure,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_watchdog_timeout,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_cancel_watchdog,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_spi1_avail,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_idle_timeout,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+TRACE_EVENT(dw3000_nfcc_coex_enable,
+ TP_PROTO(struct dw3000 *dw, int channel),
+ TP_ARGS(dw, channel),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(int, channel)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->channel = channel;
+ ),
+ TP_printk(DW_PR_FMT ", channel: %d", DW_PR_ARG, __entry->channel)
+);
+
+DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_disable,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw)
+);
+
+/*************************************************************
+ * dw3000 optional functions traces *
+ *************************************************************/
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+TRACE_EVENT(dw3000_tm_cmd,
+ TP_PROTO(struct dw3000 *dw, u32 cmd),
+ TP_ARGS(dw, cmd),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ DW_TM_CMD_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ DW_TM_CMD_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT ", " DW_TM_CMD_PR_FMT, DW_PR_ARG, DW_TM_CMD_PR_ARG)
+);
+#endif
+
+TRACE_EVENT(dw3000_set_pdoa,
+ TP_PROTO(struct dw3000 *dw, u32 mode),
+ TP_ARGS(dw, mode),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, mode)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->mode = mode;
+ ),
+ TP_printk(DW_PR_FMT ", mode: %d", DW_PR_ARG,
+ __entry->mode)
+ );
+
+TRACE_EVENT(dw3000_read_pdoa,
+ TP_PROTO(struct dw3000 *dw, u32 pdoa),
+ TP_ARGS(dw, pdoa),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u32, pdoa)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->pdoa = pdoa;
+ ),
+ TP_printk(DW_PR_FMT ", pdoa: 0x%08x", DW_PR_ARG,
+ __entry->pdoa)
+ );
+
+TRACE_EVENT(dw3000_testmode_continuous_tx_start,
+ TP_PROTO(struct dw3000 *dw, u8 chan, u32 len, u32 rate),
+ TP_ARGS(dw, chan, len, rate),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(u8, chan)
+ __field(u32, len)
+ __field(u32, rate)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->chan = chan;
+ __entry->len = len;
+ __entry->rate = rate;
+ ),
+ TP_printk(DW_PR_FMT ", chan: %d, len: %d, rate: %d", DW_PR_ARG,
+ __entry->chan,
+ __entry->len,
+ __entry->rate)
+ );
+
+TRACE_EVENT(dw3000_testmode_continuous_tx_stop,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT, DW_PR_ARG)
+ );
+
+TRACE_EVENT(dw3000_read_clockoffset,
+ TP_PROTO(struct dw3000 *dw, s16 cfo),
+ TP_ARGS(dw, cfo),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ __field(s16, cfo)
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ __entry->cfo = cfo;
+ ),
+ TP_printk(DW_PR_FMT ", clockoffset: %d", DW_PR_ARG,
+ __entry->cfo)
+ );
+
+TRACE_EVENT(dw3000_nfcc_coex_prepare_config,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT, DW_PR_ARG)
+ );
+
+TRACE_EVENT(dw3000_nfcc_coex_restore_config,
+ TP_PROTO(struct dw3000 *dw),
+ TP_ARGS(dw),
+ TP_STRUCT__entry(
+ DW_ENTRY
+ ),
+ TP_fast_assign(
+ DW_ASSIGN;
+ ),
+ TP_printk(DW_PR_FMT, DW_PR_ARG)
+ );
+
+/* clang-format on */
+#endif /* !__DW3000_TRACE || TRACE_HEADER_MULTI_READ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE dw3000_trc
+#include <trace/define_trace.h>
diff --git a/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c
new file mode 100644
index 0000000..1b2ae14
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c
@@ -0,0 +1,565 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/types.h>
+#include "dw3000_trc.h"
+#include "dw3000_txpower_adjustment.h"
+
+/* Size of Tx power compensation look-up table */
+#define LUT_COMP_SIZE 64
+
+/* Margin for tx power adjustment in 0.1dB steps. */
+#define TXPOWER_ADJUSTMENT_MARGIN 5
+
+/* TxPower difference between coarse gain n and coarse gain n+1 in 0.1dB Step.
+ * Same for CH5 and CH9 */
+#define COARSE_0_TO_1 32
+#define COARSE_1_TO_2 13
+#define COARSE_2_TO_3 5
+#define NUM_COARSE_GAIN 3
+
+/* LookUpTable : 1000us - 200us */
+#define LUT_1000_200_US_NUM 33
+#define LUT_1000_200_US_STEP 25
+#define LUT_1000_200_US_MIN 200
+#define LUT_1000_200_US_MIN_BST 0
+
+/* LookUpTable : 200us - 70us */
+#define LUT_200_70_US_NUM 14
+#define LUT_200_70_US_STEP 10
+#define LUT_200_70_US_MIN 70
+#define LUT_200_70_US_MAX_BST 113
+
+/* The reference duration for a frame is 1000us. Longer frame will
+ * have 0dB boost. */
+#define FRAME_DURATION_REF 1000
+
+#define MAX_BOOST_CH5 354
+#define MAX_BOOST_CH9 305
+#define TX_POWER_COARSE_GAIN_MASK 0x03
+#define TX_POWER_FINE_GAIN_MASK 0x3F
+
+/* Reference look-up table to calculate Txpower Dial Back depending on
+ * frame duration. Each new entry in the table is relative to the previous one.
+ * Using two different tables a per logarithmic calculation:
+ * - 1000us to 200us range (index unit of 25us)
+ * - 200us to 70us (index unit of 10us)
+ * The table values are in steps 0f 0.1dB
+ * This allows having a maximum granularity of 0.5dB between two frame
+ * duration, which is in the magnitude of the DW3XXX TxOutput setting.
+ */
+static const u8
+ txpower_boost_per_frame_duration_1000_200_us[LUT_1000_200_US_NUM] = {
+ 0, /* 1000us */
+ 1, /* 975us -> 1*0.1dB boost between 975us and 1000us frames */
+ 2, /* 950us -> 2*0.1dB boost between 950us and 1000us frames */
+ 3, /* 925us */
+ 4, /* 900us */
+ 5, /* 875us */
+ 6, /* 850us */
+ 7, /* 825us */
+ 8, /* 800us */
+ 9, /* 775us */
+ 10, /* 750us */
+ 11, /* 725us */
+ 13, /* 700us */
+ 15, /* 675us */
+ 17, /* 650us */
+ 19, /* 625us */
+ 21, /* 600us */
+ 23, /* 575us */
+ 25, /* 550us */
+ 27, /* 525us */
+ 29, /* 500us */
+ 31, /* 475us */
+ 33, /* 450us */
+ 35, /* 425us */
+ 38, /* 400us */
+ 41, /* 375us */
+ 44, /* 350us */
+ 47, /* 325us */
+ 50, /* 300us */
+ 54, /* 275us */
+ 58, /* 250us */
+ 63, /* 225us */
+ 68 /* 200us */
+ };
+
+static const u8 txpower_boost_per_frame_duration_200_70_us[LUT_200_70_US_NUM] = {
+ 68, /* 200us -> 68*0.1dB boost between 200us frame and 1000us frame. */
+ 70, /* 190us -> 70*0.1dB boost between 190us and 1000us frame */
+ 72, /* 180us */
+ 74, /* 170us */
+ 77, /* 160us */
+ 80, /* 150us */
+ 83, /* 140us */
+ 86, /* 130us */
+ 89, /* 120us */
+ 93, /* 110us */
+ 97, /* 100us */
+ 102, /* 90us */
+ 107, /* 80us */
+ 113 /* 70us */
+};
+
+/* TxPower difference between the coarse gain setting (i+1) and (i)
+ * The step is in unit of 0.1dB
+ */
+static const u8 lut_coarse_gain[NUM_COARSE_GAIN] = {
+ COARSE_0_TO_1,
+ COARSE_1_TO_2,
+ COARSE_2_TO_3,
+};
+
+/* TxPower difference between the fine gain setting (i+1) and (i)
+ * The step is in unit of 0.1dB
+ */
+static const u8 fine_gain_lut_chan5[LUT_COMP_SIZE] = {
+ 0, /* Fine gain setting ( 0 ) Minimum Value */
+ 32, /* Fine gain setting ( 1 - 0 ) */
+ 29, /* Fine gain setting ( 2 - 1 ) */
+ 28, /* Fine gain setting ( 3 - 2 ) */
+ 20, /* Fine gain setting ( 4 - 3 ) */
+ 18, /* Fine gain setting ( 5 - 4 ) */
+ 12, /* Fine gain setting ( 6 - 5 ) */
+ 13, /* Fine gain setting ( 7 - 6 ) */
+ 10, /* Fine gain setting ( 8 - 7 ) */
+ 10, /* Fine gain setting ( 9 - 8 ) */
+ 7, /* Fine gain setting ( 10 - 9 ) */
+ 8, /* Fine gain setting ( 11 - 10 ) */
+ 6, /* Fine gain setting ( 12 - 11 ) */
+ 7, /* Fine gain setting ( 13 - 12 ) */
+ 5, /* Fine gain setting ( 14 - 13 ) */
+ 6, /* Fine gain setting ( 15 - 14 ) */
+ 5, /* Fine gain setting ( 16 - 15 ) */
+ 5, /* Fine gain setting ( 17 - 16 ) */
+ 4, /* Fine gain setting ( 18 - 17 ) */
+ 4, /* Fine gain setting ( 19 - 18 ) */
+ 4, /* Fine gain setting ( 20 - 19 ) */
+ 4, /* Fine gain setting ( 21 - 20 ) */
+ 3, /* Fine gain setting ( 22 - 21 ) */
+ 3, /* Fine gain setting ( 23 - 22 ) */
+ 3, /* Fine gain setting ( 24 - 23 ) */
+ 3, /* Fine gain setting ( 25 - 24 ) */
+ 2, /* Fine gain setting ( 26 - 25 ) */
+ 3, /* Fine gain setting ( 27 - 26 ) */
+ 2, /* Fine gain setting ( 28 - 27 ) */
+ 3, /* Fine gain setting ( 29 - 28 ) */
+ 2, /* Fine gain setting ( 30 - 29 ) */
+ 3, /* Fine gain setting ( 31 - 30 ) */
+ 3, /* Fine gain setting ( 32 - 31 ) */
+ 2, /* Fine gain setting ( 33 - 32 ) */
+ 2, /* Fine gain setting ( 34 - 33 ) */
+ 2, /* Fine gain setting ( 35 - 34 ) */
+ 1, /* Fine gain setting ( 36 - 35 ) */
+ 2, /* Fine gain setting ( 37 - 36 ) */
+ 1, /* Fine gain setting ( 38 - 37 ) */
+ 2, /* Fine gain setting ( 39 - 38 ) */
+ 1, /* Fine gain setting ( 40 - 39 ) */
+ 2, /* Fine gain setting ( 41 - 40 ) */
+ 1, /* Fine gain setting ( 42 - 41 ) */
+ 1, /* Fine gain setting ( 43 - 42 ) */
+ 1, /* Fine gain setting ( 44 - 43 ) */
+ 1, /* Fine gain setting ( 45 - 44 ) */
+ 1, /* Fine gain setting ( 46 - 45 ) */
+ 1, /* Fine gain setting ( 47 - 46 ) */
+ 1, /* Fine gain setting ( 48 - 47 ) */
+ 1, /* Fine gain setting ( 49 - 48 ) */
+ 1, /* Fine gain setting ( 50 - 49 ) */
+ 1, /* Fine gain setting ( 51 - 50 ) */
+ 1, /* Fine gain setting ( 52 - 51 ) */
+ 1, /* Fine gain setting ( 53 - 52 ) */
+ 1, /* Fine gain setting ( 54 - 53 ) */
+ 1, /* Fine gain setting ( 55 - 54 ) */
+ 1, /* Fine gain setting ( 56 - 55 ) */
+ 1, /* Fine gain setting ( 57 - 56 ) */
+ 1, /* Fine gain setting ( 58 - 57 ) */
+ 1, /* Fine gain setting ( 59 - 58 ) */
+ 1, /* Fine gain setting ( 60 - 59 ) */
+ 1, /* Fine gain setting ( 61 - 60 ) */
+ 1, /* Fine gain setting ( 62 - 61 ) */
+ 1 /* Fine gain setting ( 63 - 62 ) */
+};
+
+static const u8 fine_gain_lut_chan9[LUT_COMP_SIZE] = {
+ 0, /* Fine gain setting ( 0 ) Minimum Value */
+ 11, /* Fine gain setting ( 1 - 0 ) */
+ 14, /* Fine gain setting ( 2 - 1 ) */
+ 18, /* Fine gain setting ( 3 - 2 ) */
+ 15, /* Fine gain setting ( 4 - 3 ) */
+ 15, /* Fine gain setting ( 5 - 4 ) */
+ 10, /* Fine gain setting ( 6 - 5 ) */
+ 12, /* Fine gain setting ( 7 - 6 ) */
+ 9, /* Fine gain setting ( 8 - 7 ) */
+ 9, /* Fine gain setting ( 9 - 8 ) */
+ 7, /* Fine gain setting ( 10 - 9 ) */
+ 8, /* Fine gain setting ( 11 - 10 ) */
+ 6, /* Fine gain setting ( 12 - 11 ) */
+ 7, /* Fine gain setting ( 13 - 12 ) */
+ 5, /* Fine gain setting ( 14 - 13 ) */
+ 6, /* Fine gain setting ( 15 - 14 ) */
+ 5, /* Fine gain setting ( 16 - 15 ) */
+ 5, /* Fine gain setting ( 17 - 16 ) */
+ 4, /* Fine gain setting ( 18 - 17 ) */
+ 5, /* Fine gain setting ( 19 - 18 ) */
+ 4, /* Fine gain setting ( 20 - 19 ) */
+ 4, /* Fine gain setting ( 21 - 20 ) */
+ 3, /* Fine gain setting ( 22 - 21 ) */
+ 4, /* Fine gain setting ( 23 - 22 ) */
+ 3, /* Fine gain setting ( 24 - 23 ) */
+ 3, /* Fine gain setting ( 25 - 24 ) */
+ 3, /* Fine gain setting ( 26 - 25 ) */
+ 3, /* Fine gain setting ( 27 - 26 ) */
+ 3, /* Fine gain setting ( 28 - 27 ) */
+ 3, /* Fine gain setting ( 29 - 28 ) */
+ 2, /* Fine gain setting ( 30 - 29 ) */
+ 3, /* Fine gain setting ( 31 - 30 ) */
+ 3, /* Fine gain setting ( 32 - 31 ) */
+ 2, /* Fine gain setting ( 33 - 32 ) */
+ 2, /* Fine gain setting ( 34 - 33 ) */
+ 2, /* Fine gain setting ( 35 - 34 ) */
+ 2, /* Fine gain setting ( 36 - 35 ) */
+ 2, /* Fine gain setting ( 37 - 36 ) */
+ 1, /* Fine gain setting ( 38 - 37 ) */
+ 2, /* Fine gain setting ( 39 - 38 ) */
+ 2, /* Fine gain setting ( 40 - 39 ) */
+ 2, /* Fine gain setting ( 41 - 40 ) */
+ 2, /* Fine gain setting ( 42 - 41 ) */
+ 2, /* Fine gain setting ( 43 - 42 ) */
+ 1, /* Fine gain setting ( 44 - 43 ) */
+ 2, /* Fine gain setting ( 45 - 44 ) */
+ 1, /* Fine gain setting ( 46 - 45 ) */
+ 2, /* Fine gain setting ( 47 - 46 ) */
+ 1, /* Fine gain setting ( 48 - 47 ) */
+ 1, /* Fine gain setting ( 49 - 48 ) */
+ 1, /* Fine gain setting ( 50 - 49 ) */
+ 2, /* Fine gain setting ( 51 - 50 ) */
+ 1, /* Fine gain setting ( 52 - 51 ) */
+ 1, /* Fine gain setting ( 53 - 52 ) */
+ 1, /* Fine gain setting ( 54 - 53 ) */
+ 1, /* Fine gain setting ( 55 - 54 ) */
+ 1, /* Fine gain setting ( 56 - 55 ) */
+ 1, /* Fine gain setting ( 57 - 56 ) */
+ 1, /* Fine gain setting ( 58 - 57 ) */
+ 1, /* Fine gain setting ( 59 - 58 ) */
+ 0, /* Fine gain setting ( 60 - 59 ) */
+ 1, /* Fine gain setting ( 61 - 60 ) */
+ 1, /* Fine gain setting ( 62 - 61 ) */
+ 1 /* Fine gain setting ( 63 - 62 ) */
+};
+
+/* Calculate the power_boost for a frame_duration_us relative to 1ms */
+static u8 calculate_power_boost(u16 frame_duration_us)
+{
+ const u8 *lut = NULL;
+ u16 lut_i;
+ u16 lut_num;
+ u16 lut_min;
+ u16 lut_step;
+ u16 limit;
+
+ /* Calculating the LUT index corresponding to the frameduration */
+ if (frame_duration_us >= FRAME_DURATION_REF) {
+ return LUT_1000_200_US_MIN_BST;
+ } else if (frame_duration_us < LUT_200_70_US_MIN) {
+ return LUT_200_70_US_MAX_BST;
+ } else if (frame_duration_us > LUT_1000_200_US_MIN) {
+ lut_num = LUT_1000_200_US_NUM;
+ lut_min = LUT_1000_200_US_MIN;
+ lut_step = LUT_1000_200_US_STEP;
+ lut = txpower_boost_per_frame_duration_1000_200_us;
+ } else {
+ lut_num = LUT_200_70_US_NUM;
+ lut_min = LUT_200_70_US_MIN;
+ lut_step = LUT_200_70_US_STEP;
+ lut = txpower_boost_per_frame_duration_200_70_us;
+ }
+
+ lut_i = (lut_num - (frame_duration_us - lut_min) / lut_step);
+ limit = (lut_num - lut_i) * lut_step + lut_min;
+
+ /* Selecting the index that gives the closest LUT */
+ if (abs(frame_duration_us - limit) > lut_step / 2) {
+ lut_i--;
+ }
+
+ return lut[lut_i - 1];
+}
+
+/**
+ * adjust_tx_power() - actual TxPower setting calculation
+ * @frame_duration_us: the frame (headers, payload, FCS) duration
+ * @ref_tx_power: the power setting corresponding to a frame of 1ms (0dB)
+ * @channel: the current RF channel used for transmission of UWB frames
+ * @th_boost: pointer to store the theorical boost to be applied
+ * @applied_boost: pointer to store the calculated, actually applied boost
+ *
+ * The reference TxPower setting should correspond to a 1ms frame (or 0dB)
+ * boost. The boost to be applied should be provided in unit of 0.1dB boost.
+ *
+ * Return: the adjusted_tx_power setting that can be used to configure the
+ * chip transmission level
+ */
+static u32 adjust_tx_power(u16 frame_duration_us, u32 ref_tx_power, u8 channel,
+ u16 *th_boost, u16 *applied_boost)
+{
+ u32 adjusted_tx_power;
+ u16 target_boost = 0;
+ u16 base_target_boost = 0;
+ u16 current_boost = 0;
+ u16 best_boost_abs = 0;
+ u16 best_boost = 0;
+ u16 upper_limit = 0;
+ u16 lower_limit = 0;
+
+ const u8 *lut = NULL;
+ uint8_t ref_tx_power_byte[4]; /* txpwr of each segment (UM 8.2.2.20) */
+ uint8_t adj_tx_power_byte[4];
+ uint8_t adj_tx_power_boost[4];
+ u8 best_index;
+ u8 best_coarse_gain;
+ u8 ref_coarse_gain;
+ u8 ref_fine_gain;
+ bool within_margin_flag;
+ bool reached_max_fine_gain_flag;
+ bool shortcut_optim_flag;
+ u8 unlock;
+ u8 i, j;
+ int k;
+ int ret = 0;
+
+ base_target_boost = calculate_power_boost(frame_duration_us);
+ if (th_boost) {
+ *th_boost = base_target_boost;
+ }
+ switch (channel) {
+ case 5:
+ lut = fine_gain_lut_chan5;
+ if (base_target_boost > MAX_BOOST_CH5)
+ base_target_boost = MAX_BOOST_CH5;
+ break;
+ default:
+ lut = fine_gain_lut_chan9;
+ if (base_target_boost > MAX_BOOST_CH9)
+ base_target_boost = MAX_BOOST_CH9;
+ break;
+ }
+
+ for (k = 0; k < 4; k++) {
+ target_boost = base_target_boost;
+ current_boost = 0;
+ best_boost_abs = 0;
+ best_boost = 0;
+ best_index = 0;
+ best_coarse_gain = 0;
+ within_margin_flag = false;
+ reached_max_fine_gain_flag = false;
+ shortcut_optim_flag = false;
+ unlock = 0;
+ i = 0;
+ ref_tx_power_byte[k] = (u8)(ref_tx_power >> (k << 3));
+ ref_coarse_gain = ((u8)ref_tx_power_byte[k]) &
+ TX_POWER_COARSE_GAIN_MASK;
+ ref_fine_gain = ((u8)ref_tx_power_byte[k] >> 2) &
+ TX_POWER_FINE_GAIN_MASK;
+ adj_tx_power_boost[k] = 0;
+ i = ref_fine_gain;
+
+ /* Avoid re-doing the same math four times */
+ for (j = 0; !shortcut_optim_flag && (j < k); j++) {
+ if (ref_tx_power_byte[k] == ref_tx_power_byte[j]) {
+ adj_tx_power_byte[k] = adj_tx_power_byte[j];
+ shortcut_optim_flag = true;
+ }
+ }
+ if (shortcut_optim_flag)
+ continue;
+
+ /* PHR power must be 6dB lower than PSDU */
+ if (k == 1) {
+ uint8_t psdu_boost_err =
+ (target_boost > adj_tx_power_boost[0] ?
+ target_boost - adj_tx_power_boost[0] :
+ 0);
+ if (psdu_boost_err < 0)
+ psdu_boost_err = 0;
+ target_boost -= psdu_boost_err;
+ }
+
+ upper_limit = target_boost + TXPOWER_ADJUSTMENT_MARGIN;
+ lower_limit =
+ (target_boost > TXPOWER_ADJUSTMENT_MARGIN ?
+ target_boost - TXPOWER_ADJUSTMENT_MARGIN :
+ 0);
+ best_boost_abs = TXPOWER_ADJUSTMENT_MARGIN;
+
+ if (target_boost < TXPOWER_ADJUSTMENT_MARGIN &&
+ target_boost < lut[i + 1] - TXPOWER_ADJUSTMENT_MARGIN) {
+ if (k == 0 && applied_boost)
+ *applied_boost = 0;
+ adj_tx_power_byte[k] = ref_tx_power_byte[k];
+ continue;
+ }
+
+ /* Increase coarse setting if the required boost is greater than the
+ * TxPower gain using the increased coarse setting.
+ * NB : Coarse gain = 0x3 should not be used in CHAN9 */
+ while (ref_coarse_gain < 0x2) {
+ if (lut_coarse_gain[ref_coarse_gain] <
+ target_boost - current_boost) {
+ current_boost +=
+ lut_coarse_gain[ref_coarse_gain];
+ ref_coarse_gain++;
+ } else {
+ break;
+ }
+ }
+
+ /* Increase current_boost until reaching value closest to target_boost */
+ while (current_boost != target_boost) {
+ unlock++;
+ /* Ensure loop does not got "locked" */
+ if (unlock > 2 * LUT_COMP_SIZE) {
+ ret = -EFAULT;
+ break;
+ }
+
+ if (current_boost > lower_limit &&
+ current_boost < upper_limit) {
+ if (abs((s16)(target_boost - current_boost)) <=
+ best_boost_abs) {
+ best_boost_abs = abs((s16)target_boost - current_boost);
+ best_boost = current_boost;
+ best_index = i;
+ best_coarse_gain = ref_coarse_gain;
+ within_margin_flag = true;
+ } else if (within_margin_flag) {
+ i = best_index;
+ ref_coarse_gain = best_coarse_gain;
+ break;
+ }
+ } else if (within_margin_flag) {
+ current_boost -= lut[i];
+ i = best_index;
+ break;
+ }
+
+ /* Corner case: when fine gain setting is very low, it can happened that
+ * current boost is already larger than target_boost but not within margin.
+ * Then, just return current solution. */
+ if (current_boost >= upper_limit &&
+ !reached_max_fine_gain_flag) {
+ break;
+ }
+
+ /* Search for max fine gain value */
+ if (i == LUT_COMP_SIZE - 1) {
+ reached_max_fine_gain_flag = true;
+
+ /* return previously found solution */
+ if (within_margin_flag) {
+ i = best_index;
+ ref_coarse_gain = best_coarse_gain;
+ current_boost = best_boost;
+ break;
+ }
+
+ if ((ref_coarse_gain == 0x3) ||
+ (ref_coarse_gain == 0x2 && channel == 9)) {
+ break;
+ }
+
+ if (current_boost +
+ lut_coarse_gain[ref_coarse_gain] <=
+ target_boost) {
+ current_boost +=
+ lut_coarse_gain[ref_coarse_gain];
+ ref_coarse_gain++;
+ break;
+ } else {
+ current_boost +=
+ lut_coarse_gain[ref_coarse_gain];
+ ref_coarse_gain++;
+ }
+ }
+
+ /* Adjust fine gain */
+ if (!reached_max_fine_gain_flag) {
+ i++;
+ i &= TX_POWER_FINE_GAIN_MASK;
+ current_boost += lut[i];
+ } else {
+ current_boost -= lut[i];
+ i--;
+ i &= TX_POWER_FINE_GAIN_MASK;
+ if (i == 0)
+ reached_max_fine_gain_flag = false;
+ }
+ }
+
+ if (ret) {
+ adj_tx_power_byte[k] = ref_tx_power_byte[k];
+ current_boost = 0;
+ } else {
+ adj_tx_power_byte[k] = (i << 2) | ref_coarse_gain;
+ }
+
+ adj_tx_power_boost[k] = current_boost;
+
+ if (applied_boost && (k == 0))
+ *applied_boost = current_boost;
+ }
+ adjusted_tx_power = (u32)adj_tx_power_byte[3] << 24 |
+ (u32)adj_tx_power_byte[2] << 16 |
+ (u32)adj_tx_power_byte[1] << 8 |
+ (u32)adj_tx_power_byte[0];
+
+ return adjusted_tx_power;
+}
+
+/**
+ * dw3000_adjust_tx_power() - calculates the adjusted TxPower
+ * @dw: the DW device
+ * @payload_bytes: payload skbuf's length in bytes
+ *
+ * Wraps actual TX power calculation. Converts frame length to duration (in µs)
+ * with respect to RF settings (channel, PRF, ...) then calls adjust_tx_power
+ *
+ * Return: the adjusted_tx_power setting than can be used to configure the chip
+ * transmission level
+ */
+int dw3000_adjust_tx_power(struct dw3000 *dw, int payload_bytes)
+{
+ u16 th_boost, app_boost;
+ u8 chan = dw->config.chan;
+ u16 frm_dur =
+ DTU_TO_US(dw3000_frame_duration_dtu(dw, payload_bytes, true));
+ u32 adjusted_tx_power = adjust_tx_power(frm_dur, dw->txconfig.power,
+ chan, &th_boost, &app_boost);
+
+ trace_dw3000_adjust_tx_power(dw, dw->txconfig.power, adjusted_tx_power,
+ frm_dur, payload_bytes, chan, th_boost,
+ app_boost);
+
+ return dw3000_set_tx_power_register(dw, adjusted_tx_power);
+}
diff --git a/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h
new file mode 100644
index 0000000..f25fa32
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef __DW3000_TXPOWER_ADJUSTMENT_H
+#define __DW3000_TXPOWER_ADJUSTMENT_H
+
+#include "dw3000_core.h"
+#include "dw3000_core_reg.h"
+
+static inline int dw3000_set_tx_power_register(struct dw3000 *dw, u32 value)
+{
+ return dw3000_reg_write32(dw, DW3000_TX_POWER_ID, 0, value);
+}
+
+int dw3000_adjust_tx_power(struct dw3000 *dw, int payload_bytes);
+
+#endif /* __DW3000_TXPOWER_ADJUSTMENT_H */
diff --git a/kernel/drivers/net/ieee802154/mcps802154_fake.c b/kernel/drivers/net/ieee802154/mcps802154_fake.c
new file mode 100644
index 0000000..082b20b
--- /dev/null
+++ b/kernel/drivers/net/ieee802154/mcps802154_fake.c
@@ -0,0 +1,488 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <net/mcps802154.h>
+
+MODULE_AUTHOR("Saad Zouiten <saad.zouiten@qorvo.com>");
+MODULE_DESCRIPTION("stubbed 802.15.4 mcps driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
+
+static const int g_time_interval = 1000;
+static struct timer_list g_timer;
+static struct mcps802154_llhw *driver_llhw;
+static bool rx_enabled;
+static bool tx_queued;
+static bool started;
+static bool stop_timer;
+static bool scanning;
+static u8 seq;
+static u8 curchan;
+
+const char *const calib_strings[] = { "calib.param1", NULL };
+static u8 calib;
+
+static void periodic_task(struct timer_list *unused)
+{
+ if (stop_timer)
+ return;
+ pr_info("fake_mcps: %s called\n", __func__);
+ /*Restarting the timer...*/
+ mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
+ if (rx_enabled) {
+ rx_enabled = false;
+ mcps802154_rx_frame(driver_llhw);
+ }
+ if (tx_queued) {
+ tx_queued = false;
+ mcps802154_tx_done(driver_llhw);
+ }
+}
+
+static int start(struct mcps802154_llhw *llhw)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ started = true;
+ return 0;
+}
+
+static void stop(struct mcps802154_llhw *llhw)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ started = false;
+}
+
+static int tx_frame(struct mcps802154_llhw *llhw, struct sk_buff *skb,
+ const struct mcps802154_tx_frame_config *config,
+ int frame_idx, int next_delay_dtu)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: tx_frame called skb len=%d\n", skb->len);
+ print_hex_dump(KERN_CONT, " ", DUMP_PREFIX_OFFSET, 16, 1, skb->data,
+ skb->len, false);
+ tx_queued = true;
+ return 0;
+}
+
+static int rx_enable(struct mcps802154_llhw *llhw,
+ const struct mcps802154_rx_frame_config *info,
+ int frame_idx, int next_delay_dtu)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ rx_enabled = true;
+ return 0;
+}
+
+static int rx_disable(struct mcps802154_llhw *llhw)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ rx_enabled = false;
+ return 0;
+}
+
+static struct sk_buff *create_fake_data_frame(void)
+{
+ unsigned char *data;
+ struct sk_buff *skb = dev_alloc_skb(20 + 2);
+
+ if (!skb)
+ return NULL;
+ data = skb_put(skb, 20);
+ data[0] = 0x21; /* Frame Control Field */
+ data[1] = 0xc8; /* Frame Control Field */
+ data[2] = 0x8b; /* Sequence number */
+ data[3] = 0xff; /* Destination PAN ID 0xffff */
+ data[4] = 0xff; /* Destination PAN ID */
+ data[5] = 0x02; /* Destination short address 0x0002 */
+ data[6] = 0x00; /* Destination short address */
+ data[7] = 0x23; /* Source PAN ID 0x0023 */
+ data[8] = 0x00; /* */
+ data[9] = 0x60; /* Source extended address ae:c2:4a:1c:21:16:e2:60 */
+ data[10] = 0xe2; /* */
+ data[11] = 0x16; /* */
+ data[12] = 0x21; /* */
+ data[13] = 0x1c; /* */
+ data[14] = 0x4a; /* */
+ data[15] = 0xc2; /* */
+ data[16] = 0xae; /* */
+ data[17] = 0xaa; /* Payload */
+ data[18] = 0xbb; /* */
+ data[19] = 0xcc; /* */
+ return skb;
+}
+
+static struct sk_buff *create_fake_beacon_frame(void)
+{
+ unsigned char *data;
+ struct sk_buff *skb = dev_alloc_skb(17 + 2);
+
+ if (!skb)
+ return NULL;
+ data = skb_put(skb, 17);
+ data[0] = 0x00; /* Frame Control Field: 0b00000000 */
+ data[1] = 0xd0; /* Frame Control Field: 0b11010000 */
+ data[2] = seq; /* Sequence number */
+ data[3] = curchan; /* Source PAN ID 0x0100 + channel */
+ data[4] = 0x01; /* */
+ data[5] = curchan; /* Source extended address ae:c2:4a:1c:21:16:e2:xx */
+ data[6] = 0xe2; /* */
+ data[7] = 0x16; /* */
+ data[8] = 0x21; /* */
+ data[9] = 0x1c; /* */
+ data[10] = 0x4a; /* */
+ data[11] = 0xc2; /* */
+ data[12] = 0xae; /* */
+ data[13] = 0x77; /* Superframe spec */
+ data[14] = 0xcf; /* */
+ data[15] = 0x00; /* GTS spec */
+ data[16] = 0x00; /* Pending address spec */
+ /* TODO: CFP Alloc */
+ /* TODO: CFP Usage */
+ /* Update seq for next beacon */
+ seq = (seq + 1) & 0x3f;
+ return skb;
+}
+
+static int rx_get_frame(struct mcps802154_llhw *llhw, struct sk_buff **skb,
+ struct mcps802154_rx_frame_info *info)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ if (scanning)
+ *skb = create_fake_beacon_frame();
+ else
+ *skb = create_fake_data_frame();
+ if (!*skb) {
+ pr_err("RX buffer allocation failed\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int rx_get_error_frame(struct mcps802154_llhw *llhw,
+ struct mcps802154_rx_frame_info *info)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int idle(struct mcps802154_llhw *llhw, bool timestamp, u32 timestamp_dtu)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int reset(struct mcps802154_llhw *llhw)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int get_current_timestamp_dtu(struct mcps802154_llhw *llhw,
+ u32 *timestamp_dtu)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ *timestamp_dtu = 0;
+ return 0;
+}
+
+static u64 tx_timestamp_dtu_to_rmarker_rctu(
+ struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params, int ant_set_id)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static s64 difference_timestamp_rctu(struct mcps802154_llhw *llhw,
+ u64 timestamp_a_rctu, u64 timestamp_b_rctu)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int compute_frame_duration_dtu(struct mcps802154_llhw *llhw,
+ int payload_bytes)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel,
+ u8 preamble_code)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ seq = 0; /* reset beacon sequence number */
+ curchan = channel; /* save current channel */
+ return 0;
+}
+
+static int set_hrp_uwb_params(struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *params)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int set_hw_addr_filt(struct mcps802154_llhw *llhw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED) {
+ pr_info("fake_mcps: new short addr=%x", filt->short_addr);
+ }
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) {
+ pr_info("fake_mcps: new extended addr=%llx", filt->ieee_addr);
+ }
+ if (changed & IEEE802154_AFILT_PANID_CHANGED) {
+ pr_info("fake_mcps: new pan id=%x", filt->pan_id);
+ }
+ if (changed & IEEE802154_AFILT_PANC_CHANGED) {
+ pr_info("fake_mcps: new pan coordinator=%x", filt->pan_coord);
+ }
+ return 0;
+}
+
+static int set_txpower(struct mcps802154_llhw *llhw, s32 mbm)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int set_cca_mode(struct mcps802154_llhw *llhw,
+ const struct wpan_phy_cca *cca)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int set_cca_ed_level(struct mcps802154_llhw *llhw, s32 mbm)
+{
+ if (!started) {
+ pr_err("fake_mcps: %s called and not started\n", __func__);
+ return -EIO;
+ }
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static int set_promiscuous_mode(struct mcps802154_llhw *llhw, bool on)
+{
+ pr_info("fake_mcps: %s called on=%d\n", __func__, on);
+ return 0;
+}
+
+static int set_scanning_mode(struct mcps802154_llhw *llhw, bool mode)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ scanning = mode;
+ return 0;
+}
+
+static int set_calibration(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ if (!key || !value || length != 1)
+ return -EINVAL;
+ if (strcmp(key, calib_strings[0]) == 0)
+ calib = *(u8 *)value;
+ else
+ return -ENOENT;
+ return 0;
+}
+
+static int get_calibration(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ if (!key || !value || length < 1)
+ return -EINVAL;
+ if (strcmp(key, calib_strings[0]) == 0)
+ *(u8 *)value = calib;
+ else
+ return -ENOENT;
+ return 1;
+}
+
+static const char *const *list_calibration(struct mcps802154_llhw *llhw)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ return calib_strings;
+}
+
+static int vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, u32 subcmd,
+ void *data, size_t data_len)
+{
+ pr_info("fake_mcps: %s called\n", __func__);
+ return 0;
+}
+
+static const struct mcps802154_ops fake_ops = {
+ .start = start,
+ .stop = stop,
+ .tx_frame = tx_frame,
+ .rx_enable = rx_enable,
+ .rx_disable = rx_disable,
+ .rx_get_frame = rx_get_frame,
+ .rx_get_error_frame = rx_get_error_frame,
+ .idle = idle,
+ .reset = reset,
+ .get_current_timestamp_dtu = get_current_timestamp_dtu,
+ .tx_timestamp_dtu_to_rmarker_rctu = tx_timestamp_dtu_to_rmarker_rctu,
+ .difference_timestamp_rctu = difference_timestamp_rctu,
+ .compute_frame_duration_dtu = compute_frame_duration_dtu,
+ .set_channel = set_channel,
+ .set_hrp_uwb_params = set_hrp_uwb_params,
+ .set_hw_addr_filt = set_hw_addr_filt,
+ .set_txpower = set_txpower,
+ .set_cca_mode = set_cca_mode,
+ .set_cca_ed_level = set_cca_ed_level,
+ .set_promiscuous_mode = set_promiscuous_mode,
+ .set_scanning_mode = set_scanning_mode,
+ .set_calibration = set_calibration,
+ .get_calibration = get_calibration,
+ .list_calibration = list_calibration,
+ .vendor_cmd = vendor_cmd,
+};
+
+static int __init fake_init(void)
+{
+ int r;
+
+ pr_info("fake_mcps: init\n");
+ driver_llhw = mcps802154_alloc_llhw(0, &fake_ops);
+ if (driver_llhw == NULL) {
+ return -ENOMEM;
+ }
+ driver_llhw->hw->flags =
+ (IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT |
+ IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_RX_OMIT_CKSUM);
+ driver_llhw->flags =
+ (MCPS802154_LLHW_BPRF | MCPS802154_LLHW_DATA_RATE_6M81 |
+ MCPS802154_LLHW_PHR_DATA_RATE_850K |
+ MCPS802154_LLHW_PHR_DATA_RATE_6M81 | MCPS802154_LLHW_PRF_16 |
+ MCPS802154_LLHW_PRF_64 | MCPS802154_LLHW_PSR_32 |
+ MCPS802154_LLHW_PSR_64 | MCPS802154_LLHW_PSR_128 |
+ MCPS802154_LLHW_PSR_256 | MCPS802154_LLHW_PSR_1024 |
+ MCPS802154_LLHW_PSR_4096 | MCPS802154_LLHW_SFD_4A |
+ MCPS802154_LLHW_SFD_4Z_8 | MCPS802154_LLHW_STS_SEGMENT_1 |
+ MCPS802154_LLHW_AOA_AZIMUTH | MCPS802154_LLHW_AOA_ELEVATION |
+ MCPS802154_LLHW_AOA_FOM);
+ /* UWB High band 802.15.4a-2007. */
+ driver_llhw->hw->phy->supported.channels[4] |= 0xffe0;
+
+ /* UWB symbol duration at PRF16 & PRF64 is ~1us */
+ driver_llhw->hw->phy->symbol_duration = 1;
+
+ /* Set extended address. */
+ driver_llhw->hw->phy->perm_extended_addr = 0xd6552cd6e41ceb57;
+
+ /* fake driver phy channel 5 as default */
+ driver_llhw->hw->phy->current_page = 4;
+ driver_llhw->hw->phy->current_channel = 5;
+ driver_llhw->current_preamble_code = 9;
+
+ r = mcps802154_register_llhw(driver_llhw);
+ if (r) {
+ mcps802154_free_llhw(driver_llhw);
+ driver_llhw = NULL;
+ return r;
+ }
+
+ /*Starting the periodic task.*/
+ timer_setup(&g_timer, periodic_task, 0);
+ mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
+ return 0;
+}
+
+static void __exit fake_exit(void)
+{
+ pr_info("fake_mcps: Exit\n");
+ mcps802154_unregister_llhw(driver_llhw);
+ mcps802154_free_llhw(driver_llhw);
+ driver_llhw = NULL;
+ /*Deleting the timer aka the periodic task.*/
+ stop_timer = true;
+ del_timer(&g_timer);
+}
+
+module_init(fake_init);
+module_exit(fake_exit);
diff --git a/kernel/include/net/fira_region_nl.h b/kernel/include/net/fira_region_nl.h
new file mode 120000
index 0000000..48e3fd1
--- /dev/null
+++ b/kernel/include/net/fira_region_nl.h
@@ -0,0 +1 @@
+../../../mac/include/net/fira_region_nl.h \ No newline at end of file
diff --git a/kernel/include/net/fira_region_params.h b/kernel/include/net/fira_region_params.h
new file mode 120000
index 0000000..e0d3525
--- /dev/null
+++ b/kernel/include/net/fira_region_params.h
@@ -0,0 +1 @@
+../../../mac/include/net/fira_region_params.h \ No newline at end of file
diff --git a/kernel/include/net/idle_region_nl.h b/kernel/include/net/idle_region_nl.h
new file mode 120000
index 0000000..d1e5a9c
--- /dev/null
+++ b/kernel/include/net/idle_region_nl.h
@@ -0,0 +1 @@
+../../../mac/include/net/idle_region_nl.h \ No newline at end of file
diff --git a/kernel/include/net/mcps802154.h b/kernel/include/net/mcps802154.h
new file mode 120000
index 0000000..0f40957
--- /dev/null
+++ b/kernel/include/net/mcps802154.h
@@ -0,0 +1 @@
+../../../mac/include/net/mcps802154.h \ No newline at end of file
diff --git a/kernel/include/net/mcps802154_frame.h b/kernel/include/net/mcps802154_frame.h
new file mode 120000
index 0000000..c209d7a
--- /dev/null
+++ b/kernel/include/net/mcps802154_frame.h
@@ -0,0 +1 @@
+../../../mac/include/net/mcps802154_frame.h \ No newline at end of file
diff --git a/kernel/include/net/mcps802154_nl.h b/kernel/include/net/mcps802154_nl.h
new file mode 120000
index 0000000..edeefa2
--- /dev/null
+++ b/kernel/include/net/mcps802154_nl.h
@@ -0,0 +1 @@
+../../../mac/include/net/mcps802154_nl.h \ No newline at end of file
diff --git a/kernel/include/net/mcps802154_schedule.h b/kernel/include/net/mcps802154_schedule.h
new file mode 120000
index 0000000..31ab0c8
--- /dev/null
+++ b/kernel/include/net/mcps802154_schedule.h
@@ -0,0 +1 @@
+../../../mac/include/net/mcps802154_schedule.h \ No newline at end of file
diff --git a/kernel/include/net/mcps_skb_frag.h b/kernel/include/net/mcps_skb_frag.h
new file mode 120000
index 0000000..55fa1c3
--- /dev/null
+++ b/kernel/include/net/mcps_skb_frag.h
@@ -0,0 +1 @@
+../../../mac/include/net/mcps_skb_frag.h \ No newline at end of file
diff --git a/kernel/include/net/nfcc_coex_region_nl.h b/kernel/include/net/nfcc_coex_region_nl.h
new file mode 120000
index 0000000..deaad68
--- /dev/null
+++ b/kernel/include/net/nfcc_coex_region_nl.h
@@ -0,0 +1 @@
+../../../mac/include/net/nfcc_coex_region_nl.h \ No newline at end of file
diff --git a/kernel/include/net/pctt_region_nl.h b/kernel/include/net/pctt_region_nl.h
new file mode 120000
index 0000000..55871be
--- /dev/null
+++ b/kernel/include/net/pctt_region_nl.h
@@ -0,0 +1 @@
+../../../mac/include/net/pctt_region_nl.h \ No newline at end of file
diff --git a/kernel/include/net/pctt_region_params.h b/kernel/include/net/pctt_region_params.h
new file mode 120000
index 0000000..2b147ff
--- /dev/null
+++ b/kernel/include/net/pctt_region_params.h
@@ -0,0 +1 @@
+../../../mac/include/net/pctt_region_params.h \ No newline at end of file
diff --git a/kernel/include/net/vendor_cmd.h b/kernel/include/net/vendor_cmd.h
new file mode 120000
index 0000000..ffc7086
--- /dev/null
+++ b/kernel/include/net/vendor_cmd.h
@@ -0,0 +1 @@
+../../../mac/include/net/vendor_cmd.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/Kbuild b/kernel/net/mcps802154/Kbuild
new file mode 100644
index 0000000..1fd78c3
--- /dev/null
+++ b/kernel/net/mcps802154/Kbuild
@@ -0,0 +1,71 @@
+ifndef CONFIG_MCPS802154
+CONFIG_MCPS802154:=m
+endif
+
+obj-$(CONFIG_MCPS802154) := mcps802154.o mcps802154_region_fira.o \
+ mcps802154_region_nfcc_coex.o \
+ mcps802154_region_pctt.o \
+
+ccflags-$(CONFIG_MCPS802154_TESTMODE) += -DCONFIG_MCPS802154_TESTMODE
+
+mcps802154-y := \
+ ca.o \
+ default_region.o \
+ endless_scheduler.o \
+ on_demand_scheduler.o \
+ idle_region.o \
+ fproc.o \
+ fproc_broken.o \
+ fproc_multi.o \
+ fproc_vendor.o \
+ fproc_nothing.o \
+ fproc_idle.o \
+ fproc_rx.o \
+ fproc_stopped.o \
+ fproc_tx.o \
+ frame.o \
+ ie.o \
+ mcps_main.o \
+ mcps_skb_frag.o \
+ nl.o \
+ ops.o \
+ regions.o \
+ schedule.o \
+ schedulers.o \
+ trace.o
+
+mcps802154_region_fira-y := \
+ fira_access.o \
+ fira_crypto.o \
+ fira_round_hopping_sequence.o \
+ fira_round_hopping_crypto.o \
+ fira_frame.o \
+ fira_region.o \
+ fira_region_call.o \
+ fira_session.o \
+ fira_session_fsm.o \
+ fira_session_fsm_init.o \
+ fira_session_fsm_idle.o \
+ fira_session_fsm_active.o \
+ fira_sts.o \
+ fira_trace.o \
+ mcps_crypto.o
+
+mcps802154_region_nfcc_coex-y := \
+ nfcc_coex_access.o \
+ nfcc_coex_region.o \
+ nfcc_coex_region_call.o \
+ nfcc_coex_session.o \
+ nfcc_coex_trace.o
+
+mcps802154_region_pctt-y := \
+ pctt_access.o \
+ pctt_region.o \
+ pctt_region_call.o \
+ pctt_session.o \
+ pctt_trace.o
+
+CFLAGS_trace.o := -I$(src) -I$(srctree)/$(src)
+CFLAGS_fira_trace.o := -I$(src) -I$(srctree)/$(src)
+CFLAGS_pctt_trace.o := -I$(src) -I$(srctree)/$(src)
+CFLAGS_nfcc_coex_trace.o := -I$(src) -I$(srctree)/$(src)
diff --git a/kernel/net/mcps802154/backport_nl.h b/kernel/net/mcps802154/backport_nl.h
new file mode 120000
index 0000000..d4e6c20
--- /dev/null
+++ b/kernel/net/mcps802154/backport_nl.h
@@ -0,0 +1 @@
+../../../mac/backport_nl.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/ca.c b/kernel/net/mcps802154/ca.c
new file mode 120000
index 0000000..7ba9df4
--- /dev/null
+++ b/kernel/net/mcps802154/ca.c
@@ -0,0 +1 @@
+../../../mac/ca.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/ca.h b/kernel/net/mcps802154/ca.h
new file mode 120000
index 0000000..16119fb
--- /dev/null
+++ b/kernel/net/mcps802154/ca.h
@@ -0,0 +1 @@
+../../../mac/ca.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/default_region.c b/kernel/net/mcps802154/default_region.c
new file mode 120000
index 0000000..1ece23b
--- /dev/null
+++ b/kernel/net/mcps802154/default_region.c
@@ -0,0 +1 @@
+../../../mac/default_region.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/default_region.h b/kernel/net/mcps802154/default_region.h
new file mode 120000
index 0000000..899bd4e
--- /dev/null
+++ b/kernel/net/mcps802154/default_region.h
@@ -0,0 +1 @@
+../../../mac/default_region.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/endless_scheduler.c b/kernel/net/mcps802154/endless_scheduler.c
new file mode 120000
index 0000000..ffe271c
--- /dev/null
+++ b/kernel/net/mcps802154/endless_scheduler.c
@@ -0,0 +1 @@
+../../../mac/endless_scheduler.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/endless_scheduler.h b/kernel/net/mcps802154/endless_scheduler.h
new file mode 120000
index 0000000..222d943
--- /dev/null
+++ b/kernel/net/mcps802154/endless_scheduler.h
@@ -0,0 +1 @@
+../../../mac/endless_scheduler.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_access.c b/kernel/net/mcps802154/fira_access.c
new file mode 120000
index 0000000..f10c44b
--- /dev/null
+++ b/kernel/net/mcps802154/fira_access.c
@@ -0,0 +1 @@
+../../../mac/fira_access.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_access.h b/kernel/net/mcps802154/fira_access.h
new file mode 120000
index 0000000..9516beb
--- /dev/null
+++ b/kernel/net/mcps802154/fira_access.h
@@ -0,0 +1 @@
+../../../mac/fira_access.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_crypto.c b/kernel/net/mcps802154/fira_crypto.c
new file mode 120000
index 0000000..ae20d55
--- /dev/null
+++ b/kernel/net/mcps802154/fira_crypto.c
@@ -0,0 +1 @@
+../../../mac/fira_crypto.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_crypto.h b/kernel/net/mcps802154/fira_crypto.h
new file mode 120000
index 0000000..1a5ac8c
--- /dev/null
+++ b/kernel/net/mcps802154/fira_crypto.h
@@ -0,0 +1 @@
+../../../mac/fira_crypto.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_frame.c b/kernel/net/mcps802154/fira_frame.c
new file mode 120000
index 0000000..1faceea
--- /dev/null
+++ b/kernel/net/mcps802154/fira_frame.c
@@ -0,0 +1 @@
+../../../mac/fira_frame.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_frame.h b/kernel/net/mcps802154/fira_frame.h
new file mode 120000
index 0000000..50be740
--- /dev/null
+++ b/kernel/net/mcps802154/fira_frame.h
@@ -0,0 +1 @@
+../../../mac/fira_frame.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_region.c b/kernel/net/mcps802154/fira_region.c
new file mode 120000
index 0000000..25b2744
--- /dev/null
+++ b/kernel/net/mcps802154/fira_region.c
@@ -0,0 +1 @@
+../../../mac/fira_region.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_region.h b/kernel/net/mcps802154/fira_region.h
new file mode 120000
index 0000000..d1ae2a0
--- /dev/null
+++ b/kernel/net/mcps802154/fira_region.h
@@ -0,0 +1 @@
+../../../mac/fira_region.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_region_call.c b/kernel/net/mcps802154/fira_region_call.c
new file mode 120000
index 0000000..016a88a
--- /dev/null
+++ b/kernel/net/mcps802154/fira_region_call.c
@@ -0,0 +1 @@
+../../../mac/fira_region_call.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_region_call.h b/kernel/net/mcps802154/fira_region_call.h
new file mode 120000
index 0000000..42dcdf6
--- /dev/null
+++ b/kernel/net/mcps802154/fira_region_call.h
@@ -0,0 +1 @@
+../../../mac/fira_region_call.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto.c b/kernel/net/mcps802154/fira_round_hopping_crypto.c
new file mode 100644
index 0000000..efb8259
--- /dev/null
+++ b/kernel/net/mcps802154/fira_round_hopping_crypto.c
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "fira_round_hopping_crypto_impl.h"
+
+#include <crypto/aes.h>
+#include <linux/scatterlist.h>
+
+int fira_round_hopping_crypto_encrypt(
+ const struct fira_round_hopping_sequence *round_hopping_sequence,
+ const u8 *data, u8 *out)
+{
+ struct scatterlist sg;
+ SYNC_SKCIPHER_REQUEST_ON_STACK(req, round_hopping_sequence->tfm);
+ int r;
+
+ sg_init_one(&sg, round_hopping_sequence->data, AES_BLOCK_SIZE);
+ memcpy(round_hopping_sequence->data, data, AES_BLOCK_SIZE);
+
+ skcipher_request_set_sync_tfm(req, round_hopping_sequence->tfm);
+ skcipher_request_set_callback(req, 0, NULL, NULL);
+ skcipher_request_set_crypt(req, &sg, &sg, AES_BLOCK_SIZE, NULL);
+
+ r = crypto_skcipher_encrypt(req);
+ skcipher_request_zero(req);
+
+ memcpy(out, round_hopping_sequence->data, AES_BLOCK_SIZE);
+
+ return r;
+}
+
+int fira_round_hopping_crypto_init(
+ struct fira_round_hopping_sequence *round_hopping_sequence)
+{
+ struct crypto_sync_skcipher *tfm;
+ u8 *data;
+ int r;
+
+ data = kmalloc(AES_BLOCK_SIZE, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ tfm = crypto_alloc_sync_skcipher("ecb(aes)", 0, 0);
+ if (IS_ERR(tfm)) {
+ r = PTR_ERR(tfm);
+ if (r == -ENOENT) {
+ pr_err("The crypto transform ecb(aes) seems to be missing."
+ " Please check your kernel configuration.\n");
+ }
+ goto err_free_data;
+ }
+
+ r = crypto_sync_skcipher_setkey(tfm, round_hopping_sequence->key,
+ AES_KEYSIZE_128);
+ if (r)
+ goto err_free_tfm;
+
+ round_hopping_sequence->data = data;
+ round_hopping_sequence->tfm = tfm;
+
+ return r;
+err_free_tfm:
+ crypto_free_sync_skcipher(tfm);
+err_free_data:
+ kfree(data);
+ return r;
+}
+
+void fira_round_hopping_crypto_destroy(
+ struct fira_round_hopping_sequence *round_hopping_sequence)
+{
+ kfree(round_hopping_sequence->data);
+ crypto_free_sync_skcipher(round_hopping_sequence->tfm);
+}
diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto.h b/kernel/net/mcps802154/fira_round_hopping_crypto.h
new file mode 120000
index 0000000..6f89208
--- /dev/null
+++ b/kernel/net/mcps802154/fira_round_hopping_crypto.h
@@ -0,0 +1 @@
+../../../mac/fira_round_hopping_crypto.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h b/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h
new file mode 100644
index 0000000..d5d216a
--- /dev/null
+++ b/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef FIRA_ROUND_HOPPING_CRYPTO_IMPL_H
+#define FIRA_ROUND_HOPPING_CRYPTO_IMPL_H
+
+#include "fira_round_hopping_crypto.h"
+#include <crypto/skcipher.h>
+
+struct fira_round_hopping_sequence {
+ u8 key[AES_KEYSIZE_128];
+ u8 *data;
+ struct crypto_sync_skcipher *tfm;
+};
+
+#endif /* FIRA_ROUND_HOPPING_CRYPTO_IMPL_H */
diff --git a/kernel/net/mcps802154/fira_round_hopping_sequence.c b/kernel/net/mcps802154/fira_round_hopping_sequence.c
new file mode 120000
index 0000000..52cd607
--- /dev/null
+++ b/kernel/net/mcps802154/fira_round_hopping_sequence.c
@@ -0,0 +1 @@
+../../../mac/fira_round_hopping_sequence.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_round_hopping_sequence.h b/kernel/net/mcps802154/fira_round_hopping_sequence.h
new file mode 120000
index 0000000..30b3441
--- /dev/null
+++ b/kernel/net/mcps802154/fira_round_hopping_sequence.h
@@ -0,0 +1 @@
+../../../mac/fira_round_hopping_sequence.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session.c b/kernel/net/mcps802154/fira_session.c
new file mode 120000
index 0000000..236e906
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session.c
@@ -0,0 +1 @@
+../../../mac/fira_session.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session.h b/kernel/net/mcps802154/fira_session.h
new file mode 120000
index 0000000..6fc7a5c
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session.h
@@ -0,0 +1 @@
+../../../mac/fira_session.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm.c b/kernel/net/mcps802154/fira_session_fsm.c
new file mode 120000
index 0000000..b815ae3
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm.c
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm.h b/kernel/net/mcps802154/fira_session_fsm.h
new file mode 120000
index 0000000..5506507
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm.h
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_active.c b/kernel/net/mcps802154/fira_session_fsm_active.c
new file mode 120000
index 0000000..63f915b
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_active.c
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_active.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_active.h b/kernel/net/mcps802154/fira_session_fsm_active.h
new file mode 120000
index 0000000..7654f0a
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_active.h
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_active.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_idle.c b/kernel/net/mcps802154/fira_session_fsm_idle.c
new file mode 120000
index 0000000..4725342
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_idle.c
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_idle.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_idle.h b/kernel/net/mcps802154/fira_session_fsm_idle.h
new file mode 120000
index 0000000..5182a72
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_idle.h
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_idle.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_init.c b/kernel/net/mcps802154/fira_session_fsm_init.c
new file mode 120000
index 0000000..41149be
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_init.c
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_init.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_session_fsm_init.h b/kernel/net/mcps802154/fira_session_fsm_init.h
new file mode 120000
index 0000000..f520adb
--- /dev/null
+++ b/kernel/net/mcps802154/fira_session_fsm_init.h
@@ -0,0 +1 @@
+../../../mac/fira_session_fsm_init.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_sts.c b/kernel/net/mcps802154/fira_sts.c
new file mode 120000
index 0000000..84ac15f
--- /dev/null
+++ b/kernel/net/mcps802154/fira_sts.c
@@ -0,0 +1 @@
+../../../mac/fira_sts.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_sts.h b/kernel/net/mcps802154/fira_sts.h
new file mode 120000
index 0000000..e93e356
--- /dev/null
+++ b/kernel/net/mcps802154/fira_sts.h
@@ -0,0 +1 @@
+../../../mac/fira_sts.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fira_trace.c b/kernel/net/mcps802154/fira_trace.c
new file mode 100644
index 0000000..148df18
--- /dev/null
+++ b/kernel/net/mcps802154/fira_trace.c
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#define CREATE_TRACE_POINTS
+#include "fira_trace.h"
diff --git a/kernel/net/mcps802154/fira_trace.h b/kernel/net/mcps802154/fira_trace.h
new file mode 120000
index 0000000..7dd5a65
--- /dev/null
+++ b/kernel/net/mcps802154/fira_trace.h
@@ -0,0 +1 @@
+../../../mac/fira_trace.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc.c b/kernel/net/mcps802154/fproc.c
new file mode 120000
index 0000000..808f290
--- /dev/null
+++ b/kernel/net/mcps802154/fproc.c
@@ -0,0 +1 @@
+../../../mac/fproc.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc.h b/kernel/net/mcps802154/fproc.h
new file mode 120000
index 0000000..e10c2da
--- /dev/null
+++ b/kernel/net/mcps802154/fproc.h
@@ -0,0 +1 @@
+../../../mac/fproc.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_broken.c b/kernel/net/mcps802154/fproc_broken.c
new file mode 120000
index 0000000..1938d26
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_broken.c
@@ -0,0 +1 @@
+../../../mac/fproc_broken.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_idle.c b/kernel/net/mcps802154/fproc_idle.c
new file mode 120000
index 0000000..430ae47
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_idle.c
@@ -0,0 +1 @@
+../../../mac/fproc_idle.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_multi.c b/kernel/net/mcps802154/fproc_multi.c
new file mode 120000
index 0000000..67ef311
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_multi.c
@@ -0,0 +1 @@
+../../../mac/fproc_multi.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_nothing.c b/kernel/net/mcps802154/fproc_nothing.c
new file mode 120000
index 0000000..b090714
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_nothing.c
@@ -0,0 +1 @@
+../../../mac/fproc_nothing.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_rx.c b/kernel/net/mcps802154/fproc_rx.c
new file mode 120000
index 0000000..21d4610
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_rx.c
@@ -0,0 +1 @@
+../../../mac/fproc_rx.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_stopped.c b/kernel/net/mcps802154/fproc_stopped.c
new file mode 120000
index 0000000..a2f151f
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_stopped.c
@@ -0,0 +1 @@
+../../../mac/fproc_stopped.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_tx.c b/kernel/net/mcps802154/fproc_tx.c
new file mode 120000
index 0000000..a40701d
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_tx.c
@@ -0,0 +1 @@
+../../../mac/fproc_tx.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/fproc_vendor.c b/kernel/net/mcps802154/fproc_vendor.c
new file mode 120000
index 0000000..ff613ed
--- /dev/null
+++ b/kernel/net/mcps802154/fproc_vendor.c
@@ -0,0 +1 @@
+../../../mac/fproc_vendor.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/frame.c b/kernel/net/mcps802154/frame.c
new file mode 120000
index 0000000..c37a9e9
--- /dev/null
+++ b/kernel/net/mcps802154/frame.c
@@ -0,0 +1 @@
+../../../mac/frame.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/idle_region.c b/kernel/net/mcps802154/idle_region.c
new file mode 120000
index 0000000..6e6ca32
--- /dev/null
+++ b/kernel/net/mcps802154/idle_region.c
@@ -0,0 +1 @@
+../../../mac/idle_region.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/idle_region.h b/kernel/net/mcps802154/idle_region.h
new file mode 120000
index 0000000..fe7992b
--- /dev/null
+++ b/kernel/net/mcps802154/idle_region.h
@@ -0,0 +1 @@
+../../../mac/idle_region.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/ie.c b/kernel/net/mcps802154/ie.c
new file mode 120000
index 0000000..53183af
--- /dev/null
+++ b/kernel/net/mcps802154/ie.c
@@ -0,0 +1 @@
+../../../mac/ie.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/llhw-ops.h b/kernel/net/mcps802154/llhw-ops.h
new file mode 120000
index 0000000..1b6195d
--- /dev/null
+++ b/kernel/net/mcps802154/llhw-ops.h
@@ -0,0 +1 @@
+../../../mac/llhw-ops.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/mcps802154_fproc.h b/kernel/net/mcps802154/mcps802154_fproc.h
new file mode 120000
index 0000000..add11c0
--- /dev/null
+++ b/kernel/net/mcps802154/mcps802154_fproc.h
@@ -0,0 +1 @@
+../../../mac/mcps802154_fproc.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/mcps802154_i.h b/kernel/net/mcps802154/mcps802154_i.h
new file mode 120000
index 0000000..3cd3c1e
--- /dev/null
+++ b/kernel/net/mcps802154/mcps802154_i.h
@@ -0,0 +1 @@
+../../../mac/mcps802154_i.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/mcps802154_qorvo.h b/kernel/net/mcps802154/mcps802154_qorvo.h
new file mode 120000
index 0000000..48ea31d
--- /dev/null
+++ b/kernel/net/mcps802154/mcps802154_qorvo.h
@@ -0,0 +1 @@
+../../../mac/mcps802154_qorvo.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/mcps_crypto.c b/kernel/net/mcps802154/mcps_crypto.c
new file mode 100644
index 0000000..faef7a4
--- /dev/null
+++ b/kernel/net/mcps802154/mcps_crypto.c
@@ -0,0 +1,321 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
+#include <crypto/hash.h>
+#include <crypto/skcipher.h>
+#include <crypto/aead.h>
+#include <crypto/aes.h>
+
+#include "mcps_crypto.h"
+
+#if !(defined(CONFIG_CRYPTO_HASH2) && defined(CONFIG_CRYPTO_AEAD2))
+#error "required CONFIG_CRYPTO_HASH2 && CONFIG_CRYPTO_AEAD2"
+#endif
+
+#define FIRA_CRYPTO_AEAD_AUTHSIZE 8
+
+
+struct mcps_aes_ccm_star_128_ctx {
+ struct crypto_aead *tfm;
+};
+
+struct mcps_aes_ecb_128_ctx {
+ struct crypto_skcipher *tfm;
+ bool decrypt;
+};
+
+
+int mcps_crypto_cmac_aes_128_digest(const uint8_t *key, const uint8_t *data,
+ unsigned int data_len, uint8_t *out)
+{
+ struct crypto_shash *tfm;
+ int r;
+
+ tfm = crypto_alloc_shash("cmac(aes)", 0, 0);
+ if (IS_ERR(tfm)) {
+ if (PTR_ERR(tfm) == -ENOENT)
+ pr_err("The crypto transform cmac(aes) seems to be missing."
+ " Please check your kernel configuration.\n");
+ return PTR_ERR(tfm);
+ }
+
+ r = crypto_shash_setkey(tfm, key, AES_KEYSIZE_128);
+ if (r != 0)
+ goto out;
+
+ do {
+ /* tfm need to be allocated for kernel < 4.20, so don't remove
+ * this do..while block
+ */
+ SHASH_DESC_ON_STACK(desc, tfm);
+
+ desc->tfm = tfm;
+
+ r = crypto_shash_init(desc);
+ if (r != 0)
+ goto out;
+
+ r = crypto_shash_finup(desc, data, data_len, out);
+ } while (0);
+
+out:
+ crypto_free_shash(tfm);
+
+ return r;
+}
+
+struct mcps_aes_ccm_star_128_ctx *mcps_crypto_aead_aes_ccm_star_128_create(void)
+{
+ struct mcps_aes_ccm_star_128_ctx *ctx;
+ int r;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ goto error;
+
+ ctx->tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
+ if (IS_ERR(ctx->tfm)) {
+ if (PTR_ERR(ctx->tfm) == -ENOENT)
+ pr_err("The crypto transform ccm(aes) seems to be missing."
+ " Please check your kernel configuration.\n");
+ goto error;
+ }
+
+ r = crypto_aead_setauthsize(ctx->tfm, FIRA_CRYPTO_AEAD_AUTHSIZE);
+ if (r != 0)
+ goto error;
+
+ return ctx;
+
+error:
+ mcps_crypto_aead_aes_ccm_star_128_destroy(ctx);
+
+ return NULL;
+}
+
+int mcps_crypto_aead_aes_ccm_star_128_set(struct mcps_aes_ccm_star_128_ctx *ctx,
+ const uint8_t *key)
+{
+ if (!ctx || !key)
+ return -EINVAL;
+
+ return crypto_aead_setkey(ctx->tfm, key, AES_KEYSIZE_128);
+}
+
+void mcps_crypto_aead_aes_ccm_star_128_destroy(struct mcps_aes_ccm_star_128_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ crypto_free_aead(ctx->tfm);
+ kfree(ctx);
+}
+
+int mcps_crypto_aead_aes_ccm_star_128_encrypt(
+ struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce,
+ const uint8_t *header, unsigned int header_len,
+ uint8_t *data, unsigned int data_len,
+ uint8_t *mac, unsigned int mac_len)
+{
+ struct aead_request *req = NULL;
+ struct scatterlist sg[3];
+ u8 iv[AES_BLOCK_SIZE];
+ DECLARE_CRYPTO_WAIT(wait);
+ int r = -1;
+
+ if (!ctx || !nonce || !header || header_len <= 0 || !data ||
+ data_len <= 0 || !mac ||
+ mac_len != FIRA_CRYPTO_AEAD_AUTHSIZE) {
+ return -EINVAL;
+ }
+
+ req = aead_request_alloc(ctx->tfm, GFP_KERNEL);
+ if (!req) {
+ r = -ENOMEM;
+ goto end;
+ }
+
+ sg_init_table(sg, ARRAY_SIZE(sg));
+ sg_set_buf(&sg[0], header, header_len);
+ sg_set_buf(&sg[1], data, data_len);
+ sg_set_buf(&sg[2], mac, mac_len);
+
+ iv[0] = sizeof(u16) - 1;
+ memcpy(iv + 1, nonce, MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN);
+
+ aead_request_set_callback(req,
+ CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
+ aead_request_set_ad(req, header_len);
+ aead_request_set_crypt(req, sg, sg, data_len, iv);
+
+ r = crypto_wait_req(crypto_aead_encrypt(req), &wait);
+
+end:
+ aead_request_free(req);
+
+ return r;
+}
+
+int mcps_crypto_aead_aes_ccm_star_128_decrypt(
+ struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce,
+ const uint8_t *header, unsigned int header_len,
+ uint8_t *data, unsigned int data_len,
+ uint8_t *mac, unsigned int mac_len)
+{
+ struct aead_request *req = NULL;
+ struct scatterlist sg[3];
+ u8 iv[AES_BLOCK_SIZE];
+ DECLARE_CRYPTO_WAIT(wait);
+ int r = -1;
+
+ if (!ctx || !nonce || !header || header_len <= 0 || !data ||
+ data_len <= 0 || !mac ||
+ mac_len != FIRA_CRYPTO_AEAD_AUTHSIZE) {
+ return -EINVAL;
+ }
+
+ req = aead_request_alloc(ctx->tfm, GFP_KERNEL);
+ if (!req) {
+ r = -ENOMEM;
+ goto end;
+ }
+
+ iv[0] = sizeof(u16) - 1;
+ memcpy(iv + 1, nonce, MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN);
+
+ sg_init_table(sg, ARRAY_SIZE(sg));
+ sg_set_buf(&sg[0], header, header_len);
+ sg_set_buf(&sg[1], data, data_len);
+ sg_set_buf(&sg[2], mac, mac_len);
+
+ aead_request_set_callback(req,
+ CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
+ aead_request_set_ad(req, header_len);
+ aead_request_set_crypt(req, sg, sg, data_len + mac_len, iv);
+
+ r = crypto_wait_req(crypto_aead_decrypt(req), &wait);
+
+end:
+ aead_request_free(req);
+
+ return r;
+}
+
+struct mcps_aes_ecb_128_ctx *mcps_crypto_aes_ecb_128_create(void)
+{
+ struct mcps_aes_ecb_128_ctx *ctx;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ goto error;
+
+ ctx->tfm = crypto_alloc_skcipher("ecb(aes)", 0, 0);
+ if (IS_ERR(ctx->tfm)) {
+ if (PTR_ERR(ctx->tfm) == -ENOENT)
+ pr_err("The crypto transform ecb(aes) seems to be missing."
+ " Please check your kernel configuration.\n");
+ goto error;
+ }
+
+ return ctx;
+
+error:
+ mcps_crypto_aes_ecb_128_destroy(ctx);
+
+ return NULL;
+}
+
+int mcps_crypto_aes_ecb_128_set_encrypt(struct mcps_aes_ecb_128_ctx *ctx,
+ const uint8_t *key)
+{
+ if (!ctx || !key)
+ return -EINVAL;
+
+ ctx->decrypt = false;
+
+ return crypto_skcipher_setkey(ctx->tfm, key, AES_KEYSIZE_128);
+}
+
+int mcps_crypto_aes_ecb_128_set_decrypt(struct mcps_aes_ecb_128_ctx *ctx,
+ const uint8_t *key)
+{
+ if (!ctx || !key)
+ return -EINVAL;
+
+ ctx->decrypt = true;
+
+ return crypto_skcipher_setkey(ctx->tfm, key, AES_KEYSIZE_128);
+}
+
+void mcps_crypto_aes_ecb_128_destroy(struct mcps_aes_ecb_128_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ crypto_free_skcipher(ctx->tfm);
+ kfree(ctx);
+}
+
+int mcps_crypto_aes_ecb_128_encrypt(struct mcps_aes_ecb_128_ctx *ctx,
+ const uint8_t *data, unsigned int data_len, uint8_t *out)
+{
+ struct skcipher_request *req = NULL;
+ struct scatterlist sgin, sgout;
+ DECLARE_CRYPTO_WAIT(wait);
+ int r = -1;
+
+ if (!ctx || !data || data_len <= 0 || !out)
+ return -EINVAL;
+
+ /* round to full cipher block */
+ data_len = ((data_len - 1) & -AES_KEYSIZE_128) + AES_KEYSIZE_128;
+
+ req = skcipher_request_alloc(ctx->tfm, GFP_KERNEL);
+ if (!req) {
+ r = -ENOMEM;
+ goto end;
+ }
+
+ sg_init_one(&sgin, data, data_len);
+ sg_init_one(&sgout, out, data_len);
+ skcipher_request_set_callback(req,
+ CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
+ skcipher_request_set_crypt(req, &sgin, &sgout, data_len, NULL);
+
+ if (ctx->decrypt)
+ r = crypto_skcipher_decrypt(req);
+ else
+ r = crypto_skcipher_encrypt(req);
+ r = crypto_wait_req(r, &wait);
+
+end:
+ skcipher_request_free(req);
+
+ return r;
+}
+
diff --git a/kernel/net/mcps802154/mcps_crypto.h b/kernel/net/mcps802154/mcps_crypto.h
new file mode 100644
index 0000000..5a1a5c5
--- /dev/null
+++ b/kernel/net/mcps802154/mcps_crypto.h
@@ -0,0 +1,197 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef MCPS_CRYPTO_H
+#define MCPS_CRYPTO_H
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#else
+#include <stdint.h>
+#endif
+
+#define MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN 13
+
+/**
+ * struct mcps_aes_ccm_star_128_ctx - Context containing AES-128-CCM* related
+ * information.
+ *
+ * This is an opaque structure left to the implementation.
+ */
+struct mcps_aes_ccm_star_128_ctx;
+
+/**
+ * struct mcps_aes_ecb_128_ctx - Context containing AES-128-ECB related
+ * information.
+ *
+ * This is an opaque structure left to the implementation.
+ */
+struct mcps_aes_ecb_128_ctx;
+
+
+/**
+ * mcps_crypto_cmac_aes_128_digest() - Compute a cmac AES 128.
+ * @key: AES key.
+ * @data: Input data.
+ * @data_len: Input data length in bytes.
+ * @out: Output hash, with length AES_BLOCK_SIZE.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_cmac_aes_128_digest(const uint8_t *key, const uint8_t *data,
+ unsigned int data_len, uint8_t *out);
+
+/**
+ * mcps_crypto_aead_aes_ccm_star_128_create() - Create a context using
+ * Authenticated Encryption Associated Data with AES CCM* 128.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: The pointer to the context that will be used to encrypt & decrypt.
+ */
+struct mcps_aes_ccm_star_128_ctx *mcps_crypto_aead_aes_ccm_star_128_create(void);
+
+/**
+ * mcps_crypto_aead_aes_ccm_star_128_set() - Set a context using
+ * Authenticated Encryption Associated Data with AES CCM* 128.
+ * @ctx: Context.
+ * @key: AES key.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aead_aes_ccm_star_128_set(struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *key);
+
+/**
+ * mcps_crypto_aead_aes_ccm_star_128_destroy() - Destroy the Authenticated
+ * Encryption Associated Data with AES CCM* 128 context.
+ * @ctx: Context.
+ *
+ * NOTE: This API should be implemented by platform.
+ */
+void mcps_crypto_aead_aes_ccm_star_128_destroy(struct mcps_aes_ccm_star_128_ctx *ctx);
+
+/**
+ * mcps_crypto_aead_aes_ccm_star_128_encrypt() - Encrypt using Authenticated
+ * Encryption Associated Data with AES CCM* 128.
+ * @ctx: Context.
+ * @nonce: Nonce, with length MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN.
+ * @header: Header data.
+ * @header_len: Header length in bytes.
+ * @data: Data to encrypt, will be replaced with encrypted data.
+ * @data_len: Data length in bytes.
+ * @mac: AES CCM* MAC.
+ * @mac_len: AES CCM* MAC size in bytes.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aead_aes_ccm_star_128_encrypt(
+ struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce,
+ const uint8_t *header, unsigned int header_len,
+ uint8_t *data, unsigned int data_len,
+ uint8_t *mac, unsigned int mac_len);
+
+/**
+ * mcps_crypto_aead_aes_ccm_star_128_decrypt() - Decrypt using Authenticated
+ * Encryption Associated Data with AES CCM* 128.
+ * @ctx: Context.
+ * @nonce: Nonce, with length MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN.
+ * @header: Header data.
+ * @header_len: Header length in bytes.
+ * @data: Data to decrypt, will be replaced with decrypted data.
+ * @data_len: Data length in bytes.
+ * @mac: AES CCM* MAC.
+ * @mac_len: AES CCM* MAC size in bytes.
+ *
+ * NOTE: This API should be implemented by platform. In case of mismatch
+ * between the MAC and calculated MAC, this function should return -EBADMSG.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aead_aes_ccm_star_128_decrypt(
+ struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce,
+ const uint8_t *header, unsigned int header_len,
+ uint8_t *data, unsigned int data_len,
+ uint8_t *mac, unsigned int mac_len);
+
+/**
+ * mcps_crypto_aes_ecb_128_create() - Create a context using AES ECB 128.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: The pointer to the context that will be used to encrypt & decrypt.
+ */
+struct mcps_aes_ecb_128_ctx *mcps_crypto_aes_ecb_128_create(void);
+
+/**
+ * mcps_crypto_aes_ecb_128_set_encrypt() - Set a context using
+ * Authenticated Encryption Associated Data with AES ECB* 128.
+ * @ctx: Context.
+ * @key: AES key.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aes_ecb_128_set_encrypt(struct mcps_aes_ecb_128_ctx *ctx, const uint8_t *key);
+
+/**
+ * mcps_crypto_aes_ecb_128_set_decrypt() - Set a context using
+ * Authenticated Encryption Associated Data with AES ECB* 128.
+ * @ctx: Context.
+ * @key: AES key.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aes_ecb_128_set_decrypt(struct mcps_aes_ecb_128_ctx *ctx, const uint8_t *key);
+
+/**
+ * mcps_crypto_aes_ecb_128_destroy() - Destroy the AES ECB 128 context.
+ * @ctx: Context.
+ *
+ * NOTE: This API should be implemented by platform.
+ */
+void mcps_crypto_aes_ecb_128_destroy(struct mcps_aes_ecb_128_ctx *ctx);
+
+/**
+ * mcps_crypto_aes_ecb_128_encrypt() - Encrypt using AES ECB 128.
+ * @ctx: Context.
+ * @data: Data to encrypt.
+ * @data_len: Data length in bytes, should be a multiple of AES_BLOCK_SIZE.
+ * @out: Ciphered data with same length as data.
+ *
+ * NOTE: This API should be implemented by platform.
+ *
+ * Return: 0 or error.
+ */
+int mcps_crypto_aes_ecb_128_encrypt(struct mcps_aes_ecb_128_ctx *ctx,
+ const uint8_t *data, unsigned int data_len, uint8_t *out);
+
+#endif /* MCPS_CRYPTO_H */
diff --git a/kernel/net/mcps802154/mcps_main.c b/kernel/net/mcps802154/mcps_main.c
new file mode 120000
index 0000000..25880ca
--- /dev/null
+++ b/kernel/net/mcps802154/mcps_main.c
@@ -0,0 +1 @@
+../../../mac/mcps_main.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/mcps_skb_frag.c b/kernel/net/mcps802154/mcps_skb_frag.c
new file mode 120000
index 0000000..cfdbfe1
--- /dev/null
+++ b/kernel/net/mcps802154/mcps_skb_frag.c
@@ -0,0 +1 @@
+../../../mac/mcps_skb_frag.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_access.c b/kernel/net/mcps802154/nfcc_coex_access.c
new file mode 120000
index 0000000..bb60a76
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_access.c
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_access.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_access.h b/kernel/net/mcps802154/nfcc_coex_access.h
new file mode 120000
index 0000000..94e058d
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_access.h
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_access.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_region.c b/kernel/net/mcps802154/nfcc_coex_region.c
new file mode 120000
index 0000000..6746827
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_region.c
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_region.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_region.h b/kernel/net/mcps802154/nfcc_coex_region.h
new file mode 120000
index 0000000..f6fec7b
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_region.h
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_region.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_region_call.c b/kernel/net/mcps802154/nfcc_coex_region_call.c
new file mode 120000
index 0000000..57ba9fe
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_region_call.c
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_region_call.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_region_call.h b/kernel/net/mcps802154/nfcc_coex_region_call.h
new file mode 120000
index 0000000..721c4e9
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_region_call.h
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_region_call.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_session.c b/kernel/net/mcps802154/nfcc_coex_session.c
new file mode 120000
index 0000000..0326872
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_session.c
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_session.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_session.h b/kernel/net/mcps802154/nfcc_coex_session.h
new file mode 120000
index 0000000..c326154
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_session.h
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_session.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/nfcc_coex_trace.c b/kernel/net/mcps802154/nfcc_coex_trace.c
new file mode 100644
index 0000000..fad16db
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_trace.c
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#define CREATE_TRACE_POINTS
+#include "nfcc_coex_trace.h"
diff --git a/kernel/net/mcps802154/nfcc_coex_trace.h b/kernel/net/mcps802154/nfcc_coex_trace.h
new file mode 120000
index 0000000..9561220
--- /dev/null
+++ b/kernel/net/mcps802154/nfcc_coex_trace.h
@@ -0,0 +1 @@
+../../../mac/nfcc_coex_trace.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/nl.c b/kernel/net/mcps802154/nl.c
new file mode 100644
index 0000000..80e520b
--- /dev/null
+++ b/kernel/net/mcps802154/nl.c
@@ -0,0 +1,1244 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/rtnetlink.h>
+#include <net/genetlink.h>
+#include <linux/version.h>
+#include <net/mcps802154_nl.h>
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+#include "nl.h"
+
+#define ATTR_STRING_SIZE 20
+#define ATTR_STRING_POLICY \
+ { \
+ .type = NLA_NUL_STRING, .len = ATTR_STRING_SIZE - 1 \
+ }
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0)
+#define nla_strscpy nla_strlcpy
+#endif
+
+static struct genl_family mcps802154_nl_family;
+
+static const struct nla_policy
+ mcps802154_nl_calibration_policy[MCPS802154_CALIBRATIONS_ATTR_MAX + 1] = {
+ [MCPS802154_CALIBRATIONS_ATTR_KEY] = { .type = NLA_NUL_STRING,
+ .len = 64 },
+ [MCPS802154_CALIBRATIONS_ATTR_VALUE] = { .type = NLA_BINARY },
+ [MCPS802154_CALIBRATIONS_ATTR_STATUS] = { .type = NLA_S32 },
+ };
+
+static const struct nla_policy
+ mcps802154_nl_region_policy[MCPS802154_REGION_MAX + 1] = {
+ [MCPS802154_REGION_ATTR_ID] = { .type = NLA_U32 },
+ [MCPS802154_REGION_ATTR_NAME] = ATTR_STRING_POLICY,
+ [MCPS802154_REGION_ATTR_PARAMS] = { .type = NLA_NESTED },
+ [MCPS802154_REGION_ATTR_CALL] = { .type = NLA_U32 },
+ [MCPS802154_REGION_ATTR_CALL_PARAMS] = { .type = NLA_NESTED },
+ };
+
+static const struct nla_policy mcps802154_nl_policy[MCPS802154_ATTR_MAX + 1] = {
+ [MCPS802154_ATTR_HW] = { .type = NLA_U32 },
+ [MCPS802154_ATTR_WPAN_PHY_NAME] = ATTR_STRING_POLICY,
+ [MCPS802154_ATTR_SCHEDULER_NAME] = ATTR_STRING_POLICY,
+ [MCPS802154_ATTR_SCHEDULER_PARAMS] = { .type = NLA_NESTED },
+ [MCPS802154_ATTR_SCHEDULER_REGIONS] =
+ NLA_POLICY_NESTED_ARRAY(mcps802154_nl_region_policy),
+ [MCPS802154_ATTR_SCHEDULER_CALL] = { .type = NLA_U32 },
+ [MCPS802154_ATTR_SCHEDULER_CALL_PARAMS] = { .type = NLA_NESTED },
+ [MCPS802154_ATTR_SCHEDULER_REGION_CALL] = { .type = NLA_NESTED },
+ [MCPS802154_ATTR_CALIBRATIONS] = { .type = NLA_NESTED },
+ [MCPS802154_ATTR_PWR_STATS] = { .type = NLA_NESTED },
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+ [MCPS802154_ATTR_TESTDATA] = { .type = NLA_NESTED },
+#endif
+};
+
+/**
+ * mcps802154_nl_send_hw() - Append device information to a netlink message.
+ * @local: MCPS private data.
+ * @msg: Message to write to.
+ * @portid: Destination port address.
+ * @seq: Message sequence number.
+ * @flags: Message flags (0 or NLM_F_MULTI).
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_send_hw(struct mcps802154_local *local,
+ struct sk_buff *msg, u32 portid, u32 seq,
+ int flags)
+{
+ void *hdr;
+
+ hdr = genlmsg_put(msg, portid, seq, &mcps802154_nl_family, flags,
+ MCPS802154_CMD_NEW_HW);
+ if (!hdr)
+ return -ENOBUFS;
+
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx) ||
+ nla_put_string(msg, MCPS802154_ATTR_WPAN_PHY_NAME,
+ wpan_phy_name(local->hw->phy)))
+ goto error;
+
+ if (local->ca.scheduler &&
+ nla_put_string(msg, MCPS802154_ATTR_SCHEDULER_NAME,
+ local->ca.scheduler->ops->name))
+ goto error;
+
+ genlmsg_end(msg, hdr);
+ return 0;
+error:
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+}
+
+/**
+ * mcps802154_nl_get_hw() - Request information about a device.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_get_hw(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ struct mcps802154_local *local = info->user_ptr[0];
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ if (mcps802154_nl_send_hw(local, msg, info->snd_portid, info->snd_seq,
+ 0)) {
+ nlmsg_free(msg);
+ return -ENOBUFS;
+ }
+
+ return genlmsg_reply(msg, info);
+}
+
+/**
+ * mcps802154_nl_dump_hw() - Dump information on all devices.
+ * @skb: Allocated message for response.
+ * @cb: Netlink callbacks.
+ *
+ * Return: Size of response message, or error.
+ */
+static int mcps802154_nl_dump_hw(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ int start_idx = cb->args[0];
+ int r = 0;
+ struct mcps802154_local *local;
+
+ rtnl_lock();
+ local = mcps802154_get_first_by_idx(start_idx);
+ if (local) {
+ cb->args[0] = local->hw_idx + 1;
+ r = mcps802154_nl_send_hw(local, skb,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI);
+ }
+ rtnl_unlock();
+
+ return r ? r : skb->len;
+}
+
+/**
+ * mcps802154_nl_set_regions() - Set the regions which populate the schedule.
+ * @local: MCPS private data.
+ * @scheduler_name: Name of the scheduler.
+ * @regions_attr: Nested attribute containing regions parameters.
+ * @extack: Extended ACK report structure.
+ * @update: True if we need only to update region parameters.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_set_regions(struct mcps802154_local *local,
+ const char *scheduler_name,
+ const struct nlattr *regions_attr,
+ struct netlink_ext_ack *extack,
+ bool update)
+{
+ struct nlattr *request;
+ struct nlattr *attrs[MCPS802154_REGION_MAX + 1];
+ int r, rem;
+ u32 region_id = 0;
+ char region_name[ATTR_STRING_SIZE];
+
+ if (!regions_attr)
+ return -EINVAL;
+
+ nla_for_each_nested (request, regions_attr, rem) {
+ r = nla_parse_nested(attrs, MCPS802154_REGION_MAX, request,
+ mcps802154_nl_region_policy, extack);
+ if (r)
+ return r;
+
+ if (!attrs[MCPS802154_REGION_ATTR_NAME])
+ return -EINVAL;
+
+ if (attrs[MCPS802154_REGION_ATTR_ID])
+ region_id =
+ nla_get_s32(attrs[MCPS802154_REGION_ATTR_ID]);
+ nla_strscpy(region_name, attrs[MCPS802154_REGION_ATTR_NAME],
+ sizeof(region_name));
+ mutex_lock(&local->fsm_lock);
+ if (update)
+ r = mcps802154_ca_set_region_parameters(
+ local, scheduler_name, region_id, region_name,
+ attrs[MCPS802154_REGION_ATTR_PARAMS], extack);
+ else
+ r = mcps802154_ca_set_region(
+ local, scheduler_name, region_id, region_name,
+ attrs[MCPS802154_REGION_ATTR_PARAMS], extack);
+ mutex_unlock(&local->fsm_lock);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * mcps802154_nl_generic_set_params() - Set scheduler parameters and/or regions.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_generic_set_params(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ int r;
+ struct nlattr *params_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_PARAMS];
+ struct nlattr *regions_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_REGIONS];
+ struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME];
+ char name[ATTR_STRING_SIZE];
+ enum mcps802154_commands cmd = info->genlhdr->cmd;
+
+ if (!name_attr ||
+ (cmd == MCPS802154_CMD_SET_SCHEDULER_PARAMS && !params_attr) ||
+ ((cmd == MCPS802154_CMD_SET_SCHEDULER_REGIONS ||
+ cmd == MCPS802154_CMD_SET_REGIONS_PARAMS) &&
+ (!regions_attr || params_attr)))
+ return -EINVAL;
+
+ nla_strscpy(name, name_attr, sizeof(name));
+
+ if (params_attr) {
+ mutex_lock(&local->fsm_lock);
+ r = mcps802154_ca_scheduler_set_parameters(
+ local, name, params_attr, info->extack);
+ mutex_unlock(&local->fsm_lock);
+
+ if (r)
+ return r;
+ }
+
+ if (regions_attr)
+ r = mcps802154_nl_set_regions(
+ local, name, regions_attr, info->extack,
+ cmd == MCPS802154_CMD_SET_REGIONS_PARAMS);
+
+ return r;
+}
+
+/**
+ * mcps802154_nl_set_scheduler() - Set the scheduler which manage the schedule.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_set_scheduler(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct nlattr *params_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_PARAMS];
+ struct nlattr *regions_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_REGIONS];
+ char name[ATTR_STRING_SIZE];
+ int r;
+
+ if (!info->attrs[MCPS802154_ATTR_SCHEDULER_NAME])
+ return -EINVAL;
+ nla_strscpy(name, info->attrs[MCPS802154_ATTR_SCHEDULER_NAME],
+ sizeof(name));
+
+ mutex_lock(&local->fsm_lock);
+ r = mcps802154_ca_set_scheduler(local, name, params_attr, info->extack);
+ mutex_unlock(&local->fsm_lock);
+ if (r)
+ return r;
+
+ if (regions_attr)
+ r = mcps802154_nl_set_regions(local, name, regions_attr,
+ info->extack, false);
+
+ return r;
+}
+
+/**
+ * mcps802154_nl_call_scheduler() - Call scheduler specific procedure.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_call_scheduler(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ int r;
+ struct nlattr *params_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_CALL_PARAMS];
+ struct nlattr *call_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_CALL];
+ struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME];
+ char name[ATTR_STRING_SIZE];
+ u32 call_id;
+
+ if (!name_attr || !call_attr)
+ return -EINVAL;
+
+ nla_strscpy(name, name_attr, sizeof(name));
+ call_id = nla_get_u32(call_attr);
+
+ mutex_lock(&local->fsm_lock);
+ r = mcps802154_ca_scheduler_call(local, name, call_id, params_attr,
+ info);
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+/**
+ * mcps802154_nl_call_region() - Call region specific procedure.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_call_region(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct nlattr *region_call_attr =
+ info->attrs[MCPS802154_ATTR_SCHEDULER_REGION_CALL];
+ struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME];
+ struct nlattr *attrs[MCPS802154_REGION_MAX + 1];
+ int r, call_id;
+ char scheduler_name[ATTR_STRING_SIZE];
+ u32 region_id = 0;
+ char region_name[ATTR_STRING_SIZE];
+
+ if (!name_attr || !region_call_attr)
+ return -EINVAL;
+
+ nla_strscpy(scheduler_name, name_attr, sizeof(scheduler_name));
+
+ r = nla_parse_nested(attrs, MCPS802154_REGION_MAX, region_call_attr,
+ mcps802154_nl_region_policy, info->extack);
+ if (r)
+ return r;
+
+ if (!attrs[MCPS802154_REGION_ATTR_NAME] ||
+ !attrs[MCPS802154_REGION_ATTR_CALL])
+ return -EINVAL;
+
+ if (attrs[MCPS802154_REGION_ATTR_ID])
+ region_id = nla_get_s32(attrs[MCPS802154_REGION_ATTR_ID]);
+ nla_strscpy(region_name, attrs[MCPS802154_REGION_ATTR_NAME],
+ sizeof(region_name));
+ call_id = nla_get_u32(attrs[MCPS802154_REGION_ATTR_CALL]);
+
+ mutex_lock(&local->fsm_lock);
+ local->cur_cmd_info = info;
+ r = mcps802154_ca_call_region(local, scheduler_name, region_id,
+ region_name, call_id,
+ attrs[MCPS802154_REGION_ATTR_CALL_PARAMS],
+ info);
+ local->cur_cmd_info = NULL;
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+/**
+ * mcps802154_nl_close_scheduler() - Close current scheduler and its regions.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_close_scheduler(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ mutex_lock(&local->fsm_lock);
+ local->cur_cmd_info = info;
+ mcps802154_ca_close(local);
+ local->cur_cmd_info = NULL;
+ mutex_unlock(&local->fsm_lock);
+
+ return 0;
+}
+
+struct sk_buff *
+mcps802154_region_call_alloc_reply_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ u32 call_id, int approx_len)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *call, *params;
+
+ if (WARN_ON(!local->cur_cmd_info))
+ return NULL;
+
+ msg = nlmsg_new(approx_len + NLMSG_HDRLEN, GFP_KERNEL);
+ if (!msg)
+ return NULL;
+
+ hdr = genlmsg_put(msg, local->cur_cmd_info->snd_portid,
+ local->cur_cmd_info->snd_seq, &mcps802154_nl_family,
+ 0, MCPS802154_CMD_CALL_REGION);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx))
+ goto nla_put_failure;
+
+ call = nla_nest_start(msg, MCPS802154_ATTR_SCHEDULER_REGION_CALL);
+ if (!call)
+ goto nla_put_failure;
+
+ if (nla_put_string(msg, MCPS802154_REGION_ATTR_NAME,
+ region->ops->name) ||
+ nla_put_u32(msg, MCPS802154_REGION_ATTR_CALL, call_id))
+ goto nla_put_failure;
+
+ params = nla_nest_start(msg, MCPS802154_REGION_ATTR_CALL_PARAMS);
+ if (!params)
+ goto nla_put_failure;
+
+ ((void **)msg->cb)[0] = hdr;
+ ((void **)msg->cb)[1] = call;
+ ((void **)msg->cb)[2] = params;
+
+ return msg;
+nla_put_failure:
+ kfree_skb(msg);
+ return NULL;
+}
+EXPORT_SYMBOL(mcps802154_region_call_alloc_reply_skb);
+
+int mcps802154_region_call_reply(struct mcps802154_llhw *llhw,
+ struct sk_buff *skb)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ void *hdr = ((void **)skb->cb)[0];
+ struct nlattr *call = ((void **)skb->cb)[1];
+ struct nlattr *params = ((void **)skb->cb)[2];
+
+ /* Clear CB data for netlink core to own from now on. */
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ if (WARN_ON(!local->cur_cmd_info)) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ nla_nest_end(skb, params);
+ nla_nest_end(skb, call);
+ genlmsg_end(skb, hdr);
+
+ return genlmsg_reply(skb, local->cur_cmd_info);
+}
+EXPORT_SYMBOL(mcps802154_region_call_reply);
+
+struct sk_buff *
+mcps802154_region_event_alloc_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region, u32 call_id,
+ u32 portid, int approx_len, gfp_t gfp)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *call, *params;
+
+ msg = nlmsg_new(approx_len + NLMSG_HDRLEN, gfp);
+ if (!msg)
+ return NULL;
+
+ hdr = genlmsg_put(msg, portid, 0, &mcps802154_nl_family, 0,
+ MCPS802154_CMD_CALL_REGION);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx))
+ goto nla_put_failure;
+
+ call = nla_nest_start(msg, MCPS802154_ATTR_SCHEDULER_REGION_CALL);
+ if (!call)
+ goto nla_put_failure;
+
+ if (nla_put_string(msg, MCPS802154_REGION_ATTR_NAME,
+ region->ops->name) ||
+ nla_put_u32(msg, MCPS802154_REGION_ATTR_CALL, call_id))
+ goto nla_put_failure;
+
+ params = nla_nest_start(msg, MCPS802154_REGION_ATTR_CALL_PARAMS);
+ if (!params)
+ goto nla_put_failure;
+
+ ((void **)msg->cb)[0] = hdr;
+ ((void **)msg->cb)[1] = call;
+ ((void **)msg->cb)[2] = params;
+
+ return msg;
+nla_put_failure:
+ kfree_skb(msg);
+ return NULL;
+}
+EXPORT_SYMBOL(mcps802154_region_event_alloc_skb);
+
+int mcps802154_region_event(struct mcps802154_llhw *llhw, struct sk_buff *skb)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ void *hdr = ((void **)skb->cb)[0];
+ struct nlmsghdr *nlhdr = nlmsg_hdr(skb);
+ struct nlattr *call = ((void **)skb->cb)[1];
+ struct nlattr *params = ((void **)skb->cb)[2];
+
+ /* Clear CB data for netlink core to own from now on. */
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ nla_nest_end(skb, params);
+ nla_nest_end(skb, call);
+ genlmsg_end(skb, hdr);
+
+ return genlmsg_unicast(wpan_phy_net(local->hw->phy), skb,
+ nlhdr->nlmsg_pid);
+}
+EXPORT_SYMBOL(mcps802154_region_event);
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+/**
+ * mcps802154_nl_testmode_do() - Run a testmode command.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * This function is a passthrough to send testmode command to
+ * the driver.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_testmode_do(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ int r;
+
+ if (!local->ops->testmode_cmd)
+ return -EOPNOTSUPP;
+
+ if (!info->attrs[MCPS802154_ATTR_TESTDATA])
+ return -EINVAL;
+
+ mutex_lock(&local->fsm_lock);
+ local->cur_cmd_info = info;
+ r = llhw_testmode_cmd(local,
+ nla_data(info->attrs[MCPS802154_ATTR_TESTDATA]),
+ nla_len(info->attrs[MCPS802154_ATTR_TESTDATA]));
+ local->cur_cmd_info = NULL;
+ mutex_unlock(&local->fsm_lock);
+ return r;
+}
+
+struct sk_buff *
+mcps802154_testmode_alloc_reply_skb(struct mcps802154_llhw *llhw, int approxlen)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ struct sk_buff *skb;
+ void *hdr;
+ struct nlattr *data;
+
+ if (WARN_ON(!local->cur_cmd_info))
+ return NULL;
+
+ skb = nlmsg_new(approxlen + 100, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+
+ /* Append testmode header to the netlink message */
+ hdr = genlmsg_put(skb, local->cur_cmd_info->snd_portid,
+ local->cur_cmd_info->snd_seq, &mcps802154_nl_family,
+ 0, MCPS802154_CMD_TESTMODE);
+ if (!hdr)
+ goto nla_put_failure;
+
+ /* Start putting nested testmode data into the netlink message */
+ data = nla_nest_start(skb, MCPS802154_ATTR_TESTDATA);
+ if (!data)
+ goto nla_put_failure;
+
+ /* We put our private variables there to keep them across layers */
+ ((void **)skb->cb)[0] = hdr;
+ ((void **)skb->cb)[1] = data;
+
+ return skb;
+nla_put_failure:
+ kfree_skb(skb);
+ return NULL;
+}
+EXPORT_SYMBOL(mcps802154_testmode_alloc_reply_skb);
+
+int mcps802154_testmode_reply(struct mcps802154_llhw *llhw, struct sk_buff *skb)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ void *hdr = ((void **)skb->cb)[0];
+ struct nlattr *data = ((void **)skb->cb)[1];
+
+ /* Clear CB data for netlink core to own from now on. */
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ if (WARN_ON(!local->cur_cmd_info)) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /* Stop putting nested testmode data into the netlink message */
+ nla_nest_end(skb, data);
+ genlmsg_end(skb, hdr);
+ return genlmsg_reply(skb, local->cur_cmd_info);
+}
+EXPORT_SYMBOL(mcps802154_testmode_reply);
+#endif
+
+/**
+ * mcps802154_nl_put_calibration() - put on calibration in msg.
+ * @msg: Request message.
+ * @key: calibration name
+ * @status: status of reading operation, and length of calibration value when positive.
+ * @data: calibration value, if available.
+ * @onlykey: true to put only calibration key.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_put_calibration(struct sk_buff *msg, const char *key,
+ int status, void *data, bool onlykey)
+{
+ struct nlattr *calibration;
+ int r;
+
+ calibration = nla_nest_start(msg, 1);
+ if (!calibration)
+ return -EMSGSIZE;
+
+ r = nla_put_string(msg, MCPS802154_CALIBRATIONS_ATTR_KEY, key);
+ if (r)
+ return -EMSGSIZE;
+ if (onlykey)
+ goto finish;
+
+ if (status < 0)
+ r = nla_put_s32(msg, MCPS802154_CALIBRATIONS_ATTR_STATUS,
+ -status);
+ else if (data != NULL)
+ /* when positive, the status represent the data length. */
+ r = nla_put(msg, MCPS802154_CALIBRATIONS_ATTR_VALUE, status,
+ data);
+ if (r)
+ return -EMSGSIZE;
+
+finish:
+ nla_nest_end(msg, calibration);
+ return 0;
+}
+
+/**
+ * mcps802154_nl_set_calibration() - Set calibrations parameters.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_set_calibration(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct sk_buff *msg;
+ void *hdr;
+ int err;
+
+ if (!local->ops->set_calibration)
+ return -EOPNOTSUPP;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &mcps802154_nl_family, 0,
+ MCPS802154_CMD_SET_CALIBRATIONS);
+ if (!hdr) {
+ err = -ENOBUFS;
+ goto failure;
+ }
+
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) {
+ err = -EMSGSIZE;
+ goto nla_put_failure;
+ }
+
+ if (info->attrs[MCPS802154_ATTR_CALIBRATIONS]) {
+ struct nlattr *attrs[MCPS802154_CALIBRATIONS_ATTR_MAX + 1];
+ struct nlattr *calibrations, *input;
+ int rem;
+
+ nla_for_each_nested (
+ input, info->attrs[MCPS802154_ATTR_CALIBRATIONS], rem) {
+ char *key;
+ int r;
+
+ r = nla_parse_nested(
+ attrs, MCPS802154_CALIBRATIONS_ATTR_MAX, input,
+ mcps802154_nl_calibration_policy, info->extack);
+ if (r)
+ continue;
+
+ if (!attrs[MCPS802154_CALIBRATIONS_ATTR_KEY])
+ continue;
+ key = nla_data(attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]);
+
+ if (!attrs[MCPS802154_CALIBRATIONS_ATTR_VALUE])
+ r = -EINVAL;
+ else {
+ struct nlattr *value;
+
+ value = attrs[MCPS802154_CALIBRATIONS_ATTR_VALUE];
+ r = llhw_set_calibration(local, key,
+ nla_data(value),
+ nla_len(value));
+ }
+ if (r < 0) {
+ calibrations = nla_nest_start(
+ msg, MCPS802154_ATTR_CALIBRATIONS);
+ /* Put the result in the response message. */
+ err = mcps802154_nl_put_calibration(
+ msg, key, r, NULL, false);
+ if (err)
+ goto nla_put_failure;
+ nla_nest_end(msg, calibrations);
+ break;
+ }
+ }
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+failure:
+ nlmsg_free(msg);
+ return err;
+}
+
+/**
+ * mcps802154_nl_get_calibration() - Set calibrations parameters.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_get_calibration(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct nlattr *attrs[MCPS802154_CALIBRATIONS_ATTR_MAX + 1];
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct nlattr *calibrations;
+ struct nlattr *input;
+ struct sk_buff *msg;
+ void *hdr;
+ char *key;
+ u32 tmp[32];
+ int err;
+ int r;
+
+ if (!local->ops->get_calibration)
+ return -EOPNOTSUPP;
+
+ /* NLMSG_DEFAULT_SIZE isn't enough for 4 antennas configuration.
+ So add a full page to maintain page alignment of the message size. */
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE + PAGE_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &mcps802154_nl_family, 0,
+ MCPS802154_CMD_GET_CALIBRATIONS);
+ if (!hdr) {
+ err = -ENOBUFS;
+ goto failure;
+ }
+
+ /* Build the confirm message in same time as request message. */
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) {
+ err = -EMSGSIZE;
+ goto nla_put_failure;
+ }
+
+ calibrations = nla_nest_start(msg, MCPS802154_ATTR_CALIBRATIONS);
+ if (info->attrs[MCPS802154_ATTR_CALIBRATIONS]) {
+ int rem;
+
+ nla_for_each_nested (
+ input, info->attrs[MCPS802154_ATTR_CALIBRATIONS], rem) {
+ r = nla_parse_nested(
+ attrs, MCPS802154_CALIBRATIONS_ATTR_MAX, input,
+ mcps802154_nl_calibration_policy, info->extack);
+ if (r)
+ continue;
+ if (!attrs[MCPS802154_CALIBRATIONS_ATTR_KEY])
+ continue;
+
+ key = nla_data(attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]);
+ r = llhw_get_calibration(local, key, &tmp, sizeof(tmp));
+
+ /* Put the result in the response message. */
+ err = mcps802154_nl_put_calibration(msg, key, r, &tmp,
+ false);
+ if (err)
+ goto nla_put_failure;
+ }
+ } else if (local->ops->list_calibration) {
+ const char *const *calibration;
+ const char *const *entry;
+
+ calibration = llhw_list_calibration(local);
+ if (!calibration) {
+ err = -ENOENT;
+ goto nla_put_failure;
+ }
+ for (entry = calibration; *entry; entry++) {
+ r = llhw_get_calibration(local, *entry, &tmp,
+ sizeof(tmp));
+
+ /* Put the result in the response message. */
+ err = mcps802154_nl_put_calibration(msg, *entry, r,
+ &tmp, false);
+ if (err)
+ goto nla_put_failure;
+ }
+ }
+ nla_nest_end(msg, calibrations);
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+failure:
+ nlmsg_free(msg);
+ return err;
+}
+
+/**
+ * mcps802154_nl_list_calibration() - Set calibrations parameters.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_list_calibration(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct nlattr *calibrations;
+ const char *const *list;
+ const char *const *entry;
+ struct sk_buff *msg;
+ void *hdr;
+ int err;
+
+ if (!local->ops->list_calibration)
+ return -EOPNOTSUPP;
+
+ list = llhw_list_calibration(local);
+ if (!list)
+ return -ENOENT;
+ /* NLMSG_DEFAULT_SIZE isn't enough for 4 antennas configuration.
+ So add a full page to maintain page alignment of the message size. */
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE + PAGE_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &mcps802154_nl_family, 0,
+ MCPS802154_CMD_LIST_CALIBRATIONS);
+ if (!hdr) {
+ err = -ENOBUFS;
+ goto failure;
+ }
+
+ if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) {
+ err = -EMSGSIZE;
+ goto nla_put_failure;
+ }
+
+ calibrations = nla_nest_start(msg, MCPS802154_ATTR_CALIBRATIONS);
+ for (entry = list; *entry; entry++) {
+ /* Put the result in the response message. */
+ err = mcps802154_nl_put_calibration(msg, *entry, 0, NULL, true);
+ if (err)
+ goto nla_put_failure;
+ }
+ nla_nest_end(msg, calibrations);
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+failure:
+ nlmsg_free(msg);
+ return err;
+}
+
+/**
+ * mcps802154_nl_put_pwr_stats_state() - Put a power statistic state on a netlink message.
+ * @msg: Netlink message.
+ * @state: Related power statistic state.
+ * @time: Duration of this state.
+ * @count: Transitions count to this state.
+ *
+ * Return: 0 or error.
+ */
+static int
+mcps802154_nl_put_pwr_stats_state(struct sk_buff *msg,
+ enum mcps802154_pwr_stats_attrs state,
+ u32 time, u32 count)
+{
+ struct nlattr *nl_pwr_stats_state;
+
+ nl_pwr_stats_state = nla_nest_start(msg, state);
+ if (!nl_pwr_stats_state)
+ return -EMSGSIZE;
+ if (nla_put_u32(msg, MCPS802154_PWR_STATS_STATE_ATTR_TIME, time))
+ return -EMSGSIZE;
+ if (nla_put_u32(msg, MCPS802154_PWR_STATS_STATE_ATTR_COUNT, count))
+ return -EMSGSIZE;
+ nla_nest_end(msg, nl_pwr_stats_state);
+ return 0;
+}
+
+/**
+ * mcps802154_nl_get_pwr_stats() - Get power statistics.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_get_pwr_stats(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct mcps802154_local *local = info->user_ptr[0];
+ struct mcps802154_llhw *llhw = &local->llhw;
+ struct sk_buff *msg;
+ void *hdr;
+ struct mcps802154_power_stats pwr_stats;
+ struct nlattr *nl_pwr_stats;
+ int rc;
+
+ if (!local->ops->get_power_stats)
+ return -EOPNOTSUPP;
+
+ /* Get the power statistics from the low level hardware driver. */
+ rc = local->ops->get_power_stats(llhw, &pwr_stats);
+ if (rc)
+ return rc;
+
+ /* Build the response netlink message. */
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &mcps802154_nl_family, 0,
+ MCPS802154_CMD_GET_PWR_STATS);
+ if (!hdr) {
+ rc = -ENOBUFS;
+ goto failure;
+ }
+
+ nl_pwr_stats = nla_nest_start(msg, MCPS802154_ATTR_PWR_STATS);
+ if (!nl_pwr_stats)
+ goto nla_put_failure;
+
+ /* Process the SLEEP state. */
+ rc = mcps802154_nl_put_pwr_stats_state(
+ msg, MCPS802154_PWR_STATS_ATTR_SLEEP,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_SLEEP].dur /
+ 1000000,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_SLEEP].count);
+ if (rc)
+ goto nla_put_failure;
+
+ /* Process the IDLE state. */
+ rc = mcps802154_nl_put_pwr_stats_state(
+ msg, MCPS802154_PWR_STATS_ATTR_IDLE,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_IDLE].dur /
+ 1000000,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_IDLE].count);
+ if (rc)
+ goto nla_put_failure;
+
+ /* Process the RX state. */
+ rc = mcps802154_nl_put_pwr_stats_state(
+ msg, MCPS802154_PWR_STATS_ATTR_RX,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_RX].dur /
+ 1000000,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_RX].count);
+ if (rc)
+ goto nla_put_failure;
+
+ /* Process the TX state. */
+ rc = mcps802154_nl_put_pwr_stats_state(
+ msg, MCPS802154_PWR_STATS_ATTR_TX,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_TX].dur /
+ 1000000,
+ pwr_stats.power_state_stats[MCPS802154_PWR_STATE_TX].count);
+ if (rc)
+ goto nla_put_failure;
+
+ /* Process the interrupts count. */
+ if (nla_put_u32(msg, MCPS802154_PWR_STATS_ATTR_INTERRUPTS,
+ pwr_stats.interrupts)) {
+ rc = -EMSGSIZE;
+ goto nla_put_failure;
+ }
+
+ nla_nest_end(msg, nl_pwr_stats);
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+failure:
+ nlmsg_free(msg);
+ return rc;
+}
+
+enum mcps802154_nl_internal_flags {
+ MCPS802154_NL_NEED_HW = 1,
+};
+
+/**
+ * mcps802154_get_from_info() - Retrieve private data from netlink request.
+ * information.
+ * @info: Request information.
+ *
+ * Return: Found MCPS data, or error pointer.
+ */
+static struct mcps802154_local *mcps802154_get_from_info(struct genl_info *info)
+{
+ struct nlattr **attrs = info->attrs;
+ int hw_idx;
+ struct mcps802154_local *local;
+
+ ASSERT_RTNL();
+
+ if (!attrs[MCPS802154_ATTR_HW])
+ return ERR_PTR(-EINVAL);
+
+ hw_idx = nla_get_u32(attrs[MCPS802154_ATTR_HW]);
+
+ local = mcps802154_get_first_by_idx(hw_idx);
+ if (!local || local->hw_idx != hw_idx)
+ return ERR_PTR(-ENODEV);
+
+ if (!net_eq(wpan_phy_net(local->hw->phy), genl_info_net(info)))
+ return ERR_PTR(-ENODEV);
+
+ return local;
+}
+
+/**
+ * mcps802154_nl_pre_doit() - Called before single requests (but not dump).
+ * @ops: Command to be executed ops structure.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Set MCPS private data in user_ptr[0] if needed, and lock RTNL to make it
+ * stick.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_nl_pre_doit(const struct genl_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ struct mcps802154_local *local;
+
+ if (ops->internal_flags & MCPS802154_NL_NEED_HW) {
+ rtnl_lock();
+ local = mcps802154_get_from_info(info);
+ if (IS_ERR(local)) {
+ rtnl_unlock();
+ return PTR_ERR(local);
+ }
+ info->user_ptr[0] = local;
+ }
+
+ return 0;
+}
+
+/**
+ * mcps802154_nl_post_doit() - Called after single requests (but not dump).
+ * @ops: Command to be executed ops structure.
+ * @skb: Request message.
+ * @info: Request information.
+ *
+ * Release RTNL if needed.
+ */
+static void mcps802154_nl_post_doit(const struct genl_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ if (ops->internal_flags & MCPS802154_NL_NEED_HW)
+ rtnl_unlock();
+}
+
+static const struct genl_ops mcps802154_nl_ops[] = {
+ {
+ .cmd = MCPS802154_CMD_GET_HW,
+ .doit = mcps802154_nl_get_hw,
+ .dumpit = mcps802154_nl_dump_hw,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_SET_SCHEDULER,
+ .doit = mcps802154_nl_set_scheduler,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_SET_SCHEDULER_PARAMS,
+ .doit = mcps802154_nl_generic_set_params,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_CALL_SCHEDULER,
+ .doit = mcps802154_nl_call_scheduler,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_CLOSE_SCHEDULER,
+ .doit = mcps802154_nl_close_scheduler,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_SET_SCHEDULER_REGIONS,
+ .doit = mcps802154_nl_generic_set_params,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_SET_REGIONS_PARAMS,
+ .doit = mcps802154_nl_generic_set_params,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_CALL_REGION,
+ .doit = mcps802154_nl_call_region,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+#ifdef CONFIG_MCPS802154_TESTMODE
+ {
+ .cmd = MCPS802154_CMD_TESTMODE,
+ .doit = mcps802154_nl_testmode_do,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+#endif
+ {
+ .cmd = MCPS802154_CMD_SET_CALIBRATIONS,
+ .doit = mcps802154_nl_set_calibration,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_GET_CALIBRATIONS,
+ .doit = mcps802154_nl_get_calibration,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_LIST_CALIBRATIONS,
+ .doit = mcps802154_nl_list_calibration,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+ {
+ .cmd = MCPS802154_CMD_GET_PWR_STATS,
+ .doit = mcps802154_nl_get_pwr_stats,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = MCPS802154_NL_NEED_HW,
+ },
+};
+
+static struct genl_family mcps802154_nl_family __ro_after_init = {
+ .name = MCPS802154_GENL_NAME,
+ .version = 1,
+ .maxattr = MCPS802154_ATTR_MAX,
+ .policy = mcps802154_nl_policy,
+ .netnsok = true,
+ .pre_doit = mcps802154_nl_pre_doit,
+ .post_doit = mcps802154_nl_post_doit,
+ .ops = mcps802154_nl_ops,
+ .n_ops = ARRAY_SIZE(mcps802154_nl_ops),
+ .module = THIS_MODULE,
+};
+
+int __init mcps802154_nl_init(void)
+{
+ return genl_register_family(&mcps802154_nl_family);
+}
+
+void __exit mcps802154_nl_exit(void)
+{
+ genl_unregister_family(&mcps802154_nl_family);
+}
diff --git a/kernel/net/mcps802154/nl.h b/kernel/net/mcps802154/nl.h
new file mode 100644
index 0000000..8bfe3bb
--- /dev/null
+++ b/kernel/net/mcps802154/nl.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef MCPS802154_NL_H
+#define MCPS802154_NL_H
+
+int mcps802154_nl_init(void);
+void mcps802154_nl_exit(void);
+
+#endif /* MCPS802154_NL_H */
diff --git a/kernel/net/mcps802154/on_demand_scheduler.c b/kernel/net/mcps802154/on_demand_scheduler.c
new file mode 120000
index 0000000..5899a77
--- /dev/null
+++ b/kernel/net/mcps802154/on_demand_scheduler.c
@@ -0,0 +1 @@
+../../../mac/on_demand_scheduler.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/on_demand_scheduler.h b/kernel/net/mcps802154/on_demand_scheduler.h
new file mode 120000
index 0000000..be9bdbe
--- /dev/null
+++ b/kernel/net/mcps802154/on_demand_scheduler.h
@@ -0,0 +1 @@
+../../../mac/on_demand_scheduler.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/ops.c b/kernel/net/mcps802154/ops.c
new file mode 120000
index 0000000..4e1e3ac
--- /dev/null
+++ b/kernel/net/mcps802154/ops.c
@@ -0,0 +1 @@
+../../../mac/ops.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_access.c b/kernel/net/mcps802154/pctt_access.c
new file mode 120000
index 0000000..2c1912c
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_access.c
@@ -0,0 +1 @@
+../../../mac/pctt_access.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_access.h b/kernel/net/mcps802154/pctt_access.h
new file mode 120000
index 0000000..065c8b6
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_access.h
@@ -0,0 +1 @@
+../../../mac/pctt_access.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_region.c b/kernel/net/mcps802154/pctt_region.c
new file mode 120000
index 0000000..3ba8a7e
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_region.c
@@ -0,0 +1 @@
+../../../mac/pctt_region.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_region.h b/kernel/net/mcps802154/pctt_region.h
new file mode 120000
index 0000000..912feee
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_region.h
@@ -0,0 +1 @@
+../../../mac/pctt_region.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_region_call.c b/kernel/net/mcps802154/pctt_region_call.c
new file mode 120000
index 0000000..2154d8e
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_region_call.c
@@ -0,0 +1 @@
+../../../mac/pctt_region_call.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_region_call.h b/kernel/net/mcps802154/pctt_region_call.h
new file mode 120000
index 0000000..52ebe0b
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_region_call.h
@@ -0,0 +1 @@
+../../../mac/pctt_region_call.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_session.c b/kernel/net/mcps802154/pctt_session.c
new file mode 120000
index 0000000..9e657a2
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_session.c
@@ -0,0 +1 @@
+../../../mac/pctt_session.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_session.h b/kernel/net/mcps802154/pctt_session.h
new file mode 120000
index 0000000..4171fe7
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_session.h
@@ -0,0 +1 @@
+../../../mac/pctt_session.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/pctt_trace.c b/kernel/net/mcps802154/pctt_trace.c
new file mode 100644
index 0000000..3d8754d
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_trace.c
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#define CREATE_TRACE_POINTS
+#include "pctt_trace.h"
diff --git a/kernel/net/mcps802154/pctt_trace.h b/kernel/net/mcps802154/pctt_trace.h
new file mode 120000
index 0000000..ebac311
--- /dev/null
+++ b/kernel/net/mcps802154/pctt_trace.h
@@ -0,0 +1 @@
+../../../mac/pctt_trace.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/regions.c b/kernel/net/mcps802154/regions.c
new file mode 120000
index 0000000..3c959bb
--- /dev/null
+++ b/kernel/net/mcps802154/regions.c
@@ -0,0 +1 @@
+../../../mac/regions.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/schedule.c b/kernel/net/mcps802154/schedule.c
new file mode 120000
index 0000000..22c60ab
--- /dev/null
+++ b/kernel/net/mcps802154/schedule.c
@@ -0,0 +1 @@
+../../../mac/schedule.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/schedule.h b/kernel/net/mcps802154/schedule.h
new file mode 120000
index 0000000..3c22005
--- /dev/null
+++ b/kernel/net/mcps802154/schedule.h
@@ -0,0 +1 @@
+../../../mac/schedule.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/schedulers.c b/kernel/net/mcps802154/schedulers.c
new file mode 120000
index 0000000..8204990
--- /dev/null
+++ b/kernel/net/mcps802154/schedulers.c
@@ -0,0 +1 @@
+../../../mac/schedulers.c \ No newline at end of file
diff --git a/kernel/net/mcps802154/schedulers.h b/kernel/net/mcps802154/schedulers.h
new file mode 120000
index 0000000..82a61dd
--- /dev/null
+++ b/kernel/net/mcps802154/schedulers.h
@@ -0,0 +1 @@
+../../../mac/schedulers.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/trace.c b/kernel/net/mcps802154/trace.c
new file mode 100644
index 0000000..77af338
--- /dev/null
+++ b/kernel/net/mcps802154/trace.c
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/kernel/net/mcps802154/trace.h b/kernel/net/mcps802154/trace.h
new file mode 120000
index 0000000..1df4e64
--- /dev/null
+++ b/kernel/net/mcps802154/trace.h
@@ -0,0 +1 @@
+../../../mac/trace.h \ No newline at end of file
diff --git a/kernel/net/mcps802154/warn_return.h b/kernel/net/mcps802154/warn_return.h
new file mode 120000
index 0000000..f7057b9
--- /dev/null
+++ b/kernel/net/mcps802154/warn_return.h
@@ -0,0 +1 @@
+../../../mac/warn_return.h \ No newline at end of file
diff --git a/mac/.gitignore b/mac/.gitignore
new file mode 100644
index 0000000..6e92f57
--- /dev/null
+++ b/mac/.gitignore
@@ -0,0 +1 @@
+tags
diff --git a/mac/Recipes b/mac/Recipes
new file mode 100644
index 0000000..fec5036
--- /dev/null
+++ b/mac/Recipes
@@ -0,0 +1,39 @@
+# Run with `make -f Recipes`.
+#
+# You can get help with `make -f Recipes help`.
+
+all:
+ ninja -C utest/build
+
+test: all
+ ./utest/build/test_mac $(TESTFLAGS)
+
+cov:
+ ninja -C utest/build-cov test_mac_coverage
+
+cmake:
+ cmake -S utest -B utest/build -G Ninja -DCMAKE_BUILD_TYPE=Debug
+ cmake -S utest -B utest/build-cov -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_TEST_COVERAGE=on
+
+clean:
+ test -d utest/build && ninja -C utest/build clean
+ test -d utest/build-cov && ninja -C utest/build-cov clean
+
+check: check_format check_doc
+
+check_format:
+ ../tools/check-format . ../kernel/net ../kernel/include
+
+check_doc:
+ ../deps/linux/linux-uwb/scripts/kernel-doc -Werror -none *.h *.c include/net/*.h ../kernel/include/net/*.h
+
+help:
+ @echo "cmake - run cmake to create the build tree, must be done once"
+ @echo "all - build program"
+ @echo "test - build and run tests"
+ @echo "cov - build and run tests with coverage analysis"
+ @echo "clean - remove generated files"
+ @echo ""
+ @echo "check_format - run clang-format"
+ @echo "check_doc - check kernel-doc comments"
+ @echo "check - run all checks"
diff --git a/mac/backport_nl.h b/mac/backport_nl.h
new file mode 100644
index 0000000..d22ad53
--- /dev/null
+++ b/mac/backport_nl.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef BACKPORT_NL_H
+#define BACKPORT_NL_H
+
+#include <linux/types.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
+#define NLA_POLICY_FULL_RANGE(tp, _range) \
+ { \
+ .type = (tp) \
+ }
+
+#else
+
+/* NLA_POLICY_FULL_RANGE expect to have a range defined.
+ * This define exist for backport linux compilations, as:
+ * - struct netlink_range_validation don't exist,
+ * - unused object are considered as error. */
+#define ADD_NETLINK_RANGE_VALIDATION
+
+#endif
+
+#endif /* BACKPORT_NL_H */
diff --git a/mac/ca.c b/mac/ca.c
new file mode 100644
index 0000000..6c8301f
--- /dev/null
+++ b/mac/ca.c
@@ -0,0 +1,497 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/string.h>
+
+#include "mcps802154_i.h"
+#include "schedulers.h"
+#include "trace.h"
+
+struct mcps802154_access_common_ops ca_access_ops = {};
+
+static int mcps802154_ca_trace_int(struct mcps802154_local *local, const int r)
+{
+ trace_ca_return_int(local, r);
+ return r;
+}
+
+static void mcps802154_ca_close_scheduler(struct mcps802154_local *local)
+{
+ struct mcps802154_region *region, *r;
+ struct mcps802154_ca *ca = &local->ca;
+ struct list_head *regions = &ca->regions;
+
+ mcps802154_schedule_clear(local);
+ if (local->ca.scheduler) {
+ mcps802154_scheduler_close(local->ca.scheduler);
+ local->ca.scheduler = NULL;
+ }
+ list_for_each_entry_safe (region, r, regions, ca_entry) {
+ list_del(&region->ca_entry);
+ mcps802154_region_close(&local->llhw, region);
+ }
+ ca->n_regions = 0;
+}
+
+static int check_and_get_region(struct mcps802154_ca *ca,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name,
+ struct mcps802154_region **sel_region)
+{
+ struct mcps802154_scheduler *scheduler = ca->scheduler;
+ struct list_head *regions = &ca->regions;
+ struct mcps802154_region *region;
+
+ /* Check scheduler is the correct one. */
+ if (!scheduler || strcmp(scheduler->ops->name, scheduler_name) ||
+ !region_name)
+ return -EINVAL;
+
+ list_for_each_entry (region, regions, ca_entry) {
+ if (!strcmp(region_name, region->ops->name) &&
+ region->id == region_id) {
+ *sel_region = region;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+void mcps802154_ca_init(struct mcps802154_local *local)
+{
+ struct mcps802154_ca *ca = &local->ca;
+
+ local->ca.held = false;
+ local->ca.reset = false;
+ INIT_LIST_HEAD(&ca->regions);
+ ca->n_regions = 0;
+}
+
+void mcps802154_ca_uninit(struct mcps802154_local *local)
+{
+}
+
+int mcps802154_ca_start(struct mcps802154_local *local)
+{
+ int r;
+
+ if (!local->ca.scheduler) {
+ r = mcps802154_ca_set_scheduler(local, "default", NULL, NULL);
+ if (r)
+ return r;
+
+ r = mcps802154_ca_set_region(local, "default", 0, "default",
+ NULL, NULL);
+ if (r)
+ return r;
+ }
+
+ local->start_stop_request = true;
+ mcps802154_fproc_schedule_change(local);
+
+ return local->started ? 0 : -EIO;
+}
+
+void mcps802154_ca_stop(struct mcps802154_local *local)
+{
+ local->start_stop_request = false;
+ mcps802154_fproc_schedule_change(local);
+}
+
+void mcps802154_ca_notify_stop(struct mcps802154_local *local)
+{
+ struct mcps802154_region *region;
+ struct mcps802154_ca *ca = &local->ca;
+ struct list_head *regions = &ca->regions;
+
+ mcps802154_scheduler_notify_stop(local->ca.scheduler);
+ list_for_each_entry (region, regions, ca_entry) {
+ mcps802154_region_notify_stop(&local->llhw, region);
+ }
+}
+
+void mcps802154_ca_close(struct mcps802154_local *local)
+{
+ mcps802154_ca_close_scheduler(local);
+}
+
+int mcps802154_ca_set_scheduler(struct mcps802154_local *local,
+ const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_scheduler *scheduler;
+
+ trace_ca_set_scheduler(local, name);
+
+ if (local->started)
+ return mcps802154_ca_trace_int(local, -EBUSY);
+ /* Open new scheduler. */
+ scheduler = mcps802154_scheduler_open(local, name, params_attr, extack);
+ if (!scheduler)
+ return mcps802154_ca_trace_int(local, -EINVAL);
+ /* Close previous scheduler and set the new one. */
+ mcps802154_ca_close_scheduler(local);
+ local->ca.scheduler = scheduler;
+
+ return mcps802154_ca_trace_int(local, 0);
+}
+
+int mcps802154_ca_set_region(struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_ca *ca = &local->ca;
+ struct list_head *regions = &ca->regions;
+ struct mcps802154_scheduler *scheduler;
+ struct mcps802154_region *region, *old_region;
+ struct list_head *position = regions;
+ bool region_id_present = false;
+
+ trace_ca_set_region(local, scheduler_name, region_id, region_name);
+
+ scheduler = local->ca.scheduler;
+ /* Check scheduler is the correct one. */
+ if (!scheduler || strcmp(scheduler->ops->name, scheduler_name) ||
+ !region_name)
+ return mcps802154_ca_trace_int(local, -EINVAL);
+
+ /* Check if we need to replace an already opened region. */
+ list_for_each_entry (old_region, regions, ca_entry) {
+ if (old_region->id == region_id) {
+ region_id_present = true;
+ break;
+ } else if (old_region->id < region_id) {
+ position = &old_region->ca_entry;
+ } else {
+ break;
+ }
+ }
+
+ /* Regions number is limited by the scheduler */
+ if (!region_id_present && scheduler->n_regions &&
+ ca->n_regions >= scheduler->n_regions)
+ return mcps802154_ca_trace_int(local, -ENOSPC);
+
+ region = mcps802154_region_open(&local->llhw, region_name, params_attr,
+ extack);
+
+ if (!region)
+ return mcps802154_ca_trace_int(local, -EINVAL);
+ region->id = region_id;
+
+ if (region_id_present) {
+ list_replace(&old_region->ca_entry, &region->ca_entry);
+ mcps802154_region_close(&local->llhw, old_region);
+ } else {
+ list_add(&region->ca_entry, position);
+ ca->n_regions++;
+ }
+
+ return mcps802154_ca_trace_int(local, 0);
+}
+
+int mcps802154_ca_scheduler_set_parameters(struct mcps802154_local *local,
+ const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_scheduler *scheduler;
+ int r;
+
+ trace_ca_set_scheduler_parameters(local, name);
+
+ scheduler = local->ca.scheduler;
+
+ /* Check scheduler is the correct one. */
+ if (!scheduler || strcmp(scheduler->ops->name, name))
+ return mcps802154_ca_trace_int(local, -EINVAL);
+
+ r = mcps802154_scheduler_set_parameters(scheduler, params_attr, extack);
+ return mcps802154_ca_trace_int(local, r);
+}
+
+int mcps802154_ca_scheduler_call(struct mcps802154_local *local,
+ const char *scheduler_name, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info)
+{
+ struct mcps802154_scheduler *scheduler;
+ int r;
+
+ trace_ca_scheduler_call(local, scheduler_name, call_id);
+
+ scheduler = local->ca.scheduler;
+ /* Check scheduler is the correct one. */
+ if (!scheduler || strcmp(scheduler->ops->name, scheduler_name))
+ return mcps802154_ca_trace_int(local, -EINVAL);
+ r = mcps802154_scheduler_call(scheduler, call_id, params_attr, info);
+ return mcps802154_ca_trace_int(local, r);
+}
+
+int mcps802154_ca_set_region_parameters(struct mcps802154_local *local,
+ const char *scheduler_name,
+ u32 region_id, const char *region_name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_region *region;
+ int r;
+
+ trace_ca_set_region_params(local, scheduler_name, region_id,
+ region_name);
+
+ r = check_and_get_region(&local->ca, scheduler_name, region_id,
+ region_name, &region);
+ if (r)
+ goto end;
+
+ r = mcps802154_region_set_parameters(&local->llhw, region, params_attr,
+ extack);
+
+end:
+ return mcps802154_ca_trace_int(local, r);
+}
+
+int mcps802154_ca_call_region(struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info)
+{
+ struct mcps802154_region *region;
+ int r;
+
+ trace_ca_call_region(local, scheduler_name, region_id, region_name,
+ call_id);
+
+ r = check_and_get_region(&local->ca, scheduler_name, region_id,
+ region_name, &region);
+ if (r)
+ goto end;
+
+ r = mcps802154_region_call(&local->llhw, region, call_id, params_attr,
+ info);
+
+end:
+ return mcps802154_ca_trace_int(local, r);
+}
+
+int mcps802154_ca_xmit_skb(struct mcps802154_local *local, struct sk_buff *skb)
+{
+ struct mcps802154_region *region;
+ int r = -EOPNOTSUPP;
+
+ list_for_each_entry (region, &local->ca.regions, ca_entry) {
+ if (region->ops->xmit_skb) {
+ if (region->ops->xmit_skb(region, skb)) {
+ r = 0;
+ break;
+ }
+ }
+ }
+ return r;
+}
+
+/**
+ * mcps802154_ca_next_region() - Check the current region is still valid, if not
+ * change region.
+ * @local: MCPS private data.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ *
+ * Return: 0 if unchanged, 1 if changed, or negative error.
+ */
+static int mcps802154_ca_next_region(struct mcps802154_local *local,
+ u32 next_timestamp_dtu)
+{
+ struct mcps802154_schedule *sched = &local->ca.schedule;
+ struct mcps802154_schedule_region *sched_region;
+ int next_dtu = next_timestamp_dtu - sched->start_timestamp_dtu;
+ bool changed = 0;
+ bool once;
+
+ sched_region = &sched->regions[sched->current_index];
+ once = sched_region->once;
+
+ /* If the region schedule is over, select the next region if
+ * possible. */
+ while (once || (sched_region->duration_dtu != 0 &&
+ next_dtu - sched_region->start_dtu >=
+ sched_region->duration_dtu)) {
+ sched->current_index++;
+ changed = 1;
+ once = false;
+
+ /* No more region, need a new schedule. */
+ if (sched->current_index >= sched->n_regions) {
+ /* Reduce the schedule duration when not fully used. */
+ if (sched_region->once && sched_region->duration_dtu) {
+ sched->duration_dtu =
+ next_timestamp_dtu -
+ sched->start_timestamp_dtu;
+ }
+ return mcps802154_schedule_update(local,
+ next_timestamp_dtu);
+ }
+
+ sched_region = &sched->regions[sched->current_index];
+ }
+
+ return changed;
+}
+
+/**
+ * mcps802154_ca_nothing() - Fill and return a nothing access.
+ * @local: MCPS private data.
+ * @timestamp_dtu: Start of nothing period.
+ * @duration_dtu: Duration of nothing period, or 0 for endless.
+ *
+ * Return: Pointer to access allocated inside the context.
+ */
+struct mcps802154_access *mcps802154_ca_nothing(struct mcps802154_local *local,
+ u32 timestamp_dtu,
+ int duration_dtu)
+{
+ struct mcps802154_access *access = &local->ca.idle_access;
+
+ access->method = MCPS802154_ACCESS_METHOD_NOTHING;
+ access->common_ops = &ca_access_ops;
+ access->timestamp_dtu = timestamp_dtu;
+ access->duration_dtu = duration_dtu;
+ return access;
+}
+
+struct mcps802154_access *
+mcps802154_ca_get_access(struct mcps802154_local *local, u32 next_timestamp_dtu)
+{
+ struct mcps802154_schedule *sched = &local->ca.schedule;
+ struct mcps802154_schedule_region *sched_region;
+ struct mcps802154_region *region;
+ struct mcps802154_access *access;
+ u32 idle_timestamp_dtu, region_start_timestamp_dtu;
+ int next_in_region_dtu, region_duration_dtu;
+ int r, changed;
+
+ local->ca.held = false;
+
+ trace_ca_get_access(local, next_timestamp_dtu);
+
+ if (local->ca.reset) {
+ mcps802154_schedule_clear(local);
+ local->ca.reset = false;
+ }
+
+ /* Do not examine accesses later than this date. */
+ idle_timestamp_dtu = next_timestamp_dtu + local->llhw.idle_dtu;
+ while (1) {
+ /* Need a schedule. */
+ if (!sched->n_regions)
+ r = mcps802154_schedule_update(local,
+ next_timestamp_dtu);
+ else
+ /* Need a region. */
+ r = mcps802154_ca_next_region(local,
+ next_timestamp_dtu);
+
+ /* Stay in IDLE when no schedule. */
+ if (r == -ENOENT)
+ return mcps802154_ca_nothing(local, next_timestamp_dtu,
+ 0);
+ else if (r < 0)
+ return NULL;
+
+ changed = r;
+ sched_region = &sched->regions[sched->current_index];
+ region = sched_region->region;
+ region_start_timestamp_dtu =
+ sched->start_timestamp_dtu + sched_region->start_dtu;
+ region_duration_dtu = sched_region->duration_dtu;
+
+ if (changed) {
+ if (is_before_dtu(next_timestamp_dtu,
+ region_start_timestamp_dtu)) {
+ /* Access date may be postponed. */
+ next_timestamp_dtu = region_start_timestamp_dtu;
+ }
+ }
+
+ /* Get access. */
+ if (region_duration_dtu)
+ next_in_region_dtu =
+ next_timestamp_dtu - region_start_timestamp_dtu;
+ else
+ next_in_region_dtu = 0;
+ trace_region_get_access(local, region, next_timestamp_dtu,
+ next_in_region_dtu,
+ region_duration_dtu);
+ access = region->ops->get_access(region, next_timestamp_dtu,
+ next_in_region_dtu,
+ region_duration_dtu);
+
+ if (access)
+ return access;
+
+ /* If no access is found, look for next region, or wait. */
+ if (region_duration_dtu) {
+ u32 region_end_timestamp_dtu =
+ region_start_timestamp_dtu +
+ region_duration_dtu;
+
+ if (is_before_dtu(idle_timestamp_dtu,
+ region_end_timestamp_dtu)) {
+ return mcps802154_ca_nothing(
+ local, next_timestamp_dtu,
+ region_end_timestamp_dtu -
+ next_timestamp_dtu);
+ }
+
+ /* Continue after the current region. */
+ next_timestamp_dtu = region_end_timestamp_dtu;
+ } else {
+ return mcps802154_ca_nothing(local, next_timestamp_dtu,
+ 0);
+ }
+ }
+}
+
+void mcps802154_ca_may_reschedule(struct mcps802154_local *local)
+{
+ if (!local->ca.held)
+ mcps802154_fproc_schedule_change(local);
+}
+
+void mcps802154_ca_access_hold(struct mcps802154_local *local)
+{
+ local->ca.held = true;
+}
+
+void mcps802154_ca_invalidate_schedule(struct mcps802154_local *local)
+{
+ local->ca.reset = true;
+
+ mcps802154_fproc_schedule_change(local);
+}
diff --git a/mac/ca.h b/mac/ca.h
new file mode 100644
index 0000000..a140eff
--- /dev/null
+++ b/mac/ca.h
@@ -0,0 +1,279 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_CA_H
+#define NET_MCPS802154_CA_H
+
+#include <linux/atomic.h>
+
+#include "schedule.h"
+
+struct mcps802154_local;
+
+/**
+ * struct mcps802154_ca - CA private data.
+ */
+struct mcps802154_ca {
+ /**
+ * @schedule: Current schedule.
+ */
+ struct mcps802154_schedule schedule;
+ /**
+ * @scheduler: Scheduler responsible to maintain the schedule
+ * or NULL when not chosen yet.
+ */
+ struct mcps802154_scheduler *scheduler;
+ /**
+ * @regions: List of regions currently available in the schedule.
+ */
+ struct list_head regions;
+ /**
+ * @n_regions: current number of opened regions.
+ */
+ int n_regions;
+ /**
+ * @held: Whether access is currently held and cannot change.
+ */
+ bool held;
+ /**
+ * @reset: Whether the schedule was invalidated and need to be changed.
+ */
+ bool reset;
+ /**
+ * @idle_access: Access used to wait when there is nothing to do.
+ */
+ struct mcps802154_access idle_access;
+};
+
+/**
+ * mcps802154_ca_init() - Initialize CA.
+ * @local: MCPS private data.
+ */
+void mcps802154_ca_init(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_uninit() - Uninitialize CA.
+ * @local: MCPS private data.
+ */
+void mcps802154_ca_uninit(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_start() - Start device.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_start(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_stop() - Request to stop device.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ *
+ * This is asynchronous, caller needs to wait !local->started.
+ */
+void mcps802154_ca_stop(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_notify_stop() - Notify that device has been stopped.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ *
+ */
+void mcps802154_ca_notify_stop(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_close() - Request to close all the schedules.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ */
+void mcps802154_ca_close(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_set_scheduler() - Set the scheduler responsible for managing
+ * the schedule, and configure its parameters.
+ * @local: MCPS private data.
+ * @name: Scheduler name.
+ * @params_attr: Nested attribute containing region parameters. May be NULL.
+ * @extack: Extended ACK report structure.
+ *
+ * FSM mutex should be locked.
+ *
+ * Device should not be started for the moment.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_set_scheduler(struct mcps802154_local *local,
+ const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_ca_set_region() - Set scheduler's region.
+ * @local: MCPS private data.
+ * @scheduler_name: Scheduler name.
+ * @region_id: Identifier of the region, scheduler specific.
+ * @region_name: Name of region to attach to the scheduler.
+ * @params_attr: Nested attribute containing region parameters.
+ * @extack: Extended ACK report structure.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_set_region(struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_ca_scheduler_set_parameters() - Set the scheduler parameters.
+ * @local: MCPS private data.
+ * @name: Scheduler name.
+ * @params_attr: Nested attribute containing region parameters.
+ * @extack: Extended ACK report structure.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_scheduler_set_parameters(struct mcps802154_local *local,
+ const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_ca_scheduler_call() - Call scheduler specific procedure.
+ * @local: MCPS private data.
+ * @scheduler_name: Scheduler name.
+ * @call_id: Identifier of the procedure, scheduler specific.
+ * @params_attr: Nested attribute containing procedure parameters.
+ * @info: Request information.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_scheduler_call(struct mcps802154_local *local,
+ const char *scheduler_name, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info);
+
+/**
+ * mcps802154_ca_set_region_parameters() - Set the region parameters.
+ * @local: MCPS private data.
+ * @scheduler_name: Scheduler name.
+ * @region_id: Identifier of the region, scheduler specific.
+ * @region_name: Name of the region to call.
+ * @params_attr: Nested attribute containing region parameters.
+ * @extack: Extended ACK report structure.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_set_region_parameters(struct mcps802154_local *local,
+ const char *scheduler_name,
+ u32 region_id, const char *region_name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_ca_call_region() - Call region specific procedure.
+ * @local: MCPS private data.
+ * @scheduler_name: Scheduler name.
+ * @region_id: Identifier of the region, scheduler specific.
+ * @region_name: Name of the region to call.
+ * @call_id: Identifier of the procedure, region specific.
+ * @params_attr: Nested attribute containing procedure parameters.
+ * @info: Request information.
+ *
+ * FSM mutex should be locked.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_call_region(struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info);
+
+/**
+ * mcps802154_ca_xmit_skb() - Transmit the buffer through the first region
+ * that accepts it.
+ * @local: MCPS private data.
+ * @skb: Buffer to be transmitted.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_ca_xmit_skb(struct mcps802154_local *local, struct sk_buff *skb);
+
+/**
+ * mcps802154_ca_get_access() - Compute and return access.
+ * @local: MCPS private data.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ *
+ * Return: A pointer to current access.
+ */
+struct mcps802154_access *
+mcps802154_ca_get_access(struct mcps802154_local *local,
+ u32 next_timestamp_dtu);
+
+/**
+ * mcps802154_ca_may_reschedule() - If needed, request FProc to change access.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ *
+ * When something has changed that could impact the current access, this
+ * function should be called to evaluate the change and notify FProc. This
+ * should be done for example when a new frame is queued.
+ */
+void mcps802154_ca_may_reschedule(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_access_hold() - Prevent any reschedule until access is done.
+ * @local: MCPS private data.
+ */
+void mcps802154_ca_access_hold(struct mcps802154_local *local);
+
+/**
+ * mcps802154_ca_invalidate_schedule() - Invalidate the schedule and force update.
+ * @local: MCPS private data.
+ *
+ * FSM mutex should be locked.
+ *
+ * When something has changed that impact the current schedule, this function
+ * can be called to invalidate the schedule and force an update.
+ * The update will be done after the end of current access.
+ * This API should be called for example when a region parameter changes.
+ */
+void mcps802154_ca_invalidate_schedule(struct mcps802154_local *local);
+
+#endif /* NET_MCPS802154_CA_H */
diff --git a/mac/default_region.c b/mac/default_region.c
new file mode 100644
index 0000000..197a7ab
--- /dev/null
+++ b/mac/default_region.c
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+
+#include <net/mcps802154_schedule.h>
+
+#include "mcps802154_i.h"
+
+#include "default_region.h"
+#include "warn_return.h"
+
+/**
+ * MCPS802154_DEFAULT_REGION_QUEUE_SIZE - number of buffers in the queue.
+ */
+#define MCPS802154_DEFAULT_REGION_QUEUE_SIZE 2
+
+static struct mcps802154_region_ops default_region_ops;
+
+static void
+mcps802154_default_rx_frame(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info,
+ enum mcps802154_rx_error_type error)
+{
+ struct default_local *local = access_to_local(access);
+
+ mcps802154_region_rx_skb(local->llhw, &local->region, skb, info->lqi);
+}
+
+static struct sk_buff *
+mcps802154_default_tx_frame(struct mcps802154_access *access, int frame_idx)
+{
+ struct default_local *local = access_to_local(access);
+
+ return skb_dequeue(&local->queue);
+}
+
+static void
+mcps802154_default_tx_return(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ enum mcps802154_access_tx_return_reason reason)
+{
+ struct default_local *local = access_to_local(access);
+ struct mcps802154_local *mlocal = llhw_to_local(local->llhw);
+
+ if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE) {
+ local->retries++;
+ if (local->retries <= mlocal->pib.mac_max_frame_retries) {
+ /* Retry the frame. */
+ skb_queue_head(&local->queue, skb);
+ } else {
+ local->retries = 0;
+ mcps802154_region_xmit_done(local->llhw, &local->region,
+ skb, false);
+ atomic_dec(&local->n_queued);
+ }
+ } else if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL) {
+ skb_queue_head(&local->queue, skb);
+ } else {
+ local->retries = 0;
+ mcps802154_region_xmit_done(local->llhw, &local->region, skb,
+ true);
+ atomic_dec(&local->n_queued);
+ }
+}
+
+struct mcps802154_access_ops default_access_ops = {
+ .rx_frame = mcps802154_default_rx_frame,
+ .tx_get_frame = mcps802154_default_tx_frame,
+ .tx_return = mcps802154_default_tx_return,
+};
+
+static struct mcps802154_region *
+mcps802154_default_open(struct mcps802154_llhw *llhw)
+{
+ struct default_local *local;
+
+ local = kmalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return NULL;
+
+ local->llhw = llhw;
+ local->region.ops = &default_region_ops;
+ skb_queue_head_init(&local->queue);
+ atomic_set(&local->n_queued, 0);
+ local->retries = 0;
+ return &local->region;
+}
+
+static void mcps802154_default_close(struct mcps802154_region *region)
+{
+ struct default_local *local = region_to_local(region);
+
+ skb_queue_purge(&local->queue);
+ atomic_set(&local->n_queued, 0);
+ kfree(local);
+}
+
+static void mcps802154_default_notify_stop(struct mcps802154_region *region)
+{
+ struct default_local *local = region_to_local(region);
+
+ skb_queue_purge(&local->queue);
+ atomic_set(&local->n_queued, 0);
+}
+
+static struct mcps802154_access *
+mcps802154_default_get_access(struct mcps802154_region *region,
+ u32 next_timestamp_dtu, int next_in_region_dtu,
+ int region_duration_dtu)
+{
+ struct default_local *local = region_to_local(region);
+
+ local->access.method = skb_queue_empty(&local->queue) ?
+ MCPS802154_ACCESS_METHOD_IMMEDIATE_RX :
+ MCPS802154_ACCESS_METHOD_IMMEDIATE_TX;
+ local->access.ops = &default_access_ops;
+ return &local->access;
+}
+
+static int mcps802154_default_xmit_skb(struct mcps802154_region *region,
+ struct sk_buff *skb)
+{
+ struct default_local *local = region_to_local(region);
+ int n_queued;
+
+ skb_queue_tail(&local->queue, skb);
+ n_queued = atomic_inc_return(&local->n_queued);
+ if (n_queued < MCPS802154_DEFAULT_REGION_QUEUE_SIZE)
+ mcps802154_region_xmit_resume(local->llhw, &local->region, 0);
+
+ return 1;
+}
+
+static struct mcps802154_region_ops default_region_ops = {
+ .owner = THIS_MODULE,
+ .name = "default",
+ .open = mcps802154_default_open,
+ .close = mcps802154_default_close,
+ .notify_stop = mcps802154_default_notify_stop,
+ .get_access = mcps802154_default_get_access,
+ .xmit_skb = mcps802154_default_xmit_skb
+};
+
+int __init mcps802154_default_region_init(void)
+{
+ return mcps802154_region_register(&default_region_ops);
+}
+
+void __exit mcps802154_default_region_exit(void)
+{
+ mcps802154_region_unregister(&default_region_ops);
+}
diff --git a/mac/default_region.h b/mac/default_region.h
new file mode 100644
index 0000000..d399673
--- /dev/null
+++ b/mac/default_region.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_DEFAULT_REGION_H
+#define NET_MCPS802154_DEFAULT_REGION_H
+
+#include <net/mcps802154_schedule.h>
+
+/**
+ * struct default_local - Local context.
+ */
+struct default_local {
+ /**
+ * @region: Region instance returned to MCPS.
+ */
+ struct mcps802154_region region;
+ /**
+ * @llhw: Low-level device pointer.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @access: Access returned to MCPS.
+ */
+ struct mcps802154_access access;
+ /**
+ * @queue: Queue of frames to be transmitted.
+ */
+ struct sk_buff_head queue;
+ /**
+ * @n_queued: Number of queued frames. This also includes frame being
+ * transmitted which is no longer in &queue.
+ */
+ atomic_t n_queued;
+ /**
+ * @retries: Number of retries done on the current tx frame.
+ */
+ int retries;
+};
+
+static inline struct default_local *
+region_to_local(struct mcps802154_region *region)
+{
+ return container_of(region, struct default_local, region);
+}
+
+static inline struct default_local *
+access_to_local(struct mcps802154_access *access)
+{
+ return container_of(access, struct default_local, access);
+}
+
+int mcps802154_default_region_init(void);
+void mcps802154_default_region_exit(void);
+
+#endif /* NET_MCPS802154_DEFAULT_REGION_H */
diff --git a/mac/endless_scheduler.c b/mac/endless_scheduler.c
new file mode 100644
index 0000000..76ee7f8
--- /dev/null
+++ b/mac/endless_scheduler.c
@@ -0,0 +1,139 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <net/mcps802154_schedule.h>
+
+#include "endless_scheduler.h"
+#include "warn_return.h"
+
+struct mcps802154_endless_local {
+ struct mcps802154_scheduler scheduler;
+ struct mcps802154_llhw *llhw;
+};
+
+static inline struct mcps802154_endless_local *
+scheduler_to_plocal(const struct mcps802154_scheduler *scheduler)
+{
+ return container_of(scheduler, struct mcps802154_endless_local,
+ scheduler);
+}
+
+static struct mcps802154_scheduler *
+mcps802154_endless_scheduler_open(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_endless_local *plocal;
+
+ plocal = kmalloc(sizeof(*plocal), GFP_KERNEL);
+ if (!plocal)
+ return NULL;
+ plocal->llhw = llhw;
+ plocal->scheduler.n_regions = 1;
+ return &plocal->scheduler;
+}
+
+static void
+mcps802154_endless_scheduler_close(struct mcps802154_scheduler *scheduler)
+{
+ struct mcps802154_endless_local *plocal =
+ scheduler_to_plocal(scheduler);
+
+ kfree(plocal);
+}
+static int mcps802154_endless_scheduler_update_schedule(
+ struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_schedule_update *schedule_update,
+ u32 next_timestamp_dtu)
+{
+ struct mcps802154_endless_local *plocal =
+ scheduler_to_plocal(scheduler);
+ struct list_head *regions = NULL;
+ struct mcps802154_region *region;
+ int r, n_regions;
+
+ n_regions = mcps802154_schedule_get_regions(plocal->llhw, &regions);
+ if (!n_regions)
+ return -ENOENT;
+ WARN_RETURN_ON(n_regions > 1, -EINVAL);
+
+ region = list_first_entry(regions, typeof(*region), ca_entry);
+
+ r = mcps802154_schedule_set_start(
+ schedule_update, schedule_update->expected_start_timestamp_dtu);
+ /* Can not fail, only possible error is invalid parameters. */
+ WARN_RETURN(r);
+
+ r = mcps802154_schedule_recycle(schedule_update, 0,
+ MCPS802154_DURATION_NO_CHANGE);
+ /* Can not fail, only possible error is invalid parameters. */
+ WARN_RETURN(r);
+
+ r = mcps802154_schedule_add_region(schedule_update, region, 0, 0,
+ false);
+
+ return r;
+}
+
+static struct mcps802154_scheduler_ops mcps802154_endless_scheduler_scheduler = {
+ .owner = THIS_MODULE,
+ .name = "endless",
+ .open = mcps802154_endless_scheduler_open,
+ .close = mcps802154_endless_scheduler_close,
+ .update_schedule = mcps802154_endless_scheduler_update_schedule,
+};
+
+static struct mcps802154_scheduler_ops mcps802154_default_scheduler_scheduler = {
+ .owner = THIS_MODULE,
+ .name = "default",
+ .open = mcps802154_endless_scheduler_open,
+ .close = mcps802154_endless_scheduler_close,
+ .update_schedule = mcps802154_endless_scheduler_update_schedule,
+};
+
+int __init mcps802154_endless_scheduler_init(void)
+{
+ return mcps802154_scheduler_register(
+ &mcps802154_endless_scheduler_scheduler);
+}
+
+void __exit mcps802154_endless_scheduler_exit(void)
+{
+ mcps802154_scheduler_unregister(
+ &mcps802154_endless_scheduler_scheduler);
+}
+
+int __init mcps802154_default_scheduler_init(void)
+{
+ return mcps802154_scheduler_register(
+ &mcps802154_default_scheduler_scheduler);
+}
+
+void __exit mcps802154_default_scheduler_exit(void)
+{
+ mcps802154_scheduler_unregister(
+ &mcps802154_default_scheduler_scheduler);
+}
diff --git a/mac/endless_scheduler.h b/mac/endless_scheduler.h
new file mode 100644
index 0000000..254612c
--- /dev/null
+++ b/mac/endless_scheduler.h
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_ENDLESS_SCHEDULER_H
+#define NET_MCPS802154_ENDLESS_SCHEDULER_H
+
+int mcps802154_endless_scheduler_init(void);
+void mcps802154_endless_scheduler_exit(void);
+
+int mcps802154_default_scheduler_init(void);
+void mcps802154_default_scheduler_exit(void);
+
+#endif /* NET_MCPS802154_ENDLESS_SCHEDULER_H */
diff --git a/mac/fira_access.c b/mac/fira_access.c
new file mode 100644
index 0000000..083a76f
--- /dev/null
+++ b/mac/fira_access.c
@@ -0,0 +1,1205 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "fira_round_hopping_sequence.h"
+#include "fira_access.h"
+#include "fira_session.h"
+#include "fira_frame.h"
+#include "fira_trace.h"
+#include "fira_sts.h"
+
+#include <asm/unaligned.h>
+#include <linux/string.h>
+#include <linux/ieee802154.h>
+#include <linux/math64.h>
+#include <linux/limits.h>
+#include <linux/errno.h>
+#include <net/mcps802154_frame.h>
+
+#include "warn_return.h"
+
+#define FIRA_STS_FOM_THRESHOLD 153
+#define FIRA_RSSI_MAX 0xff
+
+/**
+ * sat_fp() - Saturate the range of fixed-point
+ * @x: fixed-point value on s32.
+ *
+ * Return: value saturate to s16 range
+ */
+static s16 sat_fp(s32 x)
+{
+ if (x > S16_MAX)
+ return S16_MAX;
+ else if (x < S16_MIN)
+ return S16_MIN;
+ else
+ return x;
+}
+
+/**
+ * map_q11_to_2pi() - Map a Fixed Point angle to an signed 16 bit interger
+ * @x: angle as Q11 fixed_point value in range [-PI, PI]
+ *
+ * Return: the angle mapped to [INT16_MIN, INT16_MAX]
+ */
+static s16 map_q11_to_2pi(s16 x)
+{
+ const s16 pi = 6434; /* Same as round(M_PI * (1 << 11)). */
+
+ s32 temp = (s32)x * S16_MAX;
+ temp /= pi;
+
+ return sat_fp(temp);
+}
+
+/**
+ * fira_access_setup_frame() - Fill an access frame from a FiRa slot.
+ * @local: FiRa context.
+ * @session: Session.
+ * @frame: Access frame.
+ * @sts_params: Where to store STS parameters.
+ * @slot: Corresponding slot.
+ * @frame_dtu: frame transmission or reception date.
+ * @is_tx: true for TX.
+ */
+static void fira_access_setup_frame(struct fira_local *local,
+ struct fira_session *session,
+ struct mcps802154_access_frame *frame,
+ struct mcps802154_sts_params *sts_params,
+ const struct fira_slot *slot, u32 frame_dtu,
+ bool is_tx)
+{
+ const struct fira_session_params *params = &session->params;
+ struct mcps802154_access *access = &local->access;
+ struct mcps802154_sts_params *sts_params_for_access = NULL;
+ int rframe_config = session->params.rframe_config;
+
+ const struct fira_measurement_sequence_step *current_ms_step =
+ fira_session_get_meas_seq_step(session);
+
+ bool is_rframe = slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX;
+ bool is_last_rframe = slot->message_id == FIRA_MESSAGE_ID_RANGING_FINAL;
+ bool is_first_frame = slot->message_id == FIRA_MESSAGE_ID_CONTROL;
+ bool request_rssi = session->params.report_rssi;
+ if (is_rframe) {
+ fira_sts_get_sts_params(session, slot, sts_params->v,
+ sizeof(sts_params->v), sts_params->key,
+ sizeof(sts_params->key));
+ sts_params->n_segs = params->number_of_sts_segments;
+ sts_params->seg_len =
+ params->sts_length == FIRA_STS_LENGTH_128 ?
+ 128 :
+ params->sts_length == FIRA_STS_LENGTH_32 ? 32 :
+ 64;
+ sts_params->sp2_tx_gap_4chips = 0;
+ sts_params->sp2_rx_gap_4chips[0] = 0;
+ sts_params->sp2_rx_gap_4chips[1] = 0;
+ sts_params->sp2_rx_gap_4chips[2] = 0;
+ sts_params->sp2_rx_gap_4chips[3] = 0;
+ sts_params_for_access = sts_params;
+ }
+
+ if (is_tx) {
+ u8 flags = MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU;
+
+ /* Add a small margin to the Tx timestamps. */
+ if (!is_first_frame)
+ frame_dtu += FIRA_TX_MARGIN_US *
+ local->llhw->dtu_freq_hz / 1000000;
+
+ if (is_rframe) {
+ struct fira_ranging_info *ranging_info;
+
+ ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ ranging_info->timestamps_rctu[slot->message_id] =
+ mcps802154_tx_timestamp_dtu_to_rmarker_rctu(
+ local->llhw, frame_dtu,
+ access->hrp_uwb_params, access->channel,
+ slot->tx_ant_set);
+
+ flags |= MCPS802154_TX_FRAME_CONFIG_RANGING;
+ if (rframe_config == FIRA_RFRAME_CONFIG_SP3)
+ flags |= MCPS802154_TX_FRAME_CONFIG_SP3;
+ else
+ flags |= MCPS802154_TX_FRAME_CONFIG_SP1;
+ if (!is_last_rframe)
+ flags |=
+ MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK;
+ } else if (is_first_frame) {
+ flags |= MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND;
+ }
+ *frame = (struct mcps802154_access_frame){
+ .is_tx = true,
+ .tx_frame_config = {
+ .timestamp_dtu = frame_dtu,
+ .flags = flags,
+ .ant_set_id = slot->tx_ant_set,
+ },
+ .sts_params = sts_params_for_access,
+ };
+ } else {
+ u8 flags = MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU;
+ u16 request = 0;
+
+ if (request_rssi)
+ request |= MCPS802154_RX_FRAME_INFO_RSSI;
+ if (is_rframe) {
+ flags |= MCPS802154_RX_FRAME_CONFIG_RANGING;
+ if (rframe_config == FIRA_RFRAME_CONFIG_SP3)
+ flags |= MCPS802154_RX_FRAME_CONFIG_SP3;
+ else
+ flags |= MCPS802154_RX_FRAME_CONFIG_SP1;
+ if (!is_last_rframe)
+ flags |=
+ MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK;
+ request |= MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM;
+ if (current_ms_step->type !=
+ FIRA_MEASUREMENT_TYPE_RANGE) {
+ flags |=
+ MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA;
+ request |=
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA |
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM;
+ }
+ if (params->ranging_round_usage ==
+ FIRA_RANGING_ROUND_USAGE_SSTWR &&
+ session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLEE)
+ request |=
+ MCPS802154_RX_FRAME_INFO_RANGING_OFFSET;
+ }
+ *frame = (struct mcps802154_access_frame){
+ .is_tx = false,
+ .rx = {
+ .frame_config = {
+ .timestamp_dtu = frame_dtu,
+ .flags = flags,
+ .ant_set_id = slot->rx_ant_set,
+ },
+ .frame_info_flags_request = request,
+ },
+ .sts_params = sts_params_for_access,
+ };
+ }
+}
+
+static void fira_controlee_resync(struct fira_session *session,
+ u32 phy_sts_index, u32 timestamp_dtu)
+{
+ u32 block_idx, round_idx, slot_idx;
+ int block_start_timestamp_dtu;
+ const struct fira_session_params *params = &session->params;
+
+ fira_sts_convert_phy_sts_idx_to_time_indexes(
+ session, phy_sts_index, &block_idx, &round_idx, &slot_idx);
+ block_start_timestamp_dtu =
+ timestamp_dtu -
+ (round_idx * session->params.round_duration_slots + slot_idx) *
+ params->slot_duration_dtu;
+
+ /* Update the session. */
+ session->block_start_dtu = block_start_timestamp_dtu;
+ session->block_index = block_idx;
+ session->round_index = round_idx;
+ session->controlee.synchronised = true;
+ session->controlee.next_round_index_valid = false;
+ session->controlee.block_index_sync = block_idx;
+}
+
+static bool fira_rx_sts_good(struct fira_local *local,
+ const struct mcps802154_rx_frame_info *info)
+{
+ int i;
+ if (!(info->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM))
+ return false;
+ for (i = 0; i < MCPS802154_STS_N_SEGS_MAX; i++) {
+ if (info->ranging_sts_fom[i] < FIRA_STS_FOM_THRESHOLD)
+ return false;
+ }
+ return true;
+}
+
+static void fira_ranging_info_set_status(const struct fira_session *session,
+ struct fira_ranging_info *ranging_info,
+ enum fira_ranging_status status,
+ u8 slot_index)
+{
+ /* Report first error. */
+ if (ranging_info->status)
+ return;
+ ranging_info->status = status;
+ ranging_info->slot_index = slot_index;
+ fira_session_set_range_data_ntf_status(session, ranging_info);
+}
+
+static void
+fira_diagnostic_rssis(const struct mcps802154_rx_measurement_info *info,
+ struct fira_diagnostic *diagnostic)
+{
+ if (info->flags & MCPS802154_RX_MEASUREMENTS_RSSIS) {
+ int max = max(MCPS802154_RSSIS_N_MAX - 1, info->n_rssis);
+ int i;
+ for (i = 0; i < max; i++)
+ diagnostic->rssis_q1[i] = info->rssis_q1[i];
+ diagnostic->n_rssis = i;
+ }
+}
+
+static void
+fira_diagnostic_aoas(const struct mcps802154_rx_measurement_info *info,
+ struct fira_diagnostic *diagnostic)
+{
+ int max = max(MCPS802154_RX_AOA_MEASUREMENTS_MAX - 1, info->n_aoas);
+ int i;
+
+ for (i = 0; i < max; i++)
+ diagnostic->aoas[i] = info->aoas[i];
+ diagnostic->n_aoas = info->n_aoas;
+}
+
+static struct mcps802154_rx_cir *
+fira_diagnostic_cirs_alloc(const struct mcps802154_rx_measurement_info *info)
+{
+ const struct mcps802154_rx_cir_sample_window *si;
+ struct mcps802154_rx_cir_sample_window *so;
+ struct mcps802154_rx_cir *cirs;
+ int i;
+ int j;
+
+ cirs = kmalloc(info->n_cirs * sizeof(struct mcps802154_rx_cir),
+ GFP_KERNEL);
+ if (!cirs)
+ return NULL;
+
+ for (i = 0; i < info->n_cirs; i++) {
+ so = &cirs[i].sample_window;
+ si = &info->cirs[i].sample_window;
+ so->samples =
+ kmalloc(si->n_samples * si->sizeof_sample, GFP_KERNEL);
+
+ if (!so->samples)
+ goto failed;
+ }
+ return cirs;
+
+failed:
+ for (j = 0; j < i; j++)
+ kfree(cirs[j].sample_window.samples);
+ kfree(cirs);
+ return NULL;
+}
+
+static void
+fira_diagnostic_cirs_copy(const struct mcps802154_rx_measurement_info *info,
+ struct fira_diagnostic *diagnostic)
+{
+ int i;
+
+ for (i = 0; i < info->n_cirs; i++) {
+ struct mcps802154_rx_cir *cir_in;
+ struct mcps802154_rx_cir *cir_out;
+ struct mcps802154_rx_cir_sample_window *si;
+ struct mcps802154_rx_cir_sample_window *so;
+
+ cir_out = &diagnostic->cirs[i];
+ cir_in = &info->cirs[i];
+ so = &cir_out->sample_window;
+ si = &cir_in->sample_window;
+
+ cir_out->fp_index = cir_in->fp_index;
+ cir_out->fp_snr = cir_in->fp_snr;
+ cir_out->fp_ns_q6 = cir_in->fp_ns_q6;
+ cir_out->pp_index = cir_in->pp_index;
+ cir_out->pp_snr = cir_in->pp_snr;
+ cir_out->pp_ns_q6 = cir_in->pp_ns_q6;
+ cir_out->fp_sample_offset = cir_in->fp_sample_offset;
+ so->n_samples = si->n_samples;
+ so->sizeof_sample = si->sizeof_sample;
+
+ memcpy(so->samples, si->samples,
+ si->n_samples * si->sizeof_sample);
+ }
+ diagnostic->n_cirs = i;
+}
+
+static void
+fira_diagnostic_cirs(const struct mcps802154_rx_measurement_info *info,
+ struct fira_diagnostic *diagnostic)
+{
+ if (info->flags & MCPS802154_RX_MEASUREMENTS_CIRS) {
+ diagnostic->cirs = fira_diagnostic_cirs_alloc(info);
+ if (diagnostic->cirs)
+ fira_diagnostic_cirs_copy(info, diagnostic);
+ else
+ diagnostic->n_cirs = 0;
+ }
+}
+
+static void fira_diagnostic(struct fira_local *local,
+ struct fira_session *session, void *rx_ctx,
+ int slot_idx)
+{
+ const struct fira_session_params *params = &session->params;
+ struct fira_diagnostic *diagnostic = &local->diagnostics[slot_idx];
+ struct mcps802154_rx_measurement_info info = {};
+ int r;
+
+ WARN_ON(diagnostic->cirs);
+
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS)
+ info.flags |= MCPS802154_RX_MEASUREMENTS_RSSIS;
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS)
+ info.flags |= MCPS802154_RX_MEASUREMENTS_CIRS;
+
+ if (!info.flags)
+ return;
+
+ r = mcps802154_rx_get_measurement(local->llhw, rx_ctx, &info);
+
+ if (r)
+ return;
+
+ fira_diagnostic_rssis(&info, diagnostic);
+ fira_diagnostic_cirs(&info, diagnostic);
+}
+
+static void fira_diagnostic_free(struct fira_local *local)
+{
+ int i;
+
+ for (i = 0; i < local->access.n_frames; i++) {
+ struct fira_diagnostic *diagnostic = &local->diagnostics[i];
+ int j;
+
+ for (j = 0; j < diagnostic->n_cirs; j++)
+ kfree(diagnostic->cirs[j].sample_window.samples);
+
+ kfree(diagnostic->cirs);
+ diagnostic->cirs = NULL;
+ diagnostic->n_cirs = 0;
+ }
+}
+
+static void fira_rx_frame_ranging(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info)
+{
+ const struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ struct mcps802154_ie_get_context ie_get = {};
+ bool pdoa_info_present;
+
+ if (!fira_rx_sts_good(local, info)) {
+ fira_ranging_info_set_status(
+ session, ranging_info,
+ FIRA_STATUS_RANGING_RX_PHY_STS_FAILED, slot->index);
+ return;
+ }
+
+ if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU)) {
+ fira_ranging_info_set_status(
+ session, ranging_info,
+ FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED, slot->index);
+ return;
+ }
+ ranging_info->timestamps_rctu[slot->message_id] = info->timestamp_rctu;
+
+ pdoa_info_present = info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA;
+
+ if (pdoa_info_present) {
+ struct fira_local_aoa_info *local_aoa;
+ bool pdoa_fom_info_present =
+ info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM;
+ s16 local_pdoa_q11 = 0;
+ s16 local_aoa_q11 = 0;
+ const struct fira_measurement_sequence_step *current_step =
+ fira_session_get_meas_seq_step(session);
+ struct mcps802154_rx_measurement_info meas_info = {};
+ int r;
+
+ meas_info.flags |= MCPS802154_RX_MEASUREMENTS_AOAS;
+ r = mcps802154_rx_get_measurement(
+ local->llhw, ranging_info->rx_ctx, &meas_info);
+
+ if (!r && meas_info.flags & MCPS802154_RX_MEASUREMENTS_AOAS &&
+ meas_info.n_aoas) {
+ struct fira_diagnostic *diagnostic =
+ &local->diagnostics[slot->index];
+
+ /* TODO: Find which aoas index to use. */
+ local_pdoa_q11 = meas_info.aoas[0].pdoa_rad_q11;
+ local_aoa_q11 = meas_info.aoas[0].aoa_rad_q11;
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS)
+ fira_diagnostic_aoas(&meas_info, diagnostic);
+ }
+
+ switch (current_step->type) {
+ case FIRA_MEASUREMENT_TYPE_AOA:
+ local_aoa = &ranging_info->local_aoa;
+ break;
+ case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH:
+ local_aoa = &ranging_info->local_aoa_azimuth;
+ break;
+ case FIRA_MEASUREMENT_TYPE_AOA_ELEVATION:
+ local_aoa = &ranging_info->local_aoa_elevation;
+ break;
+ case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION:
+ local_aoa = (slot->message_id ==
+ FIRA_MESSAGE_ID_RANGING_FINAL) ?
+ &ranging_info->local_aoa_elevation :
+ &ranging_info->local_aoa_azimuth;
+ break;
+ default: /* LCOV_EXCL_START */
+ local_aoa = NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ /* LCOV_EXCL_START */
+ if (local_aoa) {
+ /* LCOV_EXCL_STOP */
+ local_aoa->present = true;
+ local_aoa->rx_ant_set = slot->rx_ant_set;
+ local_aoa->pdoa_2pi = map_q11_to_2pi(local_pdoa_q11);
+ local_aoa->aoa_2pi = map_q11_to_2pi(local_aoa_q11);
+ /* LCOV_EXCL_START */
+ /* FoM is always expected when PDoA present. */
+ if (pdoa_fom_info_present)
+ /* LCOV_EXCL_STOP */
+ local_aoa->aoa_fom = info->ranging_pdoa_fom;
+ }
+ }
+
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_OFFSET) {
+ ranging_info->clock_offset_q26 =
+ div64_s64((s64)info->ranging_offset_rctu << 26,
+ info->ranging_tracking_interval_rctu);
+ ranging_info->clock_offset_present = true;
+ }
+
+ if (skb) {
+ if (fira_frame_header_check_decrypt(local, slot, skb,
+ &ie_get) ||
+ !fira_frame_rframe_payload_check(local, slot, skb,
+ &ie_get)) {
+ fira_ranging_info_set_status(
+ session, ranging_info,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED,
+ slot->index);
+ }
+ }
+}
+
+static void fira_rx_frame_control(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info)
+{
+ struct mcps802154_access *access = &local->access;
+ struct fira_ranging_info *ri =
+ &local->ranging_info[slot->ranging_index];
+ struct mcps802154_ie_get_context ie_get = {};
+ const struct fira_session_params *params = NULL;
+ struct fira_session *session;
+ int header_len;
+ __le16 src_short_addr;
+ int last_slot_index = 0;
+ int offset_in_access_duration_dtu;
+ int left_duration_dtu;
+ unsigned n_slots;
+ u32 phy_sts_index;
+ u8 *header;
+ int r;
+
+ if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) {
+ fira_ranging_info_set_status(
+ local->current_session, ri,
+ FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED, slot->index);
+ return;
+ }
+
+ offset_in_access_duration_dtu =
+ info->timestamp_dtu - access->timestamp_dtu;
+
+ /* Read the header to capture the session context. */
+ header = skb->data;
+ session = fira_rx_frame_control_header_check(local, slot, skb, &ie_get,
+ &phy_sts_index);
+ if (!session)
+ goto failed;
+
+ fira_controlee_resync(session, phy_sts_index, info->timestamp_dtu);
+
+ params = &session->params;
+ ri->rx_ctx = session->rx_ctx[0];
+
+ header_len = skb->data - header;
+ src_short_addr = slot->controller_tx ? local->dst_short_addr :
+ slot->controlee->short_addr;
+
+ /* Continue to decode the frame. */
+ r = fira_sts_decrypt_frame(session, slot, skb, header_len, src_short_addr);
+ if (r)
+ goto failed;
+ r = fira_frame_control_payload_check(local, skb, &ie_get, &n_slots,
+ &session->stop_inband,
+ &session->block_stride_len);
+ if (!r)
+ goto failed;
+
+ left_duration_dtu =
+ access->duration_dtu - offset_in_access_duration_dtu;
+
+ /*
+ * The RCM has been received, remaining slots are: n_slots - 1.
+ * Stop if no time left to finish the ranging or if asked to.
+ */
+ if (left_duration_dtu < (n_slots - 1) * params->slot_duration_dtu ||
+ session->stop_inband) {
+ n_slots = 1;
+ } else {
+ int i;
+
+ for (i = 1; i < n_slots; i++) {
+ const struct fira_slot *slot = &local->slots[i];
+ struct mcps802154_access_frame *frame =
+ &local->frames[i];
+ struct mcps802154_sts_params *sts_params =
+ &local->sts_params[i];
+ bool is_tx;
+ u32 frame_dtu;
+
+ is_tx = !slot->controller_tx;
+ frame_dtu = info->timestamp_dtu +
+ params->slot_duration_dtu * slot->index;
+ last_slot_index = slot->index;
+
+ fira_access_setup_frame(local, session, frame,
+ sts_params, slot, frame_dtu,
+ is_tx);
+ }
+ }
+
+ /* Trace the new (or not) session context and slots received. */
+ trace_region_fira_rx_frame_control(local, session, left_duration_dtu,
+ n_slots);
+ /* Update the access. */
+ access->duration_dtu =
+ offset_in_access_duration_dtu +
+ (last_slot_index + 1) * params->slot_duration_dtu;
+ access->n_frames = n_slots;
+ return;
+
+failed:
+ session = local->current_session;
+ params = &local->current_session->params;
+ access->duration_dtu =
+ offset_in_access_duration_dtu + params->slot_duration_dtu;
+ fira_ranging_info_set_status(session, ri,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED,
+ slot->index);
+}
+
+static void
+fira_rx_frame_control_update(struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info)
+{
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ struct mcps802154_ie_get_context ie_get = {};
+
+ if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get))
+ goto failed;
+
+ return;
+failed:
+ fira_ranging_info_set_status(local->current_session, ranging_info,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED,
+ slot->index);
+}
+
+static void fira_rx_frame_measurement_report(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, const struct mcps802154_rx_frame_info *info)
+{
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ struct mcps802154_ie_get_context ie_get = {};
+
+ if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get))
+ goto failed;
+
+ if (!fira_frame_measurement_report_payload_check(local, slot, skb,
+ &ie_get))
+ goto failed;
+
+ return;
+failed:
+ fira_ranging_info_set_status(local->current_session, ranging_info,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED,
+ slot->index);
+}
+
+static void
+fira_rx_frame_result_report(struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info)
+{
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ struct mcps802154_ie_get_context ie_get = {};
+
+ if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get))
+ goto failed;
+
+ if (!fira_frame_result_report_payload_check(local, slot, skb, &ie_get))
+ goto failed;
+
+ return;
+failed:
+ fira_ranging_info_set_status(local->current_session, ranging_info,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED,
+ slot->index);
+}
+
+static bool fira_do_process_rx_frame(const struct fira_session *session,
+ enum mcps802154_rx_error_type error,
+ struct fira_ranging_info *ranging_info,
+ u8 slot_index)
+{
+ enum fira_ranging_status status = FIRA_STATUS_RANGING_INTERNAL_ERROR;
+
+ switch (error) {
+ case MCPS802154_RX_ERROR_NONE:
+ return true;
+ case MCPS802154_RX_ERROR_SFD_TIMEOUT:
+ case MCPS802154_RX_ERROR_TIMEOUT:
+ case MCPS802154_RX_ERROR_HPDWARN:
+ status = FIRA_STATUS_RANGING_RX_TIMEOUT;
+ break;
+ case MCPS802154_RX_ERROR_FILTERED:
+ case MCPS802154_RX_ERROR_BAD_CKSUM:
+ status = FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED;
+ break;
+ case MCPS802154_RX_ERROR_UNCORRECTABLE:
+ case MCPS802154_RX_ERROR_OTHER:
+ case MCPS802154_RX_ERROR_PHR_DECODE:
+ status = FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED;
+ break;
+ }
+ fira_ranging_info_set_status(session, ranging_info, status, slot_index);
+ return false;
+}
+
+static void fira_rx_frame(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info,
+ enum mcps802154_rx_error_type error)
+{
+ struct fira_local *local = access_to_local(access);
+ const struct fira_session_params *params;
+ const struct fira_slot *slot = &local->slots[frame_idx];
+ struct fira_ranging_info *ri =
+ &local->ranging_info[slot->ranging_index];
+ /* Don't initialize session before rx_frame_control. */
+ struct fira_session *session;
+
+ trace_region_fira_rx_frame(local->current_session, slot->message_id,
+ error);
+
+ if (info && info->flags & MCPS802154_RX_FRAME_INFO_RSSI) {
+ if ((ri->n_rx_rssis + 1) > FIRA_MESSAGE_ID_MAX)
+ return;
+
+ ri->rx_rssis[ri->n_rx_rssis++] =
+ info->rssi < FIRA_RSSI_MAX ? info->rssi : FIRA_RSSI_MAX;
+ }
+
+ if (fira_do_process_rx_frame(local->current_session, error, ri,
+ slot->index)) {
+ switch (slot->message_id) {
+ case FIRA_MESSAGE_ID_RANGING_INITIATION:
+ case FIRA_MESSAGE_ID_RANGING_RESPONSE:
+ case FIRA_MESSAGE_ID_RANGING_FINAL:
+ fira_rx_frame_ranging(local, slot, skb, info);
+ break;
+ case FIRA_MESSAGE_ID_CONTROL:
+ fira_rx_frame_control(local, slot, skb, info);
+ break;
+ case FIRA_MESSAGE_ID_MEASUREMENT_REPORT:
+ fira_rx_frame_measurement_report(local, slot, skb,
+ info);
+ break;
+ case FIRA_MESSAGE_ID_RESULT_REPORT:
+ fira_rx_frame_result_report(local, slot, skb, info);
+ break;
+ case FIRA_MESSAGE_ID_CONTROL_UPDATE:
+ fira_rx_frame_control_update(local, slot, skb, info);
+ break;
+ default:
+ WARN_UNREACHABLE_DEFAULT();
+ }
+ }
+ /* Current session can change after call of rx_frame_control function. */
+ session = local->current_session;
+ session->last_access_timestamp_dtu = access->timestamp_dtu;
+ params = &session->params;
+
+ kfree_skb(skb);
+ fira_diagnostic(local, session, ri->rx_ctx, slot->index);
+
+ /*
+ * Controlee: Stop round on error.
+ * Controller: Stop when all ranging fails.
+ */
+ /*
+ * TODO:
+ * The usage of ri->status is hidden in function called.
+ * The reason of the end of access is not limpid.
+ */
+ if (ri->status != FIRA_STATUS_RANGING_SUCCESS) {
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE ||
+ (slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX &&
+ --local->n_ranging_valid == 0))
+ access->n_frames = frame_idx + 1;
+ }
+}
+
+static struct sk_buff *fira_tx_get_frame(struct mcps802154_access *access,
+ int frame_idx)
+{
+ struct fira_local *local = access_to_local(access);
+ struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ const struct fira_slot *slot = &local->slots[frame_idx];
+ struct sk_buff *skb;
+ int header_len;
+
+ trace_region_fira_tx_get_frame(session, slot->message_id);
+ if (params->rframe_config == FIRA_RFRAME_CONFIG_SP3 &&
+ slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX)
+ return NULL;
+
+ skb = mcps802154_frame_alloc(local->llhw, IEEE802154_MTU, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+
+ fira_frame_header_put(local, slot, skb);
+
+ switch (slot->message_id) {
+ case FIRA_MESSAGE_ID_RANGING_INITIATION:
+ case FIRA_MESSAGE_ID_RANGING_RESPONSE:
+ fira_frame_rframe_payload_put(local, skb);
+ break;
+ case FIRA_MESSAGE_ID_RANGING_FINAL:
+ break;
+ case FIRA_MESSAGE_ID_CONTROL:
+ fira_frame_control_payload_put(local, slot, skb);
+ break;
+ case FIRA_MESSAGE_ID_MEASUREMENT_REPORT:
+ fira_frame_measurement_report_payload_put(local, slot, skb);
+ break;
+ case FIRA_MESSAGE_ID_RESULT_REPORT:
+ fira_frame_result_report_payload_put(local, slot, skb);
+ break;
+ case FIRA_MESSAGE_ID_CONTROL_UPDATE:
+ break;
+ default: /* LCOV_EXCL_START */
+ kfree_skb(skb);
+ WARN_UNREACHABLE_DEFAULT();
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ header_len = mcps802154_ie_put_end(skb, false);
+ WARN_ON(header_len < 0);
+
+ if (fira_sts_encrypt_frame(local->current_session, slot, skb, header_len,
+ local->src_short_addr)) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ return skb;
+}
+
+static void fira_tx_return(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ enum mcps802154_access_tx_return_reason reason)
+{
+ struct fira_local *local = access_to_local(access);
+ struct fira_session *session = local->current_session;
+ int i;
+
+ kfree_skb(skb);
+
+ /* Error on TX. */
+ trace_region_fira_tx_return(session, reason);
+ if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL) {
+ for (i = 0; i < local->n_ranging_info; i++) {
+ local->ranging_info[i].status =
+ FIRA_STATUS_RANGING_TX_FAILED;
+ }
+ }
+}
+
+static void fira_access_done(struct mcps802154_access *access, bool error)
+{
+ struct fira_local *local = access_to_local(access);
+ struct fira_session *session = local->current_session;
+ u32 timestamp_dtu = access->timestamp_dtu;
+
+ trace_region_fira_access_done(local, session, access->duration_dtu,
+ error);
+
+ /* propagate llhw error to fira session */
+ session->last_error = access->error;
+ fira_session_fsm_access_done(local, session, error);
+ fira_diagnostic_free(local);
+ if (!error)
+ /* No access are infinite normally. */
+ timestamp_dtu += access->duration_dtu;
+ /*
+ * Must be call after FSM access done, because
+ * shared resource in local are used.
+ */
+ fira_check_all_missed_ranging(local, session, timestamp_dtu);
+}
+
+static __le16 fira_access_set_short_address(struct fira_local *local,
+ const struct fira_session *session,
+ struct mcps802154_access *access)
+{
+ const struct fira_session_params *params = &session->params;
+ __le16 src_short_addr = mcps802154_get_short_addr(local->llhw);
+
+ if (params->short_addr != IEEE802154_ADDR_SHORT_BROADCAST &&
+ src_short_addr != params->short_addr) {
+ access->hw_addr_filt = (struct ieee802154_hw_addr_filt){
+ .short_addr = params->short_addr,
+ };
+ access->hw_addr_filt_changed = IEEE802154_AFILT_SADDR_CHANGED;
+ return params->short_addr;
+ }
+ access->hw_addr_filt_changed = 0;
+ return src_short_addr;
+}
+
+static struct mcps802154_access_ops fira_controller_access_ops = {
+ .common = {
+ .access_done = fira_access_done,
+ },
+ .rx_frame = fira_rx_frame,
+ .tx_get_frame = fira_tx_get_frame,
+ .tx_return = fira_tx_return,
+};
+
+int fira_session_get_slot_count(const struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ int nb_controlee = fira_session_controlees_running_count(session);
+ /* Control frame. */
+ int slot_count = 1;
+
+ if (nb_controlee) {
+ /* Ranging initiation frame. */
+ slot_count++;
+ /* Ranging response frame(s). */
+ slot_count += nb_controlee;
+ /* Ranging final frame. */
+ if (params->ranging_round_usage ==
+ FIRA_RANGING_ROUND_USAGE_DSTWR)
+ slot_count++;
+ /* Measurement report frame. */
+ slot_count++;
+ /* Result report frame(s). */
+ slot_count += nb_controlee;
+ }
+ return slot_count;
+}
+
+struct mcps802154_access *
+fira_get_access_controller(struct fira_local *local,
+ const struct fira_session_demand *fsd)
+{
+ struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ const struct fira_measurement_sequence_step *step =
+ fira_session_get_meas_seq_step(session);
+ struct mcps802154_access *access = &local->access;
+ struct mcps802154_access_frame *frame;
+ struct mcps802154_sts_params *sts_params;
+ struct fira_ranging_info *ri;
+ struct fira_slot *s;
+ u32 frame_dtu;
+ int index = 0;
+ int i;
+ struct fira_controlee *controlee;
+
+ trace_region_fira_get_access_controller(local, session, fsd);
+
+ /* Update local context (shared memory used by all sessions). */
+ local->src_short_addr =
+ fira_access_set_short_address(local, session, access);
+ local->dst_short_addr =
+ session->n_current_controlees == 1 ?
+ list_first_entry(&session->current_controlees,
+ struct fira_controlee, entry)
+ ->short_addr :
+ IEEE802154_ADDR_SHORT_BROADCAST;
+
+ /* Update session. */
+ session->last_access_timestamp_dtu = fsd->timestamp_dtu;
+ session->block_start_dtu = fsd->block_start_dtu;
+ session->block_index += fsd->add_blocks;
+ session->block_stride_len = params->block_stride_len;
+ session->round_index = fsd->round_index;
+ session->controller.next_block_index =
+ session->block_index + session->block_stride_len + 1;
+ session->next_round_index =
+ params->round_hopping ?
+ fira_round_hopping_sequence_get(
+ session, session->controller.next_block_index) :
+ 0;
+
+ /* Build number of controlee which are stopped. */
+ local->n_stopped_controlees = 0;
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ if (controlee->state == FIRA_CONTROLEE_STATE_STOPPING ||
+ controlee->state == FIRA_CONTROLEE_STATE_DELETING)
+ local->stopped_controlees[local->n_stopped_controlees++] =
+ controlee->short_addr;
+ }
+ /* Build number of controlee which are running. */
+ local->n_ranging_valid =
+ session->n_current_controlees - local->n_stopped_controlees;
+
+ /* Reset 'n' ranging info to store info related to controlees. */
+ local->n_ranging_info = local->n_ranging_valid;
+ ri = local->ranging_info;
+ memset(ri, 0,
+ local->n_ranging_valid * sizeof(struct fira_ranging_info));
+
+ /* Prepare control message slot for fira_rx_frame. */
+ s = local->slots;
+ s->index = index++;
+ s->controller_tx = true;
+ s->ranging_index = 0;
+ s->tx_ant_set = step->tx_ant_set_nonranging;
+ s->message_id = FIRA_MESSAGE_ID_CONTROL;
+ s->controlee = NULL;
+ s++;
+ /* Prepare other slots. */
+ if (local->n_ranging_info) {
+ s->index = index++;
+ s->controller_tx = true;
+ s->ranging_index = 0;
+ s->tx_ant_set = step->tx_ant_set_ranging;
+ s->message_id = FIRA_MESSAGE_ID_RANGING_INITIATION;
+ s->controlee = NULL;
+
+ s++;
+ i = 0;
+ list_for_each_entry (controlee, &session->current_controlees,
+ entry) {
+ if (!fira_session_controlee_active(controlee))
+ continue;
+ ri->short_addr = controlee->short_addr;
+ ri->rx_ctx = session->rx_ctx[i];
+ /* Requested in fira_report_aoa function. */
+ ri++;
+ s->index = index++;
+ s->controller_tx = false;
+ s->ranging_index = i++;
+ s->rx_ant_set = fira_session_get_rx_ant_set(
+ session, FIRA_MESSAGE_ID_RANGING_RESPONSE);
+ s->message_id = FIRA_MESSAGE_ID_RANGING_RESPONSE;
+ s->controlee = controlee;
+ s++;
+ }
+
+ if (params->ranging_round_usage ==
+ FIRA_RANGING_ROUND_USAGE_DSTWR) {
+ s->index = index++;
+ s->controller_tx = true;
+ s->ranging_index = 0;
+ s->tx_ant_set = step->tx_ant_set_ranging;
+ s->message_id = FIRA_MESSAGE_ID_RANGING_FINAL;
+ s->controlee = NULL;
+ s++;
+ }
+
+ s->index = index++;
+ s->controller_tx = true;
+ s->ranging_index = 0;
+ s->tx_ant_set = step->tx_ant_set_nonranging;
+ s->message_id = FIRA_MESSAGE_ID_MEASUREMENT_REPORT;
+ s->controlee = NULL;
+
+ s++;
+ if (params->result_report_phase) {
+ i = 0;
+ list_for_each_entry (controlee,
+ &session->current_controlees,
+ entry) {
+ if (!fira_session_controlee_active(controlee))
+ continue;
+ s->index = index++;
+ s->controller_tx = false;
+ s->ranging_index = i++;
+ s->rx_ant_set = step->rx_ant_set_nonranging;
+ s->message_id = FIRA_MESSAGE_ID_RESULT_REPORT;
+ s->controlee = controlee;
+ s++;
+ }
+ }
+ }
+
+ /* Configure frames for fproc. */
+ frame_dtu = fsd->timestamp_dtu;
+ for (i = 0; i < index; i++) {
+ s = &local->slots[i];
+ frame = &local->frames[i];
+ sts_params = &local->sts_params[i];
+
+ fira_access_setup_frame(local, session, frame, sts_params, s,
+ frame_dtu, s->controller_tx);
+
+ frame_dtu += params->slot_duration_dtu;
+ }
+
+ /*
+ * Configure the access.
+ * 'duration_dtu' can be decrease on reception error.
+ */
+ access->ops = &fira_controller_access_ops;
+ access->timestamp_dtu = fsd->timestamp_dtu;
+ access->duration_dtu = frame_dtu - fsd->timestamp_dtu;
+ access->n_frames = index;
+ return access;
+}
+
+static struct mcps802154_access_ops fira_controlee_access_ops = {
+ .common = {
+ .access_done = fira_access_done,
+ },
+ .rx_frame = fira_rx_frame,
+ .tx_get_frame = fira_tx_get_frame,
+ .tx_return = fira_tx_return,
+};
+
+struct mcps802154_access *
+fira_get_access_controlee(struct fira_local *local,
+ const struct fira_session_demand *fsd)
+{
+ /*
+ * \ Important:
+ * '-.__.-' It's almost forbidden to update session
+ * /oo |--.--,--,--. content for a controlee. Because, the
+ * \_.-'._i__i__i_.' session can change on control frame received.
+ * """""""""
+ */
+ struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ struct mcps802154_access *access = &local->access;
+ struct mcps802154_access_frame *frame;
+ struct fira_ranging_info *ri;
+ struct fira_slot *s;
+ u16 request = MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU;
+
+ trace_region_fira_get_access_controlee(local, session, fsd);
+
+ /* Update local context (shared memory used by all sessions). */
+ local->src_short_addr =
+ fira_access_set_short_address(local, session, access);
+ local->dst_short_addr = params->controller_short_addr;
+ local->n_stopped_controlees = 0;
+
+ /*
+ * Update session.
+ * Updated values are used in case of bad reception (timeout/error).
+ * Otherwise on good reception, many are override.
+ * by the controlee resync function.
+ */
+ session->block_start_dtu = fsd->block_start_dtu;
+ session->block_index += fsd->add_blocks;
+
+ /* Reset one ranging info to store info related to the controller. */
+ local->n_ranging_info = 1;
+ ri = local->ranging_info;
+ memset(ri, 0, sizeof(struct fira_ranging_info));
+ /*
+ * Warning:
+ * - short_addr is used when rx control have an error.
+ * - Be careful to not initialize too much in ri, because
+ * session can change on rx control.
+ */
+ ri->short_addr = params->controller_short_addr;
+
+ /* Prepare control message slot for fira_rx_frame. */
+ s = local->slots;
+ s->index = 0;
+ s->controller_tx = true;
+ s->ranging_index = 0;
+ s->rx_ant_set =
+ fira_session_get_meas_seq_step(session)->rx_ant_set_nonranging;
+ s->message_id = FIRA_MESSAGE_ID_CONTROL;
+ s->controlee = NULL;
+
+ /* Configure frames for fproc. */
+ if (params->report_rssi)
+ request |= MCPS802154_RX_FRAME_INFO_RSSI;
+ frame = local->frames;
+ *frame = (struct mcps802154_access_frame){
+ .is_tx = false,
+ .rx = {
+ .frame_config = {
+ .timestamp_dtu = fsd->timestamp_dtu,
+ .timeout_dtu = fsd->rx_timeout_dtu,
+ .flags = MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND |
+ MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU,
+ .ant_set_id = s->rx_ant_set,
+ },
+ .frame_info_flags_request = request,
+ },
+ };
+
+ /*
+ * Configure the access.
+ * 'duration_dtu' will be overridden on control frame reception.
+ */
+ access->ops = &fira_controlee_access_ops;
+ access->timestamp_dtu = fsd->timestamp_dtu;
+ access->duration_dtu = fsd->max_duration_dtu;
+ access->n_frames = 1;
+ return access;
+}
diff --git a/mac/fira_access.h b/mac/fira_access.h
new file mode 100644
index 0000000..3b6ca8c
--- /dev/null
+++ b/mac/fira_access.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef FIRA_ACCESS_H
+#define FIRA_ACCESS_H
+
+#include <net/mcps802154_schedule.h>
+
+/* Forward declaration. */
+struct fira_local;
+struct fira_session;
+struct fira_session_demand;
+
+/**
+ * fira_session_get_slot_count() - Return the number of slot for the session.
+ * @session: FiRa session context.
+ *
+ * Return: Number of slot.
+ */
+int fira_session_get_slot_count(const struct fira_session *session);
+
+/**
+ * fira_get_access_controller() - Build the access for controller.
+ * @local: FiRa region context.
+ * @fsd: FiRa Session Demand from the get_demand of the session fsm.
+ *
+ * Return: A valid access.
+ */
+struct mcps802154_access *
+fira_get_access_controller(struct fira_local *local,
+ const struct fira_session_demand *fsd);
+
+/**
+ * fira_get_access_controlee() - Build the access for controlee.
+ * @local: FiRa region context.
+ * @fsd: FiRa Session Demand from the get_demand of the session fsm.
+ *
+ * Return: A valid access.
+ */
+struct mcps802154_access *
+fira_get_access_controlee(struct fira_local *local,
+ const struct fira_session_demand *fsd);
+
+#endif /* FIRA_ACCESS_H */
diff --git a/mac/fira_crypto.c b/mac/fira_crypto.c
new file mode 100755
index 0000000..3613c9a
--- /dev/null
+++ b/mac/fira_crypto.c
@@ -0,0 +1,1419 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation. You should
+ * have received a copy of the GPLv2 along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifdef __KERNEL__
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
+#endif
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <crypto/aes.h>
+
+#include <asm/unaligned.h>
+
+#include "fira_crypto.h"
+#include <net/mcps802154_frame.h>
+#include "mcps_crypto.h"
+
+#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE
+#include "key_manager.h"
+#endif
+
+#ifdef __KERNEL__
+static inline void *platform_malloc(size_t s) { return kmalloc(s, GFP_KERNEL); }
+static inline void platform_free(void *p) { kfree(p); }
+#else
+#include "trace/define_trace_specific.h"
+#define pr_info(...) print_trace(__VA_ARGS__)
+#define pr_err(...) print_trace(__VA_ARGS__)
+#include "platform_alloc.h"
+#endif
+
+#define FIRA_CRYPTO_KDF_LABEL_LEN 8
+#define FIRA_CRYPTO_KDF_CONTEXT_LEN 16
+#define FIRA_CRYPTO_KEY_STS_MASK 0x7FFFFFFF
+
+#define FIRA_IE_VENDOR_OUI_LEN 3
+
+#define FIRA_CRYPTO_AEAD_AUTHSIZE 8
+
+/**
+ * struct fira_crypto_aead - Context for payload encryption/decryption.
+ */
+struct fira_crypto_aead {
+ /**
+ * @ctx: The context to be used during aead encryption & decryption.
+ */
+ struct mcps_aes_ccm_star_128_ctx *ctx;
+};
+
+/**
+ * struct fira_crypto - Context containing all crypto related
+ * information.
+ *
+ * NOTE: It is regularly used by the FiRa region core to produce the STS
+ * parameters for a given a session and to encrypt/decrypt frames.
+ */
+struct fira_crypto_base {
+ /**
+ * @key_size: Size of the key used in the AES derivation.
+ */
+ u8 key_size;
+
+ /**
+ * @config_digest: Digest of the configuration, used as input for keys
+ * derivation.
+ */
+ u8 config_digest[AES_BLOCK_SIZE];
+
+ /**
+ * @data_protection_key: Derived from the session key, the label
+ * "DataPrtK" and the config_digest. The precise size is given by the
+ * key_size.
+ */
+ u8 data_protection_key[FIRA_KEY_SIZE_MIN];
+
+ /**
+ * @derived_authentication_iv: Derived from data_protection_key, the
+ * label "DerAuthI", the current value of crypto_sts_index, and the
+ * config_digest. Used to compute the STS parameters for a slot.
+ */
+ u8 derived_authentication_iv[AES_BLOCK_SIZE];
+
+ /**
+ * @derived_authentication_key: Derived from data_protection_key, the
+ * label "DerAuthK", the current value of crypto_sts_index and the
+ * config_digest. Used to compute the STS parameters for a slot.
+ */
+ u8 derived_authentication_key[FIRA_KEY_SIZE_MIN];
+
+ /**
+ * @derived_payload_key: Derived from data_protection_key, the label
+ * "DerPaylK", the current value of crypto_sts_index and the
+ * config_digest. Used to encrypt/decrypt message PIE.
+ */
+ u8 derived_payload_key[FIRA_KEY_SIZE_MIN];
+
+ /**
+ * @aead: AEAD Context for payload encryption/decryption.
+ */
+ struct fira_crypto_aead aead;
+};
+
+struct fira_crypto {
+ /**
+ * @phy_sts_index_init: Initial phy_sts_index deduced at context init.
+ */
+ u32 phy_sts_index_init;
+ /**
+ * @sts_config: The type of STS requested for this crypto.
+ */
+ enum fira_sts_mode sts_config;
+
+ /**
+ * @base: Common parameters between all types of crypto contexts.
+ */
+ struct fira_crypto_base base;
+
+ /******* Dynamic STS Only **************/
+
+ /**
+ * @ecb_ctx: AES ECB context
+ */
+ struct mcps_aes_ecb_128_ctx *ecb_ctx;
+
+ /**
+ * @privacy_key: Derived from the session key, the label
+ * "PrivacyK" and the config_digest. Used to encrypt/decrypt message HIE.
+ */
+ u8 privacy_key[FIRA_KEY_SIZE_MIN];
+
+ /******* Static STS Only **************/
+ /**
+ * @vupper64: The vupper 64 to use when static STS is used.
+ */
+ u8 vupper64[FIRA_VUPPER64_SIZE];
+};
+
+static int fira_crypto_kdf(const u8 *input_key, unsigned int input_key_len,
+ const char *label, const u8 *context, u8 *output_key,
+ unsigned int output_key_len)
+{
+ u8 derivation_data[sizeof(u32) + FIRA_CRYPTO_KDF_LABEL_LEN +
+ FIRA_CRYPTO_KDF_CONTEXT_LEN + sizeof(u32)];
+ u8 *p;
+ int r;
+
+ if (input_key_len != AES_KEYSIZE_128) {
+ pr_err("input_key_len != AES_KEYSIZE_128");
+ return -EINVAL;
+ }
+
+ p = derivation_data;
+ put_unaligned_be32(1, p);
+ p += sizeof(u32);
+ memcpy(p, label, FIRA_CRYPTO_KDF_LABEL_LEN);
+ p += FIRA_CRYPTO_KDF_LABEL_LEN;
+ memcpy(p, context, FIRA_CRYPTO_KDF_CONTEXT_LEN);
+ p += FIRA_CRYPTO_KDF_CONTEXT_LEN;
+ put_unaligned_be32(output_key_len * 8, p);
+
+ r = mcps_crypto_cmac_aes_128_digest(input_key, derivation_data,
+ sizeof(derivation_data), output_key);
+
+ return r;
+}
+
+static int fira_crypto_aead_set_key(struct fira_crypto_aead *aead, const u8 *key)
+{
+ aead->ctx = mcps_crypto_aead_aes_ccm_star_128_create();
+
+ return aead->ctx ? mcps_crypto_aead_aes_ccm_star_128_set(aead->ctx, key) : -ENOMEM;
+}
+
+static void fira_aead_fill_nonce(u8 *nonce, __le16 src_short_addr,
+ u32 crypto_sts_index)
+{
+ u8 *p;
+
+ p = nonce;
+ memset(p, 0, IEEE802154_EXTENDED_ADDR_LEN - IEEE802154_SHORT_ADDR_LEN);
+ p += IEEE802154_EXTENDED_ADDR_LEN - IEEE802154_SHORT_ADDR_LEN;
+ put_unaligned_be16(src_short_addr, p);
+ p += IEEE802154_SHORT_ADDR_LEN;
+ put_unaligned_be32(crypto_sts_index, p);
+ p += sizeof(u32);
+ *p++ = IEEE802154_SCF_SECLEVEL_ENC_MIC64;
+}
+
+static int fira_crypto_aead_encrypt(struct fira_crypto_aead *aead,
+ struct sk_buff *skb, unsigned int header_len,
+ __le16 src_short_addr, u32 crypto_sts_index)
+{
+ u8 nonce[MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN];
+ u8 *header = skb->data;
+ u8 *payload = skb->data + header_len;
+ const int payload_len = skb->len - header_len;
+ u8 *mac = skb->data + skb->len;
+ int r;
+
+ fira_aead_fill_nonce(nonce, src_short_addr, crypto_sts_index);
+
+ skb->data[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN] =
+ IEEE802154_SCF_SECLEVEL_ENC_MIC64 |
+ IEEE802154_SCF_NO_FRAME_COUNTER;
+
+ r = mcps_crypto_aead_aes_ccm_star_128_encrypt(
+ aead->ctx, nonce, header, header_len, payload, payload_len, mac,
+ FIRA_CRYPTO_AEAD_AUTHSIZE);
+
+ if (!r)
+ skb_put(skb, FIRA_CRYPTO_AEAD_AUTHSIZE);
+
+ return r;
+}
+
+static int fira_crypto_aead_decrypt(struct fira_crypto_aead *aead,
+ struct sk_buff *skb, unsigned int header_len,
+ __le16 src_short_addr, u32 crypto_sts_index)
+{
+ u8 nonce[MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN];
+ u8 *header;
+ u8 *payload;
+ unsigned int payload_len;
+ u8 *mac;
+ int r;
+
+ header = skb->data - header_len;
+ payload = skb->data;
+ payload_len = skb->len;
+ mac = skb->data + payload_len;
+
+ fira_aead_fill_nonce(nonce, src_short_addr, crypto_sts_index);
+
+ r = mcps_crypto_aead_aes_ccm_star_128_decrypt(
+ aead->ctx, nonce, header, header_len, payload, payload_len, mac,
+ FIRA_CRYPTO_AEAD_AUTHSIZE);
+
+ if (!r) {
+ header[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN] |=
+ IEEE802154_SCF_NO_FRAME_COUNTER;
+ }
+
+ memzero_explicit(mac, FIRA_CRYPTO_AEAD_AUTHSIZE);
+
+ return r;
+}
+
+static void fira_crypto_aead_destroy(struct fira_crypto_aead *aead)
+{
+ mcps_crypto_aead_aes_ccm_star_128_destroy(aead->ctx);
+ aead->ctx = NULL;
+}
+
+static void remove_session(struct fira_crypto *session)
+{
+ fira_crypto_aead_destroy(&session->base.aead);
+ mcps_crypto_aes_ecb_128_destroy(session->ecb_ctx);
+ /* Wipe all derived keys */
+ memzero_explicit(session, sizeof(*session));
+ platform_free(session);
+}
+
+/*! ----------------------------------------------------------------------------------------------
+ * @brief This function is used to initialise the FIRA crypto backend
+ *
+ * input parameters:
+ * @param key_manager - key manager to use. Not used for the moment.
+ *
+ * output parameters
+ *
+ * return 0 if no error.
+ */
+int fira_crypto_init(void *key_manager)
+{
+#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE
+ /* Opens the UBWS - SE secure channel */
+ return key_manager_init(NULL);
+#else
+ return 0;
+#endif
+}
+
+/************************************** FIRA STS API FCTS ***************************************/
+
+int fira_crypto_context_init(const struct fira_crypto_params *crypto_params,
+ struct fira_crypto **crypto)
+{
+ int status = 0;
+ struct fira_crypto *fira_crypto_ctx;
+ u8 session_key[AES_KEYSIZE_128];
+
+ fira_crypto_ctx = platform_malloc(sizeof(*fira_crypto_ctx));
+
+ if (!fira_crypto_ctx)
+ return -ENOMEM;
+
+ memset(fira_crypto_ctx, 0, sizeof(*fira_crypto_ctx));
+
+ /* Retrieve session key */
+ switch (crypto_params->sts_config) {
+ case FIRA_STS_MODE_STATIC:
+ memcpy(session_key, "StaticTSStaticTS", AES_KEYSIZE_128);
+ fira_crypto_ctx->base.key_size = AES_KEYSIZE_128;
+ memcpy(fira_crypto_ctx->vupper64, crypto_params->vupper64, FIRA_VUPPER64_SIZE);
+ break;
+
+ case FIRA_STS_MODE_PROVISIONED:
+ case FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY:
+ if (crypto_params->key) {
+ memcpy(session_key, crypto_params->key, crypto_params->key_len);
+ fira_crypto_ctx->base.key_size = crypto_params->key_len;
+ } else {
+ /* Session key not set */
+ pr_err("Session key not provisioned !\n");
+ status = -1;
+ }
+ break;
+
+ default:
+ /* Bad value */
+ pr_err("STS config unknown !\n");
+ status = -1;
+ }
+
+ fira_crypto_ctx->sts_config = crypto_params->sts_config;
+
+ if (!status) {
+ /* Compute Config Digest */
+ static const u8 zero_key[AES_KEYSIZE_128];
+
+ status = mcps_crypto_cmac_aes_128_digest(zero_key,
+ crypto_params->concat_params,
+ crypto_params->concat_params_size,
+ fira_crypto_ctx->base.config_digest);
+ if (status)
+ goto error_out;
+
+ /* Compute secDataProtectionKey */
+ status = fira_crypto_kdf(session_key,
+ fira_crypto_ctx->base.key_size,
+ "DataPrtK",
+ fira_crypto_ctx->base.config_digest,
+ fira_crypto_ctx->base.data_protection_key,
+ FIRA_KEY_SIZE_MIN);
+ if (status)
+ goto error_out;
+
+ if (crypto_params->sts_config == FIRA_STS_MODE_STATIC) {
+ /* rotate keys only once for static_sts */
+ status = fira_crypto_rotate_elements(
+ fira_crypto_ctx,
+ 0);
+ } else {
+
+ /* Compute secPrivacy Key and setup AES ECB context */
+ status = fira_crypto_kdf(
+ session_key, fira_crypto_ctx->base.key_size,
+ "PrivacyK", fira_crypto_ctx->base.config_digest,
+ fira_crypto_ctx->privacy_key,
+ FIRA_KEY_SIZE_MIN);
+ }
+ if (status)
+ goto error_out;
+ }
+
+ /* Wipe session key */
+ memzero_explicit(session_key, AES_KEYSIZE_128);
+
+ *crypto = fira_crypto_ctx;
+
+ return status;
+
+error_out:
+ /* Wipe session key */
+ memzero_explicit(session_key, AES_KEYSIZE_128);
+ *crypto = NULL;
+ remove_session(fira_crypto_ctx);
+ return status;
+}
+
+/**
+ * fira_crypto_context_deinit() - De-initialize a crypto context.
+ * @session_id: Pointer to the session id.
+ */
+void fira_crypto_context_deinit(struct fira_crypto *fira_crypto_ctx)
+{
+ if (fira_crypto_ctx) {
+ /* Remove the context */
+ remove_session(fira_crypto_ctx);
+ fira_crypto_ctx = NULL;
+ } else {
+ /* The context doesn't exist */
+ pr_err("Crypto context NULL\n");
+ }
+}
+
+/**
+ * fira_crypto_rotate_elements() - Rotate the crypto elements contained in the
+ * crypto context.
+ *
+ * NOTE: After calling this function, all active crypto elements will be the latest
+ * rotated ones.
+ *
+ * @crypto: The context containing the elements to rotate.
+ * @crypto_sts_index: The crypto STS index to use to rotate the elements.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_rotate_elements(struct fira_crypto *fira_crypto_ctx,
+ const u32 crypto_sts_index)
+{
+ int r = 0;
+ u8 context[AES_BLOCK_SIZE];
+
+ if (!fira_crypto_ctx)
+ goto error_out;
+
+ memcpy(context, fira_crypto_ctx->base.config_digest + sizeof(u32),
+ AES_BLOCK_SIZE - sizeof(u32));
+ put_unaligned_be32(crypto_sts_index, context + AES_BLOCK_SIZE -
+ sizeof(u32));
+
+ r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key,
+ fira_crypto_ctx->base.key_size,
+ "DerAuthI", context,
+ fira_crypto_ctx->base.derived_authentication_iv,
+ fira_crypto_ctx->base.key_size);
+ if (r)
+ goto error_out;
+
+ r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key,
+ fira_crypto_ctx->base.key_size,
+ "DerAuthK", context,
+ fira_crypto_ctx->base.derived_authentication_key,
+ fira_crypto_ctx->base.key_size);
+ if (r)
+ goto error_out;
+
+ r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key,
+ fira_crypto_ctx->base.key_size,
+ "DerPaylK", context,
+ fira_crypto_ctx->base.derived_payload_key,
+ fira_crypto_ctx->base.key_size);
+ if (r)
+ goto error_out;
+
+ if (fira_crypto_ctx->base.aead.ctx == NULL)
+ r = fira_crypto_aead_set_key(&fira_crypto_ctx->base.aead,
+ fira_crypto_ctx->base.derived_payload_key);
+
+error_out:
+ memzero_explicit(context, sizeof(context));
+ return r;
+}
+
+u32 fira_crypto_get_phy_sts_index_init(const struct fira_crypto *crypto)
+{
+ if (!crypto)
+ return -1;
+ return crypto->phy_sts_index_init;
+}
+
+/**
+ * fira_crypto_build_phy_sts_index_init() - Build the phy STS index init value
+ * related to the given crypto context.
+ *
+ * @crypto: The context to use to compute the phy STS index init value.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_build_phy_sts_index_init(struct fira_crypto *fira_crypto_ctx)
+{
+ int r = -1;
+ u8 phy_sts_index_init_tmp[AES_KEYSIZE_128];
+
+ if (!fira_crypto_ctx)
+ goto error_out;
+
+ r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key,
+ fira_crypto_ctx->base.key_size,
+ "StsIndIn", fira_crypto_ctx->base.config_digest,
+ phy_sts_index_init_tmp,
+ fira_crypto_ctx->base.key_size);
+ if (r)
+ goto error_out;
+
+ fira_crypto_ctx->phy_sts_index_init =
+ get_unaligned_be32(phy_sts_index_init_tmp +
+ fira_crypto_ctx->base.key_size - sizeof(u32)) &
+ FIRA_CRYPTO_KEY_STS_MASK;
+ return 0;
+
+error_out:
+ memzero_explicit(phy_sts_index_init_tmp,
+ sizeof(phy_sts_index_init_tmp));
+ return r;
+
+}
+
+/**
+ * fira_crypto_dynamic_get_sts_params() - Get STS parameters for a given slot
+ * using a dynamic STS configuration.
+ * @crypto: The context to use to get the STS parameters.
+ * @crypto_sts_index: The crypto STS index related to the slot request slot.
+ * @sts_v: Output buffer to store the STS V.
+ * @sts_v_size: Size of the STS V buffer.
+ * @sts_key: Output buffer to store the STS key.
+ * @sts_key_size: Size of the STS key buffer.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_get_sts_params(struct fira_crypto *fira_crypto_ctx,
+ const u32 crypto_sts_index, u8 *sts_v, u32 sts_v_size,
+ u8 *sts_key, u32 sts_key_size)
+{
+ u8 *vupper64 = NULL;
+ u32 v_counter;
+ u8 *sts_v_temp;
+
+ if (!fira_crypto_ctx)
+ return -1;
+
+ if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC)
+ vupper64 = fira_crypto_ctx->vupper64;
+ else
+ vupper64 = fira_crypto_ctx->base.derived_authentication_iv;
+
+ if (sts_v_size < AES_BLOCK_SIZE || sts_key_size < AES_KEYSIZE_128)
+ return -EINVAL;
+
+ sts_v_temp = sts_v;
+
+ /* Concatenate the 128 bits of sts_v
+ * sts_v = vupper64 | crypto_sts_index | v_counter
+ */
+ memcpy(sts_v_temp, vupper64, FIRA_VUPPER64_SIZE);
+ sts_v_temp += FIRA_VUPPER64_SIZE;
+ put_unaligned_be32(crypto_sts_index, sts_v_temp);
+ sts_v_temp += sizeof(crypto_sts_index);
+ v_counter = get_unaligned_be32(
+ fira_crypto_ctx->base.derived_authentication_iv +
+ AES_BLOCK_SIZE - sizeof(v_counter)) &
+ FIRA_CRYPTO_KEY_STS_MASK;
+ put_unaligned_be32(v_counter, sts_v_temp);
+
+ memcpy(sts_key, fira_crypto_ctx->base.derived_authentication_key,
+ fira_crypto_ctx->base.key_size);
+
+ return 0;
+}
+
+/**
+ * fira_crypto_encrypt_frame() - Encrypt a 802154 frame using a given context.
+ *
+ * NOTE: The src address is given as an argument as it is a part of the nonce needed
+ * to encrypt the frame and it is not present in the 802154 frame.
+ * The length of the header is given because only the payload is encrypted even if
+ * the encryption algorithm needs the whole 802154 frame.
+ * Encryption is done in-place.
+ * When called this function shall increase the size of the skb of
+ * FIRA_CRYPTO_AEAD_AUTHSIZE and set the correct bits in the 802154 frame SCF.
+ *
+ * @crypto: The context to use to encrypt the frame.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of
+ * the 802154 frame header.
+ * @header_len: The length of the 802154 frame header. Can be used to find the
+ * position of the 802154 frame payload relative to skb->data.
+ * @src_short_addr: The short source address attached to the frame.
+ * @crypto_sts_index: The crypto STS index attached to the frame.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_encrypt_frame(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr, u32 crypto_sts_index)
+{
+ if (!fira_crypto_ctx)
+ return -1;
+
+ return fira_crypto_aead_encrypt(&fira_crypto_ctx->base.aead, skb, header_len,
+ src_short_addr, crypto_sts_index);
+}
+
+/**
+ * fira_crypto_decrypt_frame() - Decrypt a 802154 frame using a given context.
+ *
+ * NOTE: The src address is given as an argument as it is a part of the nonce needed
+ * to decrypt the frame and it is not present in the 802154 frame.
+ * The length of the header is given because only the payload is encrypted even if
+ * the encryption algorithm needs the whole 802154 frame.
+ * Decryption is done in-place.
+ * When called, this function shall reduce the
+ * size of the skb of FIRA_CRYPTO_AEAD_AUTHSIZE and verify the correct bits in the
+ * 802154 frame SCF.
+ *
+ * @crypto: The crypto to use to decrypt the frame.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of
+ * the 802154 frame payload.
+ * @header_len: The length of the 802154 frame header. Can be used to find the
+ * start of the 802154 frame payload relative to skb->data.
+ * @src_short_addr: The short source address attached to the frame.
+ * @crypto_sts_index: The crypto STS index attached to the frame.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_decrypt_frame(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr, u32 crypto_sts_index)
+{
+ if (!fira_crypto_ctx)
+ return -1;
+
+ return fira_crypto_aead_decrypt(&fira_crypto_ctx->base.aead, skb, header_len,
+ src_short_addr, crypto_sts_index);
+}
+
+/**
+ * fira_crypto_encrypt_hie() - Encrypt the HIE in a FiRa 802154 frame.
+ * @crypto: The context to use to get the STS parameters.
+ * @skb: Buffer containing the frame to encrypt.
+ * @hie_offset: Offset to the start of the HIE (relating to skb->data) to encrypt.
+ * @hie_len: Length of the HIE to encrypt.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_encrypt_hie(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb,
+ int hie_offset, int hie_len)
+{
+ int rc;
+
+ if (!fira_crypto_ctx)
+ return -1;
+
+ if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC)
+ return 0;
+
+ fira_crypto_ctx->ecb_ctx = mcps_crypto_aes_ecb_128_create();
+ if (!fira_crypto_ctx->ecb_ctx ||
+ mcps_crypto_aes_ecb_128_set_encrypt(
+ fira_crypto_ctx->ecb_ctx,
+ fira_crypto_ctx->privacy_key))
+ return -1;
+
+ rc = mcps_crypto_aes_ecb_128_encrypt(fira_crypto_ctx->ecb_ctx,
+ (const uint8_t *)(skb->data + hie_offset +
+ IEEE802154_IE_HEADER_LEN +
+ FIRA_IE_VENDOR_OUI_LEN),
+ (unsigned int)hie_len - IEEE802154_IE_HEADER_LEN -
+ FIRA_IE_VENDOR_OUI_LEN,
+ (uint8_t *)(skb->data + hie_offset +
+ IEEE802154_IE_HEADER_LEN +
+ FIRA_IE_VENDOR_OUI_LEN));
+
+ mcps_crypto_aes_ecb_128_destroy(fira_crypto_ctx->ecb_ctx);
+ fira_crypto_ctx->ecb_ctx = NULL;
+
+ return rc;
+}
+
+/**
+ * fira_crypto_decrypt_hie() - Decrypt the HIE in a FiRa 802154 frame.
+ * @crypto: The context to use to get the STS parameters.
+ * @skb: Buffer containing the frame to decrypt.
+ * @hie_offset: Offset to the start of the HIE (relative to skb->data) to decrypt.
+ * @hie_len: Length of the HIE to decrypt.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_decrypt_hie(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb,
+ int hie_offset, int hie_len)
+{
+ int rc;
+
+ if (!fira_crypto_ctx)
+ return -1;
+
+ if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC)
+ return 0;
+
+ fira_crypto_ctx->ecb_ctx = mcps_crypto_aes_ecb_128_create();
+ if (!fira_crypto_ctx->ecb_ctx ||
+ mcps_crypto_aes_ecb_128_set_decrypt(
+ fira_crypto_ctx->ecb_ctx,
+ fira_crypto_ctx->privacy_key))
+ return -1;
+
+ rc = mcps_crypto_aes_ecb_128_encrypt(fira_crypto_ctx->ecb_ctx,
+ (const uint8_t *)(skb->data + hie_offset),
+ (unsigned int)hie_len,
+ (uint8_t *)(skb->data + hie_offset));
+
+ mcps_crypto_aes_ecb_128_destroy(fira_crypto_ctx->ecb_ctx);
+ fira_crypto_ctx->ecb_ctx = NULL;
+
+ return rc;
+}
+
+/**
+ * fira_crypto_get_capabilities() - Get capabilities of the platform used.
+ *
+ * Return:
+ * bit 0 : FIRA_STS_MODE_STATIC supported
+ * bit 1 : FIRA_STS_MODE_DYNAMIC supported
+ * bit 2 : FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY supported
+ * bit 3 : FIRA_STS_MODE_PROVISIONED supported
+ * bit 4 : FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY supported
+ * other : not used
+ */
+u32 fira_crypto_get_capabilities(void)
+{
+ u32 status = 0;
+
+ status += STS_CAP(STATIC);
+ status += STS_CAP(PROVISIONED);
+ status += STS_CAP(PROVISIONED_INDIVIDUAL_KEY);
+
+#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE
+ status += STS_CAP(DYNAMIC);
+#endif
+
+ return status;
+}
+
+int fira_crypto_prepare_decrypt(struct fira_crypto *crypto, struct sk_buff *skb)
+{
+ u8 scf;
+ u8 *p;
+
+ p = skb->data - (IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN +
+ IEEE802154_SCF_LEN);
+ scf = p[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN];
+ if (!(scf == (IEEE802154_SCF_SECLEVEL_ENC_MIC64 |
+ IEEE802154_SCF_NO_FRAME_COUNTER)) ||
+ (skb->len < FIRA_CRYPTO_AEAD_AUTHSIZE))
+ return -EBADMSG;
+ skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE);
+
+ return 0;
+}
+
+static bool compare_bufs(const void *a, size_t alen, const void *b, size_t blen)
+{
+ if (alen != blen || memcmp(a, b, alen) != 0) {
+#ifdef __KERNEL__
+ print_hex_dump(KERN_ERR, "a: ", DUMP_PREFIX_OFFSET, 16, 1, a,
+ alen, false);
+ print_hex_dump(KERN_ERR, "b: ", DUMP_PREFIX_OFFSET, 16, 1, b,
+ blen, false);
+#endif
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * fira_crypto_test_static()
+ * Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS
+ * version 1.3.0 test vectors for Static STS
+ *
+ * NOTE: This APis used for unit tests only.
+ *
+ * Return: 0 if ok
+ */
+static int fira_crypto_test_static(void)
+{
+ /* Static STS */
+ static const u8 config[] = {
+ 0x02, 0x00, 0x00, 0x09, 0x07, 0xd0, 0x00, 0x03,
+ 0x0a, 0x02, 0x00, 0x01, 0x03, 0x01, 0x23, 0x45,
+ 0x67
+ };
+ static const u8 vUpp[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+ static const struct fira_crypto_params param = {
+ .session_id = 0x01234567,
+ .sts_config = FIRA_STS_MODE_STATIC,
+ .concat_params = config,
+ .concat_params_size = sizeof(config),
+ .vupper64 = vUpp
+ };
+ static const u8 configDigest[] = {
+ 0xa0, 0x43, 0x90, 0xcf, 0x8a, 0x33, 0xf6, 0xeb,
+ 0x7e, 0x2f, 0xc3, 0x78, 0x87, 0xb6, 0xb2, 0xa3
+ };
+ static const u8 secDataProtectionKey[] = {
+ 0xf3, 0x21, 0x6c, 0x87, 0xd0, 0xc6, 0x93, 0x2e,
+ 0x39, 0x57, 0xb4, 0x81, 0xfa, 0xb8, 0xb2, 0x09
+ };
+ static const u8 derived_authentication_iv[] = {
+ 0x8b, 0x54, 0x37, 0x6e, 0x7c, 0xd7, 0xa5, 0xd6,
+ 0x6b, 0xd1, 0x20, 0x00, 0x97, 0x27, 0x41, 0x19
+ };
+ static const u8 derived_authentication_key[] = {
+ 0xdd, 0x98, 0x97, 0xf2, 0xb8, 0x5c, 0x9d, 0xc8,
+ 0xa7, 0xde, 0xc0, 0x1c, 0xca, 0x5b, 0x61, 0xdb
+ };
+ static const u8 derived_payload_key[] = {
+ 0xa5, 0x5f, 0xab, 0x83, 0xb6, 0x20, 0xf9, 0xf6,
+ 0xa4, 0x7c, 0xdb, 0x72, 0x91, 0x7c, 0x73, 0x8a
+ };
+ static const u8 sts_v_ref[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x17, 0x27, 0x41, 0x19
+ };
+ /* build the RCM Frame */
+ /* build the header */
+ static const u8 RCM[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18,
+ 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55,
+ 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55,
+ 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55,
+ 0x0b
+ };
+ static const u8 RCMRef[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b, 0x00, 0x3f, 0xcb, 0xa4, 0xfd, 0x37,
+ 0xd1, 0x99, 0x44, 0x88, 0x7c, 0x2b, 0xec, 0x2e,
+ 0x1a, 0x99, 0x8e, 0x80, 0x61, 0x7c, 0x44, 0xb5,
+ 0xe8, 0xe3, 0xf3, 0x35, 0x3a, 0xb9, 0xf2, 0x29,
+ 0x1b, 0x80, 0x4b, 0xba, 0xe1, 0xa9, 0x2a, 0x20,
+ 0x28
+ };
+ static const u8 Header[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b
+ };
+ static const u8 HeaderRef[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b
+ };
+ /* Decrypt Frame */
+ static const u8 RCM_Rcv_Ref[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18,
+ 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55,
+ 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55,
+ 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55,
+ 0x0b
+ };
+ static const u8 Frame_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b, 0x00, 0x3f, 0xcb, 0xa4, 0xfd, 0x37,
+ 0xd1, 0x99, 0x44, 0x88, 0x7c, 0x2b, 0xec, 0x2e,
+ 0x1a, 0x99, 0x8e, 0x80, 0x61, 0x7c, 0x44, 0xb5,
+ 0xe8, 0xe3, 0xf3, 0x35, 0x3a, 0xb9, 0xf2, 0x29,
+ 0x1b, 0x80, 0x4b, 0xba, 0xe1, 0xa9, 0x2a, 0x20,
+ 0x28
+ };
+ static const u8 Header_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b
+ };
+ static const u8 HeaderRef_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe,
+ 0x9b, 0x0b
+ };
+ int err = -1, r;
+ struct fira_crypto *fira_crypto_ctx;
+ const u32 crypto_sts_index = 0;
+ u8 sts_v[16];
+ u8 sts_key[16];
+ struct sk_buff *skb = NULL;
+
+ r = fira_crypto_context_init(&param, &fira_crypto_ctx);
+ if (r != 0 || !fira_crypto_ctx) {
+ pr_err("fira_crypto_context_init fail: %d\n", r);
+ goto end;
+ }
+
+ if (!compare_bufs(configDigest, sizeof(configDigest),
+ fira_crypto_ctx->base.config_digest,
+ sizeof(fira_crypto_ctx->base.config_digest))) {
+ pr_err("compare configDigest fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(secDataProtectionKey, sizeof(secDataProtectionKey),
+ fira_crypto_ctx->base.data_protection_key,
+ sizeof(fira_crypto_ctx->base.data_protection_key))) {
+ pr_err("compare secDataProtectionKey fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(derived_authentication_iv,
+ sizeof(derived_authentication_iv),
+ fira_crypto_ctx->base.derived_authentication_iv,
+ sizeof(fira_crypto_ctx->base.derived_authentication_iv))) {
+ pr_err("compare derived_authentication_iv fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(derived_authentication_key,
+ sizeof(derived_authentication_key),
+ fira_crypto_ctx->base.derived_authentication_key,
+ sizeof(fira_crypto_ctx->base.derived_authentication_key))) {
+ pr_err("compare derived_authentication_key fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(derived_payload_key, sizeof(derived_payload_key),
+ fira_crypto_ctx->base.derived_payload_key,
+ sizeof(fira_crypto_ctx->base.derived_payload_key))) {
+ pr_err("compare derived_payload_key fail\n");
+ goto end;
+ }
+
+ r = fira_crypto_build_phy_sts_index_init(fira_crypto_ctx);
+ if (r != 0) {
+ pr_err("fira_crypto_build_phy_sts_index_init fail: %d\n", r);
+ goto end;
+ }
+ if (fira_crypto_ctx->phy_sts_index_init != 0x0b9bbe78) {
+ pr_err("phy_sts_index_init fail\n");
+ goto end;
+ }
+
+ r = fira_crypto_get_sts_params(fira_crypto_ctx, crypto_sts_index, sts_v,
+ sizeof(sts_v), sts_key, sizeof(sts_key));
+ if (r != 0) {
+ pr_err("fira_crypto_get_sts_params fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(derived_authentication_key,
+ sizeof(derived_authentication_key),
+ sts_key, sizeof(sts_key))) {
+ pr_err("compare sts_key fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(sts_v_ref, sizeof(sts_v_ref), sts_v, sizeof(sts_v))) {
+ pr_err("compare sts_v fail\n");
+ goto end;
+ }
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Header, sizeof(Header));
+
+ /* Encrypt Header first (NOP in Static STS) */
+ r = fira_crypto_encrypt_hie(fira_crypto_ctx, skb, 5, 21);
+ if (r != 0) {
+ pr_err("fira_crypto_encrypt_hie fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(HeaderRef, sizeof(HeaderRef), skb->data, skb->len)) {
+ pr_err("fira_crypto_encrypt_hie compare HeaderRef fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, RCM, sizeof(RCM));
+
+ r = fira_crypto_encrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0);
+ if (r != 0) {
+ pr_err("fira_crypto_encrypt_frame fail: %d\n", r);
+ goto end;
+ }
+
+ if (!compare_bufs(RCMRef, sizeof(RCMRef), skb->data, skb->len)) {
+ pr_err("fira_crypto_encrypt_frame compare RCMRef fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Frame_Rcv, sizeof(Frame_Rcv));
+ skb_pull(skb, 28); /* skip header */
+
+ skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE);
+ r = fira_crypto_decrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0);
+ if (r != 0) {
+ pr_err("fira_crypto_decrypt_frame fail: %d\n", r);
+ goto end;
+ }
+
+ skb_push(skb, 28); /* restore header */
+
+ if (!compare_bufs(RCM_Rcv_Ref, sizeof(RCM_Rcv_Ref), skb->data, skb->len)) {
+ pr_err("fira_crypto_decrypt_frame compare RCM_Rcv_Ref fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Header_Rcv, sizeof(Header_Rcv));
+
+ /* Decrypt header (NOP in Static STS) */
+ r = fira_crypto_decrypt_hie(fira_crypto_ctx, skb, 10, 16);
+ if (r != 0) {
+ pr_err("fira_crypto_decrypt_hie fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(HeaderRef_Rcv, sizeof(HeaderRef_Rcv), skb->data,
+ skb->len)) {
+ pr_err("fira_crypto_decrypt_hie compare HeaderRef_Rcv fail\n");
+ goto end;
+ }
+
+ err = 0;
+
+ pr_info("Static STS tests success\n");
+
+end:
+ if (skb)
+ kfree_skb(skb);
+ if (fira_crypto_ctx)
+ fira_crypto_context_deinit(fira_crypto_ctx);
+
+ return err;
+}
+
+/**
+ * fira_crypto_test_provisioned()
+ * Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS
+ * version 1.3.0 test vectors for Dynamic STS (Provisioned STS is ran instead of
+ * pure dynamic)
+ *
+ * NOTE: This APis used for unit tests only.
+ *
+ * Return: 0 if ok
+ */
+static int fira_crypto_test_provisioned(void)
+{
+ /* Provisioned STS (equivalent to D-STS) */
+ static const u8 config_P_STS[] = {
+ 0x02, 0x01, 0x00, 0x09, 0x07, 0xD0, 0x00, 0x03,
+ 0x0a, 0x02, 0x00, 0x01, 0x03, 0x01, 0x23, 0x45,
+ 0x67
+ };
+ static const u8 sessionKey[] = {
+ 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x53,
+ 0x54, 0x53, 0x4e, 0x6f, 0x52, 0x6f, 0x74, 0x30
+ };
+ static const struct fira_crypto_params param_p_sts = {
+ .session_id = 0x01234567,
+ .sts_config = FIRA_STS_MODE_PROVISIONED,
+ .concat_params = config_P_STS,
+ .concat_params_size = sizeof(config_P_STS),
+ .key = sessionKey,
+ .key_len = sizeof(sessionKey)
+ };
+ static const u8 configDigest_p_sts[] = {
+ 0x08, 0x93, 0x66, 0xba, 0xfb, 0x3b, 0x24, 0xbf,
+ 0xd2, 0x93, 0x33, 0x77, 0x61, 0xb8, 0x8f, 0xc3
+ };
+ static const u8 secDataPrivacyKey_p_sts[] = {
+ 0x3a, 0x4b, 0xab, 0x18, 0x74, 0x4a, 0xee, 0x93,
+ 0x86, 0x50, 0xf1, 0xa0, 0x3f, 0x58, 0x5a, 0x49
+ };
+ static const u8 secDataProtectionKey_p_sts[] = {
+ 0x67, 0xf7, 0x02, 0x7e, 0xa6, 0x2d, 0x84, 0xa5,
+ 0xe1, 0xa8, 0xd7, 0xb8, 0xb8, 0xac, 0xae, 0xaf
+ };
+ static const u8 derived_authentication_iv_p_sts[] = {
+ 0xfa, 0x32, 0x6f, 0xed, 0x87, 0xd2, 0xef, 0x7e,
+ 0xb6, 0x80, 0xb2, 0xd6, 0xd1, 0x19, 0xa9, 0xb8
+ };
+ static const u8 derived_authentication_key_p_sts[] = {
+ 0x91, 0xa2, 0xde, 0x58, 0xff, 0x3b, 0x5e, 0x85,
+ 0x15, 0x33, 0x58, 0xd6, 0x15, 0x64, 0x64, 0xff
+ };
+ static const u8 derived_payload_key_p_sts[] = {
+ 0x97, 0xe4, 0xab, 0x69, 0x61, 0x77, 0xbb, 0x39,
+ 0x92, 0x77, 0xb8, 0x35, 0x9f, 0xa5, 0x5d, 0x19
+ };
+ static const u8 sts_v_ref_p_sts[] = {
+ 0xfa, 0x32, 0x6f, 0xed, 0x87, 0xd2, 0xef, 0x7e,
+ 0x04, 0x1f, 0x3b, 0xa0, 0x51, 0x19, 0xa9, 0xb8
+ };
+ /* build the RCM Frame */
+ static const u8 RCM_p_sts[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18,
+ 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55,
+ 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55,
+ 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55,
+ 0x0b
+ };
+ static const u8 RCMRef_p_sts[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03, 0x00, 0x3f, 0x82, 0x76, 0xe0, 0x44,
+ 0xf3, 0x78, 0xab, 0xbe, 0xd2, 0x39, 0x86, 0x7e,
+ 0xd2, 0xfe, 0x5c, 0x9d, 0xcd, 0x13, 0x1d, 0x1f,
+ 0x63, 0x38, 0xf1, 0xf7, 0x9d, 0xb1, 0x84, 0x71,
+ 0x72, 0x7a, 0x10, 0xfc, 0x80, 0x04, 0x7e, 0xdb,
+ 0x0f
+ };
+ static const u8 Header_p_sts[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0xa0, 0x3b,
+ 0x1f, 0x04
+ };
+ static const u8 HeaderRef_p_sts[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03
+ };
+ /* Decrypt Frame */
+ static const u8 Header_RCM_p_sts_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03, 0x00, 0x3f, 0x82, 0x76, 0xe0, 0x44,
+ 0xf3, 0x78, 0xab, 0xbe, 0xd2, 0x39, 0x86, 0x7e,
+ 0xd2, 0xfe, 0x5c, 0x9d, 0xcd, 0x13, 0x1d, 0x1f,
+ 0x63, 0x38, 0xf1, 0xf7, 0x9d, 0xb1, 0x84, 0x71,
+ 0x72, 0x7a, 0x10, 0xfc, 0x80, 0x04, 0x7e, 0xdb,
+ 0x0f
+ };
+ static const u8 RCMRef_p_sts_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18,
+ 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55,
+ 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55,
+ 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55,
+ 0x0b
+ };
+ static const u8 Header_p_sts_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb,
+ 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e,
+ 0x4e, 0x03
+ };
+ static const u8 HeaderRef_p_sts_Rcv[] = {
+ 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff,
+ 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0xa0, 0x3b,
+ 0x1f, 0x04
+ };
+ int err = -1, r;
+ struct fira_crypto *fira_crypto_ctx;
+ u32 crypto_sts_index_p_sts = 0;
+ u8 sts_v[16];
+ u8 sts_key[16];
+ struct sk_buff *skb = NULL;
+
+ r = fira_crypto_context_init(&param_p_sts, &fira_crypto_ctx);
+ if (r != 0 || !fira_crypto_ctx) {
+ pr_err("fira_crypto_context_init fail: %d\n", r);
+ goto end;
+ }
+
+ if (!compare_bufs(configDigest_p_sts, sizeof(configDigest_p_sts),
+ fira_crypto_ctx->base.config_digest,
+ sizeof(fira_crypto_ctx->base.config_digest))) {
+ pr_err("compare configDigest_p_sts fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(secDataPrivacyKey_p_sts,
+ sizeof(secDataPrivacyKey_p_sts),
+ fira_crypto_ctx->privacy_key,
+ sizeof(fira_crypto_ctx->privacy_key))) {
+ pr_err("compare secDataPrivacyKey_p_sts fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(secDataProtectionKey_p_sts,
+ sizeof(secDataProtectionKey_p_sts),
+ fira_crypto_ctx->base.data_protection_key,
+ sizeof(fira_crypto_ctx->base.data_protection_key))) {
+ pr_err("compare secDataProtectionKey fail\n");
+ goto end;
+ }
+
+ r = fira_crypto_build_phy_sts_index_init(fira_crypto_ctx);
+ if (r != 0) {
+ pr_err("fira_crypto_build_phy_sts_index_init fail: %d\n", r);
+ goto end;
+ }
+ if (fira_crypto_ctx->phy_sts_index_init != 0x041f3ba0) {
+ pr_err("phy_sts_index_init fail\n");
+ goto end;
+ }
+
+ r = fira_crypto_rotate_elements(fira_crypto_ctx,
+ fira_crypto_ctx->phy_sts_index_init);
+
+ if (r != 0) {
+ pr_err("fira_crypto_rotate_elements fail: %d\n", r);
+ goto end;
+ }
+
+ if (!compare_bufs(derived_authentication_iv_p_sts,
+ sizeof(derived_authentication_iv_p_sts),
+ fira_crypto_ctx->base.derived_authentication_iv,
+ sizeof(fira_crypto_ctx->base.derived_authentication_iv))) {
+ pr_err("compare derived_authentication_iv_p_sts fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(derived_authentication_key_p_sts,
+ sizeof(derived_authentication_key_p_sts),
+ fira_crypto_ctx->base.derived_authentication_key,
+ sizeof(fira_crypto_ctx->base.derived_authentication_key))) {
+ pr_err("compare derived_authentication_key_p_sts fail\n");
+ goto end;
+ }
+
+ if (!compare_bufs(derived_payload_key_p_sts,
+ sizeof(derived_payload_key_p_sts),
+ fira_crypto_ctx->base.derived_payload_key,
+ sizeof(fira_crypto_ctx->base.derived_payload_key))) {
+ pr_err("compare derived_payload_key fail\n");
+ goto end;
+ }
+
+ /* Return STS parameters slot 0 */
+ crypto_sts_index_p_sts = 0x041f3ba0;
+ r = fira_crypto_get_sts_params(fira_crypto_ctx, crypto_sts_index_p_sts, sts_v,
+ sizeof(sts_v), sts_key, sizeof(sts_key));
+ if (r != 0) {
+ pr_err("fira_crypto_get_sts_params fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(derived_authentication_key_p_sts,
+ sizeof(derived_authentication_key_p_sts),
+ sts_key, sizeof(sts_key))) {
+ pr_err("compare sts_key Fail\n");
+ goto end;
+ }
+ if (!compare_bufs(sts_v_ref_p_sts, sizeof(sts_v_ref_p_sts), sts_v,
+ sizeof(sts_v))) {
+ pr_err("compare sts_v_ref_p_sts Fail\n");
+ goto end;
+ }
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Header_p_sts, sizeof(Header_p_sts));
+
+ /* Encrypt Header first */
+ r = fira_crypto_encrypt_hie(fira_crypto_ctx, skb, 5, 16);
+ if (r != 0) {
+ pr_err("fira_crypto_encrypt_hie fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(HeaderRef_p_sts, sizeof(HeaderRef_p_sts), skb->data,
+ skb->len)) {
+ pr_err("compare HeaderRef_p_sts fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, RCM_p_sts, sizeof(RCM_p_sts));
+
+ r = fira_crypto_encrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0x041f3ba0);
+
+ if (r != 0) {
+ pr_err("fira_crypto_encrypt_frame fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(RCMRef_p_sts, sizeof(RCMRef_p_sts), skb->data,
+ skb->len)) {
+ pr_err("compare RCMRef_p_sts fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Header_RCM_p_sts_Rcv, sizeof(Header_RCM_p_sts_Rcv));
+ skb_pull(skb, 28); /* skip header */
+
+ skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE);
+ r = fira_crypto_decrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0x041f3ba0);
+ if (r != 0) {
+ pr_err("fira_crypto_decrypt_frame fail: %d\n", r);
+ goto end;
+ }
+
+ skb_push(skb, 28); /* restore header */
+
+ if (!compare_bufs(RCMRef_p_sts_Rcv, sizeof(RCMRef_p_sts_Rcv), skb->data,
+ skb->len)) {
+ pr_err("compare RCMRef_p_sts_Rcv fail\n");
+ goto end;
+ }
+
+ kfree_skb(skb);
+
+ skb = alloc_skb(128, GFP_KERNEL);
+ if (!skb) {
+ pr_err("cannot allocate skb\n");
+ goto end;
+ }
+
+ skb_put_data(skb, Header_p_sts_Rcv, sizeof(Header_p_sts_Rcv));
+
+ /* Decrypt header */
+ r = fira_crypto_decrypt_hie(fira_crypto_ctx, skb, 10, 16);
+ if (r != 0) {
+ pr_err("fira_crypto_decrypt_hie fail: %d\n", r);
+ goto end;
+ }
+ if (!compare_bufs(HeaderRef_p_sts_Rcv, sizeof(HeaderRef_p_sts_Rcv),
+ skb->data, skb->len)) {
+ pr_err("compare HeaderRef_p_sts_Rcv fail\n");
+ goto end;
+ }
+
+ err = 0;
+
+ pr_info("Provisioned STS tests success\n");
+
+end:
+ if (skb)
+ kfree_skb(skb);
+ if (fira_crypto_ctx)
+ fira_crypto_context_deinit(fira_crypto_ctx);
+
+ return err;
+}
+
+/**
+ * fira_crypto_test() - Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS
+ * version 1.3.0 test vectors for Static STS and Dynamic STS (Provisioned STS is
+ * ran instead of pure dynnamic)
+ *
+ *
+ * NOTE: This APis used for unit tests only.
+ *
+ * Return: 0 if ok,
+ */
+int fira_crypto_test(void)
+{
+ int r = 0;
+
+ r = fira_crypto_test_static() || r;
+ r = fira_crypto_test_provisioned() || r;
+
+ return r ? -1 : 0;
+}
diff --git a/mac/fira_crypto.h b/mac/fira_crypto.h
new file mode 100644
index 0000000..e89a1e2
--- /dev/null
+++ b/mac/fira_crypto.h
@@ -0,0 +1,260 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_CRYPTO_H
+#define NET_MCPS802154_FIRA_CRYPTO_H
+
+#include <linux/skbuff.h>
+#include <linux/kernel.h>
+
+#include <linux/errno.h>
+#include <asm/unaligned.h>
+#include <linux/string.h>
+#include <linux/ieee802154.h>
+#include <linux/printk.h>
+#include <net/fira_region_params.h>
+
+struct fira_crypto;
+
+/**
+ * fira_crypto_init() - Callback to initialize crypto module implementation.
+ *
+ * @key_manager: Handler to key manager.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_init(void *key_manager);
+
+/**
+ * struct fira_crypto_params - Arguments grouping structure for the crypto context
+ * initialization function.
+ */
+struct fira_crypto_params {
+ /**
+ * @session_id: Id of the session using the current fira_crypto.
+ */
+ u32 session_id;
+ /**
+ * @sub_session_id: Id of the sub-session using the current fira_crypto.
+ * It is only used in case of Responder Specific Sub-session Key STS mode 0x04.
+ */
+ u32 sub_session_id;
+ /**
+ * @sts_config: The type of STS requested for this crypto.
+ */
+ enum fira_sts_mode sts_config;
+ /**
+ * @concat_params: The concatenated parameters of the session/sub-session according
+ * to the FiRa specs.
+ */
+ const u8 *concat_params;
+ /**
+ * @concat_params_size: The size of the concatenated parameters.
+ */
+ int concat_params_size;
+ /**
+ * @vupper64: The vupper 64 to use when static STS is used.
+ */
+ const u8 *vupper64;
+ /**
+ * @key: The key when provisioned STS is used.
+ */
+ const u8 *key;
+ /**
+ * @key_len: The length of the key when provisioned STS is used.
+ */
+ u8 key_len;
+};
+
+/**
+ * fira_crypto_get_capabilities() - Query FiRa STS capabilities
+ *
+ * Return: FiRa crypto backend capabilities as a bitfield
+ * (see &enum fira_sts_mode).
+ */
+u32 fira_crypto_get_capabilities(void);
+
+/**
+ * fira_crypto_context_init() - Initialize a crypto context containing the crypto
+ * elements for a session or sub-session.
+ * @crypto_params: Parameters to initialize the crypto context.
+ * @crypto: The initialized crypto context.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_context_init(const struct fira_crypto_params *crypto_params,
+ struct fira_crypto **crypto);
+
+/**
+ * fira_crypto_context_deinit() - Deinitialize a crypto context.
+ * @crypto: The crypto context to deinitialize.
+ */
+void fira_crypto_context_deinit(struct fira_crypto *crypto);
+
+/**
+ * fira_crypto_rotate_elements() - Rotate the crypto elements contained in the
+ * crypto context.
+ *
+ * NOTE: After calling this function, all active crypto elements will be the latest
+ * rotated ones.
+ *
+ * @crypto: The context containing the elements to rotate.
+ * @crypto_sts_index: The crypto STS index to use to rotate the elements.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_rotate_elements(struct fira_crypto *crypto,
+ const u32 crypto_sts_index);
+
+/**
+ * fira_crypto_build_phy_sts_index_init() - Build the phy STS index init value
+ * related to the given crypto context.
+ *
+ * @crypto: The context to use to compute the phy STS index init value.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_build_phy_sts_index_init(struct fira_crypto *crypto);
+
+/**
+ * fira_crypto_get_phy_sts_index_init() - Get phy_sts_index_init related to the current crypto context.
+ * @crypto: Context storing the crypto-related parameters.
+ *
+ * Return: phy_sts_index_init deduced at crypto context initialization.
+ */
+u32 fira_crypto_get_phy_sts_index_init(const struct fira_crypto *crypto);
+
+/**
+ * fira_crypto_get_sts_params() - Build and get the STS parameters according to
+ * a specific crypto context.
+ *
+ * NOTE: The elements built are the STS value and the STS key. Their construction
+ * depends on the STS config and is described in the FiRa MAC specification.
+ *
+ * @crypto: The context to use to build the STS parameters.
+ * @crypto_sts_index: The crypto STS index to use to build the STS parameters.
+ * @sts_v: The output buffer for STS V.
+ * @sts_v_size: The size of the output buffer for STS V.
+ * @sts_key: The output buffer for STS key.
+ * @sts_key_size: The size of the output buffer for STS key.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_get_sts_params(struct fira_crypto *crypto, u32 crypto_sts_index,
+ u8 *sts_v, u32 sts_v_size, u8 *sts_key,
+ u32 sts_key_size);
+
+/**
+ * fira_crypto_prepare_decrypt() - Prepare skb for header decryption and verification.
+ * @crypto: The crypto context used to decrypt the frame.
+ * @skb: Buffer containing the frame to decrypt.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_prepare_decrypt(struct fira_crypto *crypto,
+ struct sk_buff *skb);
+
+/**
+ * fira_crypto_encrypt_frame() - Encrypt a 802154 frame using a given context.
+ *
+ * NOTE: The src address is given as an argument as it is a part of the nonce needed
+ * to encrypt the frame and it is not present in the 802154 frame.
+ * The length of the header is given because only the payload is encrypted even if
+ * the encryption algorithm needs the whole 802154 frame.
+ * Encryption is done in-place.
+ * When called this function shall increase the size of the skb of
+ * FIRA_CRYPTO_AEAD_AUTHSIZE and set the correct bits in the 802154 frame SCF.
+ *
+ * @crypto: The context to use to encrypt the frame.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of
+ * the 802154 frame header.
+ * @header_len: The length of the 802154 frame header. Can be used to find the
+ * position of the 802154 frame payload relative to skb->data.
+ * @src_short_addr: The short source address attached to the frame.
+ * @crypto_sts_index: The crypto STS index attached to the frame.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_encrypt_frame(struct fira_crypto *crypto, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr,
+ u32 crypto_sts_index);
+
+/**
+ * fira_crypto_decrypt_frame() - Decrypt a 802.15.4 frame using the given context.
+ *
+ * NOTE: The src address is given as an argument as it is a part of the nonce needed to perform the
+ * decryption and it is not present in the 802.15.4 frame. The length of the header is given as
+ * only the payload should be decrypted but the algorithm needs the whole 802.15.4 frame.
+ * Decryption is performed in-place. When calling this function, it shall decrease the size of the
+ * skb in FIRA_CRYPTO_AEAD_AUTHSIZE bytes and verify the correct bits in the 802.15.4 frame SCF.
+ *
+ * @crypto: The context used for frame decryption.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of the 802.15.4
+ * frame payload.
+ * @header_len: The length of the 802.15.4 frame header. Can be used to find the start of the
+ * 802.15.4 frame payload relative to skb->data.
+ * @src_short_addr: The short source address in the current frame.
+ * @crypto_sts_index: The current crypto STS index.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_decrypt_frame(struct fira_crypto *crypto, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr,
+ u32 crypto_sts_index);
+
+/**
+ * fira_crypto_encrypt_hie() - Encrypt a 802154 header using a given context.
+ *
+ * @crypto: The crypto to use to encrypt the frame.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of
+ * the 802154 frame header.
+ * @hie_offset: Offset of the FiRa HIE relative to skb->data.
+ * @hie_len: The length of the FiRa HIE.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_encrypt_hie(struct fira_crypto *crypto, struct sk_buff *skb,
+ int hie_offset, int hie_len);
+
+/**
+ * fira_crypto_decrypt_hie() - Decrypt a 802154 header using a given context.
+ *
+ * @crypto: The crypto to use to encrypt the frame.
+ * @skb: The buffer containing the whole frame, skb->data points to the start of
+ * the 802154 frame payload.
+ * @hie_offset: Offset of the FiRa HIE relative to skb->data.
+ * @hie_len: The length of 802154 header.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_decrypt_hie(struct fira_crypto *crypto, struct sk_buff *skb,
+ int hie_offset, int hie_len);
+
+/**
+ * fira_crypto_test() - Autotest for FiRa crypto.
+ *
+ * Return: 0 or error.
+ */
+int fira_crypto_test(void);
+
+#endif /* NET_MCPS802154_FIRA_CRYPTO_H */
diff --git a/mac/fira_frame.c b/mac/fira_frame.c
new file mode 100644
index 0000000..ffb27f8
--- /dev/null
+++ b/mac/fira_frame.c
@@ -0,0 +1,965 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "fira_frame.h"
+#include "fira_session.h"
+#include "fira_crypto.h"
+#include "fira_trace.h"
+
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/math64.h>
+#include <net/af_ieee802154.h>
+#include <net/mcps802154_frame.h>
+
+#include "warn_return.h"
+
+bool fira_frame_check_n_controlees(const struct fira_session *session,
+ size_t n_controlees, bool active)
+{
+ /*
+ * TODO: use more parameters (embedded mode, ranging mode, device
+ * type...) to calculate the size of frames.
+ * Currently only SS-TWR vs DS-TWR mode is considered.
+ * The computation MUST stay "pessimistic" (aka strict).
+ * E.g.: for control frame, each new controlee consumes 8 bytes so
+ * we need AT LEAST 8 * n_controlee bytes of "free space".
+ */
+ const struct fira_session_params *params = &session->params;
+ size_t mrm_size, rcm_size;
+ size_t n_msg_controller;
+ size_t n_msg_controlee = 2;
+
+ if (n_controlees > FIRA_CONTROLEES_MAX)
+ return false;
+ if (!active)
+ return true;
+
+ if (params->ranging_round_usage == FIRA_RANGING_ROUND_USAGE_DSTWR) {
+ mrm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN +
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(
+ 1, n_controlees);
+ n_msg_controller = 4;
+ } else {
+ mrm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN +
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN(
+ 1, 0, n_controlees);
+ n_msg_controller = 3;
+ }
+
+ rcm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN +
+ FIRA_IE_PAYLOAD_CONTROL_LEN(n_msg_controller +
+ n_msg_controlee * n_controlees);
+
+ return mrm_size <= IEEE802154_MTU && rcm_size <= IEEE802154_MTU;
+}
+
+void fira_frame_header_put(const struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb)
+{
+ const struct fira_session *session = local->current_session;
+ u16 fc = (IEEE802154_FC_TYPE_DATA | IEEE802154_FC_SECEN |
+ IEEE802154_FC_INTRA_PAN | IEEE802154_FC_NO_SEQ |
+ (IEEE802154_ADDR_SHORT << IEEE802154_FC_DAMODE_SHIFT) |
+ (2 << IEEE802154_FC_VERSION_SHIFT) |
+ (IEEE802154_ADDR_NONE << IEEE802154_FC_SAMODE_SHIFT));
+ u8 *p;
+ int i;
+ u8 *p_hie;
+
+ p = skb_put(skb, IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN +
+ IEEE802154_SCF_LEN);
+ put_unaligned_le16(fc, p);
+ p += IEEE802154_FC_LEN;
+ put_unaligned_le16(local->dst_short_addr, p);
+ p += IEEE802154_SHORT_ADDR_LEN;
+ *p = IEEE802154_SCF_NO_FRAME_COUNTER;
+
+ p_hie = skb->data + skb->len;
+ mcps802154_ie_put_begin(skb);
+ p = mcps802154_ie_put_header_ie(skb, IEEE802154_IE_HEADER_VENDOR_ID,
+ FIRA_IE_HEADER_LEN);
+ put_unaligned_le24(FIRA_IE_VENDOR_OUI, p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ for (i = 0; i < FIRA_IE_HEADER_PADDING_LEN; i++)
+ *p++ = FIRA_IE_HEADER_PADDING;
+ put_unaligned_le32(session->id, p);
+ p += FIRA_IE_HEADER_SESSION_ID_LEN;
+ put_unaligned_le32(fira_sts_get_phy_sts_index(session, slot), p);
+ fira_sts_encrypt_hie(local->current_session, slot, skb, p_hie - skb->data,
+ FIRA_IE_HEADER_LEN + IEEE802154_IE_HEADER_LEN);
+}
+
+static u8 *fira_frame_common_payload_put(struct sk_buff *skb, unsigned int len,
+ enum fira_message_id message_id)
+{
+ u8 *p;
+
+ p = mcps802154_ie_put_payload_ie(skb, IEEE802154_IE_PAYLOAD_VENDOR_GID,
+ len);
+ WARN_RETURN_ON(!p, NULL);
+
+ put_unaligned_le24(FIRA_IE_VENDOR_OUI, p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ *p++ = message_id;
+
+ return p;
+}
+
+void fira_frame_control_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb)
+{
+ const struct fira_session *session = local->current_session;
+ int n_mngt;
+ u8 *p;
+ int i;
+
+ n_mngt = local->access.n_frames - 1 + local->n_stopped_controlees;
+
+ p = fira_frame_common_payload_put(skb,
+ FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt),
+ FIRA_MESSAGE_ID_CONTROL);
+
+ *p++ = n_mngt;
+ *p++ = 0;
+ *p++ = session->block_stride_len;
+
+ for (i = 0; i < local->access.n_frames - 1; i++) {
+ const struct fira_slot *slot = &local->slots[i + 1];
+ int initiator = slot->controller_tx;
+ int slot_index = slot->index;
+ __le16 short_addr = slot->controller_tx ?
+ local->src_short_addr :
+ slot->controlee->short_addr;
+ int message_id = slot->message_id;
+ u32 mngt = FIELD_PREP(FIRA_MNGT_RANGING_ROLE, initiator) |
+ FIELD_PREP(FIRA_MNGT_SLOT_INDEX, slot_index) |
+ FIELD_PREP(FIRA_MNGT_SHORT_ADDR, short_addr) |
+ FIELD_PREP(FIRA_MNGT_MESSAGE_ID, message_id);
+ put_unaligned_le32(mngt, p);
+ p += sizeof(u32);
+ }
+
+ for (i = 0; i < local->n_stopped_controlees; i++) {
+ __le16 short_addr = local->stopped_controlees[i];
+ u32 mngt = FIELD_PREP(FIRA_MNGT_SHORT_ADDR, short_addr) |
+ FIELD_PREP(FIRA_MNGT_STOP, 1);
+ put_unaligned_le32(mngt, p);
+ p += sizeof(u32);
+ }
+}
+
+void fira_frame_measurement_report_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb)
+{
+ const struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ const struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ u8 *p;
+ int hopping_mode = params->round_hopping;
+ int round_index_present = 1;
+ int reply_time_present = 0; /* for initiator */
+ int n_reply_time = local->n_ranging_valid;
+ int i;
+ u32 first_round_trip_time;
+ u32 reply_time;
+ u64 initiation_rctu, response_rctu, final_rctu;
+ bool double_sided = params->ranging_round_usage ==
+ FIRA_RANGING_ROUND_USAGE_DSTWR;
+
+ p = fira_frame_common_payload_put(
+ skb,
+ (double_sided ? FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(
+ round_index_present, n_reply_time) :
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN(
+ round_index_present, reply_time_present,
+ n_reply_time)),
+ FIRA_MESSAGE_ID_MEASUREMENT_REPORT);
+
+ *p++ = FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE,
+ hopping_mode) |
+ FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT,
+ round_index_present) |
+ FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME,
+ n_reply_time);
+
+ if (!double_sided)
+ *p++ = FIELD_PREP(
+ FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT,
+ reply_time_present);
+
+ put_unaligned_le16(session->next_round_index, p);
+ p += sizeof(u16);
+
+ /*
+ * No handling for failed measurement, as there is only one, a failed
+ * measurement will cancel the ranging round.
+ * With several measurements, make sure a later measurement can still be
+ * done if an earlier one is failed.
+ */
+ initiation_rctu =
+ ranging_info
+ ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_INITIATION];
+ final_rctu =
+ ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_FINAL];
+
+ /* Retrieve first measurement. */
+ for (i = 0; i < local->n_ranging_info; i++) {
+ ranging_info = &local->ranging_info[i];
+ if (!ranging_info->status)
+ break;
+ }
+ response_rctu =
+ ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_RESPONSE];
+ if (double_sided) {
+ /* Add first round trip measurement. */
+ first_round_trip_time = mcps802154_difference_timestamp_rctu(
+ local->llhw, response_rctu, initiation_rctu);
+ put_unaligned_le32(first_round_trip_time, p);
+ p += sizeof(u32);
+ }
+ /* Retrieve reply measurement. */
+ for (; i < local->n_ranging_info; i++) {
+ ranging_info = &local->ranging_info[i];
+ if (ranging_info->status)
+ continue;
+ put_unaligned_le16(ranging_info->short_addr, p);
+ p += sizeof(u16);
+ response_rctu = ranging_info->timestamps_rctu
+ [FIRA_MESSAGE_ID_RANGING_RESPONSE];
+ if (double_sided) {
+ reply_time = mcps802154_difference_timestamp_rctu(
+ local->llhw, final_rctu, response_rctu);
+ } else {
+ reply_time = mcps802154_difference_timestamp_rctu(
+ local->llhw, response_rctu, initiation_rctu);
+ }
+ put_unaligned_le32(reply_time, p);
+ p += sizeof(u32);
+ }
+}
+
+void fira_frame_result_report_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb)
+{
+ const struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ const struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ bool tof_present, aoa_azimuth_present, aoa_elevation_present,
+ aoa_fom_present, neg_tof_present;
+ u8 *p;
+
+ tof_present = ranging_info->tof_present && params->report_tof;
+ aoa_azimuth_present = ranging_info->local_aoa_azimuth.present &&
+ params->report_aoa_azimuth;
+ aoa_elevation_present = ranging_info->local_aoa_elevation.present &&
+ params->report_aoa_elevation;
+ aoa_fom_present = (ranging_info->local_aoa_azimuth.aoa_fom ||
+ ranging_info->local_aoa_elevation.aoa_fom) &&
+ params->report_aoa_fom;
+ neg_tof_present = tof_present && (ranging_info->tof_rctu < 0);
+ p = fira_frame_common_payload_put(
+ skb,
+ FIRA_IE_PAYLOAD_RESULT_REPORT_LEN(
+ tof_present, aoa_azimuth_present, aoa_elevation_present,
+ aoa_fom_present, neg_tof_present),
+ FIRA_MESSAGE_ID_RESULT_REPORT);
+
+ *p++ = FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT, tof_present) |
+ FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT,
+ aoa_azimuth_present) |
+ FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT,
+ aoa_elevation_present) |
+ FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT,
+ aoa_fom_present) |
+ FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT,
+ neg_tof_present);
+
+ if (tof_present) {
+ put_unaligned_le32(
+ ranging_info->tof_rctu > 0 ? ranging_info->tof_rctu : 0,
+ p);
+ p += sizeof(u32);
+ }
+ if (aoa_azimuth_present) {
+ put_unaligned_le16(ranging_info->local_aoa_azimuth.aoa_2pi, p);
+ p += sizeof(u16);
+ if (aoa_fom_present) {
+ *p = ranging_info->local_aoa_azimuth.aoa_fom;
+ p++;
+ }
+ }
+ if (aoa_elevation_present) {
+ put_unaligned_le16(
+ ranging_info->local_aoa_elevation.aoa_2pi * 2, p);
+ p += sizeof(u16);
+ if (aoa_fom_present) {
+ *p = ranging_info->local_aoa_elevation.aoa_fom;
+ p++;
+ }
+ }
+ if (neg_tof_present) {
+ put_unaligned_le32(-ranging_info->tof_rctu, p);
+ p += sizeof(u32);
+ }
+}
+
+void fira_frame_rframe_payload_put(struct fira_local *local,
+ struct sk_buff *skb)
+{
+ struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ u8 *p;
+
+ if (session->data_payload.seq == params->data_payload_seq)
+ return;
+
+ p = mcps802154_ie_put_payload_ie(skb, IEEE802154_IE_PAYLOAD_VENDOR_GID,
+ FIRA_IE_VENDOR_OUI_LEN +
+ params->data_payload_len);
+ WARN_RETURN_VOID_ON(!p);
+ put_unaligned_le24(params->data_vendor_oui, p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ memcpy(p, params->data_payload, params->data_payload_len);
+ session->data_payload.seq = params->data_payload_seq;
+ session->data_payload.sent = true;
+}
+
+bool fira_frame_header_check(struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get,
+ u32 *phy_sts_index, u32 *session_id)
+{
+ struct fira_session *session = local->current_session;
+ u16 fc = (IEEE802154_FC_TYPE_DATA | IEEE802154_FC_SECEN |
+ IEEE802154_FC_INTRA_PAN | IEEE802154_FC_NO_SEQ |
+ IEEE802154_FC_IE_PRESENT |
+ (IEEE802154_ADDR_SHORT << IEEE802154_FC_DAMODE_SHIFT) |
+ (2 << IEEE802154_FC_VERSION_SHIFT) |
+ (IEEE802154_ADDR_NONE << IEEE802154_FC_SAMODE_SHIFT));
+ u8 ciphered_hie[FIRA_IE_HEADER_PADDING_LEN +
+ FIRA_IE_HEADER_SESSION_ID_LEN +
+ FIRA_IE_HEADER_STS_INDEX_LEN] = { 0 };
+ bool fira_header_seen = false;
+ int r;
+ u8 *p;
+
+ p = skb->data;
+ if (!skb_pull(skb, IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN +
+ IEEE802154_SCF_LEN) ||
+ get_unaligned_le16(p) != fc)
+ return false;
+
+ if (fira_sts_prepare_decrypt(session, slot, skb))
+ return false;
+
+ for (r = mcps802154_ie_get(skb, ie_get); r == 0 && !ie_get->in_payload;
+ r = mcps802154_ie_get(skb, ie_get)) {
+ p = skb->data;
+ ie_get->mlme_len = 0;
+
+ if (ie_get->id == IEEE802154_IE_HEADER_VENDOR_ID &&
+ ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) {
+ u32 vendor;
+
+ vendor = get_unaligned_le24(p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ if (vendor != FIRA_IE_VENDOR_OUI)
+ goto next;
+ if (fira_header_seen)
+ goto hie_error;
+ if (ie_get->len != FIRA_IE_HEADER_LEN)
+ goto hie_error;
+
+ memcpy(ciphered_hie, skb->data + FIRA_IE_VENDOR_OUI_LEN,
+ sizeof(ciphered_hie));
+ if (fira_sts_decrypt_hie(
+ session, slot, skb, FIRA_IE_VENDOR_OUI_LEN,
+ ie_get->len - FIRA_IE_VENDOR_OUI_LEN))
+ goto hie_error;
+ p += FIRA_IE_HEADER_PADDING_LEN;
+ *session_id = get_unaligned_le32(p);
+ p += FIRA_IE_HEADER_SESSION_ID_LEN;
+ *phy_sts_index = get_unaligned_le32(p);
+ p += FIRA_IE_HEADER_STS_INDEX_LEN;
+ fira_header_seen = true;
+ memcpy(skb->data + FIRA_IE_VENDOR_OUI_LEN, ciphered_hie,
+ ie_get->len - FIRA_IE_VENDOR_OUI_LEN);
+ memzero_explicit(ciphered_hie, sizeof(ciphered_hie));
+ }
+ next:
+ skb_pull(skb, ie_get->len);
+ }
+
+ return r >= 0 && fira_header_seen;
+
+hie_error:
+ skb_pull(skb, ie_get->len);
+ return false;
+}
+
+static u8 *fira_frame_twr_get_aoa_info(u8 *p,
+ struct fira_ranging_info *ranging_info,
+ bool aoa_azimuth_present,
+ bool aoa_elevation_present,
+ bool aoa_fom_present)
+{
+ if (aoa_azimuth_present) {
+ ranging_info->remote_aoa_azimuth_present = true;
+ ranging_info->remote_aoa_azimuth_2pi = get_unaligned_le16(p);
+ p += sizeof(s16);
+ }
+ if (aoa_elevation_present) {
+ ranging_info->remote_aoa_elevation_present = true;
+ ranging_info->remote_aoa_elevation_pi = get_unaligned_le16(p);
+ p += sizeof(s16);
+ }
+ if (aoa_fom_present) {
+ u8 aoa_azimuth_fom = 0, aoa_elevation_fom = 0;
+
+ ranging_info->remote_aoa_fom_present = true;
+ if (aoa_azimuth_present)
+ aoa_azimuth_fom = *p++;
+ if (aoa_elevation_present)
+ aoa_elevation_fom = *p++;
+ ranging_info->remote_aoa_azimuth_fom =
+ (aoa_azimuth_fom > 100) ? 0 : aoa_azimuth_fom;
+ ranging_info->remote_aoa_elevation_fom =
+ (aoa_elevation_fom > 100) ? 0 : aoa_elevation_fom;
+ }
+ return p;
+}
+
+static bool fira_frame_control_read(struct fira_local *local, u8 *p,
+ unsigned int ie_len, unsigned int *n_slots,
+ bool *stop, int *block_stride_len)
+{
+ const struct fira_session *session = local->current_session;
+ struct fira_slot *slot, last;
+ int n_mngt, i;
+ u16 msg_ids = 0;
+ bool stop_found = false;
+ const struct fira_measurement_sequence_step *step =
+ fira_session_get_meas_seq_step(session);
+
+ n_mngt = *p++;
+ if (ie_len < FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt))
+ return false;
+ p++;
+
+ *block_stride_len = *p++;
+
+ slot = local->slots;
+ last = *slot++;
+ for (i = 0; i < n_mngt; i++) {
+ u32 mngt;
+ bool initiator;
+ int slot_index;
+ __le16 short_addr;
+ enum fira_message_id message_id;
+ bool stop_ranging;
+ bool is_rframe;
+
+ mngt = get_unaligned_le32(p);
+ p += sizeof(u32);
+
+ initiator = !!(mngt & FIRA_MNGT_RANGING_ROLE);
+ slot_index = FIELD_GET(FIRA_MNGT_SLOT_INDEX, mngt);
+ short_addr = FIELD_GET(FIRA_MNGT_SHORT_ADDR, mngt);
+ message_id = FIELD_GET(FIRA_MNGT_MESSAGE_ID, mngt);
+ stop_ranging = !!(mngt & FIRA_MNGT_STOP);
+
+ is_rframe = message_id <= FIRA_MESSAGE_ID_RFRAME_MAX;
+ if (stop_ranging) {
+ if (short_addr == local->src_short_addr) {
+ stop_found = true;
+ }
+ continue;
+ }
+
+ if (slot_index <= last.index ||
+ slot_index >= session->params.round_duration_slots)
+ return false;
+ if (initiator && short_addr == local->src_short_addr)
+ return false;
+
+ last.index = slot_index;
+ if (message_id <= FIRA_MESSAGE_ID_MAX &&
+ (initiator || short_addr == local->src_short_addr)) {
+ u16 msg_id = 1 << message_id;
+ if (message_id == FIRA_MESSAGE_ID_CONTROL_UPDATE &&
+ !initiator)
+ msg_id <<= 1;
+ if (msg_id < msg_ids || msg_id & msg_ids)
+ return false;
+ msg_ids |= msg_id;
+ if (slot == local->slots + FIRA_CONTROLEE_FRAMES_MAX)
+ return false;
+ last.controller_tx = initiator;
+ last.ranging_index = 0;
+ last.message_id = message_id;
+ if (!initiator) {
+ last.tx_ant_set =
+ is_rframe ? step->tx_ant_set_ranging :
+ step->tx_ant_set_nonranging;
+ } else {
+ last.rx_ant_set = fira_session_get_rx_ant_set(
+ session, message_id);
+ }
+ *slot++ = last;
+ }
+ }
+ *stop = stop_found;
+ *n_slots = slot - local->slots;
+
+ return true;
+}
+
+bool fira_frame_control_payload_check(struct fira_local *local,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get,
+ unsigned int *n_slots, bool *stop_ranging,
+ int *block_stride_len)
+{
+ bool fira_payload_seen = false;
+ int r;
+ u8 *p;
+
+ for (r = mcps802154_ie_get(skb, ie_get); r == 0;
+ r = mcps802154_ie_get(skb, ie_get)) {
+ p = skb->data;
+ skb_pull(skb, ie_get->len);
+
+ if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID &&
+ ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) {
+ u32 vendor;
+ int message_id;
+
+ vendor = get_unaligned_le24(p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ if (vendor != FIRA_IE_VENDOR_OUI)
+ continue;
+
+ if (ie_get->len < FIRA_IE_PAYLOAD_CONTROL_LEN(0))
+ return false;
+ message_id = (*p++) & 0xf;
+ if (message_id != FIRA_MESSAGE_ID_CONTROL)
+ return false;
+
+ if (fira_payload_seen)
+ return false;
+
+ if (!fira_frame_control_read(local, p, ie_get->len,
+ n_slots, stop_ranging,
+ block_stride_len))
+ return false;
+
+ fira_payload_seen = true;
+ }
+ }
+
+ return r >= 0 && fira_payload_seen;
+}
+
+static bool
+fira_frame_measurement_report_fill_ranging_info(struct fira_local *local,
+ const struct fira_slot *slot,
+ u8 *p, unsigned int ie_len)
+{
+ struct fira_session *session = local->current_session;
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ u8 control;
+ bool hopping_mode, round_index_present, reply_time_present;
+ unsigned int n_reply_time;
+ u32 remote_round_trip_rctu, remote_reply_rctu = 0;
+ u64 rx_initiation_rctu, tx_response_rctu, rx_final_rctu;
+ u32 local_round_trip_rctu, local_reply_rctu;
+ int tof_rctu, i;
+ bool double_sided = session->params.ranging_round_usage ==
+ FIRA_RANGING_ROUND_USAGE_DSTWR;
+ control = *p++;
+ hopping_mode = FIELD_GET(FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE,
+ control);
+ round_index_present = FIELD_GET(
+ FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT, control);
+ n_reply_time = FIELD_GET(FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME,
+ control);
+
+ if (!double_sided) {
+ control = *p++;
+ /* Is reply time present? Not supported. */
+ reply_time_present = FIELD_GET(
+ FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT,
+ control);
+ if (reply_time_present) {
+ trace_fira_nondeferred_not_supported(session);
+ return false;
+ }
+ }
+
+ if (ie_len < (double_sided ?
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(
+ round_index_present, n_reply_time) :
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN(
+ round_index_present, reply_time_present,
+ n_reply_time)))
+ return false;
+
+ if (round_index_present) {
+ int next_round_index;
+
+ next_round_index = get_unaligned_le16(p);
+ p += sizeof(u16);
+
+ session->controlee.next_round_index_valid = true;
+ session->next_round_index = next_round_index;
+ }
+
+ if (double_sided) {
+ /* Remote_round_trip = first_round_trip + first_reply - my_reply. */
+ remote_round_trip_rctu = get_unaligned_le32(p);
+ p += sizeof(u32);
+ /* Add first_reply. */
+ remote_round_trip_rctu += get_unaligned_le32(p + sizeof(u16));
+ }
+
+ for (i = 0; i < n_reply_time; i++) {
+ __le16 short_addr = get_unaligned_le16(p);
+ p += sizeof(u16);
+ if (local->src_short_addr == short_addr) {
+ remote_reply_rctu = get_unaligned_le32(p);
+ break;
+ }
+ p += sizeof(u32);
+ }
+ /* Reply time not found. */
+ if (i == n_reply_time)
+ return false;
+ if (double_sided)
+ /* Substract my_reply. */
+ remote_round_trip_rctu -= remote_reply_rctu;
+ else
+ remote_round_trip_rctu = remote_reply_rctu;
+
+ rx_initiation_rctu =
+ ranging_info
+ ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_INITIATION];
+ tx_response_rctu =
+ ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_RESPONSE];
+ local_reply_rctu = mcps802154_difference_timestamp_rctu(
+ local->llhw, tx_response_rctu, rx_initiation_rctu);
+
+ if (double_sided) {
+ rx_final_rctu =
+ ranging_info
+ ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_FINAL];
+ local_round_trip_rctu = mcps802154_difference_timestamp_rctu(
+ local->llhw, rx_final_rctu, tx_response_rctu);
+ tof_rctu = div64_s64(
+ (s64)remote_round_trip_rctu * local_round_trip_rctu -
+ (s64)remote_reply_rctu * local_reply_rctu,
+ (s64)remote_round_trip_rctu + local_round_trip_rctu +
+ remote_reply_rctu + local_reply_rctu);
+ } else {
+ static const s32 Q26 = 1 << 26;
+ s32 adjusted_reply_rctu =
+ (ranging_info->clock_offset_present) ?
+ (((u64)local_reply_rctu * Q26) /
+ (Q26 - ranging_info->clock_offset_q26)) :
+ local_reply_rctu;
+ tof_rctu =
+ ((s32)remote_round_trip_rctu - adjusted_reply_rctu) / 2;
+ }
+ ranging_info->tof_rctu = (!slot->controller_tx) ? -tof_rctu : tof_rctu;
+ ranging_info->tof_present = true;
+ session->controlee.hopping_mode = hopping_mode;
+ return true;
+}
+
+bool fira_frame_measurement_report_payload_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get)
+{
+ const struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ bool fira_payload_seen = false;
+ unsigned int minimum_payload_len;
+ int r;
+ u8 *p;
+
+ if (params->ranging_round_usage == FIRA_RANGING_ROUND_USAGE_DSTWR)
+ minimum_payload_len =
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(false, 0);
+ else
+ minimum_payload_len =
+ FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN(false,
+ false, 0);
+
+ for (r = mcps802154_ie_get(skb, ie_get); r == 0;
+ r = mcps802154_ie_get(skb, ie_get)) {
+ p = skb->data;
+ skb_pull(skb, ie_get->len);
+
+ if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID &&
+ ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) {
+ u32 vendor;
+ int message_id;
+
+ vendor = get_unaligned_le24(p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ if (vendor != FIRA_IE_VENDOR_OUI)
+ continue;
+
+ if (ie_get->len < minimum_payload_len)
+ return false;
+ message_id = (*p++) & 0xf;
+ if (message_id != FIRA_MESSAGE_ID_MEASUREMENT_REPORT)
+ return false;
+
+ if (fira_payload_seen)
+ return false;
+
+ if (!fira_frame_measurement_report_fill_ranging_info(
+ local, slot, p, ie_get->len))
+ return false;
+
+ fira_payload_seen = true;
+ }
+ }
+
+ return r >= 0 && fira_payload_seen;
+}
+
+static bool
+fira_frame_result_report_fill_ranging_info(struct fira_local *local,
+ const struct fira_slot *slot, u8 *p,
+ unsigned int ie_len)
+{
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ u8 control;
+ bool tof_present, neg_tof_present, aoa_azimuth_present, aoa_elevation_present,
+ aoa_fom_present;
+
+ control = *p++;
+ tof_present = !!(control & FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT);
+ aoa_azimuth_present =
+ !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT);
+ aoa_elevation_present =
+ !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT);
+ aoa_fom_present =
+ !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT);
+ neg_tof_present = !!(control & FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT);
+ if (ie_len < FIRA_IE_PAYLOAD_RESULT_REPORT_LEN(
+ tof_present, aoa_azimuth_present,
+ aoa_elevation_present, aoa_fom_present, neg_tof_present))
+ return false;
+
+ if (tof_present) {
+ ranging_info->tof_present = true;
+ ranging_info->tof_rctu = get_unaligned_le32(p);
+ p += sizeof(u32);
+ }
+ p = fira_frame_twr_get_aoa_info(p, ranging_info, aoa_azimuth_present,
+ aoa_elevation_present, aoa_fom_present);
+ if (neg_tof_present) {
+ /* When negative ToF is present at end of frame,
+ * ToF read ahead MUST be 0, so, is safe to overwrite */
+ ranging_info->tof_rctu = -get_unaligned_le32(p);
+ p += sizeof(u32);
+ }
+
+ return true;
+}
+
+bool fira_frame_result_report_payload_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get)
+{
+ bool fira_payload_seen = false;
+ int r;
+ u8 *p;
+
+ for (r = mcps802154_ie_get(skb, ie_get); r == 0;
+ r = mcps802154_ie_get(skb, ie_get)) {
+ p = skb->data;
+ skb_pull(skb, ie_get->len);
+
+ if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID &&
+ ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) {
+ u32 vendor;
+ int message_id;
+
+ vendor = get_unaligned_le24(p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ if (vendor != FIRA_IE_VENDOR_OUI)
+ continue;
+
+ if (ie_get->len < FIRA_IE_PAYLOAD_RESULT_REPORT_LEN(
+ false, false, false, false, false))
+ return false;
+ message_id = (*p++) & 0xf;
+ if (message_id != FIRA_MESSAGE_ID_RESULT_REPORT)
+ return false;
+
+ if (fira_payload_seen)
+ return false;
+
+ if (!fira_frame_result_report_fill_ranging_info(
+ local, slot, p, ie_get->len))
+ return false;
+
+ fira_payload_seen = true;
+ }
+ }
+
+ return r >= 0 && fira_payload_seen;
+}
+
+bool fira_frame_rframe_payload_check(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get)
+{
+ const struct fira_session *session = local->current_session;
+ const struct fira_session_params *params = &session->params;
+ struct fira_ranging_info *ranging_info =
+ &local->ranging_info[slot->ranging_index];
+ bool rframe_payload_seen = false;
+ int r;
+ u8 *p;
+
+ for (r = mcps802154_ie_get(skb, ie_get); r == 0;
+ r = mcps802154_ie_get(skb, ie_get)) {
+ p = skb->data;
+ skb_pull(skb, ie_get->len);
+
+ if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID &&
+ ie_get->len >= FIRA_IE_VENDOR_OUI_LEN &&
+ ie_get->len <= FIRA_IE_VENDOR_OUI_LEN + FIRA_DATA_PAYLOAD_SIZE_MAX) {
+ u32 vendor;
+ unsigned int data_len;
+
+ vendor = get_unaligned_le24(p);
+ p += FIRA_IE_VENDOR_OUI_LEN;
+ if (vendor != params->data_vendor_oui)
+ continue;
+
+ if (ie_get->len < FIRA_IE_VENDOR_OUI_LEN + 1)
+ continue;
+
+ if (rframe_payload_seen)
+ return false;
+
+ data_len = ie_get->len - FIRA_IE_VENDOR_OUI_LEN;
+ memcpy(&ranging_info->data_payload, p, data_len);
+ ranging_info->data_payload_len = data_len;
+
+ rframe_payload_seen = true;
+ }
+ }
+
+ return r >= 0;
+}
+
+struct fira_session *fira_rx_frame_control_header_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get,
+ u32 *phy_sts_index)
+{
+ const struct fira_session *session = local->current_session;
+ struct fira_session *session_found = NULL;
+ u32 session_id;
+
+ if (!fira_frame_header_check(local, slot, skb, ie_get, phy_sts_index,
+ &session_id))
+ return NULL;
+ if (session->id == session_id) {
+ session_found = local->current_session;
+ } else if (session->controlee.synchronised) {
+ return NULL;
+ } else {
+ session_found =
+ fira_get_session_by_session_id(local, session_id);
+ if (!session_found ||
+ session_found->params.device_type !=
+ FIRA_DEVICE_TYPE_CONTROLEE ||
+ !fira_session_is_active(session_found))
+ return NULL;
+ /*
+ * FIXME: The previous session will not sent a ranging round
+ * report failure.
+ *
+ * The most simple is probably to remove a round ranging?
+ * or keep somewhere, previous value.
+ * or choice number 3.
+ * ```
+ * int remove_blocks = session->block_stride_len + 1;
+ *
+ * session->block_start_dtu -= remove_blocks *
+ * params->block_duration_dtu;
+ * session->block_index -= remove_blocks;
+ * ```
+ */
+ }
+ /* Update current and allow content of session to be updated. */
+ local->current_session = session_found;
+ return session_found;
+}
+
+int fira_frame_header_check_decrypt(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get)
+{
+ struct fira_session *session = local->current_session;
+ int header_len;
+ __le16 src_short_addr;
+ u32 phy_sts_index;
+ u32 session_id;
+ u8 *header;
+
+ header = skb->data;
+
+ if (!fira_frame_header_check(local, slot, skb, ie_get, &phy_sts_index,
+ &session_id))
+ return -EBADMSG;
+ if (session_id != session->id)
+ return -EBADMSG;
+
+ if (phy_sts_index != fira_sts_get_phy_sts_index(session, slot))
+ return -EBADMSG;
+
+ header_len = skb->data - header;
+ src_short_addr = slot->controller_tx ? local->dst_short_addr :
+ slot->controlee->short_addr;
+ return fira_sts_decrypt_frame(session, slot, skb, header_len, src_short_addr);
+}
diff --git a/mac/fira_frame.h b/mac/fira_frame.h
new file mode 100644
index 0000000..64e7f81
--- /dev/null
+++ b/mac/fira_frame.h
@@ -0,0 +1,268 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef FIRA_FRAME_H
+#define FIRA_FRAME_H
+
+#include <linux/types.h>
+
+struct fira_local;
+struct fira_session;
+struct fira_slot;
+struct sk_buff;
+struct mcps802154_ie_get_context;
+struct fira_session_params;
+
+#define FIRA_IE_VENDOR_OUI_LEN 3
+#define FIRA_IE_HEADER_PADDING_LEN 8
+#define FIRA_IE_HEADER_SESSION_ID_LEN 4
+#define FIRA_IE_HEADER_STS_INDEX_LEN 4
+#define FIRA_IE_HEADER_LEN \
+ (FIRA_IE_VENDOR_OUI_LEN + FIRA_IE_HEADER_PADDING_LEN + \
+ FIRA_IE_HEADER_SESSION_ID_LEN + FIRA_IE_HEADER_STS_INDEX_LEN)
+
+#define FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt) \
+ (FIRA_IE_VENDOR_OUI_LEN + 4 + 4 * (n_mngt))
+#define FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(round_index_present, \
+ n_reply_time) \
+ (FIRA_IE_VENDOR_OUI_LEN + 2 + 2 * (round_index_present) + 4 + \
+ 6 * (n_reply_time))
+#define FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN( \
+ round_index_present, reply_time_present, n_reply_time) \
+ (FIRA_IE_VENDOR_OUI_LEN + 3 + 2 * (round_index_present) + \
+ 4 * (reply_time_present) + 6 * (n_reply_time))
+#define FIRA_IE_PAYLOAD_RESULT_REPORT_LEN(tof_present, aoa_azimuth_present, \
+ aoa_elevation_present, \
+ aoa_fom_present, neg_tof_present) \
+ (FIRA_IE_VENDOR_OUI_LEN + 2 + 4 * (tof_present) + \
+ 2 * (aoa_azimuth_present) + 2 * (aoa_elevation_present) + \
+ (aoa_fom_present) * \
+ (1 * (aoa_azimuth_present) + 1 * (aoa_elevation_present)) + \
+ 4 * (neg_tof_present))
+
+#define FIRA_MIC_LEVEL 64
+#define FIRA_MIC_LEN (FIRA_MIC_LEVEL / 8)
+
+/* 3 IE headers in the frame : vendor IE, header terminator and payload. */
+#define FIRA_FRAME_WITHOUT_PAYLOAD_LEN \
+ (IEEE802154_FC_LEN + IEEE802154_SCF_LEN + IEEE802154_SHORT_ADDR_LEN + \
+ 3 * IEEE802154_IE_HEADER_LEN + FIRA_IE_HEADER_LEN + FIRA_MIC_LEN + \
+ IEEE802154_FCS_LEN)
+
+#define FIRA_IE_VENDOR_OUI 0x5a18ff
+#define FIRA_IE_HEADER_PADDING 0x08
+
+#define FIRA_MNGT_RANGING_ROLE (1 << 0)
+#define FIRA_MNGT_SLOT_INDEX (0xff << 1)
+#define FIRA_MNGT_SHORT_ADDR (0xffff << 9)
+#define FIRA_MNGT_MESSAGE_ID (0xf << 25)
+#define FIRA_MNGT_STOP (1 << 29)
+#define FIRA_MNGT_RESERVED (0x3U << 30)
+
+#define FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE (1 << 0)
+#define FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT (1 << 1)
+#define FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME (0x3f << 2)
+
+#define FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT (1 << 0)
+
+#define FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT (1 << 0)
+#define FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT (1 << 1)
+#define FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT (1 << 2)
+#define FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT (1 << 3)
+#define FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT (1 << 4)
+
+/**
+ * fira_frame_check_n_controlees() - Check the number of wanted
+ * controlees.
+ * @session: Current session.
+ * @n_controlees: Wanted number of controlees.
+ * @active: Is the session (supposed to be) active?
+ *
+ * Return: true if number of controlees fits.
+ *
+ * For an inactive session, the number of controlees is limited by the list
+ * size, aka FIRA_CONTROLEES_MAX.
+ * For an active session, it depends on the space left in messages, which is
+ * determined by the session parameters.
+ */
+bool fira_frame_check_n_controlees(const struct fira_session *session,
+ size_t n_controlees, bool active);
+
+/**
+ * fira_frame_header_put() - Fill FiRa frame header.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ */
+void fira_frame_header_put(const struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb);
+
+/**
+ * fira_frame_control_payload_put() - Fill FiRa frame payload for a control
+ * message.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ */
+void fira_frame_control_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb);
+
+/**
+ * fira_frame_measurement_report_payload_put() - Fill FiRa frame payload for
+ * a measurement report message.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ */
+void fira_frame_measurement_report_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb);
+
+/**
+ * fira_frame_result_report_payload_put() - Fill FiRa frame payload for a result
+ * report message.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ */
+void fira_frame_result_report_payload_put(const struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb);
+
+/**
+ * fira_frame_rframe_payload_put() - Check availability of a custom data
+ * payload, write it to tx frame.
+ * @local: FiRa context.
+ * @skb: Frame buffer.
+ */
+void fira_frame_rframe_payload_put(struct fira_local *local,
+ struct sk_buff *skb);
+
+/**
+ * fira_frame_header_check() - Check and consume FiRa header.
+ * @local: FiRa context.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must be zero initialized.
+ * @phy_sts_index: STS index read from header.
+ * @session_id: Session id read from header.
+ *
+ * Return: true if header is correct.
+ */
+bool fira_frame_header_check(struct fira_local *local,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get,
+ u32 *phy_sts_index, u32 *session_id);
+
+/**
+ * fira_frame_control_payload_check() - Check FiRa frame payload for a control
+ * message.
+ * @local: FiRa context.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must have been used to read header first.
+ * @n_slots: Pointer where to store number of used slots.
+ * @stop_ranging: True if the message indicates that the ranging must be stopped.
+ * @block_stride_len: Pointer where to store number of blocks to stride.
+ *
+ * Return: true if message is correct. Extra payload is accepted.
+ */
+bool fira_frame_control_payload_check(struct fira_local *local,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get,
+ unsigned int *n_slots, bool *stop_ranging,
+ int *block_stride_len);
+
+/**
+ * fira_frame_measurement_report_payload_check() - Check FiRa frame payload for
+ * a measurement report message.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must have been used to read header first.
+ *
+ * Return: true if message is correct. Extra payload is accepted.
+ */
+bool fira_frame_measurement_report_payload_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get);
+
+/**
+ * fira_frame_result_report_payload_check() - Check FiRa frame payload for
+ * a result report message.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must have been used to read header first.
+ *
+ * Return: true if message is correct. Extra payload is accepted.
+ */
+bool fira_frame_result_report_payload_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get);
+
+/**
+ * fira_frame_rframe_payload_check() - Parse custom data from ranging frame.
+ * @local: FiRa context.
+ * @slot: Slot information.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must have been used to read header first.
+ *
+ * Return: true if message is correct. Extra payload is accepted.
+ */
+bool fira_frame_rframe_payload_check(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get);
+
+/**
+ * fira_rx_frame_control_header_check() - Check control frame and consume
+ * header.
+ * @local: FiRa context.
+ * @slot: Corresponding slot.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must be zero initialized.
+ * @phy_sts_index: STS index received.
+ *
+ * Return: Session context or NULL.
+ */
+struct fira_session *fira_rx_frame_control_header_check(
+ struct fira_local *local, const struct fira_slot *slot,
+ struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get,
+ u32 *phy_sts_index);
+
+/**
+ * fira_frame_header_check_decrypt() - Check and consume header, and decrypt
+ * payload.
+ * @local: FiRa context.
+ * @slot: Corresponding slot.
+ * @skb: Frame buffer.
+ * @ie_get: Context used to read IE, must be zero initialized.
+ *
+ * Return: 0 or error.
+ */
+int fira_frame_header_check_decrypt(struct fira_local *local,
+ const struct fira_slot *slot,
+ struct sk_buff *skb,
+ struct mcps802154_ie_get_context *ie_get);
+
+#endif /* FIRA_FRAME_H */
diff --git a/mac/fira_region.c b/mac/fira_region.c
new file mode 100644
index 0000000..9e4bf53
--- /dev/null
+++ b/mac/fira_region.c
@@ -0,0 +1,482 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+
+#include <net/mcps802154_schedule.h>
+#include <net/fira_region_nl.h>
+
+#include "fira_region.h"
+#include "fira_region_call.h"
+#include "fira_access.h"
+#include "fira_session.h"
+#include "fira_crypto.h"
+#include "warn_return.h"
+
+static struct mcps802154_region_ops fira_region_ops;
+static bool do_crypto_selftest_on_module_init;
+
+static void fira_report_event(struct work_struct *work)
+{
+ struct fira_local *local =
+ container_of(work, struct fira_local, report_work);
+ struct sk_buff *skb;
+ int r;
+
+ while (!skb_queue_empty(&local->report_queue)) {
+ skb = skb_dequeue(&local->report_queue);
+ r = mcps802154_region_event(local->llhw, skb);
+ if (r == -ECONNREFUSED)
+ /* TODO stop. */
+ ;
+ }
+}
+
+static struct mcps802154_region *fira_open(struct mcps802154_llhw *llhw)
+{
+ struct fira_local *local;
+
+ local = kzalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return NULL;
+ local->llhw = llhw;
+ local->region.ops = &fira_region_ops;
+ INIT_LIST_HEAD(&local->inactive_sessions);
+ INIT_LIST_HEAD(&local->active_sessions);
+ skb_queue_head_init(&local->report_queue);
+ INIT_WORK(&local->report_work, fira_report_event);
+ /* FIXME: Hack to simplify unit test, which is borderline. */
+ local->block_duration_rx_margin_ppm = UWB_BLOCK_DURATION_MARGIN_PPM;
+ fira_crypto_init(NULL);
+ return &local->region;
+}
+
+static void fira_close(struct mcps802154_region *region)
+{
+ struct fira_local *local = region_to_local(region);
+ struct fira_session *session, *s;
+
+ list_for_each_entry_safe (session, s, &local->inactive_sessions,
+ entry) {
+ fira_session_free(local, session);
+ }
+
+ cancel_work_sync(&local->report_work);
+ skb_queue_purge(&local->report_queue);
+ kfree_sensitive(local);
+}
+
+static void fira_notify_stop(struct mcps802154_region *region)
+{
+ struct fira_local *local = region_to_local(region);
+ struct fira_session *session, *s;
+
+ list_for_each_entry_safe (session, s, &local->active_sessions, entry) {
+ fira_session_stop_controlees(session);
+ fira_session_fsm_stop(local, session);
+ }
+}
+
+static int fira_call(struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *attrs, const struct genl_info *info)
+{
+ struct fira_local *local = region_to_local(region);
+
+ switch (call_id) {
+ case FIRA_CALL_GET_CAPABILITIES:
+ return fira_get_capabilities(local, info);
+ default:
+ return fira_session_control(local, call_id, attrs, info);
+ }
+}
+
+/**
+ * fira_session_init_block_start_dtu() - Build the first block start dtu.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @timestamp_dtu: First access opportunity.
+ */
+static void fira_session_init_block_start_dtu(struct fira_local *local,
+ struct fira_session *session,
+ u32 timestamp_dtu)
+{
+ if (!session->block_start_valid) {
+ const struct fira_session_params *params = &session->params;
+ int dtu_freq_khz = local->llhw->dtu_freq_hz / 1000;
+
+ session->block_start_valid = true;
+ session->block_start_dtu =
+ timestamp_dtu +
+ params->initiation_time_ms * dtu_freq_khz;
+ session->next_access_timestamp_dtu = session->block_start_dtu;
+ }
+}
+
+/**
+ * fira_get_next_session() - Find the next session which should have the
+ * access.
+ * @local: FiRa context.
+ * @next_timestamp_dtu: Next access opportunity.
+ * @max_duration_dtu: Max duration of the next access opportunity.
+ * @adopted_demand: Output updated related to next session returned.
+ *
+ * Return: Pointer to the next session which should have the access.
+ */
+static struct fira_session *
+fira_get_next_session(struct fira_local *local, u32 next_timestamp_dtu,
+ int max_duration_dtu,
+ struct fira_session_demand *adopted_demand)
+{
+ struct fira_session *adopted_session = NULL;
+ struct fira_session *session;
+ bool is_candidate_unsync, is_adopted_unsync;
+ int max_unsync_duration_dtu = max_duration_dtu;
+ int r;
+
+ /*
+ * Little cheat to start all sessions as initiation_time_ms is a
+ * delay, and not a absolute time. This is the only function
+ * which change the session content.
+ */
+ list_for_each_entry (session, &local->active_sessions, entry) {
+ fira_session_init_block_start_dtu(local, session,
+ next_timestamp_dtu);
+ }
+
+ /*
+ * Reminder: active_sessions list is sorted by session->priority
+ * from highest priority to lowest priority.
+ */
+ list_for_each_entry (session, &local->active_sessions, entry) {
+ /*
+ * Welcome in sessions election!
+ *
+ * First, the candidate session will provide its wish in
+ * 'candidate_demand'.
+ * And then the candidate will be compared with the adopted
+ * session. The "best" will be become or stay the adopted
+ * session.
+ * So the session election will process candidate after
+ * candidate, to find the most appropriate session.
+ */
+ struct fira_session_demand candidate_demand;
+
+ /*
+ * Sessions with lower priority are not allowed to overlap
+ * the adopted session. But a lower priority can start and
+ * stop before the session adopted.
+ */
+ if (adopted_session && adopted_session->params.priority !=
+ session->params.priority) {
+ /* Is there some time left? */
+ if (is_before_dtu(next_timestamp_dtu,
+ adopted_demand->timestamp_dtu))
+ /*
+ * Limit max duration for session with lower
+ * priority to not overlap sessions which have
+ * an higher priority.
+ */
+ max_duration_dtu =
+ adopted_demand->timestamp_dtu -
+ next_timestamp_dtu;
+ else
+ /* No more time left. */
+ break;
+ if (!max_unsync_duration_dtu ||
+ max_unsync_duration_dtu > max_duration_dtu)
+ max_unsync_duration_dtu = max_duration_dtu;
+ }
+
+ is_candidate_unsync = session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLEE &&
+ !session->controlee.synchronised;
+ /* Retrieve the wish of the session candidate. */
+ r = fira_session_fsm_get_demand(
+ local, session, next_timestamp_dtu,
+ is_candidate_unsync ? max_unsync_duration_dtu :
+ max_duration_dtu,
+ &candidate_demand);
+ /* When 'r' is one, the session have a demand. */
+ if (r != 1)
+ /* The session doesn't have a demand. */
+ continue;
+
+ /*
+ * If there is no adopted session, the candidate is the
+ * adopted session.
+ */
+ if (!adopted_session)
+ goto candidate_adopted;
+ /*
+ * Is session finish before the adopted session ?
+ * adopted_demand | [-----]
+ * candidate | [------]
+ * --+-----------------------> Time
+ */
+ if (is_before_dtu(candidate_demand.timestamp_dtu +
+ candidate_demand.max_duration_dtu,
+ adopted_demand->timestamp_dtu))
+ /*
+ * Candidate is adopted and replace the
+ * previous one.
+ */
+ goto candidate_adopted;
+ /*
+ * Is session start after the adopted session ?
+ * adopted_demand | [------]
+ * candidate | [--------]
+ * --+----------------------> Time
+ */
+ if (is_before_dtu(adopted_demand->timestamp_dtu +
+ adopted_demand->max_duration_dtu,
+ candidate_demand.timestamp_dtu))
+ /* Candidate is not adopted. */
+ continue;
+ /*
+ * The candidate session have an overlap with the adopted
+ * session. Try the negotiation first to find an agreement
+ * about the access usage.
+ *
+ * But take care, synchronized session have a better
+ * eloquence in case of negotiation failure with an
+ * unsynchronized session.
+ */
+ is_adopted_unsync = adopted_session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLEE &&
+ !adopted_session->controlee.synchronised;
+ /*
+ * The candidate session have an overlap with the adopted
+ * session.
+ *
+ * adopted_demand | [------]
+ * candidate | [--------]
+ * --+----------------------> Time
+ *
+ * Request a duration reduction to the adopted session.
+ */
+ if (is_adopted_unsync &&
+ !is_before_dtu(candidate_demand.timestamp_dtu,
+ adopted_demand->timestamp_dtu)) {
+ int limit_duration_dtu =
+ candidate_demand.timestamp_dtu -
+ adopted_demand->timestamp_dtu;
+ struct fira_session_demand tmp;
+
+ if (limit_duration_dtu)
+ /* Ask to reduce the duration. */
+ r = fira_session_fsm_get_demand(
+ local, adopted_session,
+ next_timestamp_dtu, limit_duration_dtu,
+ &tmp);
+ else
+ /* Both sessions start at same time. */
+ r = 0;
+ if (r == 1) {
+ /*
+ * The adopted session accept to
+ * reduction its max duration.
+ */
+ *adopted_demand = tmp;
+ max_unsync_duration_dtu = limit_duration_dtu;
+ continue;
+ }
+ if (!is_candidate_unsync)
+ /*
+ * In this corrupted world, synchronized
+ * session have better relation.
+ */
+ goto candidate_adopted;
+ }
+ /*
+ * The candidate session have an overlap with the adopted
+ * session.
+ *
+ * adopted_demand | [-----]
+ * candidate | [------]
+ * --+----------------------> Time
+ *
+ * Request a duration reduction to the candidate session.
+ */
+ if (is_candidate_unsync &&
+ !is_before_dtu(adopted_demand->timestamp_dtu,
+ candidate_demand.timestamp_dtu)) {
+ int limit_duration_dtu = adopted_demand->timestamp_dtu -
+ candidate_demand.timestamp_dtu;
+ struct fira_session_demand tmp;
+
+ if (limit_duration_dtu)
+ /* Ask to reduce the duration. */
+ r = fira_session_fsm_get_demand(
+ local, session, next_timestamp_dtu,
+ limit_duration_dtu, &tmp);
+ else
+ /* Both sessions start at same time. */
+ r = 0;
+ if (r == 1) {
+ /*
+ * The candidate session accept to
+ * reduction its max duration.
+ */
+ adopted_session = session;
+ *adopted_demand = tmp;
+ max_unsync_duration_dtu = limit_duration_dtu;
+ continue;
+ }
+ if (!is_adopted_unsync)
+ /*
+ * In this corrupted world, synchronized
+ * session have better relation.
+ */
+ continue;
+ }
+
+ /*
+ * Finally, negotiation between adopted and candidate fails.
+ * One of the session will probably have ranging not done.
+ * Choose the session which have the oldest access.
+ */
+ if (is_before_dtu(session->last_access_timestamp_dtu,
+ adopted_session->last_access_timestamp_dtu))
+ goto candidate_adopted;
+
+ /* Candidate is not adopted. */
+ continue;
+
+ candidate_adopted:
+ adopted_session = session;
+ *adopted_demand = candidate_demand;
+ }
+ return adopted_session;
+}
+
+static struct mcps802154_access *
+fira_get_access(struct mcps802154_region *region, u32 next_timestamp_dtu,
+ int next_in_region_dtu, int region_duration_dtu)
+{
+ struct fira_local *local = region_to_local(region);
+ /* 'fsd' acronyms is FiRa Session Demand. */
+ struct fira_session_demand fsd;
+ struct fira_session *session;
+ int max_duration_dtu =
+ region_duration_dtu ? region_duration_dtu - next_in_region_dtu :
+ 0;
+
+ session = fira_get_next_session(local, next_timestamp_dtu,
+ max_duration_dtu, &fsd);
+ if (session)
+ return fira_session_fsm_get_access(local, session, &fsd);
+ return NULL;
+}
+
+static int fira_get_demand(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *next_demand)
+{
+ struct fira_local *local = region_to_local(region);
+ /* 'fsd' for FiRa Session Demand. */
+ struct fira_session_demand fsd;
+ struct fira_session *session;
+
+ session = fira_get_next_session(local, next_timestamp_dtu, 0, &fsd);
+ if (session) {
+ next_demand->timestamp_dtu = fsd.timestamp_dtu;
+ next_demand->max_duration_dtu = fsd.max_duration_dtu;
+ return 1;
+ }
+ return 0;
+}
+
+static struct mcps802154_region_ops fira_region_ops = {
+ .owner = THIS_MODULE,
+ .name = "fira",
+ .open = fira_open,
+ .close = fira_close,
+ .notify_stop = fira_notify_stop,
+ .call = fira_call,
+ .get_access = fira_get_access,
+ .get_demand = fira_get_demand,
+};
+
+struct fira_session *fira_get_session_by_session_id(struct fira_local *local,
+ u32 session_id)
+{
+ struct fira_session *session;
+
+ list_for_each_entry (session, &local->active_sessions, entry) {
+ if (session->id == session_id)
+ return session;
+ }
+ list_for_each_entry (session, &local->inactive_sessions, entry) {
+ if (session->id == session_id)
+ return session;
+ }
+ return NULL;
+}
+
+void fira_check_all_missed_ranging(struct fira_local *local,
+ const struct fira_session *recent_session,
+ u32 timestamp_dtu)
+{
+ struct fira_session *session, *s;
+
+ /*
+ * Process sessions with safe function, as the session FSM can leave
+ * the active list for many stop reasons.
+ */
+ list_for_each_entry_safe (session, s, &local->active_sessions, entry) {
+ if (recent_session == session)
+ continue;
+ /*
+ * Does the session started during the access of
+ * recent_session?
+ */
+ if (!session->block_start_valid)
+ continue;
+ fira_session_fsm_check_missed_ranging(local, session,
+ timestamp_dtu);
+ }
+}
+
+int __init fira_region_init(void)
+{
+ if (do_crypto_selftest_on_module_init)
+ WARN_RETURN(fira_crypto_test());
+
+ return mcps802154_region_register(&fira_region_ops);
+}
+
+void __exit fira_region_exit(void)
+{
+ mcps802154_region_unregister(&fira_region_ops);
+}
+
+module_param_named(crypto_selftest, do_crypto_selftest_on_module_init, bool, 0644);
+module_init(fira_region_init);
+module_exit(fira_region_exit);
+
+MODULE_DESCRIPTION("FiRa Region for IEEE 802.15.4 MCPS");
+MODULE_AUTHOR("Nicolas Schodet <nicolas.schodet@qorvo.com>");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
diff --git a/mac/fira_region.h b/mac/fira_region.h
new file mode 100644
index 0000000..6038d09
--- /dev/null
+++ b/mac/fira_region.h
@@ -0,0 +1,473 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_FIRA_REGION_H
+#define NET_FIRA_REGION_H
+
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/math64.h>
+#include <net/mcps802154_schedule.h>
+
+#include "net/fira_region_params.h"
+
+#define FIRA_SLOT_DURATION_RSTU_DEFAULT 2400
+#define FIRA_BLOCK_DURATION_MS_DEFAULT 200
+#define FIRA_ROUND_DURATION_SLOTS_DEFAULT 30
+#define FIRA_MAX_RR_RETRY_DEFAULT 0
+#define FIRA_PRIORITY_MAX 100
+#define FIRA_PRIORITY_DEFAULT 50
+#define FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MAX 10
+#define FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MIN 1
+#define FIRA_BOOLEAN_MAX 1
+#define FIRA_BLOCK_STRIDE_LEN_MAX 255
+#define FIRA_FRAMES_MAX (3 + 3 * FIRA_CONTROLEES_MAX)
+#define FIRA_CONTROLEE_FRAMES_MAX (3 + 3 + 1)
+/* IEEE 802.15.4z 2020 section 6.9.7.2 */
+#define UWB_BLOCK_DURATION_MARGIN_PPM 100
+/* FiRa Tx should arrive between 0 and 10 us, always add 2 us. */
+#define FIRA_TX_MARGIN_US 2
+
+/*
+ * FIRA_SESSION_DATA_NTF_LOWER_/UPPER_BOUND_AOA min/max :
+ * Azimuth in rad_2pi_q16 : -32768 / 32767 (equal to -180 / ~180 degrees)
+ * Elevation in rad_2pi_q16 : -16384 / 16384 (equal to -90 / 90 degrees)
+ */
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MIN -32768
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MAX 32767
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MIN -32768
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MAX 32767
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MIN -16384
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MAX 16384
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MIN -16384
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MAX 16384
+
+/**
+ * enum fira_message_id - Message identifiers, used in internal state and in
+ * messages.
+ * @FIRA_MESSAGE_ID_RANGING_INITIATION: Initial ranging message.
+ * @FIRA_MESSAGE_ID_RANGING_RESPONSE: Response ranging message.
+ * @FIRA_MESSAGE_ID_RANGING_FINAL: Final ranging message, only for DS-TWR.
+ * @FIRA_MESSAGE_ID_CONTROL: Control message, sent by the controller.
+ * @FIRA_MESSAGE_ID_MEASUREMENT_REPORT: Deferred report of ranging measures.
+ * @FIRA_MESSAGE_ID_RESULT_REPORT: Report computed ranging result.
+ * @FIRA_MESSAGE_ID_CONTROL_UPDATE: Message to change hopping.
+ * @FIRA_MESSAGE_ID_RFRAME_MAX: Maximum identifier of message transmitted using
+ * an RFRAME.
+ * @FIRA_MESSAGE_ID_MAX: Maximum message identifier.
+ */
+enum fira_message_id {
+ FIRA_MESSAGE_ID_RANGING_INITIATION = 0,
+ FIRA_MESSAGE_ID_RANGING_RESPONSE = 1,
+ FIRA_MESSAGE_ID_RANGING_FINAL = 2,
+ FIRA_MESSAGE_ID_CONTROL = 3,
+ FIRA_MESSAGE_ID_MEASUREMENT_REPORT = 4,
+ FIRA_MESSAGE_ID_RESULT_REPORT = 5,
+ FIRA_MESSAGE_ID_CONTROL_UPDATE = 6,
+ FIRA_MESSAGE_ID_RFRAME_MAX = FIRA_MESSAGE_ID_RANGING_FINAL,
+ FIRA_MESSAGE_ID_MAX = FIRA_MESSAGE_ID_CONTROL_UPDATE,
+};
+
+/**
+ * struct fira_diagnostic - Diagnostic result.
+ */
+struct fira_diagnostic {
+ /**
+ * @rssis_q1: Received signal strength indication (RSSI), absolute value
+ * in Q1 fixed point format, unit is dBm.
+ */
+ u8 rssis_q1[MCPS802154_RSSIS_N_MAX];
+ /**
+ * @n_rssis: The number of RSSI in the array below.
+ */
+ size_t n_rssis;
+ /**
+ * @aoas: Angle of arrival, ordered by increasing measurement type.
+ */
+ struct mcps802154_rx_aoa_measurements
+ aoas[MCPS802154_RX_AOA_MEASUREMENTS_MAX];
+ /**
+ * @n_aoas: Number of angle of arrival.
+ */
+ size_t n_aoas;
+ /**
+ * @cirs: CIR for different parts of the frame.
+ *
+ * Set by low-level driver, must be kept valid until next received
+ * frame.
+ */
+ struct mcps802154_rx_cir *cirs;
+ /**
+ * @n_cirs: Number of parts of CIR.
+ */
+ size_t n_cirs;
+};
+
+/**
+ * struct fira_slot - Information on an active slot.
+ */
+struct fira_slot {
+ /**
+ * @index: Index of this slot, add it to the block STS index to get the
+ * slot STS index. Note: there can be holes for a controlee as only
+ * relevant slots are recorded.
+ */
+ int index;
+ /**
+ * @controller_tx: True if Tx is performed by the controller.
+ */
+ bool controller_tx;
+ /**
+ * @ranging_index: Index of the ranging in the ranging information
+ * table, -1 if none.
+ */
+ int ranging_index;
+ /**
+ * @message_id: Identifier of the message exchanged in this slot.
+ */
+ enum fira_message_id message_id;
+ /**
+ * @tx_ant_set: Tx antenna set.
+ */
+ int tx_ant_set;
+ /**
+ * @rx_ant_set: Rx antenna set.
+ */
+ int rx_ant_set;
+ /**
+ * @controlee: Controlee.
+ */
+ struct fira_controlee *controlee;
+};
+
+/**
+ * struct fira_local_aoa_info - Ranging AoA information.
+ */
+struct fira_local_aoa_info {
+ /**
+ * @pdoa_2pi: Phase Difference of Arrival.
+ */
+ s16 pdoa_2pi;
+ /**
+ * @aoa_2pi: Angle of Arrival.
+ */
+ s16 aoa_2pi;
+ /**
+ * @aoa_fom: Figure of merit of the AoA.
+ */
+ u8 aoa_fom;
+ /**
+ * @rx_ant_set: Antenna set index.
+ */
+ u8 rx_ant_set;
+ /**
+ * @present: true if AoA information is present.
+ */
+ bool present;
+};
+
+/**
+ * enum fira_range_data_ntf_status - Device (controller or controlee)
+ * status, used for range_data_ntf.
+ * @FIRA_RANGE_DATA_NTF_NONE: Undetermined, no ranging data for this
+ * device yet, or N/A (not applicable).
+ * @FIRA_RANGE_DATA_NTF_IN: Last ranging data for this device
+ * were inside given boudaries.
+ * @FIRA_RANGE_DATA_NTF_OUT: Last ranging data for this device
+ * were outside given boudaries.
+ * @FIRA_RANGE_DATA_NTF_ERROR: Last ranging round(s) for this device
+ * failed (timeout, error, ...). No info about a previous state or N/A.
+ * @FIRA_RANGE_DATA_NTF_IN_ERROR: Last ranging round(s) for this device
+ * failed (timeout, error, ...). Previous data were inside given boudaries.
+ * @FIRA_RANGE_DATA_NTF_OUT_ERROR: Last ranging round(s) for this device
+ * failed (timeout, error, ...). Previous data were inside given boudaries.
+*/
+enum fira_range_data_ntf_status {
+ FIRA_RANGE_DATA_NTF_NONE,
+ FIRA_RANGE_DATA_NTF_IN,
+ FIRA_RANGE_DATA_NTF_OUT,
+ FIRA_RANGE_DATA_NTF_ERROR,
+ FIRA_RANGE_DATA_NTF_IN_ERROR,
+ FIRA_RANGE_DATA_NTF_OUT_ERROR,
+};
+
+/**
+ * struct fira_ranging_info - Ranging information.
+ */
+struct fira_ranging_info {
+ /**
+ * @timestamps_rctu: Timestamps of the ranging messages.
+ */
+ u64 timestamps_rctu[FIRA_MESSAGE_ID_RFRAME_MAX + 1];
+ /**
+ * @tof_rctu: Computed Time of Flight.
+ */
+ int tof_rctu;
+ /**
+ * @local_aoa: Local ranging AoA information.
+ */
+ struct fira_local_aoa_info local_aoa;
+ /**
+ * @local_aoa_azimuth: Azimuth ranging AoA information.
+ */
+ struct fira_local_aoa_info local_aoa_azimuth;
+ /**
+ * @local_aoa_elevation: Elevation ranging AoA information.
+ */
+ struct fira_local_aoa_info local_aoa_elevation;
+ /**
+ * @remote_aoa_azimuth_2pi: Remote azimuth AoA.
+ */
+ s16 remote_aoa_azimuth_2pi;
+ /**
+ * @remote_aoa_elevation_pi: Remote elevation AoA.
+ */
+ s16 remote_aoa_elevation_pi;
+ /**
+ * @remote_aoa_azimuth_fom: Remote azimuth FoM.
+ */
+ u8 remote_aoa_azimuth_fom;
+ /**
+ * @remote_aoa_elevation_fom: Remote elevation FoM.
+ */
+ u8 remote_aoa_elevation_fom;
+ /**
+ * @rx_rssis: RSSI value measured for individual Rx frames.
+ */
+ u8 rx_rssis[FIRA_MESSAGE_ID_MAX + 1];
+ /**
+ * @n_rx_rssis: Number of Rx RSSI saved.
+ */
+ int n_rx_rssis;
+ /**
+ * @short_addr: Peer short address.
+ */
+ __le16 short_addr;
+ /**
+ * @status: Success or failure reason.
+ */
+ enum fira_ranging_status status;
+ /**
+ * @slot_index: In case of failure, the slot index where it has occured.
+ */
+ u8 slot_index;
+ /**
+ * @tof_present: true if time of flight information is present.
+ */
+ bool tof_present;
+ /**
+ * @remote_aoa_azimuth_present: true if azimuth AoA information is present.
+ */
+ bool remote_aoa_azimuth_present;
+ /**
+ * @remote_aoa_elevation_present: true if elevation AoA information is present.
+ */
+ bool remote_aoa_elevation_present;
+ /**
+ * @remote_aoa_fom_present: true if FoM AoA is present.
+ */
+ bool remote_aoa_fom_present;
+ /**
+ * @clock_offset_present: true if the driver provided clock_offset info.
+ */
+ bool clock_offset_present;
+ /**
+ * @clock_offset_q26: clock offset value, as signed Q26, if present.
+ */
+ s16 clock_offset_q26;
+ /**
+ * @data_payload: Custom data payload.
+ */
+ u8 data_payload[FIRA_DATA_PAYLOAD_SIZE_MAX];
+ /**
+ * @data_payload_len: Custom data payload length.
+ */
+ int data_payload_len;
+ /**
+ * @rx_ctx: Pointer to the current rx_ctx context controlee.
+ */
+ void *rx_ctx;
+ /**
+ * @range_data_ntf_status: range_data_ntf status of the remote device.
+ */
+ enum fira_range_data_ntf_status range_data_ntf_status;
+ /**
+ * @notify: if true, add this ranging to the notification report.
+ */
+ bool notify;
+};
+
+/**
+ * struct fira_local - Local context.
+ */
+struct fira_local {
+ /**
+ * @region: Region instance returned to MCPS.
+ */
+ struct mcps802154_region region;
+ /**
+ * @llhw: Low-level device pointer.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @access: Access returned to MCPS.
+ */
+ struct mcps802154_access access;
+ /**
+ * @report_queue: Queue of report frame to be processed.
+ */
+ struct sk_buff_head report_queue;
+ /**
+ * @report_work: Process work of report event.
+ */
+ struct work_struct report_work;
+ /**
+ * @frames: Access frames referenced from access.
+ */
+ struct mcps802154_access_frame frames[FIRA_FRAMES_MAX];
+ /**
+ * @sts_params: STS parameters for access frames.
+ */
+ struct mcps802154_sts_params sts_params[FIRA_FRAMES_MAX];
+ /**
+ * @channel: Channel parameters for access.
+ */
+ struct mcps802154_channel channel;
+ /**
+ * @inactive_sessions: List of inactive sessions.
+ */
+ struct list_head inactive_sessions;
+ /**
+ * @active_sessions: List of active sessions.
+ */
+ struct list_head active_sessions;
+ /**
+ * @current_session: Pointer to the current session.
+ */
+ struct fira_session *current_session;
+ /**
+ * @src_short_addr: Source address for the current session (actually
+ * never put as a source address in a frame, but used for control
+ * message).
+ */
+ __le16 src_short_addr;
+ /**
+ * @dst_short_addr: Destination address for the current session. When
+ * controller, this is broadcast or the address of the only controlee.
+ * When controlee, this is the address of the controller.
+ */
+ __le16 dst_short_addr;
+ /**
+ * @block_duration_rx_margin_ppm: Block duration rx margin for
+ * controlees.
+ */
+ int block_duration_rx_margin_ppm;
+ /**
+ * @slots: Descriptions of each active slots for the current session.
+ * When controller, this is filled when the access is requested. When
+ * controlee, the first slot is filled when the access is requested and
+ * the other slots are filled when the control message is received.
+ */
+ struct fira_slot slots[FIRA_FRAMES_MAX];
+ /**
+ * @ranging_info: Information on ranging for the current session. Index
+ * in the table is determined by the order of the ranging messages.
+ * First ranging exchange is put at index 0. When a message is shared
+ * between several exchanges, its information is stored at index 0.
+ * Reset when access is requested.
+ */
+ struct fira_ranging_info ranging_info[FIRA_CONTROLEES_MAX];
+ /**
+ * @n_ranging_info: Number of element in the ranging information table.
+ */
+ int n_ranging_info;
+ /**
+ * @n_ranging_valid: Number of valid ranging in the current ranging
+ * information table.
+ */
+ int n_ranging_valid;
+ /**
+ * @diagnostics: Diagnostic collected for each slot.
+ */
+ struct fira_diagnostic diagnostics[FIRA_FRAMES_MAX];
+ /**
+ * @stopped_controlees: Short addresses of the stopped controlees for
+ * which an element must be added to the Device Management List of
+ * the control message.
+ */
+ __le16 stopped_controlees[FIRA_CONTROLEES_MAX];
+ /**
+ * @n_stopped_controlees: Number of elements in the stopped controlees .
+ */
+ int n_stopped_controlees;
+};
+
+static const s64 speed_of_light_mm_per_s = 299702547000ull;
+
+static inline s64 fira_rctu_to_mm(s64 rctu_freq_hz, s32 rctu)
+{
+ s64 temp = speed_of_light_mm_per_s * rctu + rctu_freq_hz / 2;
+ return div64_s64(temp, rctu_freq_hz);
+}
+
+static inline s64 fira_mm_to_rctu(struct fira_local *local, s32 mm)
+{
+ s64 temp = (s64)mm * local->llhw->dtu_freq_hz * local->llhw->dtu_rctu +
+ speed_of_light_mm_per_s / 2;
+ return div64_s64(temp, speed_of_light_mm_per_s);
+}
+
+static inline struct fira_local *
+region_to_local(struct mcps802154_region *region)
+{
+ return container_of(region, struct fira_local, region);
+}
+
+static inline struct fira_local *
+access_to_local(struct mcps802154_access *access)
+{
+ return container_of(access, struct fira_local, access);
+}
+
+/**
+ * fira_get_session_by_session_id() - Get a session by its identifier.
+ * @local: FiRa context.
+ * @session_id: Session identifier.
+ *
+ * Return: The session or NULL if not found.
+ */
+struct fira_session *fira_get_session_by_session_id(struct fira_local *local,
+ u32 session_id);
+
+/**
+ * fira_check_all_missed_ranging() - Check missed ranging round for all active
+ * session except the recent.
+ * @local: FiRa context.
+ * @recent_session: FiRa session to not check in active list.
+ * @timestamp_dtu: Timestamp used to trig (or not) a report of ranging failure.
+ */
+void fira_check_all_missed_ranging(struct fira_local *local,
+ const struct fira_session *recent_session,
+ u32 timestamp_dtu);
+
+#endif /* NET_FIRA_REGION_H */
diff --git a/mac/fira_region_call.c b/mac/fira_region_call.c
new file mode 100644
index 0000000..2ed37f2
--- /dev/null
+++ b/mac/fira_region_call.c
@@ -0,0 +1,1235 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/string.h>
+
+#include <net/fira_region_nl.h>
+#include <net/mcps802154_frame.h>
+
+#include "fira_session.h"
+#include "fira_access.h"
+#include "fira_region_call.h"
+#include "fira_trace.h"
+#include "fira_sts.h"
+
+static const struct nla_policy fira_call_nla_policy[FIRA_CALL_ATTR_MAX + 1] = {
+ [FIRA_CALL_ATTR_SESSION_ID] = { .type = NLA_U32 },
+ [FIRA_CALL_ATTR_SESSION_PARAMS] = { .type = NLA_NESTED },
+ [FIRA_CALL_ATTR_CONTROLEES] = { .type = NLA_NESTED_ARRAY },
+};
+
+static const struct nla_policy fira_session_param_nla_policy[FIRA_SESSION_PARAM_ATTR_MAX +
+ 1] = {
+ [FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_DEVICE_TYPE_CONTROLLER),
+ [FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_DEVICE_ROLE_INITIATOR),
+ [FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_RANGING_ROUND_USAGE_DSTWR),
+ [FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_MULTI_NODE_MODE_MANY_TO_MANY),
+ [FIRA_SESSION_PARAM_ATTR_SHORT_ADDR] = { .type = NLA_U16 },
+ [FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR] = { .type = NLA_U16 },
+ [FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH] =
+ NLA_POLICY_MAX(NLA_U32, FIRA_BLOCK_STRIDE_LEN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_PRIORITY] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_PRIORITY_MAX),
+ [FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_MEASUREMENT_REPORT_AT_INITIATOR),
+ [FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_EMBEDDED_MODE_NON_DEFERRED),
+ [FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT] =
+ NLA_POLICY_RANGE(NLA_U32,
+ FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MIN,
+ FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MAX),
+ [FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 },
+ [FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX] = { .type = NLA_U8 },
+ [FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_RFRAME_CONFIG_SP3),
+ [FIRA_SESSION_PARAM_ATTR_PRF_MODE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_PRF_MODE_HPRF_HIGH_RATE),
+ [FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_PREAMBULE_DURATION_64),
+ [FIRA_SESSION_PARAM_ATTR_SFD_ID] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_SFD_ID_4),
+ [FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_STS_SEGMENTS_4),
+ [FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_PSDU_DATA_RATE_31M2),
+ [FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_PHR_DATA_RATE_6M81),
+ [FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_MAC_FCS_TYPE_CRC_32),
+ [FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE] = { .type = NLA_NESTED_ARRAY },
+ [FIRA_SESSION_PARAM_ATTR_STS_CONFIG] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY),
+ [FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_VUPPER64] =
+ NLA_POLICY_EXACT_LEN(FIRA_VUPPER64_SIZE),
+ [FIRA_SESSION_PARAM_ATTR_SESSION_KEY] = {
+ .type = NLA_BINARY, .len = FIRA_KEY_SIZE_MAX, },
+ [FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY] = {
+ .type = NLA_BINARY, .len = FIRA_KEY_SIZE_MAX, },
+ [FIRA_SESSION_PARAM_ATTR_KEY_ROTATION] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE] = { .type = NLA_U8 },
+ [FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_REPORT_TOF] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_REPORT_RSSI] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_RSSI_REPORT_AVERAGE),
+ [FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI] = { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD] = {
+ .type = NLA_BINARY, .len = FIRA_DATA_PAYLOAD_SIZE_MAX, },
+ [FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX),
+ [FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS] = {.type = NLA_U32},
+ [FIRA_SESSION_PARAM_ATTR_STS_LENGTH] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_STS_LENGTH_128),
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG] =
+ NLA_POLICY_MAX(NLA_U8, FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING),
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM] =
+ { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM] =
+ { .type = NLA_U32 },
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI] =
+ NLA_POLICY_RANGE(NLA_S16,
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MIN,
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MAX),
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI] =
+ NLA_POLICY_RANGE(NLA_S16,
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MIN,
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MAX),
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI] =
+ NLA_POLICY_RANGE(NLA_S16,
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MIN,
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MAX),
+ [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI] =
+ NLA_POLICY_RANGE(NLA_S16,
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MIN,
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MAX),
+};
+
+/**
+ * fira_get_state_by_session_id() - Get state of the session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: current session state.
+ */
+static enum fira_session_state_id
+fira_get_state_by_session_id(struct fira_local *local, u32 session_id)
+{
+ struct fira_session *session =
+ fira_get_session_by_session_id(local, session_id);
+
+ if (session)
+ return fira_session_get_state_id(session);
+ return FIRA_SESSION_STATE_ID_DEINIT;
+}
+
+/**
+ * fira_session_init() - Initialize FiRa session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_init(struct fira_local *local, u32 session_id)
+{
+ struct fira_session *session =
+ fira_get_session_by_session_id(local, session_id);
+
+ if (session)
+ return -EBUSY;
+
+ session = fira_session_new(local, session_id);
+ if (!session)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * fira_session_start() - Start FiRa session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_start(struct fira_local *local, u32 session_id,
+ const struct genl_info *info)
+{
+ struct fira_session *session;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ return fira_session_fsm_start(local, session, info);
+}
+
+/**
+ * fira_session_stop() - Stop FiRa session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_stop(struct fira_local *local, u32 session_id)
+{
+ struct fira_session *session;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ return fira_session_fsm_stop(local, session);
+}
+
+/**
+ * fira_session_deinit() - Deinitialize FiRa session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_deinit(struct fira_local *local, u32 session_id)
+{
+ struct fira_session *session =
+ fira_get_session_by_session_id(local, session_id);
+
+ if (!session)
+ return -ENOENT;
+ if (fira_session_is_active(session))
+ return -EBUSY;
+
+ fira_session_free(local, session);
+ return 0;
+}
+
+/**
+ * fira_session_params_set_measurement_sequence_step() - Retrieve a
+ * measurement sequence step from a NL message and store it in the session
+ * parameters.
+ * @params: The parameters contained in the NL message.
+ * @info: NL context info.
+ * @step: The step where to store the information.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_params_set_measurement_sequence_step(
+ const struct nlattr *params, const struct genl_info *info,
+ struct fira_measurement_sequence_step *step)
+{
+#define STEP_ATTR(x) FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_##x
+#define ASR_ATTR(x) \
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_##x
+ static const struct nla_policy meas_seq_step_policy[STEP_ATTR(MAX) +
+ 1] = {
+ [STEP_ATTR(MEASUREMENT_TYPE)] = { .type = NLA_U8 },
+ [STEP_ATTR(N_MEASUREMENTS)] = { .type = NLA_U8 },
+ [STEP_ATTR(RX_ANT_SET_NONRANGING)] = { .type = NLA_U8 },
+ [STEP_ATTR(RX_ANT_SETS_RANGING)] = { .type = NLA_NESTED },
+ [STEP_ATTR(TX_ANT_SET_NONRANGING)] = { .type = NLA_U8 },
+ [STEP_ATTR(TX_ANT_SET_RANGING)] = { .type = NLA_U8 },
+ };
+ static const struct nla_policy
+ rx_ant_sets_ranging_policy[ASR_ATTR(MAX) + 1] = {
+ [ASR_ATTR(0)] = { .type = NLA_U8 },
+ [ASR_ATTR(1)] = { .type = NLA_U8 },
+ };
+ struct nlattr *step_attrs[STEP_ATTR(MAX) + 1];
+ struct nlattr *rx_ant_sets_attrs[ASR_ATTR(MAX) + 1];
+ int r = 0;
+ u8 n_measurements = 0;
+
+ enum fira_measurement_type type = __FIRA_MEASUREMENT_TYPE_AFTER_LAST;
+
+ r = nla_parse_nested(step_attrs, STEP_ATTR(MAX), params,
+ meas_seq_step_policy, info->extack);
+ /* LCOV_EXCL_START */
+ if (r)
+ return r;
+ /* LCOV_EXCL_STOP */
+
+ memset(step, 0, sizeof(struct fira_measurement_sequence_step));
+ if (!step_attrs[STEP_ATTR(MEASUREMENT_TYPE)] ||
+ !step_attrs[STEP_ATTR(N_MEASUREMENTS)])
+ return -EINVAL;
+
+ type = nla_get_u8(step_attrs[STEP_ATTR(MEASUREMENT_TYPE)]);
+ if (type >= __FIRA_MEASUREMENT_TYPE_AFTER_LAST)
+ return -EINVAL;
+ step->type = type;
+
+ n_measurements = nla_get_u8(step_attrs[STEP_ATTR(N_MEASUREMENTS)]);
+ if (n_measurements == 0)
+ return -EINVAL;
+ step->n_measurements = n_measurements;
+
+#define GET_ANTENNA(nl_attr, ant_set) \
+ (ant_set) = !(nl_attr) ? -1 : nla_get_u8(nl_attr);
+
+ GET_ANTENNA(step_attrs[STEP_ATTR(RX_ANT_SET_NONRANGING)],
+ step->rx_ant_set_nonranging);
+ GET_ANTENNA(step_attrs[STEP_ATTR(TX_ANT_SET_NONRANGING)],
+ step->tx_ant_set_nonranging);
+ GET_ANTENNA(step_attrs[STEP_ATTR(TX_ANT_SET_RANGING)],
+ step->tx_ant_set_ranging);
+
+ if (!step_attrs[STEP_ATTR(RX_ANT_SETS_RANGING)])
+ return -EINVAL;
+
+ r = nla_parse_nested(rx_ant_sets_attrs, ASR_ATTR(MAX),
+ step_attrs[STEP_ATTR(RX_ANT_SETS_RANGING)],
+ rx_ant_sets_ranging_policy, info->extack);
+ /* LCOV_EXCL_START */
+ if (r)
+ return r;
+ /* LCOV_EXCL_STOP */
+
+ GET_ANTENNA(rx_ant_sets_attrs[ASR_ATTR(0)],
+ step->rx_ant_sets_ranging[0]);
+ GET_ANTENNA(rx_ant_sets_attrs[ASR_ATTR(1)],
+ step->rx_ant_sets_ranging[1]);
+
+#undef GET_ANTENNA
+#undef STEP_ATTR
+#undef ASR_ATTR
+
+ if (step->rx_ant_sets_ranging[0] == -1)
+ step->rx_ant_sets_ranging[0] = 0;
+ if (step->rx_ant_sets_ranging[1] == -1)
+ step->rx_ant_sets_ranging[1] = 0;
+ if (step->rx_ant_set_nonranging == -1)
+ step->rx_ant_set_nonranging = step->rx_ant_sets_ranging[0];
+ if (step->tx_ant_set_ranging == -1)
+ step->tx_ant_set_ranging = 0;
+ if (step->tx_ant_set_nonranging == -1)
+ step->tx_ant_set_nonranging = step->tx_ant_set_ranging;
+
+ return 0;
+}
+
+/**
+ * fira_session_params_set_measurement_sequence() - Retrieve the measurement
+ * schedule from a NL message and store it in the session parameters.
+ * @params: The parameters contained in the NL message.
+ * @info: NL context info.
+ * @meas_seq: The measurement sequence where to store the information.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_params_set_measurement_sequence(
+ const struct nlattr *params, const struct genl_info *info,
+ struct fira_measurement_sequence *meas_seq)
+{
+ struct nlattr *request;
+ int r, rem = 0;
+ size_t n_steps = 0;
+
+ nla_for_each_nested (request, params, rem) {
+ if (n_steps >= FIRA_MEASUREMENT_SEQUENCE_STEP_MAX)
+ return -EINVAL;
+ r = fira_session_params_set_measurement_sequence_step(
+ request, info, &meas_seq->steps[n_steps]);
+ if (r)
+ return r;
+ n_steps++;
+ }
+ if (!n_steps)
+ return -EINVAL;
+ meas_seq->n_steps = n_steps;
+ return 0;
+}
+
+/**
+ * fira_session_set_parameters() - Set FiRa session parameters.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ * @params: Nested attribute containing session parameters.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_set_parameters(struct fira_local *local, u32 session_id,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ struct nlattr *attrs[FIRA_SESSION_PARAM_ATTR_MAX + 1];
+ struct fira_session *session;
+ struct fira_session_params *p;
+ struct fira_measurement_sequence meas_seq = {};
+ int r;
+
+ if (!params)
+ return -EINVAL;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ r = nla_parse_nested(attrs, FIRA_SESSION_PARAM_ATTR_MAX, params,
+ fira_session_param_nla_policy, info->extack);
+ if (r)
+ return r;
+ /* Check attribute validity. */
+ if (attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE]) {
+ r = fira_session_params_set_measurement_sequence(
+ attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE],
+ info, &meas_seq);
+ if (r)
+ return r;
+ }
+ r = fira_session_fsm_check_parameters(session, attrs);
+ if (r)
+ return r;
+
+ p = &session->params;
+#define P(attr, member, type, conv) \
+ do { \
+ int x; \
+ if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \
+ x = nla_get_##type( \
+ attrs[FIRA_SESSION_PARAM_ATTR_##attr]); \
+ p->member = conv; \
+ } \
+ } while (0)
+#define PMEMCPY(attr, member) \
+ do { \
+ if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \
+ struct nlattr *attr = \
+ attrs[FIRA_SESSION_PARAM_ATTR_##attr]; \
+ memcpy(p->member, nla_data(attr), nla_len(attr)); \
+ } \
+ } while (0)
+#define PMEMNCPY(attr, member, size) \
+ do { \
+ if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \
+ struct nlattr *attr = \
+ attrs[FIRA_SESSION_PARAM_ATTR_##attr]; \
+ int len = nla_len(attr); \
+ memcpy(p->member, nla_data(attr), len); \
+ p->size = len; \
+ } \
+ } while (0)
+ /* Main session parameters. */
+ P(DEVICE_TYPE, device_type, u8, x);
+ P(RANGING_ROUND_USAGE, ranging_round_usage, u8, x);
+ P(MULTI_NODE_MODE, multi_node_mode, u8, x);
+ P(SHORT_ADDR, short_addr, u16, x);
+ P(DESTINATION_SHORT_ADDR, controller_short_addr, u16, x);
+ /* Timings parameters. */
+ P(INITIATION_TIME_MS, initiation_time_ms, u32, x);
+ P(SLOT_DURATION_RSTU, slot_duration_dtu, u32,
+ x * local->llhw->rstu_dtu);
+ P(BLOCK_DURATION_MS, block_duration_dtu, u32,
+ x * (local->llhw->dtu_freq_hz / 1000));
+ P(ROUND_DURATION_SLOTS, round_duration_slots, u32, x);
+ /* Behaviour parameters. */
+ P(BLOCK_STRIDE_LENGTH, block_stride_len, u32, x);
+ P(MAX_NUMBER_OF_MEASUREMENTS, max_number_of_measurements, u32, x);
+ P(MAX_RR_RETRY, max_rr_retry, u32, x);
+ P(ROUND_HOPPING, round_hopping, u8, !!x);
+ P(PRIORITY, priority, u8, x);
+ P(RESULT_REPORT_PHASE, result_report_phase, u8, !!x);
+ /* Radio parameters. */
+ P(CHANNEL_NUMBER, channel_number, u8, x);
+ P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x);
+ P(RFRAME_CONFIG, rframe_config, u8, x);
+ P(PREAMBLE_DURATION, preamble_duration, u8, x);
+ P(SFD_ID, sfd_id, u8, x);
+ P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x);
+ P(PSDU_DATA_RATE, psdu_data_rate, u8, x);
+ P(MAC_FCS_TYPE, mac_fcs_type, u8, x);
+ P(PRF_MODE, prf_mode, u8, x);
+ P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x);
+ /* Measurement Sequence */
+ if (attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE]) {
+ p->meas_seq = meas_seq;
+ session->measurements.reset = true;
+ }
+ /* STS and crypto parameters. */
+ P(STS_CONFIG, sts_config, u8, x);
+ PMEMCPY(VUPPER64, vupper64);
+ if (attrs[FIRA_SESSION_PARAM_ATTR_SESSION_KEY]) {
+ struct nlattr *attr =
+ attrs[FIRA_SESSION_PARAM_ATTR_SESSION_KEY];
+ memcpy(p->session_key, nla_data(attr), nla_len(attr));
+ p->session_key_len = nla_len(attr);
+ }
+ P(SUB_SESSION_ID, sub_session_id, u32, x);
+ if (attrs[FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY]) {
+ struct nlattr *attr =
+ attrs[FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY];
+ memcpy(p->sub_session_key, nla_data(attr), nla_len(attr));
+ p->sub_session_key_len = nla_len(attr);
+ }
+ P(KEY_ROTATION, key_rotation, u8, !!x);
+ P(KEY_ROTATION_RATE, key_rotation_rate, u8, x);
+ /* Report parameters. */
+ P(AOA_RESULT_REQ, aoa_result_req, u8, !!x);
+ P(REPORT_TOF, report_tof, u8, !!x);
+ P(REPORT_AOA_AZIMUTH, report_aoa_azimuth, u8, !!x);
+ P(REPORT_AOA_ELEVATION, report_aoa_elevation, u8, !!x);
+ P(REPORT_AOA_FOM, report_aoa_fom, u8, !!x);
+ P(REPORT_RSSI, report_rssi, u8, x);
+ /* Custom data */
+ P(DATA_VENDOR_OUI, data_vendor_oui, u32, x);
+
+ PMEMNCPY(DATA_PAYLOAD, data_payload, data_payload_len);
+ /* Increment payload sequence number if a new data is received. */
+ if (attrs[FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD])
+ p->data_payload_seq++;
+ /* Diagnostics */
+ P(DIAGNOSTICS, report_diagnostics, u8, x);
+ P(DIAGNOSTICS_FRAME_REPORTS_FIELDS, diagnostic_report_flags, u32, x);
+ /* Misc */
+ P(STS_LENGTH, sts_length, u8, x);
+ P(RANGE_DATA_NTF_CONFIG, range_data_ntf_config, u8, x);
+ P(RANGE_DATA_NTF_PROXIMITY_NEAR_MM, range_data_ntf_proximity_near_rctu,
+ u32, fira_mm_to_rctu(local, x));
+ P(RANGE_DATA_NTF_PROXIMITY_FAR_MM, range_data_ntf_proximity_far_rctu,
+ u32, fira_mm_to_rctu(local, x));
+ P(RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI,
+ range_data_ntf_lower_bound_aoa_azimuth_2pi, s16, x);
+ P(RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI,
+ range_data_ntf_upper_bound_aoa_azimuth_2pi, s16, x);
+ P(RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI,
+ range_data_ntf_lower_bound_aoa_elevation_2pi, s16, x);
+ P(RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI,
+ range_data_ntf_upper_bound_aoa_elevation_2pi, s16, x);
+#undef PMEMNCPY
+#undef PMEMCPY
+#undef P
+
+ fira_session_fsm_parameters_updated(local, session);
+ return 0;
+}
+
+/**
+ * fira_session_get_state() - Get state of the session.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_get_state(struct fira_local *local, u32 session_id)
+{
+ struct sk_buff *msg;
+ enum fira_session_state_id state;
+
+ state = fira_get_state_by_session_id(local, session_id);
+
+ msg = mcps802154_region_call_alloc_reply_skb(
+ local->llhw, &local->region, FIRA_CALL_SESSION_GET_STATE,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session_id))
+ goto nla_put_failure;
+
+ if (nla_put_u8(msg, FIRA_CALL_ATTR_SESSION_STATE, state))
+ goto nla_put_failure;
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+/**
+ * fira_session_params_get_measurement_sequence_step() - Retrieve a
+ * measurement sequence step in a NL message.
+ * @step: The measurement sequence step to add to the message.
+ * @msg_buf: NL message buffer.
+ * @idx: Index of the step.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_params_get_measurement_sequence_step(
+ const struct fira_measurement_sequence_step *step,
+ struct sk_buff *msg_buf, size_t idx)
+{
+#define P(attr, member, type, conv) \
+ do { \
+ type x = member; \
+ if (nla_put_##type(msg_buf, attr, conv)) \
+ return -ENOBUFS; \
+ } while (0)
+#define STEP_ATTR(x) FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_##x
+#define ASR_ATTR(x) \
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_##x
+
+ struct nlattr *step_params = NULL;
+ struct nlattr *rx_ant_sets_ranging_params = NULL;
+
+ step_params = nla_nest_start(msg_buf, idx);
+
+ if (!step_params)
+ return -ENOBUFS;
+ P(STEP_ATTR(MEASUREMENT_TYPE), step->type, u8, x);
+ P(STEP_ATTR(N_MEASUREMENTS), step->n_measurements, u8, x);
+ P(STEP_ATTR(RX_ANT_SET_NONRANGING), step->rx_ant_set_nonranging, u8, x);
+
+ rx_ant_sets_ranging_params =
+ nla_nest_start(msg_buf, STEP_ATTR(RX_ANT_SETS_RANGING));
+ if (!rx_ant_sets_ranging_params)
+ return -ENOBUFS;
+
+ P(ASR_ATTR(0), step->rx_ant_sets_ranging[0], u8, x);
+ P(ASR_ATTR(1), step->rx_ant_sets_ranging[1], u8, x);
+
+ nla_nest_end(msg_buf, rx_ant_sets_ranging_params);
+
+ P(STEP_ATTR(TX_ANT_SET_NONRANGING), step->tx_ant_set_nonranging, u8, x);
+ P(STEP_ATTR(TX_ANT_SET_RANGING), step->tx_ant_set_ranging, u8, x);
+
+ nla_nest_end(msg_buf, step_params);
+
+#undef STEP_ATTR
+#undef ASR_ATTR
+#undef P
+ return 0;
+}
+
+/**
+ * fira_session_params_get_measurement_sequence() - Retrieve a
+ * measurement sequence in a NL message.
+ * @meas_seq: The measurement sequence to add to the message.
+ * @msg_buf: NL message buffer.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_params_get_measurement_sequence(
+ const struct fira_measurement_sequence *meas_seq,
+ struct sk_buff *msg_buf)
+{
+ struct nlattr *meas_seq_params = NULL;
+ size_t i;
+
+ meas_seq_params = nla_nest_start(
+ msg_buf, FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE);
+ if (!meas_seq_params)
+ return -ENOBUFS;
+
+ for (i = 0; i < meas_seq->n_steps; ++i) {
+ int r = 0;
+ r = fira_session_params_get_measurement_sequence_step(
+ meas_seq->steps + i, msg_buf, i);
+ if (r)
+ return r;
+ }
+
+ nla_nest_end(msg_buf, meas_seq_params);
+
+ return 0;
+}
+
+/**
+ * fira_session_get_parameters() - Get FiRa session parameters.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_get_parameters(struct fira_local *local, u32 session_id)
+{
+ const struct fira_session *session;
+ const struct fira_session_params *p;
+ struct sk_buff *msg;
+ struct nlattr *params;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ p = &session->params;
+ msg = mcps802154_region_call_alloc_reply_skb(
+ local->llhw, &local->region, FIRA_CALL_SESSION_GET_PARAMS,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id))
+ goto nla_put_failure;
+
+ params = nla_nest_start(msg, FIRA_CALL_ATTR_SESSION_PARAMS);
+ if (!params)
+ goto nla_put_failure;
+
+#define P(attr, member, type, conv) \
+ do { \
+ type x = p->member; \
+ if (nla_put_##type(msg, FIRA_SESSION_PARAM_ATTR_##attr, conv)) \
+ goto nla_put_failure; \
+ } while (0)
+#define PMEMCPY(attr, member) \
+ do { \
+ if (nla_put(msg, FIRA_SESSION_PARAM_ATTR_##attr, \
+ sizeof(p->member), p->member)) \
+ goto nla_put_failure; \
+ } while (0)
+ /* Main session parameters. */
+ P(DEVICE_TYPE, device_type, u8, x);
+ P(RANGING_ROUND_USAGE, ranging_round_usage, u8, x);
+ P(MULTI_NODE_MODE, multi_node_mode, u8, x);
+ if (p->short_addr != IEEE802154_ADDR_SHORT_BROADCAST)
+ P(SHORT_ADDR, short_addr, u16, x);
+ if (p->controller_short_addr != IEEE802154_ADDR_SHORT_BROADCAST)
+ P(DESTINATION_SHORT_ADDR, controller_short_addr, u16, x);
+ /* Timings parameters. */
+ P(INITIATION_TIME_MS, initiation_time_ms, u32, x);
+ P(SLOT_DURATION_RSTU, slot_duration_dtu, u32,
+ x / local->llhw->rstu_dtu);
+ P(BLOCK_DURATION_MS, block_duration_dtu, u32,
+ x / (local->llhw->dtu_freq_hz / 1000));
+ P(ROUND_DURATION_SLOTS, round_duration_slots, u32, x);
+ /* Behaviour parameters. */
+ P(BLOCK_STRIDE_LENGTH, block_stride_len, u32, x);
+ P(MAX_NUMBER_OF_MEASUREMENTS, max_number_of_measurements, u32, x);
+ P(MAX_RR_RETRY, max_rr_retry, u32, x);
+ P(ROUND_HOPPING, round_hopping, u8, !!x);
+ P(PRIORITY, priority, u8, x);
+ P(RESULT_REPORT_PHASE, result_report_phase, u8, !!x);
+ /* Radio parameters. */
+ if (p->channel_number)
+ P(CHANNEL_NUMBER, channel_number, u8, x);
+ if (p->preamble_code_index)
+ P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x);
+ P(RFRAME_CONFIG, rframe_config, u8, x);
+ P(PREAMBLE_DURATION, preamble_duration, u8, x);
+ P(SFD_ID, sfd_id, u8, x);
+ P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x);
+ P(PSDU_DATA_RATE, psdu_data_rate, u8, x);
+ P(PRF_MODE, prf_mode, u8, x);
+ P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x);
+ P(MAC_FCS_TYPE, mac_fcs_type, u8, x);
+ /* Measurement Sequence */
+ if (fira_session_params_get_measurement_sequence(
+ &session->measurements.sequence, msg))
+ goto nla_put_failure;
+ /* STS and crypto parameters. */
+ PMEMCPY(VUPPER64, vupper64);
+ P(SUB_SESSION_ID, sub_session_id, u32, x);
+ P(STS_CONFIG, sts_config, u8, x);
+ P(KEY_ROTATION, key_rotation, u8, x);
+ P(KEY_ROTATION_RATE, key_rotation_rate, u8, x);
+ /* Report parameters. */
+ P(AOA_RESULT_REQ, aoa_result_req, u8, !!x);
+ P(REPORT_TOF, report_tof, u8, !!x);
+ P(REPORT_AOA_AZIMUTH, report_aoa_azimuth, u8, !!x);
+ P(REPORT_AOA_ELEVATION, report_aoa_elevation, u8, !!x);
+ P(REPORT_AOA_FOM, report_aoa_fom, u8, !!x);
+ P(REPORT_RSSI, report_rssi, u8, !!x);
+ /* Custom data */
+ if (p->data_vendor_oui)
+ P(DATA_VENDOR_OUI, data_vendor_oui, u32, x);
+ /* Diagnostics */
+ P(DIAGNOSTICS, report_diagnostics, u8, x);
+ P(DIAGNOSTICS_FRAME_REPORTS_FIELDS, diagnostic_report_flags, u32, x);
+ /* Misc */
+ P(STS_LENGTH, sts_length, u8, x);
+ P(RANGE_DATA_NTF_CONFIG, range_data_ntf_config, u8, x);
+ P(RANGE_DATA_NTF_PROXIMITY_NEAR_MM, range_data_ntf_proximity_near_rctu,
+ u32, fira_rctu_to_mm((s64)local->llhw->dtu_freq_hz * local->llhw->dtu_rctu, x));
+ P(RANGE_DATA_NTF_PROXIMITY_FAR_MM, range_data_ntf_proximity_far_rctu,
+ u32, fira_rctu_to_mm((s64)local->llhw->dtu_freq_hz * local->llhw->dtu_rctu, x));
+ P(RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI,
+ range_data_ntf_lower_bound_aoa_azimuth_2pi, s16, x);
+ P(RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI,
+ range_data_ntf_upper_bound_aoa_azimuth_2pi, s16, x);
+ P(RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI,
+ range_data_ntf_lower_bound_aoa_elevation_2pi, s16, x);
+ P(RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI,
+ range_data_ntf_upper_bound_aoa_elevation_2pi, s16, x);
+#undef P
+#undef PMEMCPY
+
+ nla_nest_end(msg, params);
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+/**
+ * fira_manage_controlees() - Manage controlees.
+ * @local: FiRa context.
+ * @call_id: FiRa call id.
+ * @session_id: FiRa session id.
+ * @params: Nested attribute containing controlee parameters.
+ * @info: Request information.
+ * Return: 0 or error.
+ */
+static int fira_manage_controlees(struct fira_local *local,
+ enum fira_call call_id, u32 session_id,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ static const struct nla_policy new_controlee_nla_policy[FIRA_CALL_CONTROLEE_ATTR_MAX +
+ 1] = {
+ [FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR] = { .type = NLA_U16 },
+ [FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID] = { .type = NLA_U32 },
+ [FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY] = { .type = NLA_BINARY,
+ .len = FIRA_KEY_SIZE_MAX },
+ };
+ struct nlattr *request;
+ struct nlattr *attrs[FIRA_CALL_CONTROLEE_ATTR_MAX + 1];
+ int r, rem, i, slot_duration_us, n_controlees = 0;
+ struct fira_session *session;
+ struct fira_controlee *controlee = NULL, *tmp_controlee;
+ bool is_active;
+ struct list_head controlees;
+
+ if (!params)
+ return -EINVAL;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ INIT_LIST_HEAD(&controlees);
+
+ nla_for_each_nested (request, params, rem) {
+ if (n_controlees >= FIRA_CONTROLEES_MAX) {
+ r = -EINVAL;
+ goto end;
+ }
+
+ r = nla_parse_nested(attrs, FIRA_CALL_CONTROLEE_ATTR_MAX,
+ request, new_controlee_nla_policy,
+ info->extack);
+ if (r)
+ goto end;
+
+ controlee = kzalloc(sizeof(struct fira_controlee), GFP_KERNEL);
+ if (!controlee) {
+ r = -ENOMEM;
+ goto end;
+ }
+
+ if (!attrs[FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR] ||
+ (!attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID] &&
+ attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY])) {
+ kfree(controlee);
+ r = -EINVAL;
+ goto end;
+ }
+
+ controlee->short_addr = nla_get_le16(
+ attrs[FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR]);
+ if (attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID]) {
+ if (call_id == FIRA_CALL_DEL_CONTROLEE) {
+ kfree(controlee);
+ r = -EINVAL;
+ goto end;
+ }
+ controlee->sub_session = true;
+ controlee->sub_session_id = nla_get_u32(
+ attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID]);
+ if (attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]) {
+ memcpy(controlee->sub_session_key,
+ nla_data(
+ attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]),
+ nla_len(attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]));
+ controlee->sub_session_key_len = nla_len(
+ attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]);
+ }
+ } else {
+ controlee->sub_session = false;
+ }
+ controlee->range_data_ntf_status = FIRA_RANGE_DATA_NTF_NONE;
+ controlee->state = FIRA_CONTROLEE_STATE_RUNNING;
+ /* Check and reject a duplication of short_addr. */
+ list_for_each_entry (tmp_controlee, &controlees, entry) {
+ if (controlee->short_addr ==
+ tmp_controlee->short_addr) {
+ kfree(controlee);
+ r = -EINVAL;
+ goto end;
+ }
+ }
+ list_add_tail(&controlee->entry, &controlees);
+ n_controlees++;
+ }
+ if (list_empty(&controlees)) {
+ r = -EINVAL;
+ goto end;
+ }
+
+ /*
+ * Be careful, active is not equal to
+ * 'local->current_session == session'.
+ */
+ is_active = fira_session_is_active(session);
+
+ if (is_active) {
+ /* If unicast refuse to add more than one controlee. */
+ switch (call_id) {
+ case FIRA_CALL_NEW_CONTROLEE:
+ if (session->params.multi_node_mode ==
+ FIRA_MULTI_NODE_MODE_UNICAST) {
+ r = -EPERM;
+ goto end;
+ }
+ break;
+ case FIRA_CALL_SET_CONTROLEE:
+ r = -EBUSY;
+ goto end;
+ default:
+ break;
+ }
+ }
+
+ slot_duration_us = (session->params.slot_duration_dtu * 1000) /
+ (local->llhw->dtu_freq_hz / 1000);
+
+ switch (call_id) {
+ case FIRA_CALL_SET_CONTROLEE:
+ r = fira_session_set_controlees(local, session, &controlees,
+ slot_duration_us, n_controlees);
+ break;
+ case FIRA_CALL_DEL_CONTROLEE:
+ if (n_controlees > 1) {
+ r = -EINVAL;
+ goto end;
+ } else {
+ /* 'controlee' points the unique entry in the list. */
+ r = fira_session_del_controlee(session, controlee,
+ is_active);
+ }
+ break;
+ /* FIRA_CALL_NEW_CONTROLEE. */
+ default:
+ if (n_controlees > 1) {
+ r = -EINVAL;
+ goto end;
+ } else {
+ /* 'controlee' points the unique entry in the list. */
+ r = fira_session_new_controlee(local, session,
+ controlee,
+ slot_duration_us,
+ is_active);
+ }
+ }
+ if (r)
+ goto end;
+
+ if (!is_active && local->llhw->rx_ctx_size) {
+ for (i = 0; i < session->n_current_controlees; i++) {
+ memset(session->rx_ctx[i], 0, local->llhw->rx_ctx_size);
+ }
+ }
+
+ fira_session_fsm_controlee_list_updated(local, session);
+end:
+ list_for_each_entry_safe (controlee, tmp_controlee, &controlees,
+ entry) {
+ kfree(controlee);
+ }
+ return r;
+}
+
+/**
+ * fira_session_get_controlees() - Get list of controlees.
+ * @local: FiRa context.
+ * @session_id: FiRa session id.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int fira_session_get_controlees(struct fira_local *local, u32 session_id,
+ const struct genl_info *info)
+{
+ const struct fira_session *session;
+ struct sk_buff *msg;
+ struct nlattr *controlees, *controlee_attr;
+ struct fira_controlee *controlee;
+
+ session = fira_get_session_by_session_id(local, session_id);
+ if (!session)
+ return -ENOENT;
+
+ msg = mcps802154_region_call_alloc_reply_skb(local->llhw,
+ &local->region,
+ FIRA_CALL_GET_CONTROLEES,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id))
+ goto nla_put_failure;
+
+ controlees = nla_nest_start(msg, FIRA_CALL_ATTR_CONTROLEES);
+ if (!controlees)
+ goto nla_put_failure;
+
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ controlee_attr = nla_nest_start(msg, 1);
+ if (!controlee_attr)
+ goto nla_put_failure;
+ if (nla_put_le16(msg, FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR,
+ controlee->short_addr))
+ goto nla_put_failure;
+ if (controlee->sub_session &&
+ nla_put_u32(msg, FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID,
+ controlee->sub_session_id))
+ goto nla_put_failure;
+ nla_nest_end(msg, controlee_attr);
+ }
+
+ nla_nest_end(msg, controlees);
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+int fira_get_capabilities(struct fira_local *local,
+ const struct genl_info *info)
+{
+ struct sk_buff *msg;
+ struct nlattr *capabilities;
+ u64 hw_flags = local->llhw->flags;
+ u32 sts_caps = fira_crypto_get_capabilities();
+
+ if (!info)
+ return 0;
+
+ msg = mcps802154_region_call_alloc_reply_skb(local->llhw,
+ &local->region,
+ FIRA_CALL_GET_CAPABILITIES,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ capabilities = nla_nest_start(msg, FIRA_CALL_ATTR_CAPABILITIES);
+ if (!capabilities) {
+ goto nla_put_failure;
+ }
+
+#define P(name, type, value) \
+ do { \
+ if (nla_put_##type(msg, FIRA_CAPABILITY_ATTR_##name, value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+#define F(name) \
+ do { \
+ if (nla_put_flag(msg, FIRA_CAPABILITY_ATTR_##name)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+#define C(name, hw_flag) \
+ do { \
+ if (hw_flags & (hw_flag)) \
+ F(name); \
+ } while (0)
+#define S(mode) \
+ do { \
+ if (sts_caps & (1 << FIRA_STS_MODE_##mode)) \
+ F(STS_##mode); \
+ } while (0)
+
+ /* Main session capabilities. */
+ P(FIRA_PHY_VERSION_RANGE, u32, 0x01010101);
+ P(FIRA_MAC_VERSION_RANGE, u32, 0x01010101);
+ P(DEVICE_CLASS, u8, 1);
+ F(DEVICE_TYPE_CONTROLEE_RESPONDER);
+ F(DEVICE_TYPE_CONTROLLER_INITIATOR);
+ F(MULTI_NODE_MODE_UNICAST);
+ F(MULTI_NODE_MODE_ONE_TO_MANY);
+ F(RANGING_ROUND_USAGE_DS_TWR);
+ P(NUMBER_OF_CONTROLEES_MAX, u32, FIRA_CONTROLEES_MAX);
+ /* Behaviour. */
+ F(ROUND_HOPPING);
+ F(BLOCK_STRIDING);
+ /* Radio. */
+ P(CHANNEL_NUMBER, u16,
+ local->llhw->hw->phy->supported
+ .channels[local->llhw->hw->phy->current_page]);
+ C(RFRAME_CONFIG_SP1,
+ MCPS802154_LLHW_STS_SEGMENT_1 | MCPS802154_LLHW_STS_SEGMENT_2);
+ C(RFRAME_CONFIG_SP3,
+ MCPS802154_LLHW_STS_SEGMENT_1 | MCPS802154_LLHW_STS_SEGMENT_2);
+ C(PRF_MODE_BPRF, MCPS802154_LLHW_BPRF);
+ C(PRF_MODE_HPRF, MCPS802154_LLHW_HPRF);
+ C(PREAMBLE_DURATION_32, MCPS802154_LLHW_PSR_32);
+ C(PREAMBLE_DURATION_64, MCPS802154_LLHW_PSR_64);
+ C(SFD_ID_0, MCPS802154_LLHW_SFD_4A);
+ C(SFD_ID_1, MCPS802154_LLHW_SFD_4Z_4);
+ C(SFD_ID_2, MCPS802154_LLHW_SFD_4Z_8);
+ C(SFD_ID_3, MCPS802154_LLHW_SFD_4Z_16);
+ C(SFD_ID_4, MCPS802154_LLHW_SFD_4Z_32);
+ F(NUMBER_OF_STS_SEGMENTS_0);
+ C(NUMBER_OF_STS_SEGMENTS_1, MCPS802154_LLHW_STS_SEGMENT_1);
+ C(NUMBER_OF_STS_SEGMENTS_2, MCPS802154_LLHW_STS_SEGMENT_2);
+ C(NUMBER_OF_STS_SEGMENTS_3, MCPS802154_LLHW_STS_SEGMENT_3);
+ C(NUMBER_OF_STS_SEGMENTS_4, MCPS802154_LLHW_STS_SEGMENT_4);
+ C(PSDU_DATA_RATE_6M81, MCPS802154_LLHW_DATA_RATE_6M81);
+ C(PSDU_DATA_RATE_7M80, MCPS802154_LLHW_DATA_RATE_7M80);
+ C(PSDU_DATA_RATE_27M2, MCPS802154_LLHW_DATA_RATE_27M2);
+ C(PSDU_DATA_RATE_31M2, MCPS802154_LLHW_DATA_RATE_31M2);
+ C(BPRF_PHR_DATA_RATE_850K, MCPS802154_LLHW_PHR_DATA_RATE_850K);
+ C(BPRF_PHR_DATA_RATE_6M81, MCPS802154_LLHW_PHR_DATA_RATE_6M81);
+ F(TX_ADAPTIVE_PAYLOAD_POWER);
+ /* Antenna. */
+ P(RX_ANTENNA_PAIRS, u32, local->llhw->rx_antenna_pairs);
+ P(TX_ANTENNAS, u32, local->llhw->tx_antennas);
+ /* STS and crypto capabilities. */
+ S(STATIC);
+ S(DYNAMIC);
+ S(DYNAMIC_INDIVIDUAL_KEY);
+ S(PROVISIONED);
+ S(PROVISIONED_INDIVIDUAL_KEY);
+ /* Report. */
+ C(AOA_AZIMUTH, MCPS802154_LLHW_AOA_AZIMUTH);
+ C(AOA_AZIMUTH_FULL, MCPS802154_LLHW_AOA_AZIMUTH_FULL);
+ C(AOA_ELEVATION, MCPS802154_LLHW_AOA_ELEVATION);
+ C(AOA_FOM, MCPS802154_LLHW_AOA_FOM);
+#undef C
+#undef F
+#undef P
+
+ nla_nest_end(msg, capabilities);
+ return mcps802154_region_call_reply(local->llhw, msg);
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+int fira_session_get_count(struct fira_local *local)
+{
+ struct fira_session *session;
+ struct sk_buff *msg;
+ u32 count = 0;
+
+ list_for_each_entry (session, &local->inactive_sessions, entry) {
+ count++;
+ }
+
+ list_for_each_entry (session, &local->active_sessions, entry) {
+ count++;
+ }
+
+ msg = mcps802154_region_call_alloc_reply_skb(
+ local->llhw, &local->region, FIRA_CALL_SESSION_GET_COUNT,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_COUNT, count))
+ goto nla_put_failure;
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+int fira_session_control(struct fira_local *local, enum fira_call call_id,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ u32 session_id;
+ struct nlattr *attrs[FIRA_CALL_ATTR_MAX + 1];
+ int r;
+
+ /* Special case of get count that doesn't need params. */
+ if (call_id == FIRA_CALL_SESSION_GET_COUNT)
+ return fira_session_get_count(local);
+
+ if (!params)
+ return -EINVAL;
+ r = nla_parse_nested(attrs, FIRA_CALL_ATTR_MAX, params,
+ fira_call_nla_policy, info->extack);
+
+ if (r)
+ return r;
+
+ if (!attrs[FIRA_CALL_ATTR_SESSION_ID])
+ return -EINVAL;
+
+ session_id = nla_get_u32(attrs[FIRA_CALL_ATTR_SESSION_ID]);
+ trace_region_fira_session_control(local, session_id, call_id);
+
+ switch (call_id) {
+ case FIRA_CALL_SESSION_INIT:
+ return fira_session_init(local, session_id);
+ case FIRA_CALL_SESSION_START:
+ return fira_session_start(local, session_id, info);
+ case FIRA_CALL_SESSION_STOP:
+ return fira_session_stop(local, session_id);
+ case FIRA_CALL_SESSION_DEINIT:
+ return fira_session_deinit(local, session_id);
+ case FIRA_CALL_SESSION_SET_PARAMS:
+ return fira_session_set_parameters(
+ local, session_id, attrs[FIRA_CALL_ATTR_SESSION_PARAMS],
+ info);
+ case FIRA_CALL_SET_CONTROLEE:
+ case FIRA_CALL_NEW_CONTROLEE:
+ case FIRA_CALL_DEL_CONTROLEE:
+ return fira_manage_controlees(local, call_id, session_id,
+ attrs[FIRA_CALL_ATTR_CONTROLEES],
+ info);
+ case FIRA_CALL_GET_CONTROLEES:
+ return fira_session_get_controlees(local, session_id, info);
+ case FIRA_CALL_SESSION_GET_PARAMS:
+ return fira_session_get_parameters(local, session_id);
+ case FIRA_CALL_SESSION_GET_STATE:
+ return fira_session_get_state(local, session_id);
+ default:
+ return -EINVAL;
+ }
+}
diff --git a/mac/fira_region_call.h b/mac/fira_region_call.h
new file mode 100644
index 0000000..670b6b3
--- /dev/null
+++ b/mac/fira_region_call.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef NET_MCPS802154_FIRA_REGION_CALL_H
+#define NET_MCPS802154_FIRA_REGION_CALL_H
+
+#include "fira_region.h"
+
+/**
+ * fira_get_capabilities() - Get FiRa capabilities.
+ * @local: FiRa context.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+int fira_get_capabilities(struct fira_local *local,
+ const struct genl_info *info);
+
+/**
+ * fira_session_control() - Control FiRa session.
+ * @local: FiRa context.
+ * @call_id: Identifier of the FiRa procedure.
+ * @params: Nested attribute containing procedure parameters.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+int fira_session_control(struct fira_local *local, enum fira_call call_id,
+ const struct nlattr *params,
+ const struct genl_info *info);
+
+/**
+ * fira_session_get_count() - Get count of active and inactive sessions.
+ * @local: FiRa context.
+ *
+ * Return: 0 or error.
+ */
+int fira_session_get_count(struct fira_local *local);
+
+#endif /* NET_MCPS802154_FIRA_REGION_CALL_H */
diff --git a/mac/fira_round_hopping_crypto.h b/mac/fira_round_hopping_crypto.h
new file mode 100644
index 0000000..0f0603a
--- /dev/null
+++ b/mac/fira_round_hopping_crypto.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef FIRA_ROUND_HOPPING_CRYPTO_H
+#define FIRA_ROUND_HOPPING_CRYPTO_H
+
+#include <linux/types.h>
+#include <crypto/aes.h>
+
+struct fira_round_hopping_sequence;
+
+/**
+ * fira_round_hopping_crypto_encrypt() - Compute a cipher using AES.
+ * @round_hopping_sequence: Round hopping context.
+ * @data: Input data, with length AES_BLOCK_SIZE.
+ * @out: Output hash, with length AES_BLOCK_SIZE.
+ *
+ * Return: 0 or error.
+ */
+int fira_round_hopping_crypto_encrypt(
+ const struct fira_round_hopping_sequence *round_hopping_sequence,
+ const u8 *data, u8 *out);
+
+/**
+ * fira_round_hopping_crypto_init() - Initialize round hopping context.
+ * @round_hopping_sequence: Round hopping context.
+ *
+ * Return: 0 or error.
+ */
+int fira_round_hopping_crypto_init(
+ struct fira_round_hopping_sequence *round_hopping_sequence);
+
+/**
+ * fira_round_hopping_crypto_destroy() - Destroy round hopping context.
+ * @round_hopping_sequence: Round hopping context.
+ */
+void fira_round_hopping_crypto_destroy(
+ struct fira_round_hopping_sequence *round_hopping_sequence);
+
+#endif /* FIRA_ROUND_HOPPING_CRYPTO_H */
diff --git a/mac/fira_round_hopping_sequence.c b/mac/fira_round_hopping_sequence.c
new file mode 100644
index 0000000..921f8cc
--- /dev/null
+++ b/mac/fira_round_hopping_sequence.c
@@ -0,0 +1,73 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "fira_session.h"
+#include <linux/string.h>
+#include <asm/unaligned.h>
+
+int fira_round_hopping_sequence_init(struct fira_session *session)
+{
+ struct fira_round_hopping_sequence *round_hopping_sequence =
+ &session->round_hopping_sequence;
+
+ memset(round_hopping_sequence->key, 0, AES_KEYSIZE_128 - sizeof(u32));
+ put_unaligned_be32(session->id, round_hopping_sequence->key +
+ AES_KEYSIZE_128 - sizeof(u32));
+ return fira_round_hopping_crypto_init(round_hopping_sequence);
+}
+
+void fira_round_hopping_sequence_destroy(struct fira_session *session)
+{
+ struct fira_round_hopping_sequence *round_hopping_sequence =
+ &session->round_hopping_sequence;
+
+ fira_round_hopping_crypto_destroy(round_hopping_sequence);
+}
+
+int fira_round_hopping_sequence_get(const struct fira_session *session,
+ int block_index)
+{
+ const struct fira_session_params *params = &session->params;
+ const struct fira_round_hopping_sequence *round_hopping_sequence =
+ &session->round_hopping_sequence;
+ int block_duration_slots =
+ params->block_duration_dtu / params->slot_duration_dtu;
+ int n_rounds = block_duration_slots / params->round_duration_slots;
+ u8 block[AES_BLOCK_SIZE];
+ u8 out[AES_BLOCK_SIZE];
+ int r;
+
+ if (!block_index)
+ return 0;
+ memset(block, 0, AES_BLOCK_SIZE - sizeof(u32));
+ put_unaligned_be32(block_index, block + AES_BLOCK_SIZE - sizeof(u32));
+ r = fira_round_hopping_crypto_encrypt(round_hopping_sequence, block,
+ out);
+ if (!r) {
+ u16 hash =
+ get_unaligned_be16(out + AES_BLOCK_SIZE - sizeof(u16));
+
+ return hash * n_rounds >> 16;
+ }
+ return 0;
+}
diff --git a/mac/fira_round_hopping_sequence.h b/mac/fira_round_hopping_sequence.h
new file mode 100644
index 0000000..a89d91b
--- /dev/null
+++ b/mac/fira_round_hopping_sequence.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H
+#define NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H
+
+struct fira_session;
+
+/**
+ * fira_round_hopping_sequence_init() - Initialize round hopping context.
+ * @session: Session.
+ *
+ * Return: 0 or error.
+ */
+int fira_round_hopping_sequence_init(struct fira_session *session);
+
+/**
+ * fira_round_hopping_sequence_destroy() - Destroy round hopping context.
+ * @session: Session.
+ */
+void fira_round_hopping_sequence_destroy(struct fira_session *session);
+
+/**
+ * fira_round_hopping_sequence_get() - Get round index for block index.
+ * @session: Session.
+ * @block_index: Block index.
+ *
+ * Return: Round index.
+ */
+int fira_round_hopping_sequence_get(const struct fira_session *session,
+ int block_index);
+
+#endif /* NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H */
diff --git a/mac/fira_session.c b/mac/fira_session.c
new file mode 100644
index 0000000..b4c8e75
--- /dev/null
+++ b/mac/fira_session.c
@@ -0,0 +1,1217 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/string.h>
+#include <linux/limits.h>
+
+#include <net/mcps802154_frame.h>
+#include <net/fira_region_nl.h>
+
+#include "fira_session.h"
+#include "fira_round_hopping_sequence.h"
+#include "fira_access.h"
+#include "fira_frame.h"
+#include "fira_trace.h"
+
+inline static int
+fira_compute_minimum_rssi(const struct fira_ranging_info *ranging_data)
+{
+ /*
+ * We want the WORST RSSI level.
+ * Please Note : RSSI is actually a negative number, but encoded
+ * as an absolute value.
+ */
+ u8 min_rssi = 0;
+ int i;
+
+ for (i = 0; i < ranging_data->n_rx_rssis; i++)
+ min_rssi = max(ranging_data->rx_rssis[i], min_rssi);
+ return min_rssi;
+}
+
+inline static int
+fira_compute_average_rssi(const struct fira_ranging_info *ranging_data)
+{
+ unsigned sum;
+ int i;
+
+ if (!ranging_data->n_rx_rssis)
+ return 0;
+
+ for (i = 0, sum = 0; i < ranging_data->n_rx_rssis; i++)
+ sum += ranging_data->rx_rssis[i];
+ return sum / i;
+}
+
+static int fira_report_local_aoa(struct sk_buff *msg, int nest_attr_id,
+ const struct fira_local_aoa_info *info)
+{
+ struct nlattr *aoa;
+
+ aoa = nla_nest_start(msg, nest_attr_id);
+ if (!aoa)
+ goto nla_put_failure;
+#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_##x
+ if (nla_put_u8(msg, A(RX_ANTENNA_SET), info->rx_ant_set))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(AOA_2PI), info->aoa_2pi))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(PDOA_2PI), info->pdoa_2pi))
+ goto nla_put_failure;
+ if (nla_put_u8(msg, A(AOA_FOM), info->aoa_fom))
+ goto nla_put_failure;
+#undef A
+ nla_nest_end(msg, aoa);
+ return 0;
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+inline static int fira_session_report_measurement(
+ const struct fira_session *session, struct sk_buff *msg,
+ const struct fira_ranging_info *ranging_data, s64 rctu_freq_hz)
+{
+ const struct fira_session_params *params = &session->params;
+ bool report_rssi_val_present = false;
+ int report_rssi_val = 0;
+
+ if (params->report_rssi &&
+ ranging_data->status == FIRA_STATUS_RANGING_SUCCESS) {
+ switch (params->report_rssi) {
+ case FIRA_RSSI_REPORT_MINIMUM:
+ report_rssi_val_present = true;
+ report_rssi_val =
+ fira_compute_minimum_rssi(ranging_data);
+ break;
+ case FIRA_RSSI_REPORT_AVERAGE:
+ report_rssi_val_present = true;
+ report_rssi_val =
+ fira_compute_average_rssi(ranging_data);
+ break;
+ default:
+ break;
+ }
+ }
+
+#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_ATTR_##x
+
+ if (nla_put_u16(msg, A(SHORT_ADDR), ranging_data->short_addr) ||
+ nla_put_u8(msg, A(STATUS), ranging_data->status))
+ goto nla_put_failure;
+
+ if (ranging_data->status) {
+ if (nla_put_u8(msg, A(SLOT_INDEX), ranging_data->slot_index))
+ goto nla_put_failure;
+ return 0;
+ }
+ if (ranging_data->tof_present) {
+ if (nla_put_s32(msg, A(DISTANCE_MM),
+ fira_rctu_to_mm(rctu_freq_hz, ranging_data->tof_rctu)))
+ goto nla_put_failure;
+ }
+ if (ranging_data->local_aoa.present) {
+ if (fira_report_local_aoa(msg, A(LOCAL_AOA),
+ &ranging_data->local_aoa))
+ goto nla_put_failure;
+ }
+ if (ranging_data->local_aoa_azimuth.present) {
+ if (fira_report_local_aoa(msg, A(LOCAL_AOA_AZIMUTH),
+ &ranging_data->local_aoa_azimuth))
+ goto nla_put_failure;
+ }
+ if (ranging_data->local_aoa_elevation.present) {
+ if (fira_report_local_aoa(msg, A(LOCAL_AOA_ELEVATION),
+ &ranging_data->local_aoa_elevation))
+ goto nla_put_failure;
+ }
+ if (ranging_data->remote_aoa_azimuth_present) {
+ if (nla_put_s16(msg, A(REMOTE_AOA_AZIMUTH_2PI),
+ ranging_data->remote_aoa_azimuth_2pi))
+ goto nla_put_failure;
+ if (ranging_data->remote_aoa_fom_present) {
+ if (nla_put_u8(msg, A(REMOTE_AOA_AZIMUTH_FOM),
+ ranging_data->remote_aoa_azimuth_fom))
+ goto nla_put_failure;
+ }
+ }
+ if (ranging_data->remote_aoa_elevation_present) {
+ if (nla_put_s16(msg, A(REMOTE_AOA_ELEVATION_PI),
+ ranging_data->remote_aoa_elevation_pi))
+ goto nla_put_failure;
+ if (ranging_data->remote_aoa_fom_present) {
+ if (nla_put_u8(msg, A(REMOTE_AOA_ELEVATION_FOM),
+ ranging_data->remote_aoa_elevation_fom))
+ goto nla_put_failure;
+ }
+ }
+ if (report_rssi_val_present) {
+ if (nla_put_u8(msg, A(RSSI), report_rssi_val))
+ goto nla_put_failure;
+ }
+ if (ranging_data->data_payload_len > 0) {
+ if (nla_put(msg, A(DATA_PAYLOAD_RECV),
+ ranging_data->data_payload_len,
+ ranging_data->data_payload))
+ goto nla_put_failure;
+ }
+ if (session->data_payload.sent) {
+ if (nla_put_u32(msg, A(DATA_PAYLOAD_SEQ_SENT),
+ session->data_payload.seq))
+ goto nla_put_failure;
+ }
+
+#undef A
+ return 0;
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+inline static int fira_report_measurement_stopped_controlee(struct sk_buff *msg,
+ __le16 short_addr)
+{
+#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_ATTR_##x
+
+ if (nla_put_u16(msg, A(SHORT_ADDR), short_addr) ||
+ nla_put_u8(msg, A(STOPPED), 1))
+ goto nla_put_failure;
+
+#undef A
+ return 0;
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+inline static int
+fira_session_report_ranging_data(const struct fira_session *session,
+ const struct fira_report_info *report_info,
+ int dtu_freq_hz, int dtu_rctu,
+ struct sk_buff *msg)
+{
+ const struct fira_session_params *params = &session->params;
+ struct nlattr *data, *measurements, *measurement;
+ int ranging_interval_ms = params->block_duration_dtu *
+ (session->block_stride_len + 1) /
+ (dtu_freq_hz / 1000);
+ s64 rctu_freq_hz = (s64)dtu_freq_hz * dtu_rctu;
+
+ int i;
+ int n_ranging_to_send = 0;
+
+ data = nla_nest_start(msg, FIRA_CALL_ATTR_RANGING_DATA);
+ if (!data)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, FIRA_RANGING_DATA_ATTR_BLOCK_INDEX,
+ session->block_index) ||
+ nla_put_u32(msg, FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS,
+ ranging_interval_ms))
+ goto nla_put_failure;
+
+ if (report_info->stopped) {
+ enum fira_ranging_data_attrs_stopped_values stopped;
+
+ if (session->stop_no_response)
+ stopped = FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE;
+ else if (session->stop_inband)
+ stopped = FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND;
+ else
+ stopped = FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST;
+
+ if (nla_put_u8(msg, FIRA_RANGING_DATA_ATTR_STOPPED, stopped))
+ goto nla_put_failure;
+
+ /*
+ Case where measurements are not available:
+ - A controller stop request.
+ - A controller max measurements reached.
+ - A controlee stop in band.
+ */
+ if ((session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLLER &&
+ stopped == FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST) ||
+ (session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLEE &&
+ stopped == FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND))
+ goto end_report;
+ }
+
+ for (i = 0; i < report_info->n_ranging_data; i++) {
+ if (report_info->ranging_data[i].notify)
+ n_ranging_to_send++;
+ }
+
+ if (n_ranging_to_send + report_info->n_stopped_controlees) {
+ measurements = nla_nest_start(
+ msg, FIRA_RANGING_DATA_ATTR_MEASUREMENTS);
+ if (!measurements)
+ goto nla_put_failure;
+
+ for (i = 0; i < report_info->n_ranging_data; i++) {
+ if (!report_info->ranging_data[i].notify)
+ continue;
+ measurement = nla_nest_start(msg, 1);
+ if (fira_session_report_measurement(
+ session, msg, &report_info->ranging_data[i],
+ rctu_freq_hz))
+ goto nla_put_failure;
+ nla_nest_end(msg, measurement);
+ }
+
+ for (i = 0; i < report_info->n_stopped_controlees; i++) {
+ measurement = nla_nest_start(msg, 1);
+ if (fira_report_measurement_stopped_controlee(
+ msg, report_info->stopped_controlees[i]))
+ goto nla_put_failure;
+ nla_nest_end(msg, measurement);
+ }
+
+ nla_nest_end(msg, measurements);
+ }
+
+end_report:
+ nla_nest_end(msg, data);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int
+fira_session_report_diagnostic_rssi(const struct fira_diagnostic *diagnostic,
+ struct sk_buff *msg)
+{
+ struct nlattr *nest;
+ int i;
+
+ nest = nla_nest_start(
+ msg, FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS);
+ if (!nest)
+ goto nla_put_failure;
+ for (i = 0; i < diagnostic->n_rssis; i++) {
+ if (nla_put_u8(msg, i, diagnostic->rssis_q1[i]))
+ goto nla_put_failure;
+ }
+ nla_nest_end(msg, nest);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int
+fira_session_report_diagnostic_aoa(const struct fira_diagnostic *diagnostic,
+ struct sk_buff *msg)
+{
+ const struct mcps802154_rx_aoa_measurements *aoa;
+ struct nlattr *aoas, *aoa_report;
+ int i;
+
+ aoas = nla_nest_start(msg,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS);
+ if (!aoas)
+ goto nla_put_failure;
+ for (i = 0; i < diagnostic->n_aoas; i++) {
+ aoa = &diagnostic->aoas[i];
+
+ aoa_report = nla_nest_start(msg, i);
+ if (!aoa_report)
+ goto nla_put_failure;
+#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_##x
+ if (nla_put_s16(msg, A(TDOA), aoa->tdoa_rctu))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(PDOA), aoa->pdoa_rad_q11))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(AOA), aoa->aoa_rad_q11))
+ goto nla_put_failure;
+ if (nla_put_u8(msg, A(FOM), aoa->fom))
+ goto nla_put_failure;
+ if (nla_put_u8(msg, A(TYPE), aoa->type))
+ goto nla_put_failure;
+#undef A
+ nla_nest_end(msg, aoa_report);
+ }
+ nla_nest_end(msg, aoas);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int fira_session_report_cir_samples(const struct mcps802154_rx_cir *cir,
+ struct sk_buff *msg)
+{
+ const struct mcps802154_rx_cir_sample_window *sw = &cir->sample_window;
+ const u8 *samples = sw->samples;
+ struct nlattr *sample_nest;
+ int i;
+
+ sample_nest = nla_nest_start(
+ msg,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW);
+ if (!sample_nest)
+ goto nla_put_failure;
+ for (i = 0; i < sw->n_samples; i++) {
+ const u8 *sample = &samples[i * sw->sizeof_sample];
+
+ if (nla_put(msg, i, sw->sizeof_sample, sample))
+ goto nla_put_failure;
+ }
+ nla_nest_end(msg, sample_nest);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int
+fira_session_report_diagnostic_cir(const struct fira_diagnostic *diagnostic,
+ struct sk_buff *msg)
+{
+ struct nlattr *cirs_nest, *cir_nest;
+ int i;
+
+ cirs_nest = nla_nest_start(
+ msg, FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS);
+ if (!cirs_nest)
+ goto nla_put_failure;
+ for (i = 0; i < diagnostic->n_cirs; i++) {
+ struct mcps802154_rx_cir *cir = &diagnostic->cirs[i];
+
+ cir_nest = nla_nest_start(msg, i);
+ if (!cir_nest)
+ goto nla_put_failure;
+#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_##x
+ if (nla_put_u16(msg, A(FP_IDX), cir->fp_index))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(FP_SNR), cir->fp_snr))
+ goto nla_put_failure;
+ if (nla_put_u16(msg, A(FP_NS), cir->fp_ns_q6))
+ goto nla_put_failure;
+ if (nla_put_u16(msg, A(PP_IDX), cir->pp_index))
+ goto nla_put_failure;
+ if (nla_put_s16(msg, A(PP_SNR), cir->pp_snr))
+ goto nla_put_failure;
+ if (nla_put_u16(msg, A(PP_NS), cir->pp_ns_q6))
+ goto nla_put_failure;
+ if (nla_put_u16(msg, A(FP_SAMPLE_OFFSET),
+ cir->fp_sample_offset))
+ goto nla_put_failure;
+#undef A
+ if (fira_session_report_cir_samples(cir, msg))
+ goto nla_put_failure;
+ nla_nest_end(msg, cir_nest);
+ }
+ nla_nest_end(msg, cirs_nest);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int fira_session_report_frame_diagnostics(
+ const struct fira_session *session,
+ const struct fira_report_info *report_info, struct sk_buff *msg)
+{
+ const struct fira_session_params *params = &session->params;
+ struct nlattr *frame_nest, *reports_nest;
+ bool is_controller = params->device_type == FIRA_DEVICE_TYPE_CONTROLLER;
+ int i;
+
+ frame_nest = nla_nest_start(
+ msg, FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS);
+ if (!frame_nest)
+ goto nla_put_failure;
+
+ for (i = 0; i < report_info->n_slots; i++) {
+ const struct fira_slot *slot = &report_info->slots[i];
+ int is_tx = slot->controller_tx ? is_controller :
+ !is_controller;
+
+ reports_nest = nla_nest_start(msg, i);
+ if (!reports_nest)
+ goto nla_put_failure;
+#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_##x
+ if (nla_put_u8(msg, A(ANT_SET),
+ is_tx ? slot->tx_ant_set : slot->rx_ant_set))
+ goto nla_put_failure;
+ if (nla_put_u8(msg, A(ACTION), is_tx))
+ goto nla_put_failure;
+ if (nla_put_u8(msg, A(MSG_ID), slot->message_id))
+ goto nla_put_failure;
+#undef A
+ /* Specific reports are done for Rx frames only. */
+ if (!is_tx) {
+ const struct fira_diagnostic *diagnostic =
+ &report_info->diagnostics[i];
+
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS) {
+ if (fira_session_report_diagnostic_rssi(
+ diagnostic, msg))
+ goto nla_put_failure;
+ }
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS) {
+ if (fira_session_report_diagnostic_aoa(
+ diagnostic, msg))
+ goto nla_put_failure;
+ }
+ if (params->diagnostic_report_flags &
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS) {
+ if (fira_session_report_diagnostic_cir(
+ diagnostic, msg))
+ goto nla_put_failure;
+ }
+ }
+ nla_nest_end(msg, reports_nest);
+ }
+ nla_nest_end(msg, frame_nest);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static inline int fira_session_report_ranging_diagnostics(
+ const struct fira_session *session,
+ const struct fira_report_info *report_info, struct sk_buff *msg)
+{
+ const struct fira_session_params *params = &session->params;
+ struct nlattr *diagnostics_nest;
+
+ if (!params->report_diagnostics)
+ return 0;
+
+ diagnostics_nest =
+ nla_nest_start(msg, FIRA_CALL_ATTR_RANGING_DIAGNOSTICS);
+ if (!diagnostics_nest)
+ goto nla_put_failure;
+
+ if (fira_session_report_frame_diagnostics(session, report_info, msg))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, diagnostics_nest);
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+struct fira_session *fira_session_new(struct fira_local *local, u32 session_id)
+{
+ struct fira_session *session;
+ struct fira_session_params *params;
+ int all_rx_ctx_size = FIRA_CONTROLEES_MAX * local->llhw->rx_ctx_size;
+ void *rx_ctx_base = NULL;
+ int i;
+
+ session = kzalloc(sizeof(*session), GFP_KERNEL);
+ if (!session)
+ return NULL;
+ if (all_rx_ctx_size) {
+ rx_ctx_base = kzalloc(all_rx_ctx_size, GFP_KERNEL);
+ if (!rx_ctx_base)
+ goto failed;
+ }
+
+ params = &session->params;
+ session->id = session_id;
+ session->measurements.reset = true;
+
+ /* Explicit default parameters as implicit is zero. */
+ params->ranging_round_usage = FIRA_RANGING_ROUND_USAGE_DSTWR;
+ params->short_addr = IEEE802154_ADDR_SHORT_BROADCAST;
+ params->controller_short_addr = IEEE802154_ADDR_SHORT_BROADCAST;
+ params->slot_duration_dtu =
+ FIRA_SLOT_DURATION_RSTU_DEFAULT * local->llhw->rstu_dtu;
+ params->block_duration_dtu = FIRA_BLOCK_DURATION_MS_DEFAULT *
+ (local->llhw->dtu_freq_hz / 1000);
+ params->round_duration_slots = FIRA_ROUND_DURATION_SLOTS_DEFAULT;
+ params->max_rr_retry = FIRA_MAX_RR_RETRY_DEFAULT;
+ params->round_hopping = false;
+ params->priority = FIRA_PRIORITY_DEFAULT;
+ params->sts_length = FIRA_STS_LENGTH_64;
+ params->sts_config = FIRA_STS_MODE_STATIC;
+ params->rframe_config = FIRA_RFRAME_CONFIG_SP3;
+ params->preamble_duration = FIRA_PREAMBULE_DURATION_64;
+ params->sfd_id = FIRA_SFD_ID_2;
+ params->number_of_sts_segments = FIRA_STS_SEGMENTS_1;
+ params->meas_seq.n_steps = 1;
+ params->meas_seq.steps[0].type = FIRA_MEASUREMENT_TYPE_RANGE;
+ params->meas_seq.steps[0].n_measurements = 1;
+ params->meas_seq.steps[0].rx_ant_set_nonranging = 0;
+ params->meas_seq.steps[0].rx_ant_sets_ranging[0] = 0;
+ params->meas_seq.steps[0].rx_ant_sets_ranging[1] = 0;
+ params->meas_seq.steps[0].tx_ant_set_nonranging = 0;
+ params->meas_seq.steps[0].tx_ant_set_ranging = 0;
+ /* Report parameters. */
+ params->aoa_result_req = true;
+ params->report_tof = true;
+ params->result_report_phase = true;
+ params->range_data_ntf_config = FIRA_RANGE_DATA_NTF_ALWAYS;
+ params->range_data_ntf_proximity_near_rctu = 0;
+ params->range_data_ntf_proximity_far_rctu = fira_mm_to_rctu(
+ local, FIRA_RANGE_DATA_NTF_PROXIMITY_FAR_MM_DEFAULT);
+ params->range_data_ntf_lower_bound_aoa_azimuth_2pi =
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_DEFAULT;
+ params->range_data_ntf_upper_bound_aoa_azimuth_2pi =
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_DEFAULT;
+ params->range_data_ntf_lower_bound_aoa_elevation_2pi =
+ FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_DEFAULT;
+ params->range_data_ntf_upper_bound_aoa_elevation_2pi =
+ FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_DEFAULT;
+
+ if (fira_round_hopping_sequence_init(session))
+ goto failed;
+
+ if (all_rx_ctx_size) {
+ for (i = 0; i < FIRA_CONTROLEES_MAX; i++) {
+ void *rx_ctx = (char *)rx_ctx_base +
+ i * local->llhw->rx_ctx_size;
+ session->rx_ctx[i] = rx_ctx;
+ }
+ }
+
+ INIT_LIST_HEAD(&session->current_controlees);
+
+ fira_session_fsm_initialise(local, session);
+ return session;
+
+failed:
+ kfree(rx_ctx_base);
+ kfree(session);
+ return NULL;
+}
+
+void fira_session_free(struct fira_local *local, struct fira_session *session)
+{
+ struct fira_controlee *controlee, *tmp_controlee;
+
+ list_for_each_entry_safe (controlee, tmp_controlee,
+ &session->current_controlees, entry) {
+ list_del(&controlee->entry);
+ kfree(controlee);
+ }
+ fira_session_fsm_uninit(local, session);
+ fira_round_hopping_sequence_destroy(session);
+ kfree(session->rx_ctx[0]);
+ kfree_sensitive(session);
+}
+
+int fira_session_set_controlees(struct fira_local *local,
+ struct fira_session *session,
+ struct list_head *controlees,
+ int slot_duration_us, int n_controlees)
+{
+ int r;
+ struct fira_controlee *controlee, *tmp_controlee;
+
+ if (!fira_frame_check_n_controlees(session, n_controlees, false))
+ return -EINVAL;
+
+ list_for_each_entry_safe (controlee, tmp_controlee,
+ &session->current_controlees, entry) {
+ fira_sts_controlee_deinit(controlee);
+ list_del(&controlee->entry);
+ kfree(controlee);
+ }
+ list_for_each_entry_safe (controlee, tmp_controlee, controlees, entry) {
+ r = fira_sts_controlee_init(
+ session, controlee, slot_duration_us,
+ mcps802154_get_current_channel(local->llhw));
+ if (r)
+ return r;
+ list_move_tail(&controlee->entry, &session->current_controlees);
+ }
+ session->n_current_controlees = n_controlees;
+ return 0;
+}
+
+int fira_session_new_controlee(struct fira_local *local,
+ struct fira_session *session,
+ struct fira_controlee *new_controlee,
+ int slot_duration_us, bool active_session)
+{
+ int r;
+ struct fira_controlee *controlee;
+
+ if (session->n_current_controlees == FIRA_CONTROLEES_MAX)
+ return -ENOBUFS;
+
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ if (new_controlee->short_addr == controlee->short_addr)
+ return -EEXIST;
+ }
+
+ if (active_session) {
+ new_controlee->state = FIRA_CONTROLEE_STATE_PENDING_RUN;
+ } else {
+ r = fira_sts_controlee_init(session, new_controlee,
+ slot_duration_us,
+ mcps802154_get_current_channel(local->llhw));
+ if (r)
+ return r;
+ }
+ list_move_tail(&new_controlee->entry, &session->current_controlees);
+ session->n_current_controlees++;
+ return 0;
+}
+
+int fira_session_del_controlee(struct fira_session *session,
+ struct fira_controlee *del_controlee,
+ bool active_session)
+{
+ struct fira_controlee *controlee, *tmp_controlee;
+ int ret = -ENOENT;
+
+ if (session->n_current_controlees < 1)
+ return -ENOENT;
+
+ list_for_each_entry_safe (controlee, tmp_controlee,
+ &session->current_controlees, entry) {
+ if (del_controlee->short_addr == controlee->short_addr) {
+ if (active_session) {
+ if (controlee->state ==
+ FIRA_CONTROLEE_STATE_STOPPING)
+ controlee->state =
+ FIRA_CONTROLEE_STATE_DELETING;
+ else if (controlee->state !=
+ FIRA_CONTROLEE_STATE_DELETING) {
+ controlee->state =
+ FIRA_CONTROLEE_STATE_PENDING_DEL;
+ } else {
+ fira_sts_controlee_deinit(controlee);
+ list_del(&controlee->entry);
+ kfree(controlee);
+ session->n_current_controlees--;
+ }
+ }
+ list_del(&del_controlee->entry);
+ kfree(del_controlee);
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+void fira_session_stop_controlees(struct fira_session *session)
+{
+ struct fira_controlee *controlee;
+
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ controlee->state = FIRA_CONTROLEE_STATE_PENDING_STOP;
+ }
+}
+
+void fira_session_restart_controlees(struct fira_session *session)
+{
+ struct fira_controlee *controlee;
+
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ if (controlee->state != FIRA_CONTROLEE_STATE_PENDING_DEL &&
+ controlee->state != FIRA_CONTROLEE_STATE_DELETING)
+ controlee->state = FIRA_CONTROLEE_STATE_RUNNING;
+ }
+}
+
+int fira_session_controlees_running_count(const struct fira_session *session)
+{
+ struct fira_controlee *controlee;
+ int count = 0;
+
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ if (controlee->state == FIRA_CONTROLEE_STATE_RUNNING ||
+ controlee->state == FIRA_CONTROLEE_STATE_PENDING_STOP ||
+ controlee->state == FIRA_CONTROLEE_STATE_PENDING_DEL)
+ count++;
+ }
+ return count;
+}
+
+void fira_session_update_controlees(struct fira_local *local,
+ struct fira_session *session)
+{
+ struct fira_controlee *controlee, *tmp_controlee;
+ bool reset_rx_ctx = false;
+ int i, slot_duration_us;
+
+ slot_duration_us = (session->params.slot_duration_dtu * 1000) /
+ (local->llhw->dtu_freq_hz / 1000);
+
+ list_for_each_entry_safe (controlee, tmp_controlee,
+ &session->current_controlees, entry) {
+ if (controlee->state == FIRA_CONTROLEE_STATE_PENDING_RUN) {
+ controlee->state = FIRA_CONTROLEE_STATE_RUNNING;
+ reset_rx_ctx = true;
+ if (fira_sts_controlee_init(
+ session, controlee, slot_duration_us,
+ mcps802154_get_current_channel(local->llhw)))
+ controlee->state =
+ FIRA_CONTROLEE_STATE_DELETING;
+ } else if (controlee->state == FIRA_CONTROLEE_STATE_RUNNING) {
+ /* Stop raised by max number of measurements threshold. */
+ if (session->stop_request)
+ controlee->state =
+ FIRA_CONTROLEE_STATE_STOPPING;
+ } else if (controlee->state ==
+ FIRA_CONTROLEE_STATE_PENDING_STOP) {
+ controlee->state = FIRA_CONTROLEE_STATE_STOPPING;
+ } else if (controlee->state ==
+ FIRA_CONTROLEE_STATE_PENDING_DEL) {
+ controlee->state = FIRA_CONTROLEE_STATE_DELETING;
+ } else if (controlee->state == FIRA_CONTROLEE_STATE_DELETING) {
+ list_del(&controlee->entry);
+ kfree(controlee);
+ session->n_current_controlees--;
+ reset_rx_ctx = true;
+ }
+ }
+
+ if (reset_rx_ctx && local->llhw->rx_ctx_size) {
+ for (i = 0; i < session->n_current_controlees; i++) {
+ memset(session->rx_ctx[i], 0, local->llhw->rx_ctx_size);
+ }
+ }
+}
+
+/**
+ * check_parameter_proximity_range() - Check proximity range consistency.
+ * @params: Current session parameters.
+ *
+ * Return: 0 or error.
+ */
+static inline int
+check_parameter_proximity_range(const struct fira_session_params *params)
+{
+ return (params->range_data_ntf_proximity_near_rctu >
+ params->range_data_ntf_proximity_far_rctu) ?
+ -ERANGE :
+ 0;
+}
+
+/**
+ * check_parameter_bound_aoa_azimuth() - Check aoa azimuth range consistency.
+ * @params: Current session parameters.
+ *
+ * Return: 0 or error.
+ */
+static inline int
+check_parameter_bound_aoa_azimuth(const struct fira_session_params *params)
+{
+ return (params->range_data_ntf_lower_bound_aoa_azimuth_2pi >
+ params->range_data_ntf_upper_bound_aoa_azimuth_2pi) ?
+ -ERANGE :
+ 0;
+}
+
+/**
+ * check_parameter_bound_aoa_elevation() - Check aoa elevation range consistency.
+ * @params: Current session parameters.
+ *
+ * Return: 0 or error.
+ */
+static inline int
+check_parameter_bound_aoa_elevation(const struct fira_session_params *params)
+{
+ return (params->range_data_ntf_lower_bound_aoa_elevation_2pi >
+ params->range_data_ntf_upper_bound_aoa_elevation_2pi) ?
+ -ERANGE :
+ 0;
+}
+
+bool fira_session_is_ready(const struct fira_local *local,
+ const struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ int round_duration_dtu;
+
+ if (params->multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST) {
+ if (session->n_current_controlees > 1)
+ return false;
+ } else {
+ /* On success, session will become active, so assume it is. */
+ if (!fira_frame_check_n_controlees(
+ session, session->n_current_controlees, true))
+ return false;
+ }
+
+ /* Check uwb parameters. */
+ if (params->prf_mode == FIRA_PRF_MODE_BPRF) {
+ /* FIXME: when preamble code index is not set, we will use
+ * the default set one, that may be for HPRF... */
+ if (params->preamble_code_index != 0 &&
+ (params->preamble_code_index < 9 ||
+ params->preamble_code_index > 24))
+ return false;
+ if (params->sfd_id != FIRA_SFD_ID_0 &&
+ params->sfd_id != FIRA_SFD_ID_2)
+ return false;
+ if (params->psdu_data_rate != FIRA_PSDU_DATA_RATE_6M81)
+ return false;
+ if (params->preamble_duration != FIRA_PREAMBULE_DURATION_64)
+ return false;
+ if (params->number_of_sts_segments > FIRA_STS_SEGMENTS_1)
+ return false;
+ } else {
+ if (params->preamble_code_index != 0 &&
+ (params->preamble_code_index < 25 ||
+ params->preamble_code_index > 32))
+ return false;
+ if (params->sfd_id == FIRA_SFD_ID_0)
+ return false;
+ if (params->prf_mode == FIRA_PRF_MODE_HPRF &&
+ params->psdu_data_rate > FIRA_PSDU_DATA_RATE_7M80)
+ return false;
+ if (params->prf_mode == FIRA_PRF_MODE_HPRF_HIGH_RATE &&
+ params->psdu_data_rate < FIRA_PSDU_DATA_RATE_27M2)
+ return false;
+ }
+ if ((params->rframe_config == FIRA_RFRAME_CONFIG_SP0) &&
+ (params->number_of_sts_segments != FIRA_STS_SEGMENTS_0))
+ return false;
+ if ((params->rframe_config != FIRA_RFRAME_CONFIG_SP0) &&
+ (params->number_of_sts_segments == FIRA_STS_SEGMENTS_0))
+ return false;
+
+ /* Check range data ntf parameters consistency. */
+ switch (params->range_data_ntf_config) {
+ case FIRA_RANGE_DATA_NTF_PROXIMITY:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING:
+ if (check_parameter_proximity_range(&session->params))
+ return false;
+ default:
+ break;
+ }
+ switch (params->range_data_ntf_config) {
+ case FIRA_RANGE_DATA_NTF_AOA:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA:
+ case FIRA_RANGE_DATA_NTF_AOA_CROSSING:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING:
+ if (check_parameter_bound_aoa_azimuth(&session->params))
+ return false;
+ if (check_parameter_bound_aoa_elevation(&session->params))
+ return false;
+ default:
+ break;
+ }
+ round_duration_dtu =
+ params->slot_duration_dtu * params->round_duration_slots;
+ return params->slot_duration_dtu != 0 &&
+ params->block_duration_dtu != 0 &&
+ params->round_duration_slots != 0 &&
+ round_duration_dtu <= params->block_duration_dtu;
+}
+
+/**
+ * is_ranging_in_proximity_bounds() - Check if ranging data are in
+ * range_data_ntf proximity boundaries.
+ * @session: FiRa session.
+ * @ranging_data: Ranging data to be evaluated.
+ *
+ * Return: true if the ranging data are inside proximity boundaries.
+ */
+static inline bool
+is_ranging_in_proximity_bounds(const struct fira_session *session,
+ const struct fira_ranging_info *ranging_data)
+{
+ return (ranging_data->tof_rctu >=
+ session->params.range_data_ntf_proximity_near_rctu &&
+ ranging_data->tof_rctu <=
+ session->params.range_data_ntf_proximity_far_rctu);
+}
+
+/**
+ * is_ranging_in_aoa_bounds() - Check if ranging data are in
+ * range_data_ntf AoA boundaries.
+ * @session: FiRa local session.
+ * @ranging_data: Ranging data to be evaluated.
+ *
+ * Return: true if ranging data are inside AoA boundaries.
+ */
+static inline bool
+is_ranging_in_aoa_bounds(const struct fira_session *session,
+ const struct fira_ranging_info *ranging_data)
+{
+ if (ranging_data->local_aoa_azimuth.present &&
+ ((ranging_data->local_aoa_azimuth.aoa_2pi <
+ session->params.range_data_ntf_lower_bound_aoa_azimuth_2pi) ||
+ (ranging_data->local_aoa_azimuth.aoa_2pi >
+ session->params.range_data_ntf_upper_bound_aoa_azimuth_2pi)))
+ return false;
+
+ if (ranging_data->local_aoa_elevation.present &&
+ ((ranging_data->local_aoa_elevation.aoa_2pi <
+ session->params.range_data_ntf_lower_bound_aoa_elevation_2pi) ||
+ (ranging_data->local_aoa_elevation.aoa_2pi >
+ session->params.range_data_ntf_upper_bound_aoa_elevation_2pi)))
+ return false;
+ return true;
+}
+
+void fira_session_set_range_data_ntf_status(const struct fira_session *session,
+ struct fira_ranging_info *ri)
+{
+ switch (session->params.range_data_ntf_config) {
+ default:
+ case FIRA_RANGE_DATA_NTF_DISABLED:
+ case FIRA_RANGE_DATA_NTF_ALWAYS:
+ ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_NONE;
+ break;
+ case FIRA_RANGE_DATA_NTF_PROXIMITY:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING:
+ if (ri->status != FIRA_STATUS_RANGING_SUCCESS ||
+ !ri->tof_present)
+ ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR;
+ else
+ ri->range_data_ntf_status =
+ is_ranging_in_proximity_bounds(session, ri) ?
+ FIRA_RANGE_DATA_NTF_IN :
+ FIRA_RANGE_DATA_NTF_OUT;
+ break;
+ case FIRA_RANGE_DATA_NTF_AOA:
+ case FIRA_RANGE_DATA_NTF_AOA_CROSSING:
+ if (ri->status != FIRA_STATUS_RANGING_SUCCESS)
+ ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR;
+ else
+ ri->range_data_ntf_status =
+ is_ranging_in_aoa_bounds(session, ri) ?
+ FIRA_RANGE_DATA_NTF_IN :
+ FIRA_RANGE_DATA_NTF_OUT;
+ break;
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING:
+ if (FIRA_STATUS_RANGING_SUCCESS || !ri->tof_present)
+ ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR;
+ else
+ ri->range_data_ntf_status =
+ is_ranging_in_proximity_bounds(session, ri) &&
+ is_ranging_in_aoa_bounds(
+ session, ri) ?
+ FIRA_RANGE_DATA_NTF_IN :
+ FIRA_RANGE_DATA_NTF_OUT;
+ break;
+ }
+}
+
+/**
+ * send_ranging_data -- Compute if a device has data to report.
+ * @config: range_data_ntf_config session parameter.
+ * @ranging_status: Ranging range_data_ntf_status for the device this round.
+ * @device_status: Previous device range_data_ntf_status.
+ *
+ * Return: true if the device has data to report, else false.
+ */
+static inline bool
+send_ranging_data(enum fira_range_data_ntf_config config,
+ enum fira_range_data_ntf_status ranging_status,
+ enum fira_range_data_ntf_status device_status)
+{
+ switch (config) {
+ default:
+ case FIRA_RANGE_DATA_NTF_DISABLED:
+ return false;
+ case FIRA_RANGE_DATA_NTF_ALWAYS:
+ return true;
+ case FIRA_RANGE_DATA_NTF_PROXIMITY:
+ case FIRA_RANGE_DATA_NTF_AOA:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA:
+ return (ranging_status == FIRA_RANGE_DATA_NTF_IN) ||
+ (ranging_status == FIRA_RANGE_DATA_NTF_ERROR) ||
+ (ranging_status == FIRA_RANGE_DATA_NTF_IN_ERROR) ||
+ (ranging_status == FIRA_RANGE_DATA_NTF_OUT_ERROR);
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING:
+ case FIRA_RANGE_DATA_NTF_AOA_CROSSING:
+ case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING:
+ return (((ranging_status == FIRA_RANGE_DATA_NTF_IN) &&
+ ((device_status == FIRA_RANGE_DATA_NTF_OUT) ||
+ (device_status == FIRA_RANGE_DATA_NTF_OUT_ERROR) ||
+ (device_status == FIRA_RANGE_DATA_NTF_IN_ERROR) ||
+ (device_status == FIRA_RANGE_DATA_NTF_ERROR) ||
+ (device_status == FIRA_RANGE_DATA_NTF_NONE))) ||
+ ((ranging_status == FIRA_RANGE_DATA_NTF_OUT) &&
+ ((device_status == FIRA_RANGE_DATA_NTF_IN) ||
+ (device_status == FIRA_RANGE_DATA_NTF_IN_ERROR))) ||
+ ((ranging_status == FIRA_RANGE_DATA_NTF_ERROR) &&
+ (device_status == FIRA_RANGE_DATA_NTF_IN)));
+ }
+}
+
+/**
+ * get_range_data_status_update -- Compute new device's range_data_ntf_status,
+ * given the ranging status and previous device status.
+ * @ranging_status: Ranging range_data_ntf_status for the device this round.
+ * @device_status: Previous device range_data_ntf_status.
+ *
+ * Return: new device range_data_ntf_status.
+ */
+static inline enum fira_range_data_ntf_status
+get_range_data_status_update(enum fira_range_data_ntf_status ranging_status,
+ enum fira_range_data_ntf_status device_status)
+{
+ enum fira_range_data_ntf_status ret = FIRA_RANGE_DATA_NTF_NONE;
+ switch (ranging_status) {
+ case FIRA_RANGE_DATA_NTF_NONE:
+ case FIRA_RANGE_DATA_NTF_IN:
+ case FIRA_RANGE_DATA_NTF_OUT:
+ ret = ranging_status;
+ break;
+ case FIRA_RANGE_DATA_NTF_ERROR:
+ switch (device_status) {
+ case FIRA_RANGE_DATA_NTF_NONE:
+ case FIRA_RANGE_DATA_NTF_ERROR:
+ ret = FIRA_RANGE_DATA_NTF_ERROR;
+ break;
+ case FIRA_RANGE_DATA_NTF_IN:
+ case FIRA_RANGE_DATA_NTF_IN_ERROR:
+ ret = FIRA_RANGE_DATA_NTF_IN_ERROR;
+ break;
+ case FIRA_RANGE_DATA_NTF_OUT:
+ case FIRA_RANGE_DATA_NTF_OUT_ERROR:
+ ret = FIRA_RANGE_DATA_NTF_OUT_ERROR;
+ break;
+ }
+ break;
+ /* defensive check, should not happened */
+ /* LCOV_EXCL_START */
+ case FIRA_RANGE_DATA_NTF_IN_ERROR:
+ case FIRA_RANGE_DATA_NTF_OUT_ERROR:
+ default:
+ ret = FIRA_RANGE_DATA_NTF_NONE;
+ break;
+ }
+ /* LCOV_EXCL_STOP */
+ return ret;
+}
+
+/**
+ * range_data_notif_update -- Update remote device(s) range_data_ntf_status
+ * and determine if report is to be sent.
+ * @local: FiRa context.
+ * @session: FiRa session.
+ * @report_info: Report info to be analysed.
+ *
+ * Return: true if there is actually data to report, else false.
+ */
+static bool range_data_notif_update(struct fira_local *local,
+ struct fira_session *session,
+ struct fira_report_info *report_info)
+{
+ bool send_report = false;
+ enum fira_range_data_ntf_config config =
+ session->params.range_data_ntf_config;
+
+ if (report_info->stopped || report_info->n_stopped_controlees)
+ send_report = true;
+
+ if (session->params.device_type == FIRA_DEVICE_TYPE_CONTROLLER) {
+ int i;
+
+ for (i = 0; i < report_info->n_ranging_data; i++) {
+ enum fira_range_data_ntf_status ctlee_status;
+ struct fira_ranging_info *ri =
+ &report_info->ranging_data[i];
+ struct fira_controlee *controlee =
+ fira_session_get_controlee(session,
+ ri->short_addr);
+
+ fira_session_set_range_data_ntf_status(session, ri);
+ if (!controlee) {
+ /*
+ * This case can happen in Contention Based mode.
+ * In this mode, controlees are unknown. Let's notify.
+ */
+ ri->notify = true;
+ send_report = true;
+ continue;
+ }
+ ctlee_status = controlee->range_data_ntf_status;
+ ri->notify = send_ranging_data(
+ config, ri->range_data_ntf_status,
+ ctlee_status);
+ controlee->range_data_ntf_status =
+ get_range_data_status_update(
+ ri->range_data_ntf_status,
+ ctlee_status);
+ send_report |= ri->notify;
+ }
+ } else {
+ /* Controlee, so we can assume n_ranging_data == 0 or 1 */
+ if (report_info->n_ranging_data) {
+ enum fira_range_data_ntf_status ctlr_status =
+ session->controlee.ctlr_range_data_ntf_status;
+ struct fira_ranging_info *ri =
+ &report_info->ranging_data[0];
+ fira_session_set_range_data_ntf_status(session, ri);
+ ri->notify = send_report = send_ranging_data(
+ config, ri->range_data_ntf_status, ctlr_status);
+ session->controlee.ctlr_range_data_ntf_status =
+ get_range_data_status_update(
+ ri->range_data_ntf_status, ctlr_status);
+ }
+ }
+ return send_report;
+}
+
+void fira_session_report(struct fira_local *local, struct fira_session *session,
+ struct fira_report_info *report_info)
+{
+ struct sk_buff *msg;
+
+ if (!range_data_notif_update(local, session, report_info))
+ return;
+
+ trace_region_fira_session_report(session, report_info);
+ msg = mcps802154_region_event_alloc_skb(local->llhw, &local->region,
+ FIRA_CALL_SESSION_NOTIFICATION,
+ session->event_portid,
+ NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id))
+ goto nla_put_failure;
+ if (nla_put_u32(msg, FIRA_CALL_ATTR_SEQUENCE_NUMBER,
+ session->sequence_number))
+ goto nla_put_failure;
+ if (fira_session_report_ranging_data(session, report_info,
+ local->llhw->dtu_freq_hz,
+ local->llhw->dtu_rctu, msg))
+ goto nla_put_failure;
+ if (fira_session_report_ranging_diagnostics(session, report_info, msg))
+ goto nla_put_failure;
+ session->sequence_number++;
+ session->data_payload.sent = false;
+
+ skb_queue_tail(&local->report_queue, msg);
+ schedule_work(&local->report_work);
+ return;
+
+nla_put_failure:
+ kfree_skb(msg);
+}
diff --git a/mac/fira_session.h b/mac/fira_session.h
new file mode 100644
index 0000000..d4e9252
--- /dev/null
+++ b/mac/fira_session.h
@@ -0,0 +1,683 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_SESSION_H
+#define NET_MCPS802154_FIRA_SESSION_H
+
+#include "fira_session_fsm.h"
+#include "fira_region.h"
+#include "fira_sts.h"
+#include "fira_crypto.h"
+#include "fira_round_hopping_crypto_impl.h"
+
+/**
+ * enum fira_controlee_state - State of controlee.
+ * @FIRA_CONTROLEE_STATE_PENDING_RUN: The controlee will be set to running state
+ * at the end of round.
+ * @FIRA_CONTROLEE_STATE_RUNNING: The controlee is running.
+ * @FIRA_CONTROLEE_STATE_PENDING_STOP: The controlee will be set to stopping
+ * state at the end of round.
+ * @FIRA_CONTROLEE_STATE_STOPPING: The controlee is stopping.
+ * @FIRA_CONTROLEE_STATE_PENDING_DEL: The controlee will be set to deleting
+ * state at the end of round.
+ * @FIRA_CONTROLEE_STATE_DELETING: The controlee is being deleted.
+ */
+enum fira_controlee_state {
+ FIRA_CONTROLEE_STATE_PENDING_RUN,
+ FIRA_CONTROLEE_STATE_RUNNING,
+ FIRA_CONTROLEE_STATE_PENDING_STOP,
+ FIRA_CONTROLEE_STATE_STOPPING,
+ FIRA_CONTROLEE_STATE_PENDING_DEL,
+ FIRA_CONTROLEE_STATE_DELETING,
+};
+
+/**
+ * struct fira_controlee - Represent a controlee.
+ */
+struct fira_controlee {
+ /**
+ * @sub_session_id: Sub-session ID for the controlee device.
+ */
+ __u32 sub_session_id;
+ /**
+ * @short_addr: Short address of the controlee.
+ */
+ __le16 short_addr;
+ /**
+ * @sub_session_key_len: Length of the sub-session key used by
+ * the controlee.
+ */
+ __u8 sub_session_key_len;
+ /**
+ * @sub_session_key: Sub-session key used by the controlee.
+ */
+ u8 sub_session_key[FIRA_KEY_SIZE_MAX];
+ /**
+ * @sub_session: Is the controlee using a sub-session.
+ */
+ bool sub_session;
+ /**
+ * @state: Current state of the controlee.
+ */
+ enum fira_controlee_state state;
+ /**
+ * @range_data_ntf_status: range_data_ntf status of the controlee.
+ */
+ enum fira_range_data_ntf_status range_data_ntf_status;
+ /*
+ * @crypto: Crypto related variables in the sub-session. Only valid if the current device
+ * is a controller/initiator and the sts_config is FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY.
+ */
+ struct fira_crypto *crypto;
+ /**
+ * @entry: Entry in list of controlees.
+ */
+ struct list_head entry;
+};
+
+struct fira_measurement_sequence_step {
+ enum fira_measurement_type type;
+ u8 n_measurements;
+ s8 rx_ant_set_nonranging;
+ s8 rx_ant_sets_ranging[2];
+ s8 tx_ant_set_nonranging;
+ s8 tx_ant_set_ranging;
+};
+
+struct fira_measurement_sequence {
+ struct fira_measurement_sequence_step
+ steps[FIRA_MEASUREMENT_SEQUENCE_STEP_MAX];
+ size_t n_steps;
+};
+
+struct fira_session_params {
+ /* Main parameters. */
+ enum fira_device_type device_type;
+ enum fira_ranging_round_usage ranging_round_usage;
+ enum fira_multi_node_mode multi_node_mode;
+ __le16 short_addr;
+ __le16 controller_short_addr;
+ /* Timings parameters. */
+ int initiation_time_ms;
+ int slot_duration_dtu;
+ int block_duration_dtu;
+ int round_duration_slots;
+ /* Behaviour parameters. */
+ u32 block_stride_len;
+ u32 max_number_of_measurements;
+ u32 max_rr_retry;
+ bool round_hopping;
+ u8 priority;
+ bool result_report_phase;
+ /* Radio. */
+ int channel_number;
+ int preamble_code_index;
+ enum fira_rframe_config rframe_config;
+ enum fira_preambule_duration preamble_duration;
+ enum fira_sfd_id sfd_id;
+ enum fira_sts_segments number_of_sts_segments;
+ enum fira_psdu_data_rate psdu_data_rate;
+ enum fira_mac_fcs_type mac_fcs_type;
+ enum fira_prf_mode prf_mode;
+ enum fira_phr_data_rate phr_data_rate;
+ /* STS and crypto. */
+ enum fira_sts_mode sts_config;
+ u8 vupper64[FIRA_VUPPER64_SIZE];
+ u8 session_key_len;
+ u8 session_key[FIRA_KEY_SIZE_MAX];
+ u32 sub_session_id;
+ u8 sub_session_key_len;
+ u8 sub_session_key[FIRA_KEY_SIZE_MAX];
+ bool key_rotation;
+ u8 key_rotation_rate;
+ bool aoa_result_req;
+ bool report_tof;
+ bool report_aoa_azimuth;
+ bool report_aoa_elevation;
+ bool report_aoa_fom;
+ enum fira_rssi_report_type report_rssi;
+ struct fira_measurement_sequence meas_seq;
+ u32 data_vendor_oui;
+ u8 data_payload[FIRA_DATA_PAYLOAD_SIZE_MAX];
+ u32 data_payload_seq;
+ int data_payload_len;
+ bool report_diagnostics;
+ enum fira_ranging_diagnostics_frame_report_flags diagnostic_report_flags;
+ /* Misc */
+ enum fira_sts_length sts_length;
+ enum fira_range_data_ntf_config range_data_ntf_config;
+ u32 range_data_ntf_proximity_near_rctu;
+ u32 range_data_ntf_proximity_far_rctu;
+ s16 range_data_ntf_lower_bound_aoa_azimuth_2pi;
+ s16 range_data_ntf_upper_bound_aoa_azimuth_2pi;
+ s16 range_data_ntf_lower_bound_aoa_elevation_2pi;
+ s16 range_data_ntf_upper_bound_aoa_elevation_2pi;
+};
+
+/**
+ * struct fira_session - Session information.
+ */
+struct fira_session {
+ /**
+ * @id: Session identifier.
+ */
+ u32 id;
+ /**
+ * @sequence_number: Session notification counter.
+ */
+ u32 sequence_number;
+ /**
+ * @entry: Entry in list of sessions.
+ */
+ struct list_head entry;
+ /**
+ * @state: State of the session.
+ */
+ const struct fira_session_fsm_state *state;
+ /**
+ * @params: Session parameters, mostly read only while the session is
+ * active.
+ */
+ struct fira_session_params params;
+ /**
+ * @hrp_uwb_params: HRP UWB parameters, read only while the session is
+ * active.
+ */
+ struct mcps802154_hrp_uwb_params hrp_uwb_params;
+ /**
+ * @event_portid: Port identifier to use for notifications.
+ */
+ u32 event_portid;
+ /**
+ * @block_start_valid: True when block_start_dtu is valid.
+ * It's false on the first access wo initiation delay.
+ */
+ bool block_start_valid;
+ /**
+ * @block_start_dtu: Block start timestamp in dtu of the last
+ * get_access.
+ */
+ u32 block_start_dtu;
+ /**
+ * @next_access_timestamp_dtu: Next access timestamp in dtu.
+ * It's equal to block_start_dtu when the hopping is disabled.
+ * Otherwise it's beyond the block_start_dtu.
+ * It's updated after each good or missed ranging round.
+ */
+ u32 next_access_timestamp_dtu;
+ /**
+ * @last_access_timestamp_dtu: Last timestamp where the session got
+ * the access.
+ * It's used only on session's election, when a negotiation between
+ * two session fails.
+ */
+ u32 last_access_timestamp_dtu;
+ /**
+ * @block_index: Block index used on the last access.
+ */
+ u32 block_index;
+ /**
+ * @block_stride_len: Stride length indicates how many ranging blocks
+ * will be skipped.
+ * The value is updated at the beginning of an access.
+ */
+ int block_stride_len;
+ /**
+ * @round_index: Round index used on the last access.
+ */
+ int round_index;
+ /**
+ * @next_round_index: Next round index a announced in measurement
+ * report message.
+ */
+ int next_round_index;
+ /**
+ * @stop_request: Session has been requested to stop.
+ */
+ bool stop_request;
+ /**
+ * @stop_inband: Session has been requested to stop by controller. This
+ * field is only used for controlee sessions.
+ */
+ bool stop_inband;
+ /**
+ * @stop_no_response: Session has been requested to stop because ranging
+ * failed for max_rr_retry consecutive rounds.
+ */
+ bool stop_no_response;
+ /**
+ * @n_ranging_round_retry: Number of ranging round failed.
+ * Counter reset on ranging round success.
+ */
+ int n_ranging_round_retry;
+
+ /**
+ * @round_hopping_sequence: Round hopping sequence generation context.
+ */
+ struct fira_round_hopping_sequence round_hopping_sequence;
+ /**
+ * @controlee: Group of persistent variable(s) used when session
+ * is a controlee.
+ */
+ struct {
+ /**
+ * @synchronised: Whether a controlee session was synchronised.
+ */
+ bool synchronised;
+ /**
+ * @block_index_sync: Last block index received.
+ */
+ int block_index_sync;
+ /**
+ * @hopping_mode: True when hopping is enabled on last
+ * measurement frame.
+ */
+ bool hopping_mode;
+ /**
+ * @next_round_index_valid: True when the next round index
+ * is present in measurement report frame.
+ */
+ bool next_round_index_valid;
+ /**
+ * @ctlr_range_data_ntf_status: range_data_ntf status of the
+ * controller.
+ */
+ enum fira_range_data_ntf_status ctlr_range_data_ntf_status;
+ /*
+ * @responder_specific_crypto: crypto related variables in the sub-session.
+ * Only valid if the sts_config is FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY.
+ */
+ struct fira_crypto *responder_specific_crypto;
+ } controlee;
+ /**
+ * @controller: Group of persistent variable(s) used when session
+ * is a controller.
+ */
+ struct {
+ /**
+ * @next_block_index: Next block index built on get access with
+ * next round index.
+ * It's only to avoid to rebuild the next round index on next
+ * access, when this last occur in time as block index will
+ * match.
+ */
+ u32 next_block_index;
+ } controller;
+ /**
+ * @data_payload: Local context for data_payload feature.
+ */
+ struct {
+ /**
+ * @seq: Sequence number of last sent data.
+ */
+ u32 seq;
+ /**
+ * @sent: True when data have been send during ranging round.
+ */
+ bool sent;
+ } data_payload;
+ /**
+ * @current_controlees: Current list of controlees.
+ */
+ struct list_head current_controlees;
+ /**
+ * @n_current_controlees: Number of elements in the list of current
+ * controlees.
+ */
+ size_t n_current_controlees;
+ /**
+ * @measurements: Measurement configurations which influence diagnostics.
+ */
+ struct {
+ /**
+ * @sequence: Copy of the meas_seq parameter on get_access
+ * event.
+ */
+ struct fira_measurement_sequence sequence;
+ /**
+ * @index: Index of the step in sequence array.
+ */
+ int index;
+ /**
+ * @n_achieved: Number of measurements done inside a step.
+ */
+ int n_achieved;
+ /**
+ * @n_total_achieved: Total number of measurements done.
+ */
+ int n_total_achieved;
+ /**
+ * @reset: True when new parameters have to be retrieved.
+ */
+ bool reset;
+ } measurements;
+ /**
+ * @rx_ctx: Custom rx context for all controlees.
+ */
+ void *rx_ctx[FIRA_CONTROLEES_MAX];
+ /**
+ * @crypto: crypto related variables in a session.
+ */
+ struct fira_crypto *crypto;
+ /**
+ * @sts: sts related variables.
+ */
+ struct {
+ /**
+ * @last_rotation_block_index: index to the last block where the
+ * rotation occurred.
+ */
+ u32 last_rotation_block_index;
+ } sts;
+ /*
+ * @last_error: last error that occurred during the active session.
+ */
+ int last_error;
+};
+
+/**
+ * struct fira_session_demand - Next access information for one FiRa session.
+ */
+struct fira_session_demand {
+ /**
+ * @block_start_dtu: Block start in dtu.
+ */
+ u32 block_start_dtu;
+ /**
+ * @timestamp_dtu: Access timestamp in dtu.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @max_duration_dtu: Maximum duration for the access.
+ */
+ int max_duration_dtu;
+ /**
+ * @add_blocks: Number of block to add.
+ */
+ int add_blocks;
+ /**
+ * @round_index: Round index to apply for the access.
+ */
+ int round_index;
+ /**
+ * @rx_timeout_dtu: timeout to apply when first frame of the controlee.
+ */
+ int rx_timeout_dtu;
+};
+
+/**
+ * struct fira_report_info - Report information for all peer.
+ */
+struct fira_report_info {
+ /**
+ * @ranging_data: Base address of ranging data per peer, or null
+ * pointer.
+ */
+ struct fira_ranging_info *ranging_data;
+ /**
+ * @n_ranging_data: Number of entry in ranging_data above.
+ */
+ size_t n_ranging_data;
+ /**
+ * @stopped_controlees: NULL, or short address of all stopped controlees.
+ */
+ const __le16 *stopped_controlees;
+ /**
+ * @n_stopped_controlees: Number of controlees stopped in array above.
+ */
+ size_t n_stopped_controlees;
+ /**
+ * @diagnostics: Array of diagnostic collected per slots.
+ */
+ const struct fira_diagnostic *diagnostics;
+ /**
+ * @slots: Array of information slots.
+ */
+ const struct fira_slot *slots;
+ /**
+ * @n_slots: Number of slots above.
+ */
+ size_t n_slots;
+ /**
+ * @stopped: True when the session is stopped.
+ */
+ bool stopped;
+};
+
+/**
+ * fira_session_new() - Create a new session.
+ * @local: FiRa context.
+ * @session_id: Session identifier, must be unique.
+ *
+ * Return: The new session or NULL on error.
+ */
+struct fira_session *fira_session_new(struct fira_local *local, u32 session_id);
+
+/**
+ * fira_session_free() - Remove a session.
+ * @local: FiRa context.
+ * @session: Session to remove, must be inactive.
+ */
+void fira_session_free(struct fira_local *local, struct fira_session *session);
+
+/**
+ * fira_session_set_controlees() - Set controlees.
+ * @local: FiRa context.
+ * @session: Session.
+ * @controlees: List of controlees.
+ * @slot_duration_us: Duration of a FiRa slot in us (according to session config).
+ * @n_controlees: Number of controlees.
+ *
+ * Return: 0 or error.
+ */
+int fira_session_set_controlees(struct fira_local *local,
+ struct fira_session *session,
+ struct list_head *controlees,
+ int slot_duration_us, int n_controlees);
+
+/**
+ * fira_session_new_controlee() - Add new controlee.
+ * @session: Session.
+ * @controlee: Controlee to add.
+ * @slot_duration_us: Duration of a FiRa slot in us (according to session
+ * config).
+ * @active_session: True if controlee addition should be postponed as ranging
+ * session is active.
+ *
+ * If succeed, function as taken ownership of the structure and removed it
+ * from the original list.
+ *
+ * Return: 0 or error.
+ */
+int fira_session_new_controlee(struct fira_local *local,
+ struct fira_session *session,
+ struct fira_controlee *controlee,
+ int slot_duration_us,
+ bool active_session);
+
+/**
+ * fira_session_restart_controlees() - Restart controlee and erase pending del.
+ * @session: FiRa session context.
+ *
+ * Return: Number of controlee removed.
+ */
+void fira_session_restart_controlees(struct fira_session *session);
+
+/**
+ * fira_session_del_controlee() - Delete controlee.
+ * @session: Session.
+ * @controlee: Controlee to delete.
+ * @active_session: True if controlee deletion should be postponed as ranging
+ * session is active.
+ *
+ * If succeed, function has removed given controlee from its current list and
+ * freed it.
+ *
+ * Return: 0 or error.
+ */
+int fira_session_del_controlee(struct fira_session *session,
+ struct fira_controlee *controlee,
+ bool active_session);
+
+/**
+ * fira_session_get_controlee() - Get controlee info from short address.
+ * @session: Session.
+ * @short_addr: Short address of the controlee.
+ *
+ * Return: The corresponding controlee object or NULL.
+ */
+static inline struct fira_controlee *
+fira_session_get_controlee(struct fira_session *session, u16 short_addr)
+{
+ struct fira_controlee *controlee;
+ list_for_each_entry (controlee, &session->current_controlees, entry) {
+ if (controlee->short_addr == short_addr)
+ return controlee;
+ }
+ return NULL;
+}
+
+/**
+ * fira_session_stop_controlees() - Stop controlees.
+ * @session: Session.
+ */
+void fira_session_stop_controlees(struct fira_session *session);
+
+/**
+ * fira_session_controlees_running_count() - Get the number of running controlees.
+ * @session: Session.
+ *
+ * Return: Number of running controlees.
+ */
+int fira_session_controlees_running_count(const struct fira_session *session);
+
+/**
+ * fira_session_update_controlees() - Update controlee's states.
+ * @local: FiRa context.
+ * @session: Session to test.
+ */
+void fira_session_update_controlees(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_is_ready() - Test whether a session is ready to be started.
+ * @local: FiRa context.
+ * @session: Session to test.
+ *
+ * Return: true if the session can be started.
+ */
+bool fira_session_is_ready(const struct fira_local *local,
+ const struct fira_session *session);
+
+/**
+ * fira_session_get_meas_seq_step() - Get current measurement step.
+ * @session: Session.
+ *
+ * Return: Current Measurement Sequence step for given session.
+ */
+static inline const struct fira_measurement_sequence_step *
+fira_session_get_meas_seq_step(const struct fira_session *session)
+{
+ const struct fira_measurement_sequence *seq =
+ &session->measurements.sequence;
+
+ return &seq->steps[session->measurements.index];
+}
+
+/**
+ * fira_session_get_rx_ant_set() - Get Rx antenna set for a given message ID.
+ * @session: Session.
+ * @message_id: Message ID of FiRa frame.
+ *
+ * Return: Adequate antenna set id for given frame and session parameters.
+ */
+static inline int
+fira_session_get_rx_ant_set(const struct fira_session *session,
+ enum fira_message_id message_id)
+{
+ const struct fira_session_params *params = &session->params;
+ const struct fira_measurement_sequence_step *step =
+ fira_session_get_meas_seq_step(session);
+
+ if (message_id > FIRA_MESSAGE_ID_RFRAME_MAX)
+ return step->rx_ant_set_nonranging;
+
+ /* TODO: replace this test by device_role == FIRA_DEVICE_ROLE_INITIATOR
+ * as soon as this feature is supported */
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER)
+ return step->rx_ant_sets_ranging[0];
+ else
+ switch (step->type) {
+ case FIRA_MEASUREMENT_TYPE_RANGE:
+ case FIRA_MEASUREMENT_TYPE_AOA:
+ case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH:
+ case FIRA_MEASUREMENT_TYPE_AOA_ELEVATION:
+ return step->rx_ant_sets_ranging[0];
+ case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION:
+ return step->rx_ant_sets_ranging
+ [message_id == FIRA_MESSAGE_ID_RANGING_FINAL];
+ default:
+ return -1;
+ }
+ return -1;
+}
+
+/**
+ * fira_session_set_range_data_ntf_status() - Update range_data_ntf_status
+ * for a given ranging.
+ * @session: FiRa session.
+ * @ranging_info: ranging data to be evaluated.
+ */
+void fira_session_set_range_data_ntf_status(
+ const struct fira_session *session,
+ struct fira_ranging_info *ranging_info);
+
+/**
+ * fira_session_report() - Report state change and ranging result for a session.
+ * @local: FiRa context.
+ * @session: Session to report.
+ * @report_info: report information to exploit for the reporting.
+ */
+void fira_session_report(struct fira_local *local, struct fira_session *session,
+ struct fira_report_info *report_info);
+
+/**
+ * fira_session_controlee_active() - Return whether the controlee is currently active.
+ * @controlee: Controlee.
+ *
+ * Return: True if the controlee is currently active.
+ */
+static inline bool
+fira_session_controlee_active(struct fira_controlee *controlee)
+{
+ switch (controlee->state) {
+ case FIRA_CONTROLEE_STATE_RUNNING:
+ case FIRA_CONTROLEE_STATE_PENDING_STOP:
+ case FIRA_CONTROLEE_STATE_PENDING_DEL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#endif /* NET_MCPS802154_FIRA_SESSION_H */
diff --git a/mac/fira_session_fsm.c b/mac/fira_session_fsm.c
new file mode 100644
index 0000000..3d1d478
--- /dev/null
+++ b/mac/fira_session_fsm.c
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+
+#include "fira_session_fsm_init.h"
+#include "fira_session_fsm_idle.h"
+#include "fira_session_fsm_active.h"
+#include "fira_session.h"
+#include "fira_access.h"
+#include "fira_trace.h"
+
+void fira_session_fsm_initialise(struct fira_local *local,
+ struct fira_session *session)
+{
+ list_add(&session->entry, &local->inactive_sessions);
+ session->state = &fira_session_fsm_init;
+ WARN_ON(!session->state->enter);
+ session->state->enter(local, session);
+}
+
+void fira_session_fsm_uninit(struct fira_local *local,
+ struct fira_session *session)
+{
+ if (session->state->leave)
+ session->state->leave(local, session);
+
+ trace_region_fira_session_fsm_change_state(
+ session, FIRA_SESSION_STATE_ID_DEINIT);
+ list_del(&session->entry);
+}
+
+void fira_session_fsm_change_state(
+ struct fira_local *local, struct fira_session *session,
+ const struct fira_session_fsm_state *new_state)
+{
+ if (session->state->leave)
+ session->state->leave(local, session);
+ trace_region_fira_session_fsm_change_state(session, new_state->id);
+ session->state = new_state;
+ if (session->state->enter)
+ session->state->enter(local, session);
+}
+
+bool fira_session_is_active(const struct fira_session *session)
+{
+ return session->state == &fira_session_fsm_active;
+}
+
+enum fira_session_state_id
+fira_session_get_state_id(const struct fira_session *session)
+{
+ return session->state->id;
+}
+
+int fira_session_fsm_check_parameters(const struct fira_session *session,
+ struct nlattr **attrs)
+{
+ WARN_ON(!session->state->check_parameters);
+ return session->state->check_parameters(session, attrs);
+}
+
+void fira_session_fsm_parameters_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ /* The handler is defined for all states. */
+ WARN_ON(!session->state->parameters_updated);
+ session->state->parameters_updated(local, session);
+}
+
+void fira_session_fsm_controlee_list_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ if (session->state->controlee_list_updated)
+ session->state->controlee_list_updated(local, session);
+}
+
+int fira_session_fsm_start(struct fira_local *local,
+ struct fira_session *session,
+ const struct genl_info *info)
+{
+ if (session->state->start)
+ return session->state->start(local, session, info);
+ return -EINVAL;
+}
+
+int fira_session_fsm_stop(struct fira_local *local,
+ struct fira_session *session)
+{
+ if (session->state->stop)
+ return session->state->stop(local, session);
+ return 0;
+}
+
+int fira_session_fsm_get_demand(const struct fira_local *local,
+ const struct fira_session *session,
+ u32 next_timestamp_dtu, int max_duration_dtu,
+ struct fira_session_demand *session_demand)
+{
+ /*
+ * fira_get_demand will not call this function without an
+ * active session.
+ */
+ WARN_ON(!session->state->get_demand);
+ return session->state->get_demand(local, session, next_timestamp_dtu,
+ max_duration_dtu, session_demand);
+}
+
+struct mcps802154_access *
+fira_session_fsm_get_access(struct fira_local *local,
+ struct fira_session *session,
+ const struct fira_session_demand *session_demand)
+{
+ /*
+ * fira_get_access will not call this function without an
+ * active session.
+ */
+ WARN_ON(!session->state->get_access);
+ return session->state->get_access(local, session, session_demand);
+}
+
+void fira_session_fsm_access_done(struct fira_local *local,
+ struct fira_session *session, bool error)
+{
+ WARN_ON(!session->state->access_done);
+ return session->state->access_done(local, session, error);
+}
+
+void fira_session_fsm_check_missed_ranging(struct fira_local *local,
+ struct fira_session *session,
+ u32 timestamp_dtu)
+{
+ WARN_ON(!session->state->check_missed_ranging);
+ return session->state->check_missed_ranging(local, session,
+ timestamp_dtu);
+}
diff --git a/mac/fira_session_fsm.h b/mac/fira_session_fsm.h
new file mode 100644
index 0000000..9d1b622
--- /dev/null
+++ b/mac/fira_session_fsm.h
@@ -0,0 +1,241 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_SESSION_FSM_H
+#define NET_MCPS802154_FIRA_SESSION_FSM_H
+
+#include <linux/ieee802154.h>
+
+#include "fira_access.h"
+
+/* Forward declaration. */
+struct fira_local;
+struct fira_session;
+struct fira_session_demand;
+
+/**
+ * enum fira_session_state_id - State of the FiRa session.
+ * @FIRA_SESSION_STATE_ID_INIT:
+ * Initial state, session is not ready yet.
+ * @FIRA_SESSION_STATE_ID_DEINIT:
+ * Session does not exist.
+ * @FIRA_SESSION_STATE_ID_ACTIVE:
+ * Session is currently active.
+ * @FIRA_SESSION_STATE_ID_IDLE:
+ * Session is ready to start, but not currently active.
+ */
+enum fira_session_state_id {
+ FIRA_SESSION_STATE_ID_INIT,
+ FIRA_SESSION_STATE_ID_DEINIT,
+ FIRA_SESSION_STATE_ID_ACTIVE,
+ FIRA_SESSION_STATE_ID_IDLE,
+};
+
+/**
+ * struct fira_session_fsm_state - FiRa session FSM state.
+ *
+ * This structure contains the callbacks which are called on an event to handle
+ * the transition from the current state.
+ */
+struct fira_session_fsm_state {
+ /** @id: Name of state. */
+ enum fira_session_state_id id;
+ /** @enter: Run when the state is entered. */
+ void (*enter)(struct fira_local *local, struct fira_session *session);
+ /** @leave: Run when the state is left. */
+ void (*leave)(struct fira_local *local, struct fira_session *session);
+ /** @check_parameters: Handle a check parameters. */
+ int (*check_parameters)(const struct fira_session *session,
+ struct nlattr **attrs);
+ /** @parameters_updated: Handle parameters updated event. */
+ void (*parameters_updated)(struct fira_local *local,
+ struct fira_session *session);
+ /** @controlee_list_updated: Handle controlee list updated event. */
+ void (*controlee_list_updated)(struct fira_local *local,
+ struct fira_session *session);
+ /** @start: Handle start. */
+ int (*start)(struct fira_local *local, struct fira_session *session,
+ const struct genl_info *info);
+ /** @stop: Handle stop. */
+ int (*stop)(struct fira_local *local, struct fira_session *session);
+ /** @get_demand: Handle the get demand. */
+ int (*get_demand)(const struct fira_local *local,
+ const struct fira_session *session,
+ u32 next_timestamp_dtu, int max_duration_dtu,
+ struct fira_session_demand *session_demand);
+ /** @get_access: Handle the get access. */
+ struct mcps802154_access *(*get_access)(
+ struct fira_local *local, struct fira_session *session,
+ const struct fira_session_demand *session_demand);
+ /** @access_done: Handle end of access. */
+ void (*access_done)(struct fira_local *local,
+ struct fira_session *session, bool error);
+ /** @check_missed_ranging: Handle the check of missed ranging. */
+ void (*check_missed_ranging)(struct fira_local *local,
+ struct fira_session *session,
+ u32 timestamp_dtu);
+};
+
+/**
+ * fira_session_fsm_change_state() - Change the state of the FSM.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @new_state: New to state to use in the FSM.
+ *
+ * This function shall be called only by fira_session_fsm files.
+ */
+void fira_session_fsm_change_state(
+ struct fira_local *local, struct fira_session *session,
+ const struct fira_session_fsm_state *new_state);
+
+/**
+ * fira_session_is_active() - Return the active status of the session.
+ * @session: Session context.
+ *
+ * Return: True is the session is active, false otherwise.
+ */
+bool fira_session_is_active(const struct fira_session *session);
+
+/**
+ * fira_session_fsm_initialise() - Initialize the FSM.
+ * @local: FiRa context.
+ * @session: Session context.
+ */
+void fira_session_fsm_initialise(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_fsm_uninit() - Uninitialise the FSM.
+ * @local: FiRa context.
+ * @session: Session context.
+ */
+void fira_session_fsm_uninit(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_get_state_id() - Get current state id (for reporting).
+ * @session: Session context.
+ *
+ * Return: State id value.
+ */
+enum fira_session_state_id
+fira_session_get_state_id(const struct fira_session *session);
+
+/**
+ * fira_session_fsm_check_parameters() - Check parameters change ask by upper
+ * layer.
+ * @session: Session context.
+ * @attrs: Netlink attributs.
+ *
+ * Return: 0 on success, errno when change are refused.
+ */
+int fira_session_fsm_check_parameters(const struct fira_session *session,
+ struct nlattr **attrs);
+
+/**
+ * fira_session_fsm_parameters_updated() - Parameters updated by upper layer.
+ * @local: FiRa context.
+ * @session: Session context.
+ */
+void fira_session_fsm_parameters_updated(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_fsm_controlee_list_updated() - Controlee list updated by upper
+ * layer.
+ * @local: FiRa context.
+ * @session: Session context.
+ */
+void fira_session_fsm_controlee_list_updated(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_fsm_start() - Start request from upper layer.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @info: Netlink info used only for the portid.
+ *
+ * Return: 0 on success, errno otherwise.
+ */
+int fira_session_fsm_start(struct fira_local *local,
+ struct fira_session *session,
+ const struct genl_info *info);
+
+/**
+ * fira_session_fsm_stop() - Stop request from upper layer.
+ * @local: FiRa context.
+ * @session: Session context.
+ *
+ * Return: 0 on success, errno otherwise.
+ */
+int fira_session_fsm_stop(struct fira_local *local,
+ struct fira_session *session);
+
+/**
+ * fira_session_fsm_get_demand() - Request the next ranging round of the session.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @next_timestamp_dtu: Timestamp to start a demand.
+ * @max_duration_dtu: Max duration obligation to be consider by the session.
+ * @session_demand: Wish of the session when the return value is 1.
+ *
+ * Return: 1 for a session demand otherwise 0 for no demand.
+ */
+int fira_session_fsm_get_demand(const struct fira_local *local,
+ const struct fira_session *session,
+ u32 next_timestamp_dtu, int max_duration_dtu,
+ struct fira_session_demand *session_demand);
+
+/**
+ * fira_session_fsm_get_access() - Get access to process.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @session_demand: Next access built by the get_demand.
+ *
+ * Return: The access for fproc, or NULL pointer.
+ */
+struct mcps802154_access *
+fira_session_fsm_get_access(struct fira_local *local,
+ struct fira_session *session,
+ const struct fira_session_demand *session_demand);
+
+/**
+ * fira_session_fsm_access_done() - End of the access to report.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @error: True when an error happen.
+ */
+void fira_session_fsm_access_done(struct fira_local *local,
+ struct fira_session *session, bool error);
+
+/**
+ * fira_session_fsm_check_missed_ranging() - Report a missed ranging if exist.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @timestamp_dtu: Timestamp dtu where no fallback is possible.
+ */
+void fira_session_fsm_check_missed_ranging(struct fira_local *local,
+ struct fira_session *session,
+ u32 timestamp_dtu);
+
+#endif /* NET_MCPS802154_FIRA_SESSION_FSM_H */
diff --git a/mac/fira_session_fsm_active.c b/mac/fira_session_fsm_active.c
new file mode 100644
index 0000000..75ebc7c
--- /dev/null
+++ b/mac/fira_session_fsm_active.c
@@ -0,0 +1,988 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <net/mcps802154_frame.h>
+#include <net/fira_region_nl.h>
+#include <linux/errno.h>
+#include <linux/math64.h>
+
+#include "fira_round_hopping_sequence.h"
+#include "fira_session_fsm_init.h"
+#include "fira_session_fsm_idle.h"
+#include "fira_session_fsm_active.h"
+#include "fira_session.h"
+#include "fira_trace.h"
+#include "warn_return.h"
+
+/**
+ * list_move_to_active() - Move from inactive list to active list.
+ * @local: FiRa context.
+ * @session: Session context.
+ */
+static void list_move_to_active(struct fira_local *local,
+ struct fira_session *session)
+{
+ struct list_head *position = &local->active_sessions;
+ struct fira_session *tmp;
+
+ /*
+ * Search the position to maintain a list sorted from highest to
+ * lowest priority. And for the same priority keep the call
+ * order (moved to the tail).
+ * Highest value of priority is the highest priority.
+ * Range of priority is between: 0 to FIRA_PRIORITY_MAX.
+ */
+ list_for_each_entry (tmp, &local->active_sessions, entry) {
+ if (session->params.priority <= tmp->params.priority)
+ position = &tmp->entry;
+ else
+ break;
+ }
+ list_move(&session->entry, position);
+}
+
+/**
+ * get_channel() - Retrieve the channel to applied on the access.
+ * @local: FiRa context.
+ * @session: Session context.
+ *
+ * Return: The channel.
+ */
+static const struct mcps802154_channel *
+get_channel(struct fira_local *local, const struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+
+ if (params->channel_number || params->preamble_code_index) {
+ const struct mcps802154_channel *channel =
+ mcps802154_get_current_channel(local->llhw);
+
+ local->channel = *channel;
+ if (params->channel_number)
+ local->channel.channel = params->channel_number;
+ if (params->preamble_code_index)
+ local->channel.preamble_code =
+ params->preamble_code_index;
+ return &local->channel;
+ }
+ return NULL;
+}
+
+/**
+ * get_round_index() - Return the round index for a specific block index.
+ * @session: Session context.
+ * @block_index: Block index.
+ *
+ * Return: Round index freshly computed or the round index saved.
+ */
+static int get_round_index(const struct fira_session *session, int block_index)
+{
+ const struct fira_session_params *params = &session->params;
+ int expected_block_index;
+
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ if (!params->round_hopping)
+ return 0;
+ /*
+ * Avoid to rebuild the round_index.
+ * The condition is true on first get_access too.
+ */
+ if (session->controller.next_block_index == block_index)
+ return session->next_round_index;
+ break;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ if (!session->controlee.hopping_mode)
+ return 0;
+ /*
+ * Return the round index received, only when the block index
+ * match with expected block index.
+ */
+ expected_block_index = session->controlee.block_index_sync +
+ session->block_stride_len + 1;
+ if (expected_block_index == block_index &&
+ session->controlee.next_round_index_valid)
+ return session->next_round_index;
+ break;
+ }
+ return fira_round_hopping_sequence_get(session, block_index);
+}
+
+/**
+ * get_rx_margin_duration_dtu() - Build the maximum margin tolerance for Rx.
+ * @local: FiRa context.
+ * @session: Session context.
+ *
+ * Return: Duration to apply on first and Rx frame of controlee's access.
+ */
+static int get_rx_margin_duration_dtu(const struct fira_local *local,
+ const struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ s64 duration_dtu = (s64)(session->block_stride_len + 1) *
+ params->block_duration_dtu;
+ /*
+ * TODO: Unit test should be able to predic timestamp.
+ * - Replace 'local->block_duration_rx_margin_ppm by'
+ * UWB_BLOCK_DURATION_MARGIN_PPM
+ * - Remove 'local' from args.
+ */
+ return div64_s64(duration_dtu * local->block_duration_rx_margin_ppm,
+ 1000000);
+}
+
+/**
+ * get_next_access_timestamp_dtu() - Build the next access timestamp.
+ * @local: FiRa context.
+ * @session: Session context.
+ *
+ * Return: Timestamp in dtu.
+ */
+static u32 get_next_access_timestamp_dtu(const struct fira_local *local,
+ const struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ int add_blocks = session->block_stride_len + 1;
+ int next_block_index = session->block_index + add_blocks;
+ int next_round_index = get_round_index(session, next_block_index);
+ int round_duration_dtu =
+ params->round_duration_slots * params->slot_duration_dtu;
+ u32 next_block_start_dtu = session->block_start_dtu +
+ add_blocks * params->block_duration_dtu +
+ next_round_index * round_duration_dtu;
+
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ return next_block_start_dtu;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ return next_block_start_dtu -
+ get_rx_margin_duration_dtu(local, session);
+ }
+}
+
+/**
+ * is_controlee_synchronised() - Answer to the question of the synchronisation
+ * status.
+ * @local: FiRa context.
+ * @session: Session context.
+ *
+ * Return: True when the controlee session is still synchronized.
+ */
+static bool is_controlee_synchronised(const struct fira_local *local,
+ const struct fira_session *session)
+{
+#define FIRA_DRIFT_TOLERANCE_PPM 30
+ const struct fira_session_params *params = &session->params;
+ int n_unsync_blocks;
+ s64 unsync_duration_dtu;
+ int drift_ppm, rx_margin_ppm;
+
+ if (session->controlee.synchronised) {
+ n_unsync_blocks = session->block_index -
+ session->controlee.block_index_sync;
+ unsync_duration_dtu =
+ n_unsync_blocks * params->block_duration_dtu;
+ drift_ppm = div64_s64(unsync_duration_dtu *
+ FIRA_DRIFT_TOLERANCE_PPM,
+ 1000000);
+ rx_margin_ppm = get_rx_margin_duration_dtu(local, session);
+
+ trace_region_fira_is_controlee_synchronised(session, drift_ppm,
+ rx_margin_ppm);
+ if (drift_ppm <= rx_margin_ppm)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * is_stopped() - Is the session stopped?
+ * @session: Session context.
+ *
+ * Return: True when the session is stopped, false otherwise.
+ */
+static bool is_stopped(struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ int nb_controlee = fira_session_controlees_running_count(session);
+
+ if (params->max_rr_retry &&
+ session->n_ranging_round_retry >= params->max_rr_retry)
+ session->stop_no_response = true;
+
+ return (session->stop_request && !nb_controlee) ||
+ session->stop_inband || session->stop_no_response;
+}
+
+/**
+ * forward_to_next_ranging() - Update the session to forward to next ranging.
+ * @session: Session context.
+ * @n_ranging: Number of ranging (forward).
+ */
+static void forward_to_next_ranging(struct fira_session *session, int n_ranging)
+{
+ const struct fira_session_params *params = &session->params;
+ int blocks_per_ranging = session->block_stride_len + 1;
+ int add_blocks = n_ranging * blocks_per_ranging;
+ int duration_dtu = add_blocks * params->block_duration_dtu;
+
+ session->block_index += add_blocks;
+ session->block_start_dtu += duration_dtu;
+ session->n_ranging_round_retry += n_ranging;
+}
+
+/**
+ * ranging_round_done() - Update controlee and notify the upper layer and rotate crypto keys.
+ * @local: FiRa context.
+ * @session: Session context.
+ * @report_info: Report information to forward fira_session_report.
+ */
+static void ranging_round_done(struct fira_local *local,
+ struct fira_session *session,
+ struct fira_report_info *report_info)
+{
+ const struct fira_session_params *params = &session->params;
+
+ session->next_access_timestamp_dtu =
+ get_next_access_timestamp_dtu(local, session);
+ report_info->stopped = is_stopped(session);
+
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ /* Update controlee's states between two ranging round. */
+ fira_session_update_controlees(local, session);
+ fira_sts_rotate_keys(session);
+ break;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ /* Did the controlee's access lose the synchronisation? */
+ session->controlee.synchronised =
+ is_controlee_synchronised(local, session);
+ if (session->controlee.synchronised)
+ fira_sts_rotate_keys(session);
+ break;
+ }
+
+ fira_session_report(local, session, report_info);
+
+ if (report_info->stopped) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ } else {
+ forward_to_next_ranging(session, 1);
+ }
+}
+
+static void fira_session_fsm_active_enter(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+
+ session->stop_request = false;
+ session->stop_inband = false;
+ session->stop_no_response = false;
+ session->measurements.n_total_achieved = 0;
+ session->block_stride_len = params->block_stride_len;
+ session->controlee.synchronised = false;
+ session->controlee.hopping_mode = false;
+ session->controlee.next_round_index_valid = false;
+ session->controlee.block_index_sync = 0;
+ session->round_index = 0;
+ /*
+ * Initialize to 1 when initiation_time_ms is 0,
+ * because first add_blocks built will be 0.
+ */
+ session->n_ranging_round_retry = params->initiation_time_ms ? 0 : 1;
+
+ list_move_to_active(local, session);
+}
+
+static void fira_session_fsm_active_leave(struct fira_local *local,
+ struct fira_session *session)
+{
+ fira_sts_deinit(session);
+ list_move(&session->entry, &local->inactive_sessions);
+ fira_session_restart_controlees(session);
+}
+
+static int
+fira_session_fsm_active_check_parameters(const struct fira_session *session,
+ struct nlattr **attrs)
+{
+ const struct fira_session_params *params = &session->params;
+ enum fira_session_param_attrs i;
+
+ for (i = FIRA_SESSION_PARAM_ATTR_UNSPEC + 1;
+ i <= FIRA_SESSION_PARAM_ATTR_MAX; i++) {
+ const struct nlattr *attr = attrs[i];
+
+ if (!attr)
+ /* Attribute not provided. */
+ continue;
+
+ switch (i) {
+ case FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE:
+ case FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI:
+ case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI: /* Allowed for all device type. */
+ break;
+ case FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH:
+ /* Allowed only for controller. */
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER)
+ continue;
+ return -EBUSY;
+ default:
+ return -EBUSY;
+ }
+ }
+ return 0;
+}
+
+static void
+fira_session_fsm_active_parameters_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ int i;
+
+ if (session->measurements.reset) {
+ for (i = 0; i < params->meas_seq.n_steps; i++) {
+ const struct fira_measurement_sequence_step *step;
+
+ step = &params->meas_seq.steps[i];
+ trace_region_fira_session_meas_seq_params(session, step,
+ i);
+ }
+ }
+}
+
+static int fira_session_fsm_active_start(struct fira_local *local,
+ struct fira_session *session,
+ const struct genl_info *info)
+{
+ /* Already started. */
+ return 0;
+}
+
+static int fira_session_fsm_active_stop(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ struct fira_report_info report_info = {
+ .stopped = true,
+ };
+
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ if (local->current_session == NULL) {
+ session->stop_request = true;
+ /*
+ * FIXME/BUG:
+ * In unit test, the stop_tx_frame_error (or rx),
+ * stop the current access and trig a broken event.
+ * Then the TearDown request a session_stop, but
+ * there is still more than one controlee running.
+ *
+ * Normally the controller must do an access to
+ * announce a stop of all controlees.
+ * But here, there is a missing mechanism, as:
+ * - notify_stop is not called,
+ * - error is only a boolean in access_done.
+ * And the error boolean is True on -ETIME.
+ *
+ * And as the current_session equal to NULL is a
+ * normal behavior in multi-region. We have a bug.
+ */
+ fira_session_report(local, session, &report_info);
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ } else if (session->n_current_controlees) {
+ /*
+ * A ranging round to announced all controlee
+ * stopped is required.
+ */
+ fira_session_stop_controlees(session);
+ session->stop_request = true;
+ } else if (local->current_session != session) {
+ session->stop_request = true;
+ fira_session_report(local, session, &report_info);
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ }
+ break;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ session->stop_request = true;
+ if (local->current_session != session) {
+ fira_session_report(local, session, &report_info);
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ }
+ break;
+ }
+ mcps802154_reschedule(local->llhw);
+ return 0;
+}
+
+static int
+fira_session_fsm_active_get_demand(const struct fira_local *local,
+ const struct fira_session *session,
+ u32 next_timestamp_dtu, int max_duration_dtu,
+ struct fira_session_demand *session_demand)
+{
+ const struct fira_session_params *params = &session->params;
+ int round_duration_dtu =
+ params->round_duration_slots * params->slot_duration_dtu;
+ u32 block_start_dtu;
+ u32 timestamp_dtu;
+ u32 duration_dtu;
+ int round_index = 0;
+ u32 block_index;
+ int add_blocks = 0;
+ int rx_timeout_dtu = 0;
+ int slot_count;
+
+ /* First, determine two dates: block_start_dtu and timestamp_dtu. */
+ if (!is_before_dtu(session->next_access_timestamp_dtu,
+ next_timestamp_dtu)) {
+ /*
+ * block_start_dtu is set in the future or present.
+ * It's happen mainly when initiation_time_ms is not zero.
+ */
+ timestamp_dtu = block_start_dtu = session->block_start_dtu;
+ } else {
+ /*
+ * block start is in the past, we have to evaluate the
+ * new block start dtu.
+ * It's could be the same with a controlee not synchronized.
+ *
+ * Example of time graph of what's could happen:
+ *
+ * -------x----------------x----------------x-------
+ * #x - 1 | Block Index #x | #x + 1 | #x + 2
+ * -------x----------------x------x---------x-------> Time
+ * |<--------------------->|
+ * Block | |
+ * start | next_timestamp_dtu
+ * |
+ * duration_from_block_start_dtu
+ *
+ * In the graph example, one block is missed, but it's could be
+ * more or less(controlee).
+ */
+ int duration_from_block_start_dtu =
+ next_timestamp_dtu - session->block_start_dtu;
+
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE &&
+ !session->controlee.synchronised) {
+ /*
+ * With a controlee not synchronized, consider the
+ * block as missed when there is no more left duration
+ * in the current block.
+ *
+ * block
+ * start next_timestamp_dtu
+ * | |
+ * -------x-----------------x---x------
+ * #x - 1 | #x : | #x + 1
+ * -------x-----------------x---x-----> Time
+ * | |
+ * |<--------------->|
+ * |
+ * |
+ * add_blocks = Time / block_duration
+ */
+ add_blocks = duration_from_block_start_dtu /
+ params->block_duration_dtu;
+ } else {
+ int blocks_per_ranging = session->block_stride_len + 1;
+
+ /*
+ * With a controller or a controlee synchronized,
+ * consider a block started as a missed block.
+ */
+ add_blocks = (duration_from_block_start_dtu +
+ params->block_duration_dtu - 1) /
+ params->block_duration_dtu;
+ /*
+ * Block stride feature announced/received in last
+ * access.
+ */
+ if (session->block_stride_len) {
+ int n = add_blocks % blocks_per_ranging;
+
+ /*
+ * Add more block(s) to reach block stride
+ * modulo.
+ */
+ if (n)
+ add_blocks += blocks_per_ranging - n;
+ }
+ }
+
+ /* Compute block start dtu. 'add_blocks' can be zero. */
+ block_start_dtu = session->block_start_dtu +
+ add_blocks * params->block_duration_dtu;
+ /* Determine the access timestamp. */
+ if (is_before_dtu(block_start_dtu, next_timestamp_dtu))
+ /*
+ * Only the controlee not synchronized can have its
+ * next access timestamp_dtu in the future of the
+ * block start.
+ *
+ * block_start_dtu
+ * |
+ * -------x-----------------x----------
+ * #x - 1 | Block index #x | #x + 1
+ * -------x------x----------x----------> Time
+ * |
+ * next_timestamp_dtu
+ */
+ timestamp_dtu = next_timestamp_dtu;
+ else
+ timestamp_dtu = block_start_dtu;
+ }
+
+ /*
+ * As block_start_dtu is updated with new timestamp in the future,
+ * or still in the past (controlee), then other variables will be
+ * build to fill the session_demand output.
+ *
+ * In other words, locale variables can have a new values which
+ * represent the next(future) block/access/index/...
+ * Or keep +/- the same values.
+ */
+ block_index = session->block_index + add_blocks;
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ slot_count = fira_session_get_slot_count(session);
+ round_index = get_round_index(session, block_index);
+ timestamp_dtu =
+ block_start_dtu + round_index * round_duration_dtu;
+ duration_dtu = slot_count * params->slot_duration_dtu;
+ if (max_duration_dtu &&
+ is_before_dtu(next_timestamp_dtu + max_duration_dtu,
+ timestamp_dtu + duration_dtu))
+ /* No way to start an access. */
+ return 0;
+ break;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ if (session->controlee.synchronised) {
+ int margin_less, margin_more;
+
+ /*
+ * Time graph to illustrate the controlee access
+ * and its synchronization.
+ *
+ * Next Timestamp
+ * timestamp without margin
+ * | |
+ * ----x--------x----x----x-----> Time
+ * |<---|--->|
+ * Rx enabled Rx timeout
+ * @timestamp_dtu
+ *
+ * rx_margin is the maximum margin accepted.
+ */
+ round_index = get_round_index(session, block_index);
+ timestamp_dtu += round_index * round_duration_dtu;
+ margin_less = margin_more =
+ get_rx_margin_duration_dtu(local, session);
+ if (timestamp_dtu - next_timestamp_dtu < margin_less)
+ /*
+ * Avoid to build a timestamp_dtu which is in
+ * the past of next_timestamp_dtu.
+ */
+ margin_less =
+ timestamp_dtu - next_timestamp_dtu;
+ timestamp_dtu -= margin_less;
+ rx_timeout_dtu = margin_less + margin_more;
+ duration_dtu = round_duration_dtu + margin_less;
+ if (max_duration_dtu &&
+ is_before_dtu(next_timestamp_dtu + max_duration_dtu,
+ timestamp_dtu + duration_dtu))
+ /* No way to start an access. */
+ return 0;
+ } else {
+ int unsync_max_duration_dtu =
+ params->block_duration_dtu +
+ params->slot_duration_dtu;
+
+ /*
+ * A controlee not synchronized is allowed to start/end
+ * anywhere in the block to find the controller.
+ * But the session continue to work with block duration
+ * to provide:
+ * - Regular reporting.
+ * - Time-sharing in multi-session/multi-region.
+ *
+ * Time graph:
+ *
+ * unsync_max_duration_dtu
+ * |<----------------------------->|
+ * | |
+ * --+---x-----------------------|-------x------>
+ * | Block #x | Block #x + 1
+ * --+---x-----------------------|---x---x------> Time
+ * |<------------------------->|<->|
+ * block duration slot duration
+ *
+ * The unsync duration is bigger than the block, to
+ * listen the medium for one block min. But to avoid
+ * to be in late on the next access, we must add one
+ * slot.
+ */
+ if (max_duration_dtu &&
+ is_before_dtu(next_timestamp_dtu + max_duration_dtu,
+ timestamp_dtu +
+ params->slot_duration_dtu))
+ /* No way to start an access. */
+ return 0;
+ else if (!max_duration_dtu ||
+ is_before_dtu(timestamp_dtu +
+ unsync_max_duration_dtu,
+ next_timestamp_dtu +
+ max_duration_dtu))
+ /* Maximum access granted. */
+ duration_dtu = unsync_max_duration_dtu;
+ else
+ /* Adjusted access duration. */
+ duration_dtu = next_timestamp_dtu +
+ max_duration_dtu - timestamp_dtu;
+
+ /*
+ * 'rx_timeout_dtu' is set to allow the reception
+ * of the control frame close to the end of the
+ * access, and so be synchronized for next block.
+ *
+ * But if the control message is received
+ * at the end of access, the other frames
+ * will be dropped to respect the duration_dtu.
+ * See: rx control frame.
+ */
+ rx_timeout_dtu =
+ duration_dtu - params->slot_duration_dtu;
+ }
+ break;
+ }
+
+ /*
+ * Update the session demand (output):
+ * - rx_timeout_dtu: Used only by the controlee.
+ *
+ * On the get_access, the session_demand will be applied
+ * to the session. Otherwise the session_demand is dropped.
+ *
+ * In a way, session_demand represent the next access.
+ */
+ *session_demand = (struct fira_session_demand){
+ .block_start_dtu = block_start_dtu,
+ .timestamp_dtu = timestamp_dtu,
+ .max_duration_dtu = duration_dtu,
+ .add_blocks = add_blocks,
+ .rx_timeout_dtu = rx_timeout_dtu,
+ .round_index = round_index,
+ };
+ trace_region_fira_session_fsm_active_get_demand_return(local, session,
+ session_demand);
+ return 1;
+}
+
+static struct mcps802154_access *
+fira_session_fsm_active_get_access(struct fira_local *local,
+ struct fira_session *session,
+ const struct fira_session_demand *fsd)
+{
+ const struct fira_session_params *params = &session->params;
+ const struct mcps802154_hrp_uwb_params *hrp = &session->hrp_uwb_params;
+ struct mcps802154_access *access = &local->access;
+ int blocks_per_ranging;
+
+ /*
+ * , ,
+ * (\____/) Important:
+ * (_oo_)
+ * (O) It's almost forbidden to update session
+ * __||__ \) content for a controlee.
+ * []/______\[] /
+ * / \______/ \/ Because, the session can change on control
+ * / /__\ frame reception (static STS only).
+ * (\ /____\
+ */
+ local->current_session = session;
+
+ /*
+ * Update common access fields for controlee and controller.
+ * hrp must stay const, see 'Important' above.
+ */
+ access->method = MCPS802154_ACCESS_METHOD_MULTI;
+ access->frames = local->frames;
+ access->n_frames = 0;
+ access->channel = get_channel(local, session);
+ access->hrp_uwb_params = hrp;
+
+ /*
+ * For the ranging round failure counter, consider these rounds as
+ * failed. And reset the counter in the access_done if success.
+ */
+ blocks_per_ranging = session->block_stride_len + 1;
+ session->n_ranging_round_retry += fsd->add_blocks / blocks_per_ranging;
+
+ /* Continue to 'device type' access. */
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER)
+ return fira_get_access_controller(local, fsd);
+ return fira_get_access_controlee(local, fsd);
+}
+
+static void fira_session_fsm_active_access_done(struct fira_local *local,
+ struct fira_session *session,
+ bool error)
+{
+ const struct fira_session_params *params = &session->params;
+ const struct fira_measurement_sequence_step *step;
+ struct fira_report_info report_info = {
+ .ranging_data = local->ranging_info,
+ .n_ranging_data = local->n_ranging_info,
+ .stopped_controlees = local->stopped_controlees,
+ .n_stopped_controlees = local->n_stopped_controlees,
+ .diagnostics = local->diagnostics,
+ .slots = local->slots,
+ .n_slots = local->access.n_frames,
+ };
+ struct fira_ranging_info *ri;
+ int i;
+
+ /* Update local. */
+ local->current_session = NULL;
+
+ if (error) {
+ /*
+ * FIXME:
+ * Why corrupt all status, the last slot_index is not
+ * enough?
+ * TODO: Proposal:
+ * - Set INTERNAL_ERROR on status during the get_access.
+ * - Update status on tx_return/rx_frame.
+ * - Update testu which expect the wrong status.
+ */
+ for (i = 0; i < local->n_ranging_info; i++) {
+ ri = &local->ranging_info[i];
+ ri->status = FIRA_STATUS_RANGING_INTERNAL_ERROR;
+ }
+ } else {
+ for (i = 0; i < local->n_ranging_info; i++) {
+ ri = &local->ranging_info[i];
+ if (ri->status != FIRA_STATUS_RANGING_SUCCESS)
+ break;
+ }
+ /* Reset ranging round failure counter. */
+ if (i == local->n_ranging_info)
+ session->n_ranging_round_retry = 0;
+ }
+
+ session->measurements.n_achieved++;
+ session->measurements.n_total_achieved++;
+ step = fira_session_get_meas_seq_step(session);
+ if (session->measurements.reset) {
+ /* Copy new measurement sequence. */
+ session->measurements.sequence = params->meas_seq;
+ session->measurements.index = 0;
+ session->measurements.n_achieved = 0;
+ session->measurements.reset = false;
+ } else if (session->measurements.n_achieved >= step->n_measurements) {
+ struct fira_measurement_sequence *seq =
+ &session->measurements.sequence;
+
+ session->measurements.n_achieved = 0;
+ session->measurements.index++;
+ session->measurements.index %= seq->n_steps;
+ }
+ /* Check max number of measurements. */
+ if (params->max_number_of_measurements &&
+ session->measurements.n_total_achieved >=
+ params->max_number_of_measurements) {
+ session->stop_request = true;
+ }
+
+ ranging_round_done(local, session, &report_info);
+}
+
+static void
+fira_session_fsm_active_check_missed_ranging(struct fira_local *local,
+ struct fira_session *session,
+ u32 timestamp_dtu)
+{
+ const struct fira_session_params *params = &session->params;
+ int next_block_start_dtu =
+ session->block_start_dtu + params->block_duration_dtu;
+ bool is_missed_ranging_round = false;
+
+ /*
+ * Example of possible timings (without hopping):
+ *
+ * check(timestamp_dtu)
+ * Ok Miss Miss |
+ * Session: [--] [--] [--] | [--]
+ * ------x---------x---------------x--------> Time
+ * | |
+ * block_start_dtu next_access_timestamp_dtu
+ *
+ * Tips:
+ * - 'session->block_start_dtu' is the block start of the last access.
+ * - 'session->next_access_timestamp_dtu' value can be:
+ * - Next block start when hopping is disabled.
+ * - Beyond the next block start when hopping is enabled.
+ * - When the session haven't been check since a long time,
+ * many blocks could be missed.
+ */
+
+ /* First, determine if there is missed ranging round. */
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE &&
+ !session->controlee.synchronised) {
+ /* Consider the block as missed when next block is reached. */
+ if (!is_before_dtu(timestamp_dtu, next_block_start_dtu))
+ is_missed_ranging_round = true;
+ } else if (is_before_dtu(session->next_access_timestamp_dtu,
+ timestamp_dtu)) {
+ /* A late is not accepted here. */
+ is_missed_ranging_round = true;
+ }
+
+ /* Compute the number of missed ranging. */
+ if (is_missed_ranging_round) {
+ int blocks_per_ranging = session->block_stride_len + 1;
+ int add_blocks = 0;
+
+ /* Drift probably due to multi-session or multi-region. */
+ if (is_before_dtu(next_block_start_dtu, timestamp_dtu))
+ add_blocks = (timestamp_dtu - next_block_start_dtu) /
+ params->block_duration_dtu;
+ if (add_blocks >= blocks_per_ranging) {
+ int n_ranging_failed = add_blocks / blocks_per_ranging;
+
+ if (params->max_rr_retry &&
+ session->n_ranging_round_retry + n_ranging_failed >
+ params->max_rr_retry) {
+ /*
+ * Avoid to set a block index bigger than the
+ * max ranging round retry in the report.
+ */
+ n_ranging_failed =
+ params->max_rr_retry -
+ session->n_ranging_round_retry;
+ }
+ forward_to_next_ranging(session, n_ranging_failed);
+ }
+ }
+
+ /* Finally, do the missed ranging round report. */
+ if (is_missed_ranging_round) {
+ struct fira_report_info report_info = {};
+ __le16 *pend_del;
+ struct fira_ranging_info *ri;
+ int j, k;
+ struct fira_controlee *controlee;
+
+ /*
+ * \\\||||||////
+ * \\ ~ ~ //
+ * ( @ @ )
+ * _________ oOOo-(_)-oOOo________________________________
+ * WARN_RETURN_VOID_ON: Because the 'local' information will
+ * be used until the end of this bloc.
+ * So this function must not be called during an access,
+ * to avoid to use a shared memory already used by current
+ * session.
+ * ________________Oooo.__________________________________
+ * .oooO ( )
+ * ( ) ) /
+ * \ ( (_/
+ * \_)
+ */
+ WARN_RETURN_VOID_ON(local->current_session);
+ /* Build a missed ranging round report. */
+ report_info.ranging_data = local->ranging_info;
+ switch (params->device_type) {
+ default:
+ case FIRA_DEVICE_TYPE_CONTROLLER:
+ pend_del = local->stopped_controlees;
+ j = k = 0;
+ list_for_each_entry (controlee,
+ &session->current_controlees,
+ entry) {
+ switch (controlee->state) {
+ case FIRA_CONTROLEE_STATE_RUNNING:
+ case FIRA_CONTROLEE_STATE_PENDING_STOP:
+ case FIRA_CONTROLEE_STATE_PENDING_DEL:
+ ri = &local->ranging_info[j];
+ *ri = (struct fira_ranging_info){
+ .short_addr =
+ controlee->short_addr,
+ .status =
+ FIRA_STATUS_RANGING_TX_FAILED,
+ };
+ j++;
+ break;
+ default:
+ pend_del[k++] = controlee->short_addr;
+ break;
+ }
+ }
+ report_info.stopped_controlees = pend_del;
+ report_info.n_stopped_controlees = k,
+ report_info.n_ranging_data = j;
+ break;
+ case FIRA_DEVICE_TYPE_CONTROLEE:
+ ri = &local->ranging_info[0];
+ *ri = (struct fira_ranging_info){
+ .short_addr = params->controller_short_addr,
+ .status = FIRA_STATUS_RANGING_RX_TIMEOUT,
+ };
+ report_info.n_ranging_data = 1;
+ break;
+ }
+ ranging_round_done(local, session, &report_info);
+ }
+}
+
+const struct fira_session_fsm_state fira_session_fsm_active = {
+ .id = FIRA_SESSION_STATE_ID_ACTIVE,
+ .enter = fira_session_fsm_active_enter,
+ .leave = fira_session_fsm_active_leave,
+ .check_parameters = fira_session_fsm_active_check_parameters,
+ .parameters_updated = fira_session_fsm_active_parameters_updated,
+ .start = fira_session_fsm_active_start,
+ .stop = fira_session_fsm_active_stop,
+ .get_demand = fira_session_fsm_active_get_demand,
+ .get_access = fira_session_fsm_active_get_access,
+ .access_done = fira_session_fsm_active_access_done,
+ .check_missed_ranging = fira_session_fsm_active_check_missed_ranging,
+};
diff --git a/mac/fira_session_fsm_active.h b/mac/fira_session_fsm_active.h
new file mode 100644
index 0000000..f6ad54a
--- /dev/null
+++ b/mac/fira_session_fsm_active.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H
+#define NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H
+
+#include "fira_session_fsm.h"
+
+extern const struct fira_session_fsm_state fira_session_fsm_active;
+
+#endif /* NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H */
diff --git a/mac/fira_session_fsm_idle.c b/mac/fira_session_fsm_idle.c
new file mode 100644
index 0000000..fcbcf58
--- /dev/null
+++ b/mac/fira_session_fsm_idle.c
@@ -0,0 +1,169 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <net/mcps802154_frame.h>
+
+#include "fira_session_fsm_init.h"
+#include "fira_session_fsm_idle.h"
+#include "fira_session_fsm_active.h"
+#include "fira_session.h"
+#include "fira_trace.h"
+
+static void
+fira_session_fsm_idle_parameters_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+
+ if (session->measurements.reset) {
+ session->measurements.reset = false;
+ session->measurements.sequence = params->meas_seq;
+ }
+ if (!fira_session_is_ready(local, session)) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_init);
+ }
+}
+
+static void
+fira_session_fsm_idle_controlee_list_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ if (!fira_session_is_ready(local, session)) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_init);
+ }
+}
+
+static int fira_session_fsm_idle_start(struct fira_local *local,
+ struct fira_session *session,
+ const struct genl_info *info)
+{
+ const struct fira_session_params *params = &session->params;
+ struct mcps802154_hrp_uwb_params *hrp = &session->hrp_uwb_params;
+ u32 now_dtu;
+ int r;
+ int i;
+ int slot_duration_us;
+
+ trace_region_fira_session_params(session, params);
+ for (i = 0; i < params->meas_seq.n_steps; i++) {
+ const struct fira_measurement_sequence_step *step;
+
+ step = &params->meas_seq.steps[i];
+ trace_region_fira_session_meas_seq_params(session, step, i);
+ }
+ slot_duration_us = (session->params.slot_duration_dtu * 1000) /
+ (local->llhw->dtu_freq_hz / 1000);
+
+ r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu);
+ if (r)
+ return r;
+
+ /* Update session. */
+ session->event_portid = info->snd_portid;
+ session->block_start_valid = false;
+ session->block_index = 0;
+ session->round_index = 0;
+ session->controlee.synchronised = false;
+ session->last_access_timestamp_dtu = now_dtu;
+
+ r = fira_sts_init(session, slot_duration_us,
+ mcps802154_get_current_channel(local->llhw));
+ if (r)
+ return r;
+
+ /* Set radio parameters. */
+ switch (params->prf_mode) {
+ case FIRA_PRF_MODE_BPRF:
+ hrp->prf = MCPS802154_PRF_64;
+ break;
+ case FIRA_PRF_MODE_HPRF:
+ hrp->prf = MCPS802154_PRF_125;
+ break;
+ default:
+ hrp->prf = MCPS802154_PRF_250;
+ break;
+ }
+ hrp->psr = params->preamble_duration == FIRA_PREAMBULE_DURATION_64 ?
+ MCPS802154_PSR_64 :
+ MCPS802154_PSR_32;
+ hrp->sfd_selector = (enum mcps802154_sfd)params->sfd_id;
+ hrp->phr_hi_rate = params->phr_data_rate == FIRA_PHR_DATA_RATE_6M81;
+ switch (params->psdu_data_rate) {
+ default:
+ case FIRA_PSDU_DATA_RATE_6M81:
+ hrp->data_rate = MCPS802154_DATA_RATE_6M81;
+ break;
+ case FIRA_PSDU_DATA_RATE_7M80:
+ hrp->data_rate = MCPS802154_DATA_RATE_7M80;
+ break;
+ case FIRA_PSDU_DATA_RATE_27M2:
+ hrp->data_rate = MCPS802154_DATA_RATE_27M2;
+ break;
+ case FIRA_PSDU_DATA_RATE_31M2:
+ hrp->data_rate = MCPS802154_DATA_RATE_31M2;
+ break;
+ }
+ fira_session_fsm_change_state(local, session, &fira_session_fsm_active);
+
+ mcps802154_reschedule(local->llhw);
+ return 0;
+}
+
+/* not static: shared with init state */
+int fira_session_fsm_idle_check_parameters(const struct fira_session *session,
+ struct nlattr **attrs)
+{
+ enum fira_session_param_attrs i;
+
+ for (i = FIRA_SESSION_PARAM_ATTR_UNSPEC + 1;
+ i <= FIRA_SESSION_PARAM_ATTR_MAX; i++) {
+ const struct nlattr *attr = attrs[i];
+
+ if (!attr)
+ /* Attribute not provided. */
+ continue;
+
+ switch (i) {
+ case FIRA_SESSION_PARAM_ATTR_STS_CONFIG:
+ if (fira_crypto_get_capabilities() &
+ (1 << nla_get_u8(attr)))
+ continue;
+ else
+ return -EINVAL;
+ break;
+ /* no check on other parameters */
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+const struct fira_session_fsm_state fira_session_fsm_idle = {
+ .id = FIRA_SESSION_STATE_ID_IDLE,
+ .parameters_updated = fira_session_fsm_idle_parameters_updated,
+ .controlee_list_updated = fira_session_fsm_idle_controlee_list_updated,
+ .start = fira_session_fsm_idle_start,
+ .check_parameters = fira_session_fsm_idle_check_parameters,
+};
diff --git a/mac/fira_session_fsm_idle.h b/mac/fira_session_fsm_idle.h
new file mode 100644
index 0000000..a8dd0aa
--- /dev/null
+++ b/mac/fira_session_fsm_idle.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H
+#define NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H
+
+#include "fira_session_fsm.h"
+
+extern const struct fira_session_fsm_state fira_session_fsm_idle;
+
+#endif /* NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H */
diff --git a/mac/fira_session_fsm_init.c b/mac/fira_session_fsm_init.c
new file mode 100644
index 0000000..5c12e62
--- /dev/null
+++ b/mac/fira_session_fsm_init.c
@@ -0,0 +1,77 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <net/mcps802154_frame.h>
+
+#include "fira_session_fsm_init.h"
+#include "fira_session_fsm_idle.h"
+#include "fira_session_fsm_active.h"
+#include "fira_session.h"
+
+static void fira_session_fsm_init_enter(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+
+ session->measurements.sequence = params->meas_seq;
+
+ if (fira_session_is_ready(local, session)) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ }
+}
+
+void fira_session_fsm_init_parameters_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+
+ if (session->measurements.reset) {
+ session->measurements.reset = false;
+ session->measurements.sequence = params->meas_seq;
+ }
+ if (fira_session_is_ready(local, session)) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ }
+}
+
+static void
+fira_session_fsm_init_controlee_list_updated(struct fira_local *local,
+ struct fira_session *session)
+{
+ if (fira_session_is_ready(local, session)) {
+ fira_session_fsm_change_state(local, session,
+ &fira_session_fsm_idle);
+ }
+}
+
+int fira_session_fsm_idle_check_parameters(const struct fira_session *session,
+ struct nlattr **attrs);
+
+const struct fira_session_fsm_state fira_session_fsm_init = {
+ .id = FIRA_SESSION_STATE_ID_INIT,
+ .enter = fira_session_fsm_init_enter,
+ .parameters_updated = fira_session_fsm_init_parameters_updated,
+ .controlee_list_updated = fira_session_fsm_init_controlee_list_updated,
+ .check_parameters = fira_session_fsm_idle_check_parameters,
+};
diff --git a/mac/fira_session_fsm_init.h b/mac/fira_session_fsm_init.h
new file mode 100644
index 0000000..5191a2e
--- /dev/null
+++ b/mac/fira_session_fsm_init.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FIRA_SESSION_FSM_INIT_H
+#define NET_MCPS802154_FIRA_SESSION_FSM_INIT_H
+
+#include "fira_session_fsm.h"
+
+extern const struct fira_session_fsm_state fira_session_fsm_init;
+
+#endif /* NET_MCPS802154_FIRA_SESSION_FSM_INIT_H */
diff --git a/mac/fira_sts.c b/mac/fira_sts.c
new file mode 100644
index 0000000..53f106b
--- /dev/null
+++ b/mac/fira_sts.c
@@ -0,0 +1,505 @@
+/*
+* This file is part of the UWB stack for linux.
+*
+* Copyright (c) 2022 Qorvo US, Inc.
+*
+* This software is provided under the GNU General Public License, version 2
+* (GPLv2), as well as under a Qorvo commercial license.
+*
+* You may choose to use this software under the terms of the GPLv2 License,
+* version 2 ("GPLv2"), as published by the Free Software Foundation.
+* You should have received a copy of the GPLv2 along with this program. If
+* not, see <http://www.gnu.org/licenses/>.
+* not, see <http://www.gnu.org/licenses/>.
+*
+* This program is distributed under the GPLv2 in the hope that it will be
+* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+* details.
+*
+* If you cannot meet the requirements of the GPLv2, you may not use this
+* software for any purpose without first obtaining a commercial license from
+* Qorvo. Please contact Qorvo to inquire about licensing terms.
+*/
+
+#include "fira_region.h"
+#include "fira_session.h"
+#include "fira_sts.h"
+#include "fira_crypto.h"
+
+#include <asm/unaligned.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+
+#define FIRA_CONCATENATED_PARAMS_SIZE 17
+
+/**
+* fira_sts_concatenate_params() - Concatenate the session parameters to compute
+* the digest.
+* @session: FiRa session for which we need to concatenate the parameter.
+* @channel: Channel parameter coming from the LLHW.
+* @slot_duration_us: duration of a FiRa slot in us (according to session config).
+* @concat_params: output buffer.
+* @concat_params_size: size of the output buffer.
+*/
+static void
+fira_sts_concatenate_params(const struct fira_session *session,
+ const struct mcps802154_channel *channel,
+ int slot_duration_us, u8 *concat_params,
+ u8 concat_params_size)
+{
+ u8 *p;
+
+ p = concat_params;
+ *p++ = session->params.ranging_round_usage;
+ *p++ = session->params.sts_config;
+ *p++ = session->params.multi_node_mode;
+ *p++ = session->params.channel_number != 0 ?
+ session->params.channel_number :
+ channel->channel;
+ put_unaligned_be16(slot_duration_us, p);
+ p += sizeof(u16);
+ *p++ = session->params.mac_fcs_type;
+ *p++ = session->params.rframe_config;
+ *p++ = session->params.preamble_code_index != 0 ?
+ session->params.preamble_code_index :
+ channel->preamble_code;
+ *p++ = session->params.sfd_id;
+ *p++ = session->params.psdu_data_rate;
+ *p++ = session->params.preamble_duration;
+ *p++ = 0x03;
+ put_unaligned_be32(session->id, p);
+}
+
+/**
+ * fira_sts_get_absolute_slot_index() - Compute the absolute index of the current slot.
+ * @session: The session requesting absolute slot index.
+ * @slot_index: index to the current slot in the round.
+ *
+ * Return: crypto_sts_index depending on the sts mode.
+ */
+static u32 fira_sts_get_absolute_slot_index(const struct fira_session *session,
+ const u32 slot_index)
+{
+ return (session->block_index * (session->params.block_duration_dtu /
+ session->params.slot_duration_dtu)) +
+ (session->round_index * session->params.round_duration_slots) +
+ slot_index;
+}
+
+/**
+ * fira_sts_get_crypto_sts_index() - Compute the current crypto STS index.
+ * @session: The session requesting crypto sts index.
+ * @crypto: The crypto context containing initial value of phy sts index.
+ * @slot_index: index to the current slot in the round.
+ *
+ * Return: crypto_sts_index depending on the sts mode.
+ */
+static u32 fira_sts_get_crypto_sts_index(const struct fira_session *session,
+ const struct fira_crypto *crypto,
+ const u32 slot_index)
+{
+ u32 phy_sts_index_init, absolute_slot_index;
+
+ /*
+ * In static sts, crypto_sts_index is the current slot index in the ranging round.
+ * For other sts configurations, crypto_sts_index shall be the current phy_sts_index.
+ */
+ if (session->params.sts_config == FIRA_STS_MODE_STATIC)
+ return slot_index;
+
+ phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto);
+ absolute_slot_index =
+ fira_sts_get_absolute_slot_index(session, slot_index);
+ return phy_sts_index_init + absolute_slot_index;
+}
+
+/**
+ * fira_sts_crypto_init() - Initialize crypto related variables for the current
+ * session or sub-session.
+ * @session: The FiRa session the current crypto context belongs to.
+ * @crypto_params: Parameters to initialize the crypto context.
+ * @crypto: Crypto related variables.
+ *
+ * Return: 0 or error.
+*/
+static int fira_sts_crypto_init(struct fira_session *session,
+ struct fira_crypto_params *crypto_params,
+ struct fira_crypto **crypto)
+{
+ int r;
+ u32 crypto_sts_index = 0;
+ u32 phy_sts_index_init, block_duration_in_slots;
+
+ r = fira_crypto_context_init(crypto_params, crypto);
+ if (r)
+ return r;
+
+ r = fira_crypto_build_phy_sts_index_init(*crypto);
+ if (r)
+ goto error_out;
+
+ /*
+ * crypto_sts_index shall be calculated at the block where the last key rotation occurred,
+ * not at the exact slot where crypto_context is being initialized. For Static STS, as key
+ * rotation is not supported, crypto_sts_index shall be 0 (first slot, RR and block).
+ */
+ if (session->params.sts_config != FIRA_STS_MODE_STATIC) {
+ phy_sts_index_init =
+ fira_crypto_get_phy_sts_index_init(*crypto);
+ block_duration_in_slots = session->params.block_duration_dtu /
+ session->params.slot_duration_dtu;
+ crypto_sts_index = phy_sts_index_init +
+ (session->sts.last_rotation_block_index *
+ block_duration_in_slots);
+ }
+
+ r = fira_crypto_rotate_elements(*crypto, crypto_sts_index);
+ if (r)
+ goto error_out;
+
+ return 0;
+error_out:
+ fira_crypto_context_deinit(*crypto);
+ return r;
+}
+/**
+ * fira_sts_responder_specific_crypto_init() - Initialize sub-session's crypto context
+ * in case of a controlee device with a sts_config being FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY.
+ * @session: The FiRa session the current crypto context belongs to.
+ * @crypto_params: Parameters to initialize the crypto context.
+ * @concat_params: Subsession's parameters concatenation used to calculate configDigest.
+ * @concat_params_size: Size of the parameter concatenation buffer.
+ *
+ * Return: 0 or error.
+ */
+static int fira_sts_responder_specific_crypto_init(
+ struct fira_session *session, struct fira_crypto_params *crypto_params,
+ u8 *concat_params, u8 concat_params_size)
+{
+ bool sts_mode_responder_specific_key, device_controlee_responder;
+
+ sts_mode_responder_specific_key =
+ session->params.sts_config ==
+ FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY;
+ device_controlee_responder = session->params.device_type ==
+ FIRA_DEVICE_TYPE_CONTROLEE;
+
+ /* Replace by params.device_role == FIRA_DEVICE_ROLE_RESPONDER when roles
+ * are implemented in FiRa region. Currently, FIRA_DEVICE_TYPE_CONTROLEE == FIRA_DEVICE_ROLE_RESPONDER
+ * and FIRA_DEVICE_TYPE_CONTROLLER == FIRA_DEVICE_ROLE_INITIATOR.
+ */
+ if (!sts_mode_responder_specific_key || !device_controlee_responder)
+ return 0;
+
+ /* Reused most part of crypto_params except for sub-session specific parameters. */
+ crypto_params->sub_session_id = session->params.sub_session_id;
+ crypto_params->key = session->params.sub_session_key;
+ crypto_params->key_len = session->params.sub_session_key_len;
+ return fira_sts_crypto_init(
+ session, crypto_params,
+ &session->controlee.responder_specific_crypto);
+}
+
+int fira_sts_init(struct fira_session *session, int slot_duration_us,
+ const struct mcps802154_channel *current_channel)
+{
+ int r = 0;
+ u8 concat_params[FIRA_CONCATENATED_PARAMS_SIZE];
+ struct fira_crypto_params crypto_params;
+
+ if ((session->params.sts_config ==
+ FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY) &&
+ session->params.multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST)
+ return -EINVAL;
+
+ fira_sts_concatenate_params(session, current_channel,
+ slot_duration_us, concat_params,
+ sizeof(concat_params));
+
+ crypto_params.session_id = session->id;
+ crypto_params.sts_config = session->params.sts_config;
+ crypto_params.concat_params = concat_params;
+ crypto_params.concat_params_size = sizeof(concat_params);
+ crypto_params.vupper64 = session->params.vupper64;
+ crypto_params.key = session->params.session_key;
+ crypto_params.key_len = session->params.session_key_len;
+
+ r = fira_sts_crypto_init(session, &crypto_params, &session->crypto);
+ if (r)
+ return r;
+
+ r = fira_sts_responder_specific_crypto_init(
+ session, &crypto_params, concat_params, sizeof(concat_params));
+ if (r)
+ goto error_out;
+
+ session->sts.last_rotation_block_index = 0;
+ if (r)
+ goto error_out;
+
+ return 0;
+
+error_out:
+ fira_crypto_context_deinit(session->crypto);
+ session->crypto = NULL;
+ return r;
+}
+
+void fira_sts_deinit(struct fira_session *session)
+{
+ if (session->crypto)
+ fira_crypto_context_deinit(session->crypto);
+ if (session->controlee.responder_specific_crypto)
+ fira_crypto_context_deinit(
+ session->controlee.responder_specific_crypto);
+
+ session->crypto = session->controlee.responder_specific_crypto = NULL;
+}
+
+int fira_sts_controlee_init(struct fira_session *session,
+ struct fira_controlee *controlee,
+ int slot_duration_us,
+ const struct mcps802154_channel *current_channel)
+{
+ int r;
+ u8 concat_params[FIRA_CONCATENATED_PARAMS_SIZE];
+ struct fira_crypto_params crypto_params = { 0 };
+
+ if (session->params.sts_config !=
+ FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY) {
+ controlee->crypto = NULL;
+ return 0;
+ }
+
+ /*
+ * Session should not be unicast if STS is configured in
+ * FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY
+ */
+ if (session->params.multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST)
+ return -EINVAL;
+
+ fira_sts_concatenate_params(session, current_channel,
+ slot_duration_us, concat_params,
+ sizeof(concat_params));
+
+ crypto_params.session_id = session->id;
+ crypto_params.sub_session_id = controlee->sub_session_id;
+ crypto_params.sts_config = session->params.sts_config;
+ crypto_params.concat_params = concat_params;
+ crypto_params.concat_params_size = sizeof(concat_params);
+ crypto_params.vupper64 = NULL;
+ crypto_params.key = controlee->sub_session_key;
+ crypto_params.key_len = controlee->sub_session_key_len;
+
+ r = fira_sts_crypto_init(session, &crypto_params, &controlee->crypto);
+ if (r)
+ return r;
+
+ session->sts.last_rotation_block_index = 0;
+ return 0;
+}
+
+void fira_sts_controlee_deinit(struct fira_controlee *controlee)
+{
+ if (!controlee->crypto)
+ fira_crypto_context_deinit(controlee->crypto);
+}
+
+/**
+ * fira_sts_rotate_elements() - Rotate intermediate keys.
+ * @session: The FiRa session requesting key rotation.
+ * @crypto: crypto related variables.
+ * @n_slots_per_block: Amount of slots contained in a FiRa block.
+ * @rotation_block_index: Block index where key rotation shall be placed.
+ */
+static void fira_sts_rotate_elements(const struct fira_session *session,
+ struct fira_crypto *crypto,
+ const u32 n_slots_per_block,
+ const int rotation_block_index)
+{
+ u32 crypto_sts_index, phy_sts_index_init;
+
+ phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto);
+ /* crypto_sts_index shall be calculated at the block triggering key rotation. */
+ crypto_sts_index =
+ phy_sts_index_init + (rotation_block_index * n_slots_per_block);
+ fira_crypto_rotate_elements(crypto, crypto_sts_index);
+}
+
+void fira_sts_rotate_keys(struct fira_session *session)
+{
+ const struct fira_session_params *params = &session->params;
+ struct fira_controlee *controlee, *tmp_controlee;
+ u32 next_block = session->block_index + 1;
+ u32 rotation_period, n_slots_per_block;
+ bool time_to_rotate, rotation_after_resync;
+ int rotation_block_index;
+
+ if (params->sts_config == FIRA_STS_MODE_STATIC || !params->key_rotation)
+ return;
+
+ /* Rotation is triggered after period expires or by resynchronization in the controlee. */
+ rotation_period = (1 << params->key_rotation_rate);
+ time_to_rotate =
+ (next_block - session->sts.last_rotation_block_index) >=
+ rotation_period;
+ rotation_after_resync = next_block <
+ session->sts.last_rotation_block_index;
+ if (!time_to_rotate && !rotation_after_resync)
+ return;
+
+ /* Remove extra blocks following resynchronization. */
+ rotation_block_index = next_block - (next_block % rotation_period);
+ n_slots_per_block =
+ (params->block_duration_dtu / params->slot_duration_dtu);
+ fira_sts_rotate_elements(session, session->crypto, n_slots_per_block,
+ rotation_block_index);
+
+ if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE &&
+ session->controlee.responder_specific_crypto)
+ fira_sts_rotate_elements(
+ session, session->controlee.responder_specific_crypto,
+ n_slots_per_block, rotation_block_index);
+ else if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER &&
+ session->n_current_controlees > 0)
+ list_for_each_entry_safe (controlee, tmp_controlee,
+ &session->current_controlees, entry) {
+ if (!controlee->crypto)
+ continue;
+ fira_sts_rotate_elements(session, controlee->crypto,
+ n_slots_per_block,
+ rotation_block_index);
+ }
+ session->sts.last_rotation_block_index = rotation_block_index;
+}
+
+/**
+ * fira_sts_get_fira_crypto_context() - Get the corresponding fira crypto context to be used in the current
+ * slot depending on sts configuration and device type.
+ * @session: The FiRa session requesting crypto context retrieval.
+ * @slot: The slot on which desired fira crypto context will be used.
+ *
+ * Return: FiRa crypto context to use.
+ */
+static struct fira_crypto *
+fira_sts_get_fira_crypto_context(const struct fira_session *session,
+ const struct fira_slot *slot)
+{
+ bool sts_mode_responder_specific_key =
+ session->params.sts_config ==
+ FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY;
+
+ if (!sts_mode_responder_specific_key || slot->controller_tx)
+ return session->crypto;
+
+ return session->params.device_type == FIRA_DEVICE_TYPE_CONTROLEE ?
+ session->controlee.responder_specific_crypto :
+ slot->controlee->crypto;
+}
+
+int fira_sts_get_sts_params(const struct fira_session *session,
+ const struct fira_slot *slot, u8 *sts_v,
+ u32 sts_v_size, u8 *sts_key, u32 sts_key_size)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+
+ u32 crypto_sts_index =
+ fira_sts_get_crypto_sts_index(session, crypto, slot->index);
+
+ return fira_crypto_get_sts_params(crypto, crypto_sts_index, sts_v,
+ sts_v_size, sts_key, sts_key_size);
+}
+
+u32 fira_sts_get_phy_sts_index(const struct fira_session *session,
+ const struct fira_slot *slot)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+ u32 phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto);
+ u32 absolute_slot_index =
+ fira_sts_get_absolute_slot_index(session, slot->index);
+
+ return phy_sts_index_init + absolute_slot_index;
+}
+
+int fira_sts_convert_phy_sts_idx_to_time_indexes(
+ const struct fira_session *session, const u32 current_phy_sts_index,
+ u32 *block_idx, u32 *round_idx, u32 *slot_idx)
+{
+ u32 remaining_slots, absolute_phy_sts_index, n_slots_per_block,
+ phy_sts_index_init;
+ const struct fira_session_params *params = &session->params;
+
+ phy_sts_index_init =
+ fira_crypto_get_phy_sts_index_init(session->crypto);
+
+ if (current_phy_sts_index < phy_sts_index_init)
+ return -EINVAL;
+ n_slots_per_block =
+ params->block_duration_dtu / params->slot_duration_dtu;
+ absolute_phy_sts_index = current_phy_sts_index - phy_sts_index_init;
+ *block_idx = (u32)(absolute_phy_sts_index / n_slots_per_block);
+ remaining_slots = absolute_phy_sts_index % n_slots_per_block;
+ *round_idx = (u32)(remaining_slots / params->round_duration_slots);
+ *slot_idx = remaining_slots % params->round_duration_slots;
+
+ return 0;
+}
+
+int fira_sts_prepare_decrypt(struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+
+ return fira_crypto_prepare_decrypt(crypto, skb);
+}
+
+int fira_sts_encrypt_frame(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+ u32 crypto_sts_index =
+ fira_sts_get_crypto_sts_index(session, crypto, slot->index);
+
+ return fira_crypto_encrypt_frame(crypto, skb, header_len,
+ src_short_addr, crypto_sts_index);
+}
+
+int fira_sts_decrypt_frame(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+ u32 crypto_sts_index =
+ fira_sts_get_crypto_sts_index(session, crypto, slot->index);
+
+ return fira_crypto_decrypt_frame(crypto, skb, header_len,
+ src_short_addr, crypto_sts_index);
+}
+
+int fira_sts_decrypt_hie(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int hie_offset, int hie_len)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+
+ return fira_crypto_decrypt_hie(crypto, skb, hie_offset, hie_len);
+
+}
+
+int fira_sts_encrypt_hie(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int hie_offset, int hie_len)
+{
+ struct fira_crypto *crypto =
+ fira_sts_get_fira_crypto_context(session, slot);
+
+ return fira_crypto_encrypt_hie(crypto, skb, hie_offset, hie_len);
+}
diff --git a/mac/fira_sts.h b/mac/fira_sts.h
new file mode 100644
index 0000000..dbe13e4
--- /dev/null
+++ b/mac/fira_sts.h
@@ -0,0 +1,193 @@
+/*
+* This file is part of the UWB stack for linux.
+*
+* Copyright (c) 2022 Qorvo US, Inc.
+*
+* This software is provided under the GNU General Public License, version 2
+* (GPLv2), as well as under a Qorvo commercial license.
+*
+* You may choose to use this software under the terms of the GPLv2 License,
+* version 2 ("GPLv2"), as published by the Free Software Foundation.
+* You should have received a copy of the GPLv2 along with this program. If
+* not, see <http://www.gnu.org/licenses/>.
+* not, see <http://www.gnu.org/licenses/>.
+*
+* This program is distributed under the GPLv2 in the hope that it will be
+* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+* details.
+*
+* If you cannot meet the requirements of the GPLv2, you may not use this
+* software for any purpose without first obtaining a commercial license from
+* Qorvo. Please contact Qorvo to inquire about licensing terms.
+*/
+
+#ifndef NET_MCPS802154_FIRA_STS_H
+#define NET_MCPS802154_FIRA_STS_H
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/ieee802154.h>
+
+#include <net/fira_region_params.h>
+#include <net/mcps802154_frame.h>
+
+struct fira_session;
+struct fira_local;
+
+/**
+ * fira_sts_init() - Initialization of STS context for a given
+ * session.
+ * @session: The session for which the context shall be initialized.
+ * @slot_duration_us: Duration of a slot in us.
+ * @current_channel: Current channel used by the LLHW.
+ *
+ * Return: 0 or error.
+ */
+int fira_sts_init(struct fira_session *session, int slot_duration_us,
+ const struct mcps802154_channel *current_channel);
+
+/**
+ * fira_sts_deinit() - Deinitialize STS context and release its resources.
+ * @session: The session for which the STS shall be de-initialized.
+ */
+void fira_sts_deinit(struct fira_session *session);
+
+/**
+ * * fira_sts_controlee_init() - Initialize sub-session's STS context for a given controlee.
+ * @session: The session requesting sub-session context init.
+ * @controlee: The controlee containing the sub-session context init.
+ * @slot_duration_us: Duration of a slot in us.
+ * @current_channel: Current channel used by the llhw.
+ *
+ * Return: 0 or error.
+ */
+int fira_sts_controlee_init(struct fira_session *session,
+ struct fira_controlee *controlee,
+ int slot_duration_us,
+ const struct mcps802154_channel *current_channel);
+
+/**
+ * fira_sts_controlee_deinit() - Deinitialize sub-session's STS context for a given controlee.
+ * @controlee: The controlee containing the sub-session context to deinit.
+ */
+void fira_sts_controlee_deinit(struct fira_controlee *controlee);
+
+/**
+ * fira_sts_rotate_keys() - Rotate intermediate keys if needed.
+ * @session: The session requesting key rotation.
+ */
+void fira_sts_rotate_keys(struct fira_session *session);
+
+/**
+ * fira_sts_get_sts_params() - To fetch sts_params in order to configure the
+ * current frame.
+ * @session: The session for which the sts params are requested.
+ * @slot: The index of the slot for which the STS shall be computed.
+ * @sts_v: STS Vector to be filled using the context.
+ * @sts_v_size: Size of the requested STS vector.
+ * @sts_key: STS Key to be set using the context.
+ * @sts_key_size: Size of the requested STS key.
+ *
+ * Return: 0 or error.
+ */
+int fira_sts_get_sts_params(const struct fira_session *session,
+ const struct fira_slot *slot, u8 *sts_v,
+ u32 sts_v_size, u8 *sts_key, u32 sts_key_size);
+
+/**
+ * fira_sts_get_phy_sts_index() - Compute phy sts index on a specific slot for a given session.
+ * @session: The session requesting phy sts index.
+ * @slot: The slot associated with the desired phy sts index.
+ *
+ * Return: phy sts index on the current slot.
+ */
+u32 fira_sts_get_phy_sts_index(const struct fira_session *session,
+ const struct fira_slot *slot);
+
+/**
+ * fira_sts_convert_phy_sts_idx_to_time_indexes() - Convert a given phy
+ * sts index to the corresponding time related indexes (block, round and slot
+ * indexes).
+ * @session: The session to for which the indexes are needed.
+ * @current_phy_sts_index: phy_sts_index used for time synchronization.
+ * @block_idx: The block index pointed by the given phy sts index.
+ * @round_idx: The block index pointed by the given phy sts index.
+ * @slot_idx: The block index pointed by the given phy sts index.
+ *
+ * Return: 0 or error.
+ */
+int fira_sts_convert_phy_sts_idx_to_time_indexes(
+ const struct fira_session *session, const u32 current_phy_sts_index,
+ u32 *block_idx, u32 *round_idx, u32 *slot_idx);
+
+/**
+* fira_sts_prepare_decrypt() - Prepare skb for header decryption and verification.
+* @session: The session to for which the indexes are needed.
+* @slot: The slot for which the frame should be decrypted.
+* @skb: Buffer containing the frame to decrypt.
+*
+* Return: 0 or error.
+*/
+int fira_sts_prepare_decrypt(struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb);
+
+/**
+* fira_sts_encrypt_frame() - Encrypt the given FiRa 802154 frame.
+* @session: The session to use to encrypt the frame.
+* @slot: The slot with the frame to encrypt.
+* @skb: Buffer containing the frame to encrypt.
+* @header_len: Length of the 802154 header. Can be used to find the start of the
+* payload (relative to skb->data).
+* @src_short_addr: Source short address.
+* @slot_index: The slot index of the frame to encrypt.
+*
+* Return: 0 or error.
+*/
+int fira_sts_encrypt_frame(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr);
+/**
+* fira_sts_decrypt_frame() - Decrypt the given FiRa 802154 frame.
+* @session: The session to use to decrypt the frame.
+* @skb: Buffer containing the frame to decrypt.
+* @header_len: Length of the 802154 header. Used to find the start of the
+* frame payload (relative to skb->data).
+* @src_short_addr: Source short address.
+*
+* Return: 0 or error.
+*/
+int fira_sts_decrypt_frame(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int header_len, __le16 src_short_addr);
+
+/**
+* fira_sts_encrypt_hie() - Encrypt the HIE stored in a FiRa 802154
+* frame.
+* @session: The session to attached to the HIE.
+* @slot: The slot with the HIE to encrypt.
+* @skb: Buffer containing the frame to encrypt.
+* @hie_offset: Offset to the start of the HIE (relative to skb->data) to encrypt.
+* @hie_len: Length of the HIE to encrypt.
+*
+* Return: 0 or error.
+*/
+int fira_sts_encrypt_hie(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int hie_offset, int hie_len);
+
+/**
+* fira_sts_decrypt_hie() - Decrypt the HIE stored in a FiRa 802154 frame.
+* @session: The session attached to the HIE
+* @slot: The slot with the HIE to decrypt.
+* @skb: Buffer containing the frame to decrypt.
+* @hie_offset: Offset to the start of the HIE (relative to skb->data) to decrypt.
+* @hie_len: Length of the HIE to decrypt.
+*
+* Return: 0 or error.
+*/
+
+int fira_sts_decrypt_hie(const struct fira_session *session,
+ const struct fira_slot *slot, struct sk_buff *skb,
+ int hie_offset, int hie_len);
+#endif /* NET_MCPS802154_FIRA_STS_H */
diff --git a/mac/fira_trace.h b/mac/fira_trace.h
new file mode 100644
index 0000000..8928fe3
--- /dev/null
+++ b/mac/fira_trace.h
@@ -0,0 +1,748 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mcps802154_region_fira
+
+#if !defined(FIRA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define FIRA_TRACE_H
+
+#include <linux/tracepoint.h>
+#include "fira_session.h"
+#include "net/fira_region_params.h"
+#include <net/fira_region_nl.h>
+
+/* clang-format off */
+
+#define FIRA_SESSION_ENTRY __field(u32, session_id)
+#define FIRA_SESSION_ASSIGN __entry->session_id = session->id
+#define FIRA_SESSION_PR_FMT "session_id=%d"
+#define FIRA_SESSION_PR_ARG __entry->session_id
+
+#define FIRA_DEVICE_TYPE_SYMBOLS \
+ { FIRA_DEVICE_TYPE_CONTROLEE, "controlee" }, \
+ { FIRA_DEVICE_TYPE_CONTROLLER, "controller" }
+TRACE_DEFINE_ENUM(FIRA_DEVICE_TYPE_CONTROLEE);
+TRACE_DEFINE_ENUM(FIRA_DEVICE_TYPE_CONTROLLER);
+
+#define FIRA_DEVICE_ROLE_SYMBOLS \
+ { FIRA_DEVICE_ROLE_RESPONDER, "responder" }, \
+ { FIRA_DEVICE_ROLE_INITIATOR, "initiator" }
+TRACE_DEFINE_ENUM(FIRA_DEVICE_ROLE_RESPONDER);
+TRACE_DEFINE_ENUM(FIRA_DEVICE_ROLE_INITIATOR);
+
+#define FIRA_RANGING_ROUND_SYMBOLS \
+ { FIRA_RANGING_ROUND_USAGE_OWR, "OWR" }, \
+ { FIRA_RANGING_ROUND_USAGE_SSTWR, "SSTWR" }, \
+ { FIRA_RANGING_ROUND_USAGE_DSTWR, "DSTWR" }
+TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_OWR);
+TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_SSTWR);
+TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_DSTWR);
+
+#define FIRA_MULTI_NODE_MODE_SYMBOLS \
+ { FIRA_MULTI_NODE_MODE_UNICAST, "UNICAST" }, \
+ { FIRA_MULTI_NODE_MODE_ONE_TO_MANY, "ONE_TO_MANY" }, \
+ { FIRA_MULTI_NODE_MODE_MANY_TO_MANY, "MANY_TO_MANY" }
+TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_UNICAST);
+TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_ONE_TO_MANY);
+TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_MANY_TO_MANY);
+
+#define FIRA_RFRAME_CONFIG_SYMBOLS \
+ { FIRA_RFRAME_CONFIG_SP0, "SP0" }, \
+ { FIRA_RFRAME_CONFIG_SP1, "SP1" }, \
+ { FIRA_RFRAME_CONFIG_SP2, "SP2" }, \
+ { FIRA_RFRAME_CONFIG_SP3, "SP3" }
+TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP0);
+TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP1);
+TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP2);
+TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP3);
+
+#define FIRA_PREAMBULE_DURATION_SYMBOLS \
+ { FIRA_PREAMBULE_DURATION_32, "32" }, \
+ { FIRA_PREAMBULE_DURATION_64, "64" }
+TRACE_DEFINE_ENUM(FIRA_PREAMBULE_DURATION_32);
+TRACE_DEFINE_ENUM(FIRA_PREAMBULE_DURATION_64);
+
+#define FIRA_STS_SEGMENTS_SYMBOLS \
+ { FIRA_STS_SEGMENTS_0, "0" }, \
+ { FIRA_STS_SEGMENTS_1, "1" }, \
+ { FIRA_STS_SEGMENTS_2, "2" }, \
+ { FIRA_STS_SEGMENTS_3, "3" }, \
+ { FIRA_STS_SEGMENTS_4, "4" }
+TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_0);
+TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_1);
+TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_2);
+TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_3);
+TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_4);
+
+#define FIRA_PSDU_DATA_RATE_SYMBOLS \
+ { FIRA_PSDU_DATA_RATE_6M81, "6M81" }, \
+ { FIRA_PSDU_DATA_RATE_7M80, "7M80" }, \
+ { FIRA_PSDU_DATA_RATE_27M2, "27M2" }, \
+ { FIRA_PSDU_DATA_RATE_31M2, "31M2" }
+TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_6M81);
+TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_7M80);
+TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_27M2);
+TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_31M2);
+
+#define FIRA_PHR_DATA_RATE_SYMBOLS \
+ { FIRA_PHR_DATA_RATE_850K, "850k" }, \
+ { FIRA_PHR_DATA_RATE_6M81, "6M81" }
+TRACE_DEFINE_ENUM(FIRA_PHR_DATA_RATE_850K);
+TRACE_DEFINE_ENUM(FIRA_PHR_DATA_RATE_6M81);
+
+#define FIRA_MAC_FCS_TYPE_CRC_SYMBOLS \
+ { FIRA_MAC_FCS_TYPE_CRC_16, "16" }, \
+ { FIRA_MAC_FCS_TYPE_CRC_32, "32" }
+TRACE_DEFINE_ENUM(FIRA_MAC_FCS_TYPE_CRC_16);
+TRACE_DEFINE_ENUM(FIRA_MAC_FCS_TYPE_CRC_32);
+
+#define FIRA_STS_MODE_SYMBOLS \
+ { FIRA_STS_MODE_STATIC, "static" }, \
+ { FIRA_STS_MODE_DYNAMIC, "dynamic" }, \
+ { FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY, "dynamic_individual_key" }, \
+ { FIRA_STS_MODE_PROVISIONED, "provisioned" }, \
+ { FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY, "provisioned_individual_key" }
+TRACE_DEFINE_ENUM(FIRA_STS_MODE_STATIC);
+TRACE_DEFINE_ENUM(FIRA_STS_MODE_DYNAMIC);
+TRACE_DEFINE_ENUM(FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY);
+TRACE_DEFINE_ENUM(FIRA_STS_MODE_PROVISIONED);
+TRACE_DEFINE_ENUM(FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY);
+
+#define FIRA_MESSAGE_TYPE \
+ { FIRA_MESSAGE_ID_RANGING_INITIATION, "RIM" }, \
+ { FIRA_MESSAGE_ID_RANGING_RESPONSE, "RRM" }, \
+ { FIRA_MESSAGE_ID_RANGING_FINAL, "RFM" }, \
+ { FIRA_MESSAGE_ID_CONTROL, "RCM" }, \
+ { FIRA_MESSAGE_ID_MEASUREMENT_REPORT, "MRM" }, \
+ { FIRA_MESSAGE_ID_RESULT_REPORT, "RRRM" }, \
+ { FIRA_MESSAGE_ID_CONTROL_UPDATE, "CMU" }
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_INITIATION);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_RESPONSE);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_FINAL);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_CONTROL);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_MEASUREMENT_REPORT);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RESULT_REPORT);
+TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_CONTROL_UPDATE);
+
+#define mcps802154_rx_error_name(name) \
+ { \
+ MCPS802154_RX_ERROR_##name, #name \
+ }
+#define MCPS802154_RX_ERROR_SYMBOLS \
+ mcps802154_rx_error_name(NONE), \
+ mcps802154_rx_error_name(TIMEOUT), \
+ mcps802154_rx_error_name(BAD_CKSUM), \
+ mcps802154_rx_error_name(UNCORRECTABLE), \
+ mcps802154_rx_error_name(FILTERED), \
+ mcps802154_rx_error_name(SFD_TIMEOUT), \
+ mcps802154_rx_error_name(PHR_DECODE), \
+ mcps802154_rx_error_name(OTHER)
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_NONE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_TIMEOUT);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_BAD_CKSUM);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_UNCORRECTABLE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_FILTERED);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_SFD_TIMEOUT);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_PHR_DECODE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_OTHER);
+
+#define mcps802154_tx_reason_name(name) \
+ { \
+ MCPS802154_ACCESS_TX_RETURN_REASON_##name, #name \
+ }
+#define MCPS802154_TX_REASON_SYMBOLS \
+ mcps802154_tx_reason_name(CONSUMED), \
+ mcps802154_tx_reason_name(FAILURE), \
+ mcps802154_tx_reason_name(CANCEL)
+TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED);
+TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE);
+TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL);
+
+#define FIRA_MEAS_SEQ_STEP_TYPE \
+ { FIRA_MEASUREMENT_TYPE_RANGE, "range" }, \
+ { FIRA_MEASUREMENT_TYPE_AOA, "AoA" }, \
+ { FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH, "AoA Azimuth" }, \
+ { FIRA_MEASUREMENT_TYPE_AOA_ELEVATION, "AoA Elevation" }, \
+ { FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION, \
+ "AoA Azimuth/Elevation" }
+TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_RANGE);
+TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA);
+TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH);
+TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_ELEVATION);
+TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION);
+
+#define fira_session_state_id_name(name) \
+ { \
+ FIRA_SESSION_STATE_ID_##name, #name \
+ }
+#define FIRA_SESSION_STATE_ID_SYMBOLS \
+ fira_session_state_id_name(DEINIT), \
+ fira_session_state_id_name(INIT), \
+ fira_session_state_id_name(ACTIVE), \
+ fira_session_state_id_name(IDLE)
+TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_DEINIT);
+TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_INIT);
+TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_ACTIVE);
+TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_IDLE);
+
+
+#define fira_call_name(name) \
+ { \
+ FIRA_CALL_##name, #name \
+ }
+#define FIRA_CALL_SYMBOLS \
+ fira_call_name(GET_CAPABILITIES), \
+ fira_call_name(SESSION_INIT), \
+ fira_call_name(SESSION_START), \
+ fira_call_name(SESSION_STOP), \
+ fira_call_name(SESSION_DEINIT), \
+ fira_call_name(SESSION_SET_PARAMS), \
+ fira_call_name(NEW_CONTROLEE), \
+ fira_call_name(DEL_CONTROLEE), \
+ fira_call_name(SESSION_NOTIFICATION), \
+ fira_call_name(SESSION_GET_PARAMS), \
+ fira_call_name(SESSION_GET_STATE), \
+ fira_call_name(SESSION_GET_COUNT), \
+ fira_call_name(SET_CONTROLEE), \
+ fira_call_name(GET_CONTROLEES)
+TRACE_DEFINE_ENUM(FIRA_CALL_GET_CAPABILITIES);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_INIT);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_START);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_STOP);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_DEINIT);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_SET_PARAMS);
+TRACE_DEFINE_ENUM(FIRA_CALL_NEW_CONTROLEE);
+TRACE_DEFINE_ENUM(FIRA_CALL_DEL_CONTROLEE);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_NOTIFICATION);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_PARAMS);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_STATE);
+TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_COUNT);
+TRACE_DEFINE_ENUM(FIRA_CALL_SET_CONTROLEE);
+TRACE_DEFINE_ENUM(FIRA_CALL_GET_CONTROLEES);
+
+
+TRACE_EVENT(region_fira_session_params,
+ TP_PROTO(const struct fira_session *session,
+ const struct fira_session_params *params),
+ TP_ARGS(session, params),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum fira_device_type, device_type)
+ __field(enum fira_ranging_round_usage, ranging_round_usage)
+ __field(enum fira_multi_node_mode, multi_node_mode)
+ __field(__le16, controller_short_addr)
+ __field(int, initiation_time_ms)
+ __field(int, slot_duration_dtu)
+ __field(int, block_duration_dtu)
+ __field(u32, block_stride_len)
+ __field(u32, max_number_of_measurements)
+ __field(u32, max_rr_retry)
+ __field(int, round_duration_slots)
+ __field(bool, round_hopping)
+ __field(u8, priority)
+ __field(int, channel_number)
+ __field(int, preamble_code_index)
+ __field(enum fira_rframe_config, rframe_config)
+ __field(enum fira_preambule_duration, preamble_duration)
+ __field(enum fira_sfd_id, sfd_id)
+ __field(enum fira_sts_segments, number_of_sts_segments)
+ __field(enum fira_psdu_data_rate, psdu_data_rate)
+ __field(enum fira_mac_fcs_type, mac_fcs_type)
+ __field(enum fira_sts_mode, sts_config)
+ __array(u8, vupper64, FIRA_VUPPER64_SIZE)
+ __field(u32, sub_session_id)
+ __field(u32, session_key_len)
+ __field(bool, key_rotation)
+ __field(int, key_rotation_rate)
+ __field(bool, aoa_result_req)
+ __field(bool, report_tof)
+ __field(bool, report_aoa_azimuth)
+ __field(bool, report_aoa_elevation)
+ __field(bool, report_aoa_fom)
+ __field(bool, report_diagnostics)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->device_type = params->device_type;
+ __entry->ranging_round_usage = params->ranging_round_usage;
+ __entry->multi_node_mode = params->multi_node_mode;
+ __entry->controller_short_addr = params->controller_short_addr;
+ __entry->initiation_time_ms = params->initiation_time_ms;
+ __entry->slot_duration_dtu = params->slot_duration_dtu;
+ __entry->block_duration_dtu = params->block_duration_dtu;
+ __entry->block_stride_len = params->block_stride_len;
+ __entry->max_number_of_measurements = params->max_number_of_measurements;
+ __entry->max_rr_retry = params->max_rr_retry;
+ __entry->round_duration_slots = params->round_duration_slots;
+ __entry->round_hopping = params->round_hopping;
+ __entry->priority = params->priority;
+ __entry->channel_number = params->channel_number;
+ __entry->preamble_code_index = params->preamble_code_index;
+ __entry->rframe_config = params->rframe_config;
+ __entry->preamble_duration = params->preamble_duration;
+ __entry->sfd_id = params->sfd_id;
+ __entry->number_of_sts_segments = params->number_of_sts_segments;
+ __entry->psdu_data_rate = params->psdu_data_rate;
+ __entry->mac_fcs_type = params->mac_fcs_type;
+ __entry->sts_config = params->sts_config;
+ memcpy(__entry->vupper64, params->vupper64, sizeof(params->vupper64));
+ __entry->sub_session_id = params->sub_session_id;
+ __entry->session_key_len = params->session_key_len;
+ __entry->key_rotation = params->key_rotation;
+ __entry->key_rotation_rate = params->key_rotation_rate;
+ __entry->aoa_result_req = params->aoa_result_req;
+ __entry->report_tof = params->report_tof;
+ __entry->report_aoa_azimuth = params->report_aoa_azimuth;
+ __entry->report_aoa_elevation = params->report_aoa_elevation;
+ __entry->report_aoa_fom = params->report_aoa_fom;
+ __entry->report_diagnostics = params->report_diagnostics;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " device_type=%s ranging_round_usage=%s multi_node_mode=%s "
+ "controller_short_addr=0x%x initiation_time_ms=%d slot_duration_dtu=%d "
+ "block_duration_dtu=%d block_stride_len=%d max_nb_of_measurements=%d "
+ "max_rr_retry=%d round_duration_slots=%d round_hopping=%d "
+ "priority=%d channel_number=%d preamble_code_index=%d rframe_config=%s "
+ "preamble_duration=%s sfd_id=%d number_of_sts_segments=%s psdu_data_rate=%s mac_fcs_type=%s "
+ "sts_config=%s vupper64=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x sub_session_id=%d session_key_len=%d "
+ "key_rotation=%d key_rotation_rate=%d aoa_result_req=%d report_tof=%d report_aoa_azimuth=%d "
+ "report_aoa_elevation=%d report_aoa_fom=%d diagnostics=%d",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->device_type, FIRA_DEVICE_TYPE_SYMBOLS),
+ __print_symbolic(__entry->ranging_round_usage, FIRA_RANGING_ROUND_SYMBOLS),
+ __print_symbolic(__entry->multi_node_mode, FIRA_MULTI_NODE_MODE_SYMBOLS),
+ __entry->controller_short_addr,
+ __entry->initiation_time_ms,
+ __entry->slot_duration_dtu,
+ __entry->block_duration_dtu,
+ __entry->block_stride_len,
+ __entry->max_number_of_measurements,
+ __entry->max_rr_retry,
+ __entry->round_duration_slots,
+ __entry->round_hopping,
+ __entry->priority,
+ __entry->channel_number,
+ __entry->preamble_code_index,
+ __print_symbolic(__entry->rframe_config, FIRA_RFRAME_CONFIG_SYMBOLS),
+ __print_symbolic(__entry->preamble_duration, FIRA_PREAMBULE_DURATION_SYMBOLS),
+ __entry->sfd_id,
+ __print_symbolic(__entry->number_of_sts_segments, FIRA_STS_SEGMENTS_SYMBOLS),
+ __print_symbolic(__entry->psdu_data_rate, FIRA_PSDU_DATA_RATE_SYMBOLS),
+ __print_symbolic(__entry->mac_fcs_type, FIRA_MAC_FCS_TYPE_CRC_SYMBOLS),
+ __print_symbolic(__entry->sts_config, FIRA_STS_MODE_SYMBOLS),
+ __entry->vupper64[0], __entry->vupper64[1], __entry->vupper64[2], __entry->vupper64[3],
+ __entry->vupper64[4], __entry->vupper64[5], __entry->vupper64[6], __entry->vupper64[7],
+ __entry->sub_session_id,
+ __entry->session_key_len,
+ __entry->key_rotation,
+ __entry->key_rotation_rate,
+ __entry->aoa_result_req,
+ __entry->report_tof,
+ __entry->report_aoa_azimuth,
+ __entry->report_aoa_elevation,
+ __entry->report_aoa_fom,
+ __entry->report_diagnostics
+ )
+ );
+
+TRACE_EVENT(region_fira_session_meas_seq_params,
+ TP_PROTO(const struct fira_session *session,
+ const struct fira_measurement_sequence_step *step,
+ int index),
+ TP_ARGS(session, step, index),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, index)
+ __field(enum fira_measurement_type, type)
+ __field(u8, n_measurements)
+ __field(s8, rx_ant_set_nonranging)
+ __field(s8, rx_ant_sets_ranging_0)
+ __field(s8, rx_ant_sets_ranging_1)
+ __field(s8, tx_ant_set_nonranging)
+ __field(s8, tx_ant_set_ranging)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->index = index;
+ __entry->type = step->type;
+ __entry->n_measurements = step->n_measurements;
+ __entry->rx_ant_set_nonranging = step->rx_ant_set_nonranging;
+ __entry->rx_ant_sets_ranging_0 = step->rx_ant_sets_ranging[0];
+ __entry->rx_ant_sets_ranging_1 = step->rx_ant_sets_ranging[1];
+ __entry->tx_ant_set_nonranging = step->tx_ant_set_nonranging;
+ __entry->tx_ant_set_ranging = step->tx_ant_set_ranging;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " index=%d type=%s n_measurements=%d "
+ "rx_ant_set_nonranging=%d rx_ant_sets_ranging_0=%d "
+ "rx_ant_sets_ranging_1=%d tx_ant_set_nonranging=%d "
+ "tx_ant_set_ranging=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->index,
+ __print_symbolic(__entry->type, FIRA_MEAS_SEQ_STEP_TYPE),
+ __entry->n_measurements,
+ __entry->rx_ant_set_nonranging,
+ __entry->rx_ant_sets_ranging_0,
+ __entry->rx_ant_sets_ranging_1,
+ __entry->tx_ant_set_nonranging,
+ __entry->tx_ant_set_ranging)
+ );
+
+TRACE_EVENT(region_fira_session_control,
+ TP_PROTO(const struct fira_local *local,
+ int session_id, enum fira_call call_id),
+ TP_ARGS(local, session_id, call_id),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum fira_call, call_id)
+ ),
+ TP_fast_assign(
+ __entry->session_id = session_id;
+ __entry->call_id = call_id;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " call_id=%s",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->call_id, FIRA_CALL_SYMBOLS)
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_session_fsm_active_get_demand_return,
+ TP_PROTO(const struct fira_local *local,
+ const struct fira_session *session,
+ const struct fira_session_demand *fsd),
+ TP_ARGS(local, session, fsd),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(u32, block_start_dtu)
+ __field(u32, timestamp_dtu)
+ __field(int, max_duration_dtu)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->block_start_dtu = fsd->block_start_dtu;
+ __entry->timestamp_dtu = fsd->timestamp_dtu;
+ __entry->max_duration_dtu = fsd->max_duration_dtu;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " block_start_dtu=%#x "
+ "timestamp_dtu=%#x max_duration_dtu=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->block_start_dtu,
+ __entry->timestamp_dtu,
+ __entry->max_duration_dtu
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_get_access_controller,
+ TP_PROTO(const struct fira_local *local,
+ const struct fira_session *session,
+ const struct fira_session_demand *fsd),
+ TP_ARGS(local, session, fsd),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, block_index)
+ __field(int, add_blocks)
+ __field(int, round_index)
+ __field(int, block_stride_len)
+ __field(u32, block_start_dtu)
+ __field(u32, timestamp_dtu)
+ __field(int, max_duration_dtu)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->block_index = session->block_index;
+ __entry->add_blocks = fsd->add_blocks;
+ __entry->round_index = fsd->round_index;
+ __entry->block_stride_len = session->block_stride_len;
+ __entry->block_start_dtu = fsd->block_start_dtu;
+ __entry->timestamp_dtu = fsd->timestamp_dtu;
+ __entry->max_duration_dtu = fsd->max_duration_dtu;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " block_index=%d add_blocks=%d "
+ "round_index=%d block_stride_len=%d block_start_dtu=%#x "
+ "timestamp_dtu=%#x max_duration_dtu=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->block_index,
+ __entry->add_blocks,
+ __entry->round_index,
+ __entry->block_stride_len,
+ __entry->block_start_dtu,
+ __entry->timestamp_dtu,
+ __entry->max_duration_dtu
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_get_access_controlee,
+ TP_PROTO(const struct fira_local *local,
+ const struct fira_session *session,
+ const struct fira_session_demand *fsd),
+ TP_ARGS(local, session, fsd),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, block_index)
+ __field(int, add_blocks)
+ __field(int, round_index)
+ __field(u32, block_start_dtu)
+ __field(u32, timestamp_dtu)
+ __field(int, max_duration_dtu)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->block_index = session->block_index;
+ __entry->add_blocks = fsd->add_blocks;
+ __entry->round_index = fsd->round_index;
+ __entry->block_start_dtu = fsd->block_start_dtu;
+ __entry->timestamp_dtu = fsd->timestamp_dtu;
+ __entry->max_duration_dtu = fsd->max_duration_dtu;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " block_index=%d add_blocks=%d "
+ "round_index=%d block_start_dtu=%#x timestamp_dtu=%#x "
+ "max_duration_dtu=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->block_index,
+ __entry->add_blocks,
+ __entry->round_index,
+ __entry->block_start_dtu,
+ __entry->timestamp_dtu,
+ __entry->max_duration_dtu
+ )
+ );
+
+TRACE_EVENT(region_fira_rx_frame,
+ TP_PROTO(const struct fira_session *session,
+ enum fira_message_id message_id,
+ enum mcps802154_rx_error_type error),
+ TP_ARGS(session, message_id, error),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum fira_message_id, message_id)
+ __field(enum mcps802154_rx_error_type, error)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->message_id = message_id;
+ __entry->error = error;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " message_id=%s error=%s",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->message_id, FIRA_MESSAGE_TYPE),
+ __print_symbolic(__entry->error, MCPS802154_RX_ERROR_SYMBOLS)
+ )
+ );
+
+TRACE_EVENT(region_fira_rx_frame_control,
+ TP_PROTO(const struct fira_local *local,
+ const struct fira_session *session,
+ int left_duration_dtu, int n_slots),
+ TP_ARGS(local, session, left_duration_dtu, n_slots),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(u32, block_start_dtu)
+ __field(int, block_index)
+ __field(int, round_index)
+ __field(bool, stop_inband)
+ __field(int, left_duration_dtu)
+ __field(int, n_slots)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->block_start_dtu = session->block_start_dtu;
+ __entry->block_index = session->block_index;
+ __entry->round_index = session->round_index;
+ __entry->stop_inband = session->stop_inband;
+ __entry->left_duration_dtu = left_duration_dtu;
+ __entry->n_slots = n_slots;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " block_start_dtu=%#x block_index=%d "
+ "round_index=%d stop_inband=%s left_duration_dtu=%d n_slots=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->block_start_dtu,
+ __entry->block_index,
+ __entry->round_index,
+ __entry->stop_inband ? "true": "false",
+ __entry->left_duration_dtu,
+ __entry->n_slots
+ )
+ );
+
+TRACE_EVENT(region_fira_tx_get_frame,
+ TP_PROTO(const struct fira_session *session,
+ enum fira_message_id message_id),
+ TP_ARGS(session, message_id),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum fira_message_id, message_id)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->message_id = message_id;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " message_id=%s",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->message_id, FIRA_MESSAGE_TYPE)
+ )
+ );
+
+TRACE_EVENT(region_fira_tx_return,
+ TP_PROTO(const struct fira_session *session,
+ enum mcps802154_access_tx_return_reason reason),
+ TP_ARGS(session, reason),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum mcps802154_access_tx_return_reason, reason)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->reason = reason;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " reason=%s",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->reason,
+ MCPS802154_TX_REASON_SYMBOLS)
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_session_fsm_change_state,
+ TP_PROTO(const struct fira_session *session, enum fira_session_state_id new_state_id),
+ TP_ARGS(session, new_state_id),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(enum fira_session_state_id, new_state_id)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->new_state_id = new_state_id;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " new_state_id=%s",
+ FIRA_SESSION_PR_ARG,
+ __print_symbolic(__entry->new_state_id,
+ FIRA_SESSION_STATE_ID_SYMBOLS)
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_access_done,
+ TP_PROTO(const struct fira_local *local,
+ const struct fira_session *session,
+ int access_duration_dtu, bool error),
+ TP_ARGS(local, session, access_duration_dtu, error),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, access_duration_dtu)
+ __field(bool, error)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->access_duration_dtu = access_duration_dtu;
+ __entry->error = error;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " access_duration_dtu=%d error=%s",
+ FIRA_SESSION_PR_ARG,
+ __entry->access_duration_dtu,
+ __entry->error ? "true": "false"
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_is_controlee_synchronised,
+ TP_PROTO(const struct fira_session *session,
+ int drift_ppm, int rx_margin_ppm),
+ TP_ARGS(session, drift_ppm, rx_margin_ppm),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, block_index_sync)
+ __field(int, drift_ppm)
+ __field(int, rx_margin_ppm)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->block_index_sync = session->controlee.block_index_sync;
+ __entry->drift_ppm = drift_ppm;
+ __entry->rx_margin_ppm = rx_margin_ppm;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " block_index_sync=%d drift_ppm=%d rx_margin_ppm=%d",
+ FIRA_SESSION_PR_ARG,
+ __entry->block_index_sync,
+ __entry->drift_ppm,
+ __entry->rx_margin_ppm
+ )
+ );
+
+TRACE_EVENT(
+ region_fira_session_report,
+ TP_PROTO(const struct fira_session *session,
+ const struct fira_report_info *report_info),
+ TP_ARGS(session, report_info),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ __field(int, sequence_number)
+ __field(int, block_index)
+ __field(int, n_ranging_data)
+ __field(int, n_stopped_controlees)
+ __field(int, n_slots)
+ __field(bool, stopped)
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ __entry->sequence_number = session->sequence_number;
+ __entry->block_index = session->block_index;
+ __entry->n_ranging_data = report_info->n_ranging_data;
+ __entry->n_stopped_controlees = report_info->n_stopped_controlees;
+ __entry->n_slots = report_info->n_slots;
+ __entry->stopped = report_info->stopped;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT " sequence_number=%d block_index=%d "
+ "n_ranging_data=%d n_stopped_controlees=%d n_slots=%d stopped=%s",
+ FIRA_SESSION_PR_ARG,
+ __entry->sequence_number,
+ __entry->block_index,
+ __entry->n_ranging_data,
+ __entry->n_stopped_controlees,
+ __entry->n_slots,
+ __entry->stopped ? "true": "false"
+ )
+ );
+
+TRACE_EVENT(fira_nondeferred_not_supported,
+ TP_PROTO(const struct fira_session *session),
+ TP_ARGS(session),
+ TP_STRUCT__entry(
+ FIRA_SESSION_ENTRY
+ ),
+ TP_fast_assign(
+ FIRA_SESSION_ASSIGN;
+ ),
+ TP_printk(FIRA_SESSION_PR_FMT,
+ FIRA_SESSION_PR_ARG
+ )
+ );
+
+#endif /* !FIRA_TRACE_H || TRACE_HEADER_MULTI_READ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE fira_trace
+#include <trace/define_trace.h>
diff --git a/mac/fproc.c b/mac/fproc.c
new file mode 100644
index 0000000..4e1274c
--- /dev/null
+++ b/mac/fproc.c
@@ -0,0 +1,311 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/module.h>
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+bool mcps802154_fproc_is_non_recoverable_error(struct mcps802154_access *access)
+{
+ bool rc = false;
+ if (access->error < 0) {
+ switch (access->error) {
+ case -ETIME:
+ case -EIO:
+ case -EAGAIN:
+ rc = false;
+ break;
+ default:
+ pr_err("mcps: error %d is not recoverable", access->error);
+ rc = true;
+ break;
+ }
+ }
+ return rc;
+}
+
+void mcps802154_fproc_init(struct mcps802154_local *local)
+{
+ local->fproc.state = &mcps802154_fproc_stopped;
+ WARN_ON(!local->fproc.state->enter);
+ local->fproc.state->enter(local);
+}
+
+void mcps802154_fproc_uninit(struct mcps802154_local *local)
+{
+ WARN_ON(local->fproc.access);
+ WARN_ON(local->fproc.tx_skb);
+ WARN_ON(local->started);
+ WARN_ON(local->fproc.deferred);
+}
+
+void mcps802154_fproc_change_state(
+ struct mcps802154_local *local,
+ const struct mcps802154_fproc_state *new_state)
+{
+ if (local->fproc.state->leave)
+ local->fproc.state->leave(local);
+ local->fproc.state = new_state;
+ if (local->fproc.state->enter)
+ local->fproc.state->enter(local);
+}
+
+void mcps802154_fproc_access(struct mcps802154_local *local,
+ u32 next_timestamp_dtu)
+{
+ struct mcps802154_access *access;
+
+ if (!local->start_stop_request) {
+ mcps802154_fproc_stopped_handle(local);
+ return;
+ }
+
+ access = mcps802154_ca_get_access(local, next_timestamp_dtu);
+ if (unlikely(!access)) {
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+
+ local->fproc.access = access;
+ local->fproc.frame_idx = 0;
+
+ switch (access->method) {
+ case MCPS802154_ACCESS_METHOD_NOTHING:
+ access->error = mcps802154_fproc_nothing_handle(local, access);
+ break;
+ case MCPS802154_ACCESS_METHOD_IDLE:
+ access->error = mcps802154_fproc_idle_handle(local, access);
+ break;
+ case MCPS802154_ACCESS_METHOD_IMMEDIATE_RX:
+ access->error = mcps802154_fproc_rx_handle(local, access);
+ break;
+ case MCPS802154_ACCESS_METHOD_IMMEDIATE_TX:
+ access->error = mcps802154_fproc_tx_handle(local, access);
+ break;
+ case MCPS802154_ACCESS_METHOD_MULTI:
+ access->error = mcps802154_fproc_multi_handle(local, access);
+ break;
+ case MCPS802154_ACCESS_METHOD_VENDOR:
+ access->error = mcps802154_fproc_vendor_handle(local, access);
+ break;
+ default:
+ access->error = -1;
+ }
+
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ } else {
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+ }
+ }
+}
+
+void mcps802154_fproc_access_now(struct mcps802154_local *local)
+{
+ int r = 0;
+ u32 next_timestamp_dtu = 0;
+
+ if (local->start_stop_request)
+ r = llhw_get_current_timestamp_dtu(local, &next_timestamp_dtu);
+
+ if (r)
+ mcps802154_fproc_broken_handle(local);
+ else
+ mcps802154_fproc_access(local, next_timestamp_dtu +
+ local->llhw.anticip_dtu);
+}
+
+void mcps802154_fproc_access_done(struct mcps802154_local *local, bool error)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ if (access->common_ops->access_done)
+ access->common_ops->access_done(access, error);
+ local->fproc.access = NULL;
+}
+
+void mcps802154_fproc_access_reset(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ if (access) {
+ if (local->fproc.tx_skb) {
+ WARN_ON(access->method ==
+ MCPS802154_ACCESS_METHOD_VENDOR);
+ access->ops->tx_return(
+ access, local->fproc.frame_idx,
+ local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL);
+ local->fproc.tx_skb = NULL;
+ }
+ mcps802154_fproc_access_done(local, false);
+ local->fproc.access = NULL;
+ }
+}
+
+static void mcps802154_broken_safe(struct mcps802154_local *local)
+{
+ if (local->fproc.state->broken)
+ local->fproc.state->broken(local);
+ else
+ mcps802154_fproc_broken_handle(local);
+}
+
+static void mcps802154_fproc_call_deferred(struct mcps802154_local *local)
+{
+ struct mcps802154_region *region = local->fproc.deferred;
+
+ if (region) {
+ local->fproc.deferred = NULL;
+ region->ops->deferred(region);
+ }
+}
+
+void mcps802154_fproc_schedule_change(struct mcps802154_local *local)
+{
+ local->fproc.state->schedule_change(local);
+ mcps802154_fproc_call_deferred(local);
+}
+
+void mcps802154_rx_frame(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_rx_frame(local);
+ if (local->fproc.state->rx_frame)
+ local->fproc.state->rx_frame(local);
+ else
+ mcps802154_broken_safe(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_rx_frame);
+
+void mcps802154_rx_timeout(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_rx_timeout(local);
+ if (local->fproc.state->rx_timeout)
+ local->fproc.state->rx_timeout(local);
+ else
+ mcps802154_broken_safe(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_rx_timeout);
+
+void mcps802154_rx_error(struct mcps802154_llhw *llhw,
+ enum mcps802154_rx_error_type error)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_rx_error(local, error);
+ if (local->fproc.state->rx_error)
+ local->fproc.state->rx_error(local, error);
+ else
+ mcps802154_broken_safe(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_rx_error);
+
+void mcps802154_tx_done(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_tx_done(local);
+ if (local->fproc.state->tx_done)
+ local->fproc.state->tx_done(local);
+ else
+ mcps802154_broken_safe(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_tx_done);
+
+void mcps802154_tx_too_late(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ if (local->fproc.state->tx_too_late)
+ local->fproc.state->tx_too_late(local);
+ else
+ mcps802154_broken_safe(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_tx_too_late);
+
+void mcps802154_rx_too_late(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ if (local->fproc.state->rx_too_late)
+ local->fproc.state->rx_too_late(local);
+ else
+ mcps802154_broken_safe(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_rx_too_late);
+
+void mcps802154_broken(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_broken(local);
+ mcps802154_broken_safe(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_broken);
+
+void mcps802154_timer_expired(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ trace_llhw_event_timer_expired(local);
+ if (local->fproc.state->timer_expired)
+ local->fproc.state->timer_expired(local);
+ mcps802154_fproc_call_deferred(local);
+ trace_llhw_event_done(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_timer_expired);
diff --git a/mac/fproc.h b/mac/fproc.h
new file mode 100644
index 0000000..f26f55d
--- /dev/null
+++ b/mac/fproc.h
@@ -0,0 +1,213 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FPROC_H
+#define NET_MCPS802154_FPROC_H
+
+struct mcps802154_local;
+
+/**
+ * struct mcps802154_fproc_state - FProc FSM state.
+ *
+ * This structure contains the callbacks which are called on an event to handle
+ * the transition from the active state.
+ */
+struct mcps802154_fproc_state {
+ /** @name: State name. */
+ const char *name;
+ /** @enter: Run when the state is entered. */
+ void (*enter)(struct mcps802154_local *local);
+ /** @leave: Run when the state is left. */
+ void (*leave)(struct mcps802154_local *local);
+ /** @rx_frame: Handle frame reception. */
+ void (*rx_frame)(struct mcps802154_local *local);
+ /** @rx_timeout: Handle reception timeout. */
+ void (*rx_timeout)(struct mcps802154_local *local);
+ void (*rx_too_late)(struct mcps802154_local *local);
+ /** @rx_error: Handle reception error. */
+ void (*rx_error)(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error);
+ /** @tx_done: Handle end of transmission. */
+ void (*tx_done)(struct mcps802154_local *local);
+ void (*tx_too_late)(struct mcps802154_local *local);
+ /** @broken: Handle unrecoverable error. */
+ void (*broken)(struct mcps802154_local *local);
+ /** @timer_expired: Handle timer expiration, ignored if NULL. */
+ void (*timer_expired)(struct mcps802154_local *local);
+ /** @schedule_change: Handle schedule change. */
+ void (*schedule_change)(struct mcps802154_local *local);
+};
+
+/** struct mcps802154_fproc - FProc private data. */
+struct mcps802154_fproc {
+ /** @state: Pointer to current state. */
+ const struct mcps802154_fproc_state *state;
+ /** @access: Access being handled. */
+ struct mcps802154_access *access;
+ /** @tx_skb: Buffer for frame being sent. */
+ struct sk_buff *tx_skb;
+ /** @frame_idx: Frame index for multiple frames method. */
+ size_t frame_idx;
+ /** @deferred: Pointer to region context requesting deferred call. */
+ struct mcps802154_region *deferred;
+};
+
+extern const struct mcps802154_fproc_state mcps802154_fproc_stopped;
+
+/**
+ * mcps802154_fproc_init() - Initialize FProc.
+ * @local: MCPS private data.
+ */
+void mcps802154_fproc_init(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_uninit() - Uninitialize FProc.
+ * @local: MCPS private data.
+ */
+void mcps802154_fproc_uninit(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_change_state() - Change the active state.
+ * @local: MCPS private data.
+ * @new_state: State to switch to.
+ */
+void mcps802154_fproc_change_state(
+ struct mcps802154_local *local,
+ const struct mcps802154_fproc_state *new_state);
+
+/**
+ * mcps802154_fproc_access() - Get access and handle it.
+ * @local: MCPS private data.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ */
+void mcps802154_fproc_access(struct mcps802154_local *local,
+ u32 next_timestamp_dtu);
+
+/**
+ * mcps802154_fproc_access_now() - Get access for current date, and handle it.
+ * @local: MCPS private data.
+ */
+void mcps802154_fproc_access_now(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_access_done() - Done with the access, release it.
+ * @local: MCPS private data.
+ * @error: True when an error happens during the access.
+ */
+void mcps802154_fproc_access_done(struct mcps802154_local *local, bool error);
+
+/**
+ * mcps802154_fproc_access_reset() - Reset an access when things go wrong.
+ * @local: MCPS private data.
+ *
+ * When an unexpected event is received, current transmitted frame and current
+ * access are kept as the frame buffer is possibly used by the low level driver.
+ * Later when the driver is reset or stopped, the buffer and the access can be
+ * released.
+ */
+void mcps802154_fproc_access_reset(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_schedule_change() - Try a schedule change.
+ * @local: MCPS private data.
+ *
+ * Inform the current state that the schedule has changed. To be called
+ * exclusively from CA.
+ */
+void mcps802154_fproc_schedule_change(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_stopped_handle() - Go to stopped.
+ * @local: MCPS private data.
+ */
+void mcps802154_fproc_stopped_handle(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_broken_handle() - Go to broken, or directly to stopped.
+ * @local: MCPS private data.
+ */
+void mcps802154_fproc_broken_handle(struct mcps802154_local *local);
+
+/**
+ * mcps802154_fproc_nothing_handle() - Handle inactivity.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_nothing_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+/**
+ * mcps802154_fproc_idle_handle() - Handle inactivity with trust in
+ * access->duration.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_idle_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+/**
+ * mcps802154_fproc_rx_handle() - Handle an RX access and change state.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_rx_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+/**
+ * mcps802154_fproc_tx_handle() - Handle an TX access and change state.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_tx_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+/**
+ * mcps802154_fproc_multi_handle() - Handle a multiple frames access and change
+ * state.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_multi_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+/**
+ * mcps802154_fproc_vendor_handle() - Handle a multiple frames access manage by vendor.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_fproc_vendor_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access);
+
+#endif /* NET_MCPS802154_FPROC_H */
diff --git a/mac/fproc_broken.c b/mac/fproc_broken.c
new file mode 100644
index 0000000..86bed74
--- /dev/null
+++ b/mac/fproc_broken.c
@@ -0,0 +1,77 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/printk.h>
+
+#include "mcps802154_i.h"
+#include "trace.h"
+
+static void mcps802154_fproc_broken_enter(struct mcps802154_local *local)
+{
+ trace_fproc_broken_enter(local);
+ pr_err_ratelimited("mcps802154: entering broken state for %s\n",
+ wpan_phy_name(local->hw->phy));
+ local->broken = true;
+}
+
+static void mcps802154_fproc_broken_leave(struct mcps802154_local *local)
+{
+ local->broken = false;
+}
+
+static void mcps802154_fproc_broken_ignore(struct mcps802154_local *local)
+{
+}
+
+static void
+mcps802154_fproc_broken_ignore_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+}
+
+static void
+mcps802154_fproc_broken_schedule_change(struct mcps802154_local *local)
+{
+ if (!local->start_stop_request)
+ mcps802154_fproc_stopped_handle(local);
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_broken = {
+ .name = "broken",
+ .enter = mcps802154_fproc_broken_enter,
+ .leave = mcps802154_fproc_broken_leave,
+ .rx_frame = mcps802154_fproc_broken_ignore,
+ .rx_timeout = mcps802154_fproc_broken_ignore,
+ .rx_error = mcps802154_fproc_broken_ignore_rx_error,
+ .tx_done = mcps802154_fproc_broken_ignore,
+ .broken = mcps802154_fproc_broken_ignore,
+ .schedule_change = mcps802154_fproc_broken_schedule_change,
+};
+
+void mcps802154_fproc_broken_handle(struct mcps802154_local *local)
+{
+ if (!local->start_stop_request)
+ /* Try to stop anyway. */
+ mcps802154_fproc_stopped_handle(local);
+ else
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_broken);
+}
diff --git a/mac/fproc_idle.c b/mac/fproc_idle.c
new file mode 100644
index 0000000..300ddb0
--- /dev/null
+++ b/mac/fproc_idle.c
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static void mcps802154_fproc_idle_timer_expired(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ mcps802154_fproc_access_done(local, false);
+ if (access->duration_dtu) {
+ u32 next_access_dtu =
+ access->timestamp_dtu + access->duration_dtu;
+
+ mcps802154_fproc_access(local, next_access_dtu);
+ } else {
+ mcps802154_fproc_access_now(local);
+ }
+}
+
+static void
+mcps802154_fproc_idle_schedule_change(struct mcps802154_local *local)
+{
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_idle = {
+ .name = "idle",
+ .timer_expired = mcps802154_fproc_idle_timer_expired,
+ .schedule_change = mcps802154_fproc_idle_schedule_change,
+};
+
+int mcps802154_fproc_idle_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ int r;
+
+ r = llhw_idle(local, access->duration_dtu != 0,
+ access->timestamp_dtu + access->duration_dtu);
+ if (r)
+ return r;
+
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_idle);
+
+ return 0;
+}
diff --git a/mac/fproc_multi.c b/mac/fproc_multi.c
new file mode 100644
index 0000000..2c7b2fa
--- /dev/null
+++ b/mac/fproc_multi.c
@@ -0,0 +1,415 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+
+#include "mcps802154_fproc.h"
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static int mcps802154_fproc_multi_handle_frame(struct mcps802154_local *local,
+ struct mcps802154_access *access,
+ size_t frame_idx);
+
+static int
+mcps802154_fproc_multi_restore_filter(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ int r = 0;
+
+ if (access->promiscuous) {
+ r = llhw_set_promiscuous_mode(local,
+ local->pib.mac_promiscuous);
+ } else if (access->hw_addr_filt_changed) {
+ struct ieee802154_hw_addr_filt hw_addr_filt;
+
+ hw_addr_filt.pan_id = local->pib.mac_pan_id;
+ hw_addr_filt.short_addr = local->pib.mac_short_addr;
+ hw_addr_filt.ieee_addr = local->pib.mac_extended_addr;
+ hw_addr_filt.pan_coord = local->mac_pan_coord;
+ r = llhw_set_hw_addr_filt(local, &hw_addr_filt,
+ access->hw_addr_filt_changed);
+ }
+
+ return r;
+}
+
+static int mcps802154_fproc_multi_restore(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ if (access->channel) {
+ int r;
+ const struct mcps802154_channel *channel =
+ &local->pib.phy_current_channel;
+
+ r = llhw_set_channel(local, channel->page, channel->channel,
+ channel->preamble_code);
+ if (r)
+ return r;
+ }
+
+ return mcps802154_fproc_multi_restore_filter(local, access);
+}
+
+/**
+ * mcps802154_fproc_multi_check_frames() - Check absence of Rx without timeout.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ * @frame_idx: Start frame index in the frames array.
+ *
+ * Returns: 0 on success, -errno otherwise.
+ */
+static int
+mcps802154_fproc_multi_check_frames(struct mcps802154_local *local,
+ const struct mcps802154_access *access,
+ int frame_idx)
+{
+ if (access->n_frames && !access->frames)
+ return -EINVAL;
+ for (; frame_idx < access->n_frames; frame_idx++) {
+ const struct mcps802154_access_frame *frame =
+ &access->frames[frame_idx];
+ /* Only first Rx can be without timeout. */
+ if (!frame->is_tx && frame->rx.frame_config.timeout_dtu == -1)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * mcps802154_fproc_multi_next() - Continue with the next frame, or next
+ * access.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ * @frame_idx: Frame index in current access, must be valid, will be
+ * incremented.
+ */
+static void mcps802154_fproc_multi_next(struct mcps802154_local *local,
+ struct mcps802154_access *access,
+ size_t frame_idx)
+{
+ frame_idx++;
+ if (access->ops->access_extend && frame_idx == access->n_frames) {
+ frame_idx = 0;
+ access->n_frames = 0;
+ access->ops->access_extend(access);
+ access->error = mcps802154_fproc_multi_check_frames(local, access, 0);
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ }
+ if (frame_idx < access->n_frames) {
+ /* Next frame. */
+ access->error = mcps802154_fproc_multi_handle_frame(local, access,
+ frame_idx);
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ } else {
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+ }
+ }
+ } else {
+ access->error = mcps802154_fproc_multi_restore(local, access);
+ mcps802154_fproc_access_done(local, !!access->error);
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ /* Next access. */
+ if (access->duration_dtu) {
+ u32 next_access_dtu =
+ access->timestamp_dtu + access->duration_dtu;
+ mcps802154_fproc_access(local, next_access_dtu);
+ } else {
+ mcps802154_fproc_access_now(local);
+ }
+ }
+}
+
+static void mcps802154_fproc_multi_rx_rx_frame(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+ struct mcps802154_access_frame *frame = &access->frames[frame_idx];
+
+ /* Read frame. */
+ struct sk_buff *skb = NULL;
+ struct mcps802154_rx_frame_info info = {
+ .flags = frame->rx.frame_info_flags_request,
+ };
+ access->error = llhw_rx_get_frame(local, &skb, &info);
+ if (!access->error)
+ access->ops->rx_frame(access, frame_idx, skb, &info,
+ MCPS802154_RX_ERROR_NONE);
+
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static void mcps802154_fproc_multi_rx_rx_timeout(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+
+ access->ops->rx_frame(access, frame_idx, NULL, NULL,
+ MCPS802154_RX_ERROR_TIMEOUT);
+
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static void
+mcps802154_fproc_multi_rx_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+ struct mcps802154_rx_frame_info info = {
+ .flags = MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU,
+ };
+
+ llhw_rx_get_error_frame(local, &info);
+ access->ops->rx_frame(access, frame_idx, NULL, &info, error);
+
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static void
+mcps802154_fproc_multi_rx_schedule_change(struct mcps802154_local *local)
+{
+ /* If the Rx is done without a timeout, disable RX and change the access. */
+ struct mcps802154_access *access = local->fproc.access;
+ int frame_idx = local->fproc.frame_idx;
+ struct mcps802154_access_frame *frame = &access->frames[frame_idx];
+
+ if (frame->rx.frame_config.timeout_dtu == -1) {
+ /* Disable RX. */
+ access->error = llhw_rx_disable(local);
+ if (access->error == -EBUSY)
+ /* Wait for RX result. */
+ return;
+
+ access->ops->rx_frame(access, frame_idx, NULL, NULL,
+ MCPS802154_RX_ERROR_TIMEOUT);
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+ }
+}
+
+static void mcps802154_fproc_multi_rx_too_late(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+
+ access->ops->rx_frame(access, frame_idx, NULL, NULL,
+ MCPS802154_RX_ERROR_HPDWARN);
+
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_multi_rx = {
+ .name = "multi_rx",
+ .rx_frame = mcps802154_fproc_multi_rx_rx_frame,
+ .rx_timeout = mcps802154_fproc_multi_rx_rx_timeout,
+ .rx_error = mcps802154_fproc_multi_rx_rx_error,
+ .rx_too_late = mcps802154_fproc_multi_rx_too_late,
+ .schedule_change = mcps802154_fproc_multi_rx_schedule_change,
+};
+
+static void mcps802154_fproc_multi_tx_tx_done(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+
+ access->ops->tx_return(access, frame_idx, local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED);
+ local->fproc.tx_skb = NULL;
+
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static void mcps802154_fproc_multi_tx_too_late(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ size_t frame_idx = local->fproc.frame_idx;
+
+ access->ops->tx_return(access, frame_idx, local->fproc.tx_skb,
+ MCPS802154_TX_ERROR_HPDWARN);
+ local->fproc.tx_skb = NULL;
+
+ /* Next. */
+ mcps802154_fproc_multi_next(local, access, frame_idx);
+}
+
+static void
+mcps802154_fproc_multi_tx_schedule_change(struct mcps802154_local *local)
+{
+ /* Wait for end of current frame. */
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_multi_tx = {
+ .name = "multi_tx",
+ .tx_done = mcps802154_fproc_multi_tx_tx_done,
+ .tx_too_late = mcps802154_fproc_multi_tx_too_late,
+ .schedule_change = mcps802154_fproc_multi_tx_schedule_change,
+};
+
+/**
+ * mcps802154_fproc_multi_handle_frame() - Handle a single frame and change
+ * state.
+ * @local: MCPS private data.
+ * @access: Current access to handle.
+ * @frame_idx: Frame index in current access, must be valid.
+ *
+ * Return: 0 or error.
+ */
+static int mcps802154_fproc_multi_handle_frame(struct mcps802154_local *local,
+ struct mcps802154_access *access,
+ size_t frame_idx)
+{
+ struct mcps802154_access_frame *frame;
+ struct sk_buff *skb;
+ int r;
+
+ local->fproc.frame_idx = frame_idx;
+
+ frame = &access->frames[frame_idx];
+ if (!frame->is_tx) {
+ if (frame->rx.frame_config.flags &
+ MCPS802154_RX_FRAME_CONFIG_AACK)
+ return -EINVAL;
+
+ if (frame->sts_params) {
+ r = llhw_set_sts_params(local, frame->sts_params);
+ if (r)
+ return r;
+ }
+
+ r = llhw_rx_enable(local, &frame->rx.frame_config, frame_idx,
+ 0);
+ if (r)
+ return r;
+
+ mcps802154_fproc_change_state(local,
+ &mcps802154_fproc_multi_rx);
+ } else {
+ if (frame->tx_frame_config.rx_enable_after_tx_dtu)
+ return -EINVAL;
+
+ skb = access->ops->tx_get_frame(access, frame_idx);
+
+ /* TODO: prepare next RX directly. */
+
+ if (frame->sts_params) {
+ r = llhw_set_sts_params(local, frame->sts_params);
+ if (r) {
+ access->ops->tx_return(
+ access, frame_idx, skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL);
+ return r;
+ }
+ }
+
+ r = llhw_tx_frame(local, skb, &frame->tx_frame_config,
+ frame_idx, 0);
+ if (r) {
+ access->ops->tx_return(
+ access, frame_idx, skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL);
+ return r;
+ }
+
+ local->fproc.tx_skb = skb;
+ mcps802154_ca_access_hold(local);
+ mcps802154_fproc_change_state(local,
+ &mcps802154_fproc_multi_tx);
+ }
+
+ return 0;
+}
+
+int mcps802154_fproc_multi_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ int r;
+
+ if (access->n_frames == 0)
+ return -EINVAL;
+ r = mcps802154_fproc_multi_check_frames(local, access, 1);
+ if (r)
+ return r;
+
+ if (access->promiscuous) {
+ r = llhw_set_promiscuous_mode(local, true);
+ if (r)
+ return r;
+ } else if (access->hw_addr_filt_changed) {
+ r = llhw_set_hw_addr_filt(local, &access->hw_addr_filt,
+ access->hw_addr_filt_changed);
+ if (r)
+ return r;
+ }
+
+ if (access->channel) {
+ r = llhw_set_channel(local, access->channel->page,
+ access->channel->channel,
+ access->channel->preamble_code);
+ if (r) {
+ mcps802154_fproc_multi_restore_filter(local, access);
+ return r;
+ }
+ }
+
+ if (access->hrp_uwb_params) {
+ r = llhw_set_hrp_uwb_params(local, access->hrp_uwb_params);
+ if (r)
+ return r;
+ }
+
+ return mcps802154_fproc_multi_handle_frame(local, access, 0);
+}
diff --git a/mac/fproc_nothing.c b/mac/fproc_nothing.c
new file mode 100644
index 0000000..07d3b45
--- /dev/null
+++ b/mac/fproc_nothing.c
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static void mcps802154_fproc_nothing_access(struct mcps802154_local *local)
+{
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_nothing = {
+ .name = "nothing",
+ .timer_expired = mcps802154_fproc_nothing_access,
+ .schedule_change = mcps802154_fproc_nothing_access,
+};
+
+int mcps802154_fproc_nothing_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ int r;
+
+ r = llhw_idle(local, access->duration_dtu != 0,
+ access->timestamp_dtu + access->duration_dtu);
+ if (r)
+ return r;
+
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_nothing);
+
+ return 0;
+}
diff --git a/mac/fproc_rx.c b/mac/fproc_rx.c
new file mode 100644
index 0000000..b7cc171
--- /dev/null
+++ b/mac/fproc_rx.c
@@ -0,0 +1,131 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/errno.h>
+
+#include "mcps802154_fproc.h"
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static void
+mcps802154_fproc_rx_wait_tx_done_tx_done(struct mcps802154_local *local)
+{
+ /* End current access and ask for next one. */
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+}
+
+static void
+mcps802154_fproc_rx_wait_tx_done_schedule_change(struct mcps802154_local *local)
+{
+ /* Nothing, wait for tx_done. */
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_rx_wait_tx_done = {
+ .name = "rx_wait_tx_done",
+ .tx_done = mcps802154_fproc_rx_wait_tx_done_tx_done,
+ .schedule_change = mcps802154_fproc_rx_wait_tx_done_schedule_change,
+};
+
+static void mcps802154_fproc_rx_rx_frame(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ /* Read frame. */
+ struct sk_buff *skb = NULL;
+ struct mcps802154_rx_frame_info info = {
+ .flags = MCPS802154_RX_FRAME_INFO_LQI,
+ };
+ access->error = llhw_rx_get_frame(local, &skb, &info);
+ if (!access->error) {
+ access->ops->rx_frame(access, 0, skb, &info,
+ MCPS802154_RX_ERROR_NONE);
+ /* If auto-ack was sent, wait for tx_done. */
+ if (info.flags & MCPS802154_RX_FRAME_INFO_AACK) {
+ mcps802154_fproc_change_state(
+ local, &mcps802154_fproc_rx_wait_tx_done);
+ return;
+ }
+ }
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ /* Next access. */
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+}
+
+static void mcps802154_fproc_rx_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+ mcps802154_fproc_access_done(local, true);
+ /* Next access. */
+ mcps802154_fproc_access_now(local);
+}
+
+static void mcps802154_fproc_rx_schedule_change(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ /* Disable RX. */
+ access->error = llhw_rx_disable(local);
+ if (access->error == -EBUSY)
+ /* Wait for RX result. */
+ return;
+
+ if (access->error) {
+ if (mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_access_done(local, true);
+ mcps802154_fproc_broken_handle(local);
+ return;
+ }
+ }
+ /* Next access. */
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_rx = {
+ .name = "rx",
+ .rx_frame = mcps802154_fproc_rx_rx_frame,
+ .rx_error = mcps802154_fproc_rx_rx_error,
+ .schedule_change = mcps802154_fproc_rx_schedule_change,
+};
+
+int mcps802154_fproc_rx_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ struct mcps802154_rx_frame_config rx_config = {
+ .flags = MCPS802154_RX_FRAME_CONFIG_AACK,
+ .timeout_dtu = -1,
+ };
+ access->error = llhw_rx_enable(local, &rx_config, 0, 0);
+ if (access->error)
+ return access->error;
+
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_rx);
+
+ return 0;
+}
diff --git a/mac/fproc_stopped.c b/mac/fproc_stopped.c
new file mode 100644
index 0000000..4ad3ff8
--- /dev/null
+++ b/mac/fproc_stopped.c
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static void mcps802154_fproc_stopped_enter(struct mcps802154_local *local)
+{
+ mcps802154_ca_notify_stop(local);
+ local->started = false;
+ wake_up(&local->wq);
+}
+
+static void mcps802154_fproc_stopped_leave(struct mcps802154_local *local)
+{
+ local->started = true;
+}
+
+static void mcps802154_fproc_stopped_ignore(struct mcps802154_local *local)
+{
+ WARN_ON_ONCE(1);
+}
+
+static void
+mcps802154_fproc_stopped_ignore_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+ mcps802154_fproc_stopped_ignore(local);
+}
+
+static void
+mcps802154_fproc_stopped_schedule_change(struct mcps802154_local *local)
+{
+ int r;
+
+ if (local->start_stop_request) {
+ r = llhw_start(local);
+ if (r)
+ goto err;
+
+ mcps802154_fproc_access_now(local);
+ if (local->broken)
+ goto err_stop;
+ }
+
+ return;
+
+err_stop:
+ local->start_stop_request = false;
+ local->fproc.state->schedule_change(local);
+err:
+ /* Device is broken, but stopped. */
+ WARN_ON(local->started);
+}
+
+const struct mcps802154_fproc_state mcps802154_fproc_stopped = {
+ .name = "stopped",
+ .enter = mcps802154_fproc_stopped_enter,
+ .leave = mcps802154_fproc_stopped_leave,
+ .rx_frame = mcps802154_fproc_stopped_ignore,
+ .rx_timeout = mcps802154_fproc_stopped_ignore,
+ .rx_error = mcps802154_fproc_stopped_ignore_rx_error,
+ .tx_done = mcps802154_fproc_stopped_ignore,
+ .broken = mcps802154_fproc_stopped_ignore,
+ .schedule_change = mcps802154_fproc_stopped_schedule_change,
+};
+
+void mcps802154_fproc_stopped_handle(struct mcps802154_local *local)
+{
+ llhw_stop(local);
+ mcps802154_fproc_access_reset(local);
+ mcps802154_schedule_clear(local);
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_stopped);
+}
diff --git a/mac/fproc_tx.c b/mac/fproc_tx.c
new file mode 100644
index 0000000..f96a431
--- /dev/null
+++ b/mac/fproc_tx.c
@@ -0,0 +1,171 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/netdevice.h>
+
+#include "mcps802154_fproc.h"
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+static void mcps802154_fproc_tx_tx_done(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ access->ops->tx_return(access, 0, local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED);
+ local->fproc.tx_skb = NULL;
+
+ mcps802154_fproc_access_done(local, false);
+
+ /* Next access. */
+ mcps802154_fproc_access_now(local);
+}
+
+static void mcps802154_fproc_tx_schedule_change(struct mcps802154_local *local)
+{
+ /* Nothing, wait TX done. */
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_tx = {
+ .name = "tx",
+ .tx_done = mcps802154_fproc_tx_tx_done,
+ .schedule_change = mcps802154_fproc_tx_schedule_change,
+};
+
+static void mcps802154_fproc_tx_wack_rx_frame(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ /* Read frame. */
+ struct sk_buff *skb = NULL;
+ struct mcps802154_rx_frame_info info = {
+ .flags = MCPS802154_RX_FRAME_INFO_LQI,
+ };
+ access->error = llhw_rx_get_frame(local, &skb, &info);
+ if (!access->error) {
+ /* Is it an ack frame? With same seq number? */
+ if (IEEE802154_FC_TYPE(skb->data[0]) ==
+ IEEE802154_FC_TYPE_ACK &&
+ skb->data[IEEE802154_FC_LEN] ==
+ local->fproc.tx_skb->data[IEEE802154_FC_LEN]) {
+ /* Ack frame. */
+ access->ops->tx_return(
+ access, 0, local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED);
+ } else {
+ /* Not an ack or read failure or a bad sequence number. */
+ access->ops->tx_return(
+ access, 0, local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE);
+ }
+ dev_kfree_skb_any(skb);
+ local->fproc.tx_skb = NULL;
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+ } else if (access->error && mcps802154_fproc_is_non_recoverable_error(access)) {
+ mcps802154_fproc_broken_handle(local);
+ } else {
+ mcps802154_fproc_access_done(local, false);
+ mcps802154_fproc_access_now(local);
+ }
+}
+
+/* Used for error and time-out. */
+static void mcps802154_fproc_tx_wack_rx_timeout(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ access->ops->tx_return(access, 0, local->fproc.tx_skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE);
+ local->fproc.tx_skb = NULL;
+
+ mcps802154_fproc_access_done(local, false);
+
+ /* Next access. */
+ mcps802154_fproc_access_now(local);
+}
+
+static void
+mcps802154_fproc_tx_wack_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+ mcps802154_fproc_tx_wack_rx_timeout(local);
+}
+
+static void mcps802154_fproc_tx_wack_tx_done(struct mcps802154_local *local)
+{
+ /* Nothing, wait for ack. */
+}
+
+static void
+mcps802154_fproc_tx_wack_schedule_change(struct mcps802154_local *local)
+{
+ /* Nothing, wait for ack. */
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_tx_wack = {
+ .name = "tx_wack",
+ .rx_frame = mcps802154_fproc_tx_wack_rx_frame,
+ .rx_timeout = mcps802154_fproc_tx_wack_rx_timeout,
+ .rx_error = mcps802154_fproc_tx_wack_rx_error,
+ .tx_done = mcps802154_fproc_tx_wack_tx_done,
+ .schedule_change = mcps802154_fproc_tx_wack_schedule_change,
+};
+
+#define IEEE802154_AIFS_DURATION_SYMBOLS 12
+
+int mcps802154_fproc_tx_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ int r;
+ u8 ack_req;
+ struct mcps802154_tx_frame_config tx_config = {};
+ struct sk_buff *skb = access->ops->tx_get_frame(access, 0);
+
+ if (!skb)
+ return -ENOMEM;
+
+ ack_req = skb->data[0] & IEEE802154_FC_ACK_REQ;
+ if (ack_req) {
+ tx_config.rx_enable_after_tx_dtu =
+ IEEE802154_AIFS_DURATION_SYMBOLS *
+ local->llhw.symbol_dtu;
+ }
+
+ r = llhw_tx_frame(local, skb, &tx_config, 0, 0);
+ if (r) {
+ access->ops->tx_return(
+ access, 0, skb,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL);
+ return r;
+ }
+
+ local->fproc.tx_skb = skb;
+ mcps802154_ca_access_hold(local);
+ if (ack_req)
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_tx_wack);
+ else
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_tx);
+ return 0;
+}
diff --git a/mac/fproc_vendor.c b/mac/fproc_vendor.c
new file mode 100644
index 0000000..f067e16
--- /dev/null
+++ b/mac/fproc_vendor.c
@@ -0,0 +1,162 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+
+#include "mcps802154_i.h"
+
+static void
+mcps802154_fproc_vendor_handle_callback_return(struct mcps802154_local *local,
+ int r)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ /* Fetch/copy needed data before access_done.
+ * Avoid usage of access pointer after access done call. */
+ int duration_dtu = access->duration_dtu;
+ u32 next_access_dtu = access->timestamp_dtu + duration_dtu;
+ /* Filter-out the 'stop' request as error. */
+ bool error = r != 1;
+
+ if (!r)
+ return;
+
+ access->error = r;
+ mcps802154_fproc_access_done(local, error);
+ if (error) {
+ mcps802154_fproc_broken_handle(local);
+ } else if (duration_dtu) {
+ mcps802154_fproc_access(local, next_access_dtu);
+ } else {
+ mcps802154_fproc_access_now(local);
+ }
+}
+
+static void mcps802154_fproc_vendor_rx_frame(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ int r;
+
+ if (access->vendor_ops->rx_frame)
+ r = access->vendor_ops->rx_frame(access);
+ else
+ r = -EOPNOTSUPP;
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+}
+
+static void mcps802154_fproc_vendor_rx_timeout(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ int r;
+
+ if (access->vendor_ops->rx_timeout)
+ r = access->vendor_ops->rx_timeout(access);
+ else
+ r = -EOPNOTSUPP;
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+}
+
+static void
+mcps802154_fproc_vendor_rx_error(struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ int r;
+
+ if (access->vendor_ops->rx_error)
+ r = access->vendor_ops->rx_error(access, error);
+ else
+ r = -EOPNOTSUPP;
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+}
+
+static void mcps802154_fproc_vendor_tx_done(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ int r;
+
+ if (access->vendor_ops->tx_done)
+ r = access->vendor_ops->tx_done(access);
+ else
+ r = -EOPNOTSUPP;
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+}
+
+static void mcps802154_fproc_vendor_broken(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+ int r;
+
+ if (access->vendor_ops->broken)
+ r = access->vendor_ops->broken(access);
+ else
+ r = -EOPNOTSUPP;
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+}
+
+static void
+mcps802154_fproc_vendor_timer_expired(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ if (access->vendor_ops->timer_expired) {
+ int r;
+
+ r = access->vendor_ops->timer_expired(access);
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+ }
+}
+
+static void
+mcps802154_fproc_vendor_schedule_change(struct mcps802154_local *local)
+{
+ struct mcps802154_access *access = local->fproc.access;
+
+ if (access->vendor_ops->schedule_change) {
+ int r;
+
+ r = access->vendor_ops->schedule_change(access);
+ mcps802154_fproc_vendor_handle_callback_return(local, r);
+ }
+}
+
+static const struct mcps802154_fproc_state mcps802154_fproc_vendor = {
+ .name = "vendor",
+ .rx_frame = mcps802154_fproc_vendor_rx_frame,
+ .rx_timeout = mcps802154_fproc_vendor_rx_timeout,
+ .rx_error = mcps802154_fproc_vendor_rx_error,
+ .tx_done = mcps802154_fproc_vendor_tx_done,
+ .broken = mcps802154_fproc_vendor_broken,
+ .timer_expired = mcps802154_fproc_vendor_timer_expired,
+ .schedule_change = mcps802154_fproc_vendor_schedule_change,
+};
+
+int mcps802154_fproc_vendor_handle(struct mcps802154_local *local,
+ struct mcps802154_access *access)
+{
+ mcps802154_fproc_change_state(local, &mcps802154_fproc_vendor);
+
+ if (access->vendor_ops->handle) {
+ return access->vendor_ops->handle(access);
+ }
+ return 0;
+}
diff --git a/mac/frame.c b/mac/frame.c
new file mode 100644
index 0000000..8a61512
--- /dev/null
+++ b/mac/frame.c
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/ieee802154.h>
+#include <linux/module.h>
+#include <net/mcps802154_frame.h>
+#include <net/mcps802154.h>
+
+#include "mcps802154_i.h"
+
+struct sk_buff *mcps802154_frame_alloc(struct mcps802154_llhw *llhw,
+ unsigned int size, gfp_t flags)
+{
+ struct sk_buff *skb;
+ size_t hlen;
+ size_t tlen;
+
+ hlen = llhw->hw->extra_tx_headroom;
+ tlen = IEEE802154_FCS_LEN;
+
+ skb = alloc_skb(hlen + size + tlen, flags);
+ if (!skb)
+ return NULL;
+
+ skb_reserve(skb, hlen);
+ skb_tailroom_reserve(skb, size, tlen);
+
+ return skb;
+}
+EXPORT_SYMBOL(mcps802154_frame_alloc);
diff --git a/mac/idle_region.c b/mac/idle_region.c
new file mode 100644
index 0000000..e4f62c3
--- /dev/null
+++ b/mac/idle_region.c
@@ -0,0 +1,166 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "idle_region.h"
+#include "trace.h"
+#include <net/idle_region_nl.h>
+#include <linux/errno.h>
+#include <linux/limits.h>
+
+static struct mcps802154_region_ops idle_region_ops;
+
+struct idle_local {
+ /**
+ * @region: Region instance returned to MCPS.
+ */
+ struct mcps802154_region region;
+ /**
+ * @llhw: Low-level device pointer.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @params: Parameters.
+ */
+ struct idle_params params;
+ /**
+ * @access: Access returned to ca.
+ */
+ struct mcps802154_access access;
+};
+
+static inline struct idle_local *
+region_to_local(struct mcps802154_region *region)
+{
+ return container_of(region, struct idle_local, region);
+}
+
+static struct mcps802154_region *idle_open(struct mcps802154_llhw *llhw)
+{
+ struct idle_local *local;
+
+ local = kzalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return NULL;
+ local->llhw = llhw;
+ local->region.ops = &idle_region_ops;
+
+ /* Default value of parameters. */
+ local->params.min_duration_dtu = llhw->anticip_dtu * 2;
+ local->params.max_duration_dtu = 0;
+
+ return &local->region;
+}
+
+static void idle_close(struct mcps802154_region *region)
+{
+ kfree(region_to_local(region));
+}
+
+static const struct nla_policy idle_param_nla_policy[IDLE_PARAM_ATTR_MAX + 1] = {
+ [IDLE_PARAM_ATTR_MIN_DURATION_DTU] = NLA_POLICY_MIN(NLA_S32, 0),
+ [IDLE_PARAM_ATTR_MAX_DURATION_DTU] = NLA_POLICY_MIN(NLA_S32, 0),
+};
+
+static int idle_set_parameters(struct mcps802154_region *region,
+ const struct nlattr *params,
+ struct netlink_ext_ack *extack)
+{
+ struct idle_local *local = region_to_local(region);
+ struct nlattr *attrs[IDLE_PARAM_ATTR_MAX + 1];
+ struct idle_params *p = &local->params;
+ int min_duration_dtu, max_duration_dtu;
+ int r;
+
+ r = nla_parse_nested(attrs, IDLE_PARAM_ATTR_MAX, params,
+ idle_param_nla_policy, extack);
+ if (r)
+ return r;
+
+ min_duration_dtu =
+ attrs[IDLE_PARAM_ATTR_MIN_DURATION_DTU] ?
+ nla_get_s32(attrs[IDLE_PARAM_ATTR_MIN_DURATION_DTU]) :
+ p->min_duration_dtu;
+ max_duration_dtu =
+ attrs[IDLE_PARAM_ATTR_MAX_DURATION_DTU] ?
+ nla_get_s32(attrs[IDLE_PARAM_ATTR_MAX_DURATION_DTU]) :
+ p->max_duration_dtu;
+ if (max_duration_dtu && min_duration_dtu &&
+ min_duration_dtu > max_duration_dtu)
+ return -EINVAL;
+
+ p->min_duration_dtu = min_duration_dtu;
+ p->max_duration_dtu = max_duration_dtu;
+ trace_region_idle_params(p);
+ return 0;
+}
+
+static struct mcps802154_access_ops idle_access_ops = {};
+
+static struct mcps802154_access *
+idle_get_access(struct mcps802154_region *region, u32 next_timestamp_dtu,
+ int next_in_region_dtu, int region_duration_dtu)
+{
+ struct idle_local *local = region_to_local(region);
+ struct mcps802154_access *access = &local->access;
+ const struct idle_params *p = &local->params;
+ int left_region_duration_dtu = region_duration_dtu - next_in_region_dtu;
+ int duration_dtu;
+
+ if (!left_region_duration_dtu) {
+ /* Region used with endless scheduler. */
+ duration_dtu = p->max_duration_dtu;
+ } else {
+ /* Region used directly in on_demand scheduler. */
+ if (left_region_duration_dtu < p->min_duration_dtu)
+ return NULL;
+ duration_dtu = left_region_duration_dtu;
+ }
+
+ trace_region_idle_get_access(next_timestamp_dtu, duration_dtu);
+ access->method = MCPS802154_ACCESS_METHOD_IDLE;
+ access->ops = &idle_access_ops;
+ access->timestamp_dtu = next_timestamp_dtu;
+ access->duration_dtu = duration_dtu;
+
+ return access;
+}
+
+static struct mcps802154_region_ops idle_region_ops = {
+ .owner = THIS_MODULE,
+ .name = "idle",
+ .open = idle_open,
+ .close = idle_close,
+ .set_parameters = idle_set_parameters,
+ .get_demand = NULL, /* Not wanted. */
+ .get_access = idle_get_access,
+};
+
+int mcps802154_idle_region_init(void)
+{
+ return mcps802154_region_register(&idle_region_ops);
+}
+
+void mcps802154_idle_region_exit(void)
+{
+ mcps802154_region_unregister(&idle_region_ops);
+}
diff --git a/mac/idle_region.h b/mac/idle_region.h
new file mode 100644
index 0000000..f3082d1
--- /dev/null
+++ b/mac/idle_region.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef IDLE_REGION_H
+#define IDLE_REGION_H
+
+struct idle_params {
+ /**
+ * @min_duration_dtu: Minimum duration of an access.
+ * If min is 0, no minimum is required on get_access.
+ */
+ int min_duration_dtu;
+ /**
+ * @max_duration_dtu: Maximum duration of an access.
+ */
+ int max_duration_dtu;
+};
+
+int mcps802154_idle_region_init(void);
+void mcps802154_idle_region_exit(void);
+
+#endif /* IDLE_REGION_H */
diff --git a/mac/ie.c b/mac/ie.c
new file mode 100644
index 0000000..2c7d2a9
--- /dev/null
+++ b/mac/ie.c
@@ -0,0 +1,312 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/ieee802154.h>
+#include <linux/module.h>
+#include <net/mcps802154_frame.h>
+
+/**
+ * enum mcps802154_ie_state - State of information element writing.
+ * @MCPS802154_IE_STATE_INIT: Initial state, no IE written.
+ * @MCPS802154_IE_STATE_HEADER_IE: At least one header element written.
+ * @MCPS802154_IE_STATE_PAYLOAD_IE: At least one payload element written.
+ * @MCPS802154_IE_STATE_NESTED_MLME_IE: At least one payload element written,
+ * the last one is a MLME IE containing at least one nested IE.
+ */
+enum mcps802154_ie_state {
+ MCPS802154_IE_STATE_INIT,
+ MCPS802154_IE_STATE_HEADER_IE,
+ MCPS802154_IE_STATE_PAYLOAD_IE,
+ MCPS802154_IE_STATE_NESTED_MLME_IE,
+};
+
+/**
+ * struct mcps802154_ie_cb - Control buffer used in sk_buff to store information
+ * while building IE.
+ */
+struct mcps802154_ie_cb {
+ /**
+ * @ie_state: State of IEs writing, used to know whether a terminator is
+ * needed or not.
+ */
+ enum mcps802154_ie_state ie_state;
+ /**
+ * @mlme_ie_index: Index in buffer of the MLME IE header. Valid in the
+ * corresponding state only, used to increment IE payload length.
+ */
+ u16 mlme_ie_index;
+ /**
+ * @header_len: Length of frame header. Can be used for frame
+ * encryption, the header is not encrypted but only used for
+ * authentication.
+ */
+ u16 header_len;
+};
+
+void mcps802154_ie_put_begin(struct sk_buff *skb)
+{
+ struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
+
+ cb->ie_state = MCPS802154_IE_STATE_INIT;
+ cb->header_len = skb->len;
+}
+EXPORT_SYMBOL(mcps802154_ie_put_begin);
+
+int mcps802154_ie_put_end(struct sk_buff *skb, bool data_payload)
+{
+ struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
+
+ if (data_payload) {
+ bool err = false;
+
+ if (cb->ie_state == MCPS802154_IE_STATE_HEADER_IE)
+ err = !mcps802154_ie_put_header_ie(
+ skb, IEEE802154_IE_HEADER_TERMINATION_2_ID, 0);
+ else if (cb->ie_state >= MCPS802154_IE_STATE_PAYLOAD_IE)
+ err = !mcps802154_ie_put_payload_ie(
+ skb, IEEE802154_IE_PAYLOAD_TERMINATION_GID, 0);
+ if (unlikely(err))
+ return -ENOBUFS;
+ }
+ return cb->header_len;
+}
+EXPORT_SYMBOL(mcps802154_ie_put_end);
+
+void *mcps802154_ie_put_header_ie(struct sk_buff *skb, int element_id,
+ unsigned int len)
+{
+ u16 ie_header;
+ u8 *ie;
+ struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
+
+ if (unlikely(len > FIELD_MAX(IEEE802154_HEADER_IE_HEADER_LENGTH)))
+ return NULL;
+ if (unlikely(skb_availroom(skb) < IEEE802154_IE_HEADER_LEN + len))
+ return NULL;
+
+ if (cb->ie_state == MCPS802154_IE_STATE_INIT)
+ skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8;
+ cb->ie_state = MCPS802154_IE_STATE_HEADER_IE;
+
+ ie_header =
+ FIELD_PREP(IEEE802154_HEADER_IE_HEADER_LENGTH, len) |
+ FIELD_PREP(IEEE802154_HEADER_IE_HEADER_ELEMENT_ID, element_id) |
+ IEEE802154_HEADER_IE_HEADER_TYPE;
+
+ ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
+ put_unaligned_le16(ie_header, ie);
+
+ cb->header_len = skb->len;
+
+ return ie + IEEE802154_IE_HEADER_LEN;
+}
+EXPORT_SYMBOL(mcps802154_ie_put_header_ie);
+
+void *mcps802154_ie_put_payload_ie(struct sk_buff *skb, int group_id,
+ unsigned int len)
+{
+ u16 ie_header;
+ u8 *ie;
+ struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
+ bool need_terminator = false;
+
+ if (cb->ie_state < MCPS802154_IE_STATE_PAYLOAD_IE)
+ need_terminator = true;
+
+ if (unlikely(len > FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH)))
+ return NULL;
+ if (unlikely(skb_availroom(skb) <
+ (need_terminator ? IEEE802154_IE_HEADER_LEN : 0) +
+ IEEE802154_IE_HEADER_LEN + len))
+ return NULL;
+
+ if (cb->ie_state == MCPS802154_IE_STATE_INIT)
+ skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8;
+ if (need_terminator)
+ mcps802154_ie_put_header_ie(
+ skb, IEEE802154_IE_HEADER_TERMINATION_1_ID, 0);
+ cb->ie_state = MCPS802154_IE_STATE_PAYLOAD_IE;
+
+ ie_header =
+ FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH, len) |
+ FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID, group_id) |
+ IEEE802154_PAYLOAD_IE_HEADER_TYPE;
+
+ ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
+ put_unaligned_le16(ie_header, ie);
+
+ return ie + IEEE802154_IE_HEADER_LEN;
+}
+EXPORT_SYMBOL(mcps802154_ie_put_payload_ie);
+
+void *mcps802154_ie_put_nested_mlme_ie(struct sk_buff *skb, int sub_id,
+ unsigned int len)
+{
+ u16 ie_header;
+ u8 *ie;
+ struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
+
+ if (sub_id < IEEE802154_IE_NESTED_SHORT_MIN_SID) {
+ if (unlikely(
+ len >
+ FIELD_MAX(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH)))
+ return NULL;
+ ie_header = FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH,
+ len) |
+ FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID,
+ sub_id) |
+ IEEE802154_LONG_NESTED_IE_HEADER_TYPE;
+ } else {
+ if (unlikely(len >
+ FIELD_MAX(
+ IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH)))
+ return NULL;
+ ie_header = FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH,
+ len) |
+ FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID,
+ sub_id) |
+ IEEE802154_SHORT_NESTED_IE_HEADER_TYPE;
+ }
+
+ if (cb->ie_state != MCPS802154_IE_STATE_NESTED_MLME_IE) {
+ ie = mcps802154_ie_put_payload_ie(
+ skb, IEEE802154_IE_PAYLOAD_MLME_GID,
+ IEEE802154_IE_HEADER_LEN + len);
+ if (unlikely(!ie))
+ return NULL;
+ cb->ie_state = MCPS802154_IE_STATE_NESTED_MLME_IE;
+ cb->mlme_ie_index = ie - IEEE802154_IE_HEADER_LEN - skb->data;
+ } else {
+ u8 *mlme_ie = skb->data + cb->mlme_ie_index;
+ u16 mlme_ie_header = get_unaligned_le16(mlme_ie);
+ int mlme_len = FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_LENGTH,
+ mlme_ie_header);
+
+ mlme_len += IEEE802154_IE_HEADER_LEN + len;
+ if (unlikely(mlme_len >
+ FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH)))
+ return NULL;
+ mlme_ie_header = (mlme_ie_header &
+ ~IEEE802154_PAYLOAD_IE_HEADER_LENGTH) |
+ FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH,
+ mlme_len);
+ put_unaligned_le16(mlme_ie_header, mlme_ie);
+
+ ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
+ }
+
+ put_unaligned_le16(ie_header, ie);
+
+ return ie + IEEE802154_IE_HEADER_LEN;
+}
+EXPORT_SYMBOL(mcps802154_ie_put_nested_mlme_ie);
+
+int mcps802154_ie_get(struct sk_buff *skb,
+ struct mcps802154_ie_get_context *context)
+{
+ u16 ie_header;
+ bool header;
+ bool last = false;
+
+ if (skb->len < IEEE802154_IE_HEADER_LEN) {
+ if (context->mlme_len)
+ /* This could only happen if caller made a mistake. */
+ return -EINVAL;
+ if (skb->len > 0)
+ /* Not enough for a header, but too much for nothing. */
+ return -EBADMSG;
+ context->in_payload = true;
+ context->kind = MCPS802154_IE_GET_KIND_NONE;
+ context->id = 0;
+ context->len = 0;
+ return 1;
+ }
+
+ ie_header = get_unaligned_le16(skb->data);
+ skb_pull(skb, sizeof(ie_header));
+
+ if (context->mlme_len) {
+ context->kind = MCPS802154_IE_GET_KIND_MLME_NESTED;
+ if ((ie_header & IEEE802154_IE_HEADER_TYPE) ==
+ IEEE802154_LONG_NESTED_IE_HEADER_TYPE) {
+ context->id = FIELD_GET(
+ IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID,
+ ie_header);
+ context->len = FIELD_GET(
+ IEEE802154_LONG_NESTED_IE_HEADER_LENGTH,
+ ie_header);
+ } else {
+ context->id = FIELD_GET(
+ IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID,
+ ie_header);
+ context->len = FIELD_GET(
+ IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH,
+ ie_header);
+ }
+ if (context->mlme_len < IEEE802154_IE_HEADER_LEN + context->len)
+ return -EBADMSG;
+ context->mlme_len -= IEEE802154_IE_HEADER_LEN + context->len;
+ } else {
+ header = (ie_header & IEEE802154_IE_HEADER_TYPE) ==
+ IEEE802154_HEADER_IE_HEADER_TYPE;
+ if (header != !context->in_payload)
+ return -EBADMSG;
+ if (header) {
+ context->kind = MCPS802154_IE_GET_KIND_HEADER;
+ context->id = FIELD_GET(
+ IEEE802154_HEADER_IE_HEADER_ELEMENT_ID,
+ ie_header);
+ context->len = FIELD_GET(
+ IEEE802154_HEADER_IE_HEADER_LENGTH, ie_header);
+ if (context->id ==
+ IEEE802154_IE_HEADER_TERMINATION_1_ID)
+ context->in_payload = true;
+ if (context->id ==
+ IEEE802154_IE_HEADER_TERMINATION_2_ID) {
+ context->in_payload = true;
+ last = true;
+ }
+ } else {
+ context->kind = MCPS802154_IE_GET_KIND_PAYLOAD;
+ context->id =
+ FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID,
+ ie_header);
+ context->len = FIELD_GET(
+ IEEE802154_PAYLOAD_IE_HEADER_LENGTH, ie_header);
+ if (context->id ==
+ IEEE802154_IE_PAYLOAD_TERMINATION_GID)
+ last = true;
+ else if (context->id == IEEE802154_IE_PAYLOAD_MLME_GID)
+ context->mlme_len = context->len;
+ }
+ if (skb->len < context->len)
+ return -EBADMSG;
+ }
+
+ return last ? 1 : 0;
+}
+EXPORT_SYMBOL(mcps802154_ie_get);
diff --git a/mac/include/Android.bp b/mac/include/Android.bp
new file mode 100644
index 0000000..a7d34c7
--- /dev/null
+++ b/mac/include/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2021 Qorvo US, Inc.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_headers {
+ name: "uwb.mac.net.headers",
+ host_supported: true,
+ vendor_available: true,
+ system_ext_specific: true,
+ export_include_dirs : ["."],
+}
diff --git a/mac/include/net/fira_region_nl.h b/mac/include/net/fira_region_nl.h
new file mode 100644
index 0000000..07c3fb5
--- /dev/null
+++ b/mac/include/net/fira_region_nl.h
@@ -0,0 +1,902 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef FIRA_REGION_NL_H
+#define FIRA_REGION_NL_H
+
+/**
+ * enum fira_call - FiRa calls identifiers.
+ *
+ * @FIRA_CALL_GET_CAPABILITIES:
+ * Request FiRa capabilities.
+ * @FIRA_CALL_SESSION_INIT:
+ * Initialize FiRa session.
+ * @FIRA_CALL_SESSION_START:
+ * Start FiRa session.
+ * @FIRA_CALL_SESSION_STOP:
+ * Stop FiRa session.
+ * @FIRA_CALL_SESSION_DEINIT:
+ * Deinit FiRa session.
+ * @FIRA_CALL_SESSION_SET_PARAMS:
+ * Set session parameters.
+ * @FIRA_CALL_NEW_CONTROLEE:
+ * Add a new controlee to a session.
+ * @FIRA_CALL_DEL_CONTROLEE:
+ * Delete controlee from the session.
+ * @FIRA_CALL_SESSION_NOTIFICATION:
+ * Notify session reports.
+ * @FIRA_CALL_SESSION_GET_PARAMS:
+ * Get session parameters.
+ * @FIRA_CALL_SESSION_GET_STATE:
+ * Get session state.
+ * @FIRA_CALL_SESSION_GET_COUNT:
+ * Get count of active and inactive sessions.
+ * @FIRA_CALL_SET_CONTROLEE:
+ * Set controlees to a session.
+ * @FIRA_CALL_GET_CONTROLEES:
+ * Get the list of controlees.
+ * @FIRA_CALL_MAX: Internal use.
+ */
+enum fira_call {
+ FIRA_CALL_GET_CAPABILITIES,
+ FIRA_CALL_SESSION_INIT,
+ FIRA_CALL_SESSION_START,
+ FIRA_CALL_SESSION_STOP,
+ FIRA_CALL_SESSION_DEINIT,
+ FIRA_CALL_SESSION_SET_PARAMS,
+ FIRA_CALL_NEW_CONTROLEE,
+ FIRA_CALL_DEL_CONTROLEE,
+ FIRA_CALL_SESSION_NOTIFICATION,
+ FIRA_CALL_SESSION_GET_PARAMS,
+ FIRA_CALL_SESSION_GET_STATE,
+ FIRA_CALL_SESSION_GET_COUNT,
+ FIRA_CALL_SET_CONTROLEE,
+ FIRA_CALL_GET_CONTROLEES,
+ FIRA_CALL_MAX,
+};
+
+/**
+ * enum fira_capability_attrs - FiRa capabilities.
+ *
+ * @FIRA_CAPABILITY_ATTR_FIRA_PHY_VERSION_RANGE:
+ * FiRa PHY version range supported, ex: 0x01010202 -> support from 1.1 to 2.2.
+ * @FIRA_CAPABILITY_ATTR_FIRA_MAC_VERSION_RANGE:
+ * FiRa MAC version range supported, ex: @0x01010202 -> support from 1.1 to 2.2.
+ * @FIRA_CAPABILITY_ATTR_DEVICE_CLASS:
+ * Class of FiRa device.
+ * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_RESPONDER:
+ * Acting as a controlee/responder supported.
+ * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_INITIATOR:
+ * Acting as a controlee/initiator supported.
+ * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_RESPONDER:
+ * Acting as a controller/responder supported.
+ * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_INITIATOR:
+ * Acting as a controller/initiator supported.
+ * @FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_UNICAST:
+ * Unicast supported.
+ * @FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_ONE_TO_MANY:
+ * One to many supported.
+ * @FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_SS_TWR:
+ * SS-TWR supported.
+ * @FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_DS_TWR:
+ * DS-TWR supported.
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_CONTROLEES_MAX:
+ * Maximum number of controlee, no limit if not present (but the size of
+ * the control message is a limit).
+ * @FIRA_CAPABILITY_ATTR_ROUND_HOPPING:
+ * Round hopping supported.
+ * @FIRA_CAPABILITY_ATTR_BLOCK_STRIDING:
+ * Block striding supported.
+ * @FIRA_CAPABILITY_ATTR_EMBEDDED_MODE_NON_DEFERRED:
+ * Non-deferred mode supported.
+ * @FIRA_CAPABILITY_ATTR_CHANNEL_NUMBER:
+ * Bitmask, with bits 5, 6, 8, 9, 10, 12, 13 and 14 representing
+ * the corresponding channel support.
+ * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP0:
+ * SP0 supported.
+ * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP1:
+ * SP1 supported.
+ * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP3:
+ * SP3 supported.
+ * @FIRA_CAPABILITY_ATTR_PRF_MODE_BPRF:
+ * BPRF supported.
+ * @FIRA_CAPABILITY_ATTR_PRF_MODE_HPRF:
+ * HPRF supported.
+ * @FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_64:
+ * Preamble duration 64 symbols supported.
+ * @FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_32:
+ * Preamble duration 32 symbols supported.
+ * @FIRA_CAPABILITY_ATTR_SFD_ID_0:
+ * SFD 0 supported.
+ * @FIRA_CAPABILITY_ATTR_SFD_ID_1:
+ * SFD 1 supported.
+ * @FIRA_CAPABILITY_ATTR_SFD_ID_2:
+ * SFD 2 supported.
+ * @FIRA_CAPABILITY_ATTR_SFD_ID_3:
+ * SFD 3 supported.
+ * @FIRA_CAPABILITY_ATTR_SFD_ID_4:
+ * SFD 4 supported.
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_0:
+ * 0 segment for STS supported (SP0).
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_1:
+ * 1 segment for STS supported.
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_2:
+ * 2 segments for STS supported.
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_3:
+ * 3 segments for STS supported.
+ * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_4:
+ * 4 segments for STS supported.
+ * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_6M81:
+ * 6.81 Mbps support.
+ * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_7M80:
+ * 7.80 Mbps support.
+ * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_27M2:
+ * 27.2 Mbps support.
+ * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_31M2:
+ * 31.2 Mbps support.
+ * @FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_850K:
+ * 850 kbps for PHR in BPRF supported.
+ * @FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_6M81:
+ * 6.81 Mbps for PHR in BPRF supported.
+ * @FIRA_CAPABILITY_ATTR_MAC_FCS_TYPE_CRC32:
+ * CRC32 supported.
+ * @FIRA_CAPABILITY_ATTR_TX_ADAPTIVE_PAYLOAD_POWER:
+ * Adaptive payload power for TX supported.
+ * @FIRA_CAPABILITY_ATTR_RX_ANTENNA_PAIRS:
+ * Number of antenna pairs for RX.
+ * @FIRA_CAPABILITY_ATTR_TX_ANTENNAS:
+ * Number of antennas for TX.
+ * @FIRA_CAPABILITY_ATTR_STS_STATIC:
+ * Static STS supported.
+ * @FIRA_CAPABILITY_ATTR_STS_DYNAMIC:
+ * Dynamic STS supported.
+ * @FIRA_CAPABILITY_ATTR_STS_DYNAMIC_INDIVIDUAL_KEY:
+ * Dynamic STS for controlee individual keys supported.
+ * @FIRA_CAPABILITY_ATTR_STS_PROVISIONED:
+ * Provisioned STS supported.
+ * @FIRA_CAPABILITY_ATTR_STS_PROVISIONED_INDIVIDUAL_KEY:
+ * Provisioned STS for controlee individual keys supported.
+ * @FIRA_CAPABILITY_ATTR_AOA_AZIMUTH:
+ * AoA in azimuth supported.
+ * @FIRA_CAPABILITY_ATTR_AOA_AZIMUTH_FULL:
+ * AoA in azimuth supported on full circle (front/back difference).
+ * @FIRA_CAPABILITY_ATTR_AOA_ELEVATION:
+ * AoA in elevation supported.
+ * @FIRA_CAPABILITY_ATTR_AOA_FOM:
+ * AoA FOM supported.
+ *
+ * @FIRA_CAPABILITY_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_CAPABILITY_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_CAPABILITY_ATTR_MAX: Internal use.
+ */
+enum fira_capability_attrs {
+ FIRA_CAPABILITY_ATTR_UNSPEC,
+ /* Main session capabilities. */
+ FIRA_CAPABILITY_ATTR_FIRA_PHY_VERSION_RANGE,
+ FIRA_CAPABILITY_ATTR_FIRA_MAC_VERSION_RANGE,
+ FIRA_CAPABILITY_ATTR_DEVICE_CLASS,
+ FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_RESPONDER,
+ FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_INITIATOR,
+ FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_RESPONDER,
+ FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_INITIATOR,
+ FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_UNICAST,
+ FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_ONE_TO_MANY,
+ FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_SS_TWR,
+ FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_DS_TWR,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_CONTROLEES_MAX,
+ /* Behaviour. */
+ FIRA_CAPABILITY_ATTR_ROUND_HOPPING,
+ FIRA_CAPABILITY_ATTR_BLOCK_STRIDING,
+ FIRA_CAPABILITY_ATTR_EMBEDDED_MODE_NON_DEFERRED,
+ /* Radio. */
+ FIRA_CAPABILITY_ATTR_CHANNEL_NUMBER,
+ FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP0,
+ FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP1,
+ FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP3,
+ FIRA_CAPABILITY_ATTR_PRF_MODE_BPRF,
+ FIRA_CAPABILITY_ATTR_PRF_MODE_HPRF,
+ FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_64,
+ FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_32,
+ FIRA_CAPABILITY_ATTR_SFD_ID_0,
+ FIRA_CAPABILITY_ATTR_SFD_ID_1,
+ FIRA_CAPABILITY_ATTR_SFD_ID_2,
+ FIRA_CAPABILITY_ATTR_SFD_ID_3,
+ FIRA_CAPABILITY_ATTR_SFD_ID_4,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_0,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_1,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_2,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_3,
+ FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_4,
+ FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_6M81,
+ FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_7M80,
+ FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_27M2,
+ FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_31M2,
+ FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_850K,
+ FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_6M81,
+ FIRA_CAPABILITY_ATTR_MAC_FCS_TYPE_CRC32,
+ FIRA_CAPABILITY_ATTR_TX_ADAPTIVE_PAYLOAD_POWER,
+ /* Antenna. */
+ FIRA_CAPABILITY_ATTR_RX_ANTENNA_PAIRS,
+ FIRA_CAPABILITY_ATTR_TX_ANTENNAS,
+ /* STS and crypto capabilities. */
+ FIRA_CAPABILITY_ATTR_STS_STATIC,
+ FIRA_CAPABILITY_ATTR_STS_DYNAMIC,
+ FIRA_CAPABILITY_ATTR_STS_DYNAMIC_INDIVIDUAL_KEY,
+ FIRA_CAPABILITY_ATTR_STS_PROVISIONED,
+ FIRA_CAPABILITY_ATTR_STS_PROVISIONED_INDIVIDUAL_KEY,
+ /* Report. */
+ FIRA_CAPABILITY_ATTR_AOA_AZIMUTH,
+ FIRA_CAPABILITY_ATTR_AOA_AZIMUTH_FULL,
+ FIRA_CAPABILITY_ATTR_AOA_ELEVATION,
+ FIRA_CAPABILITY_ATTR_AOA_FOM,
+
+ __FIRA_CAPABILITY_ATTR_AFTER_LAST,
+ FIRA_CAPABILITY_ATTR_MAX = __FIRA_CAPABILITY_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_call_attrs - FiRa call attributes.
+ *
+ * @FIRA_CALL_ATTR_SESSION_ID:
+ * Session identifier.
+ * @FIRA_CALL_ATTR_SESSION_PARAMS:
+ * Session parameters.
+ * @FIRA_CALL_ATTR_CONTROLEES:
+ * Controlees information.
+ * @FIRA_CALL_ATTR_RANGING_DATA:
+ * Ranging data.
+ * @FIRA_CALL_ATTR_CAPABILITIES:
+ * Capabilities.
+ * @FIRA_CALL_ATTR_SESSION_STATE:
+ * Session state.
+ * @FIRA_CALL_ATTR_SESSION_COUNT:
+ * Sessions count.
+ * @FIRA_CALL_ATTR_SEQUENCE_NUMBER:
+ * Session notification counter.
+ * @FIRA_CALL_ATTR_RANGING_DIAGNOSTICS:
+ * Diagnostic information.
+ *
+ * @FIRA_CALL_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_CALL_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_CALL_ATTR_MAX: Internal use.
+ */
+enum fira_call_attrs {
+ FIRA_CALL_ATTR_UNSPEC,
+ FIRA_CALL_ATTR_SESSION_ID,
+ FIRA_CALL_ATTR_SESSION_PARAMS,
+ FIRA_CALL_ATTR_CONTROLEES,
+ FIRA_CALL_ATTR_RANGING_DATA,
+ FIRA_CALL_ATTR_CAPABILITIES,
+ FIRA_CALL_ATTR_SESSION_STATE,
+ FIRA_CALL_ATTR_SESSION_COUNT,
+ FIRA_CALL_ATTR_SEQUENCE_NUMBER,
+ FIRA_CALL_ATTR_RANGING_DIAGNOSTICS,
+
+ __FIRA_CALL_ATTR_AFTER_LAST,
+ FIRA_CALL_ATTR_MAX = __FIRA_CALL_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_session_param_attrs - FiRa session parameters attributes.
+ *
+ * @FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE:
+ * Controlee (0) or controller (1)
+ * @FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE:
+ * Responder (0) or initiator (1)
+ * @FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE:
+ * SS-TWR (1) or DS-TWR (2, default)
+ * @FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE:
+ * Unicast (0), one to many (1) or many to many (2, unused)
+ * @FIRA_SESSION_PARAM_ATTR_SHORT_ADDR:
+ * Override device address for this session (UCI: DEVICE_MAC_ADDRESS)
+ * @FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR:
+ * Controller short addresses (UCI: DST_MAC_ADDRESS) [controlee only]
+ * @FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS:
+ * Initiation time in unit of 1200 RSTU (same as ms), default 0.
+ * @FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU:
+ * Duration of a slot in RSTU, default 2400. (2 ms)
+ * @FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS:
+ * Block size in unit of 1200 RSTU (same as ms), default 200.
+ * @FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS:
+ * Number of slots per ranging round, default 30 (UCI: SLOTS_PER_RR)
+ * @FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH:
+ * Value of block striding, default 0, can be changed when the
+ * session is active [controller only]
+ * @FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS:
+ * Unlimited (0, default) or limit of measurements (1-65535)
+ * @FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY:
+ * Number of failed ranging round attempts before stopping the session,
+ * or disabled (0, default) [controller only]
+ * @FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING:
+ * Disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_PRIORITY:
+ * Priority value, higher has more priority (1-100, default 50)
+ * @FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE:
+ * Disabled (0) or enabled (1, default) report phase [controller only]
+ * @FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR:
+ * Measurement report available at responder (0, default)
+ * or at initiator (1) [controller only]
+ * @FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE:
+ * Deferred (0, default) or non-deferred (1) (not in UCI)
+ * @FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT:
+ * 1-10, default 1 [controller only]
+ * @FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER:
+ * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14
+ * @FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX:
+ * Override preamble code for this session, BPRF (9-24), HPRF (25-32)
+ * @FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG:
+ * SP0 (0), SP1 (1), SP2 (2, unused, not in FiRa 1.1) or SP3 (3, default)
+ * @FIRA_SESSION_PARAM_ATTR_PRF_MODE:
+ * BPRF (0, default), HPRF (1) or HPRF with high data rate (2)
+ * @FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION:
+ * 64 (1, default) or 32 (0, only for HPRF)
+ * @FIRA_SESSION_PARAM_ATTR_SFD_ID:
+ * BPRF (0 or 2), HPRF (1-4), default 2
+ * @FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS:
+ * 0-2, default to 0 for SP0, default to 1 for SP1 & SP3, 2 not supported
+ * @FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE:
+ * 6.81 Mbps (0, default), 7.80 Mbps (1), 27.2 Mbps (2), 31.2 Mbps (3)
+ * @FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE:
+ * 850 kbps (0, default) or 6.81 Mbps (1)
+ * @FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE:
+ * CRC16 (0, default) or CRC32 (1, not supported)
+ * @FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER:
+ * Disable adaptive payload power for TX (0, default) or enable (1)
+ * @FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE:
+ * Sequence of measurement steps. Configures antenna flexibility.
+ * @FIRA_SESSION_PARAM_ATTR_STS_CONFIG:
+ * Static STS (0, default), Dynamic STS (1), Dynamic STS for controlee
+ * individual keys (2), Provisioned STS (3), Provisioned STS for controlee
+ * individual keys (4). See &enum fira_sts_mode.
+ * @FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID:
+ * For dynamic STS for controlee individual key, sub session ID [controlee only]
+ * @FIRA_SESSION_PARAM_ATTR_VUPPER64:
+ * vUpper64 for static STS (UCI: STATIC_STS_IV | VENDOR_ID)
+ * @FIRA_SESSION_PARAM_ATTR_SESSION_KEY:
+ * For provisioned sts only, session key.
+ * @FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY:
+ * For dynamic or provisioned STS, sub session key [controlee only]
+ * @FIRA_SESSION_PARAM_ATTR_KEY_ROTATION:
+ * Disable (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE:
+ * Key rotation rate parameter n, key rotated after 2^n rounds
+ * @FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ:
+ * No local AoA report (0) or -90 to +90 (1, default)
+ * @FIRA_SESSION_PARAM_ATTR_REPORT_TOF:
+ * Report ToF in result message, disabled (0) or enabled (1, default)
+ * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH:
+ * Report AoA azimuth in result message, disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION:
+ * Report AoA elevation in result message, disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM:
+ * Report AoA FOM in result message, disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_REPORT_RSSI:
+ * Report average RSSI of the round in result message, disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI:
+* Set the vendor OUI for custom data exchanges
+* @FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD:
+* Set the data payload to send in next ranging packet
+ * @FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS:
+ * Report diagnostic information on each round, disabled (0, default) or enabled (1)
+ * @FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS:
+ * Bitfield activating various frame diagnostics in the report (0: no frame diagnostic report, default).
+ * see &enum fira_ranging_diagnostics_frame_report_flags
+ * @FIRA_SESSION_PARAM_ATTR_STS_LENGTH:
+ * Number of symbols in a STS segment. 32 (0x00), 64 (0x01, default) or 128
+ * symbols (0x02)
+ * @FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MAX:
+ * Maximum for contention access period size
+ * @FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MIN:
+ * Minimum for contention access period size
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG:
+ * Configure range data notification
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM:
+ * Lower bound in mm above which the ranging notifications
+ * should be enabled when RANGE_DATA_NTF_CONFIG is set to "proximity" or "aoa_proximity"
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM:
+ * Upper bound in mm above which the ranging notifications
+ * should be disabled when RANGE_DATA_NTF_CONFIG is set to "proximity" or "aoa_proximity"
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI:
+ * Lower bound in rad_2pi_q16 for AOA azimuth above which the ranging notifications
+ * should automatically be enabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity".
+ * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -180° to ~180°.
+ * should be less than or equal to RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH value.
+ * (default = -180)
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI:
+ * Upper bound in rad_2pi_q16 for AOA azimuth above which the ranging notifications
+ * should automatically be disabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity".
+ * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -180° to ~180°.
+ * Should be greater than or equal to RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH value.
+ * (default = ~180)
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI:
+ * Lower bound in rad_2pi_q16 for AOA elevation above which the ranging notifications
+ * should automatically be enabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity".
+ * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -90° to +90°.
+ * Should be less than or equal to RANGE_DATA_NTF_PROXIMITY_UPPER_BOUND_A_ELEVATION value.
+ * (default = -90)
+ * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI:
+ * Upper bound in rad_2pi_q16 for AOA elevation above which the ranging notifications
+ * should automatically be disabled if RANGE_DATA_NTF_CONFIG has bit is set to "aoa" or "aoa_proximity".
+ * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -90° to +90°.
+ * Should be greater than or equal to RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION value.
+ * (default = +90)
+ * @FIRA_SESSION_PARAM_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_SESSION_PARAM_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_SESSION_PARAM_ATTR_MAX: Internal use.
+ */
+enum fira_session_param_attrs {
+ FIRA_SESSION_PARAM_ATTR_UNSPEC,
+ /* Main session parameters */
+ FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE,
+ FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE,
+ FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE,
+ FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE,
+ FIRA_SESSION_PARAM_ATTR_SHORT_ADDR,
+ FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR,
+ /* Timings */
+ FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS,
+ FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU,
+ FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS,
+ FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS,
+ FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH,
+ /* Behaviour */
+ FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS,
+ FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY,
+ FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING,
+ FIRA_SESSION_PARAM_ATTR_PRIORITY,
+ FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE,
+ FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR,
+ FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE,
+ FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT,
+ /* Radio */
+ FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER,
+ FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX,
+ FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG,
+ FIRA_SESSION_PARAM_ATTR_PRF_MODE,
+ FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION,
+ FIRA_SESSION_PARAM_ATTR_SFD_ID,
+ FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS,
+ FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE,
+ FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE,
+ FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE,
+ FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER,
+ /* Measurement Sequence */
+ FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE,
+ /* STS and crypto */
+ FIRA_SESSION_PARAM_ATTR_STS_CONFIG,
+ FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID,
+ FIRA_SESSION_PARAM_ATTR_VUPPER64,
+ FIRA_SESSION_PARAM_ATTR_SESSION_KEY,
+ FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY,
+ FIRA_SESSION_PARAM_ATTR_KEY_ROTATION,
+ FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE,
+ /* Report */
+ FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ,
+ FIRA_SESSION_PARAM_ATTR_REPORT_TOF,
+ FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH,
+ FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION,
+ FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM,
+ FIRA_SESSION_PARAM_ATTR_REPORT_RSSI,
+ /* Custom Data */
+ FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI,
+ FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD,
+ /* Diagnostics */
+ FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS,
+ FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS,
+ /* Misc */
+ FIRA_SESSION_PARAM_ATTR_STS_LENGTH,
+ /* Contention-based ranging */
+ FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MAX,
+ FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MIN,
+ /* Range data notification enable */
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI,
+ FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI,
+ __FIRA_SESSION_PARAM_ATTR_AFTER_LAST,
+ FIRA_SESSION_PARAM_ATTR_MAX = __FIRA_SESSION_PARAM_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_call_controlee_attrs - FiRa controlee parameters attributes.
+ *
+ * @FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR:
+ * Controlee short address.
+ * @FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID:
+ * Controlee sub session identifier
+ * @FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY:
+ * Controlee sub session key
+ *
+ * @FIRA_CALL_CONTROLEE_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_CALL_CONTROLEE_ATTR_MAX: Internal use.
+ */
+enum fira_call_controlee_attrs {
+ FIRA_CALL_CONTROLEE_ATTR_UNSPEC,
+ FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR,
+ FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID,
+ FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY,
+
+ __FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST,
+ FIRA_CALL_CONTROLEE_ATTR_MAX = __FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_ranging_data_attrs_stopped_values - Values for
+ * FIRA_RANGING_DATA_ATTR_STOPPED attribute.
+ *
+ * @FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST:
+ * Stopped due to stop request.
+ * @FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND:
+ * Stopped using in band signaling from the controller [controlee only].
+ * @FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE:
+ * Stopped due to maximum attempts reached with no response [controller
+ * only].
+ */
+enum fira_ranging_data_attrs_stopped_values {
+ FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST,
+ FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND,
+ FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE,
+};
+
+/**
+ * enum fira_ranging_data_attrs - FiRa ranging data attributes.
+ *
+ * @FIRA_RANGING_DATA_ATTR_STOPPED:
+ * If present, session was stopped, see
+ * &enum fira_ranging_data_attrs_stopped_values.
+ * @FIRA_RANGING_DATA_ATTR_BLOCK_INDEX:
+ * Current block index.
+ * @FIRA_RANGING_DATA_ATTR_TIMESTAMP_NS:
+ * Timestamp in nanoseconds in the CLOCK_MONOTONIC time reference.
+ * @FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS:
+ * Current ranging interval (block size * (stride + 1)) in unit of
+ * 1200 RSTU (same as ms).
+ * @FIRA_RANGING_DATA_ATTR_MEASUREMENTS:
+ * Measurements, see fira_ranging_data_measurements.
+ *
+ * @FIRA_RANGING_DATA_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DATA_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DATA_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_data_attrs {
+ FIRA_RANGING_DATA_ATTR_UNSPEC,
+ FIRA_RANGING_DATA_ATTR_STOPPED,
+ FIRA_RANGING_DATA_ATTR_BLOCK_INDEX,
+ FIRA_RANGING_DATA_ATTR_TIMESTAMP_NS,
+ FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS,
+ FIRA_RANGING_DATA_ATTR_MEASUREMENTS,
+
+ __FIRA_RANGING_DATA_ATTR_AFTER_LAST,
+ FIRA_RANGING_DATA_ATTR_MAX = __FIRA_RANGING_DATA_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_ranging_data_measurements_attrs - FiRa ranging data measurements
+ * attributes.
+ *
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SHORT_ADDR:
+ * Address of the participing device.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STOPPED:
+ * If present, ranging was stopped as requested [controller only].
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STATUS:
+ * Status of the measurement (0: success, 1: tx failed, 2: rx timeout,
+ * 3: rx phy dec error, 4: rx phy toa error, 5: rx phy sts error,
+ * 6: rx mac dec error, 7: rx mac ie dec error, 8: rx mac ie missing)
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SLOT_INDEX:
+ * In case of failure slot index where the error was detected.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_NLOS:
+ * Set if in non line of sight.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOS:
+ * Set if in line of sight.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DISTANCE_MM:
+ * Distance in mm.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA:
+ * Local AoA measurement,
+ * cf. fira_ranging_data_measurements_aoa_attrs.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_AZIMUTH:
+ * Local AoA measurement for azimuth,
+ * cf. fira_ranging_data_measurements_aoa_attrs.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_ELEVATION:
+ * Local AoA measurement for elevation,
+ * cf. fira_ranging_data_measurements_aoa_attrs.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_2PI:
+ * Estimation of reception angle in the azimuth of the participing device.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_PI:
+ * Estimation of reception angle in elevation of the participing device.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_FOM:
+ * Estimation of azimuth reliability of the participing device.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_FOM:
+ * Estimation of elevation reliability of the participing device.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_RSSI:
+ * RSSI "summary" for received frames during the ranging round,
+ * reported as Q7.1. Summary method depends on session params
+ * (average, minimum, etc).
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_SEQ_SENT:
+ * Sequence number of last data sent
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_RECV:
+ * Received Data payload in the SP1 RFRAME
+ *
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_data_measurements_attrs {
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_UNSPEC,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SHORT_ADDR,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STOPPED,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STATUS,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SLOT_INDEX,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_NLOS,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOS,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DISTANCE_MM,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_AZIMUTH,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_ELEVATION,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_2PI,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_PI,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_FOM,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_FOM,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_RSSI,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_SEQ_SENT,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_RECV,
+
+ __FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST,
+ FIRA_RANGING_DATA_MEASUREMENTS_ATTR_MAX =
+ __FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_ranging_data_measurements_aoa_attrs - FiRa ranging AoA measurements
+ * attributes.
+ *
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_RX_ANTENNA_SET:
+ * Antenna set index.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_2PI:
+ * Estimation of reception angle.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_FOM:
+ * Estimation of local AoA reliability.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_PDOA_2PI:
+ * Estimation of reception phase difference.
+ *
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_data_measurements_aoa_attrs {
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_UNSPEC,
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_RX_ANTENNA_SET,
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_2PI,
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_FOM,
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_PDOA_2PI,
+
+ __FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST,
+ FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_MAX =
+ __FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_session_param_meas_seq_step_attrs - FiRa measurement sequence
+ * step attributes.
+ *
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MEASUREMENT_TYPE:
+ * The type of measurement to perform during the step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_N_MEASUREMENTS:
+ * The number of times this type of measurement shall be performed
+ * during the step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SET_NONRANGING:
+ * The antenna set to use to receive the non-rfames during the step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SETS_RANGING:
+ * The antenna set to use to receive the rframes frame during the step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_NONRANGING:
+ * The antenna set to use to transmit the non-rframes during the step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_RANGING:
+ * The antenna set to use to transmit the rframes during the step.
+ *
+ *
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MAX: Internal use.
+ */
+enum fira_session_param_meas_seq_step_attrs {
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_UNSPEC,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MEASUREMENT_TYPE,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_N_MEASUREMENTS,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SET_NONRANGING,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SETS_RANGING,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_NONRANGING,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_RANGING,
+
+ __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MAX =
+ __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_session_params_meas_seq_step_sets_attrs - Attributes of the
+ * FiRa RX antenna sets to use during a step.
+ *
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_0:
+ * Antenna set used to receive all rframes for range, azimuth and elevation
+ * steps or initial rframe for azimuth_elevation step.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_1:
+ * Antenna set used to receive final rframes for azimuth_elevation step.
+ *
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_UNSPEC:
+ * Invalid command.
+ * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_MAX:
+ * Internal use.
+ * @__FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST:
+ * Internal use.
+ */
+enum fira_session_params_meas_seq_step_sets_attrs {
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_UNSPEC,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_0,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_1,
+
+ __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST,
+ FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_MAX =
+ __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST -
+ 1
+};
+
+/**
+ * enum fira_ranging_diagnostics_attrs - FiRa ranging diagnostic attributes.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS:
+ * Diagnostics for individual frames of the round.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST : Internal use.
+ * @FIRA_RANGING_DIAGNOSTICS_ATTR_MAX : Internal use.
+ */
+enum fira_ranging_diagnostics_attrs {
+ FIRA_RANGING_DIAGNOSTICS_ATTR_UNSPEC,
+ FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS,
+
+ __FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST,
+ FIRA_RANGING_DIAGNOSTICS_ATTR_MAX =
+ __FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_ranging_diagnostics_frame_reports_attrs - FiRa ranging
+ * diagnostic info for individual frames.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ANT_SET:
+ * Antenna set ID, used for the frame transmission.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ACTION:
+ * Action type of the frame (0: TX or 1: RX).
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MSG_ID:
+ * FiRa message ID (0: RIM, 1: RRM, 2: RFM, 3: CM,
+ * 4: MRM, 5: RRRM, 6: CU).
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS:
+ * RSSI for the current (Rx) frame, reported as a Q7.1.
+ * As many values as receivers.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS:
+ * Nested attribute reporting different AoA related information.
+ * As many as AoA types.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS:
+ * Nested attribute reporting CIR sample window information.
+ * As many array elements as receivers.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_diagnostics_frame_reports_attrs {
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_UNSPEC,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ANT_SET,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ACTION,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MSG_ID,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS,
+
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MAX =
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum fira_ranging_diagnostics_frame_reports_aoa_attrs - AoA diagnostic
+ * information per frame
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TDOA:
+ * TDoA in rctu, reported as s16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_PDOA:
+ * PDoA in radians, reported as Q5.11.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AOA:
+ * AoA in radians, reported as Q5.11.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_FOM:
+ * AoA FoM between 0 and 255 (0 being an invalid measure and 255 being
+ * a 100% confidence measure), reported as u8.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TYPE:
+ * AoA Measurement type (azimuth, elevation...), reported as u8.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_diagnostics_frame_reports_aoa_attrs {
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_UNSPEC,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TDOA,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_PDOA,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AOA,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_FOM,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TYPE,
+
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_MAX =
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST -
+ 1
+};
+
+/**
+ * enum fira_ranging_diagnostics_frame_reports_cir_attrs - CIR diagnostic
+ * information per frame
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_IDX:
+ * Absolute index of the sample considered as first path, reported as u16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SNR:
+ * SNR of the sample considered as first path, reported as s16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_NS:
+ * Timestamp of the sample considered as first path, reported as u16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_IDX:
+ * Absolute index of the sample considered as peak path, reported as u16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_SNR:
+ * SNR of the sample considered as peak path, reported as s16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_NS:
+ * Timestamp of the sample considered as peak path, reported as u16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_OFFSET:
+ * Offset of the first path in the sample window, reported as u16.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW:
+ * Sliding window containing CIR samples, each sample is considered as
+ * a byte sequence depending on sample size.
+ * As many samples as the window size.
+ *
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_UNSPEC: Invalid command.
+ * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST: Internal use.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_MAX: Internal use.
+ */
+enum fira_ranging_diagnostics_frame_reports_cir_attrs {
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_UNSPEC,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_IDX,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SNR,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_NS,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_IDX,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_SNR,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_NS,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_OFFSET,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW,
+
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_MAX =
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST -
+ 1
+};
+
+#endif /* FIRA_REGION_NL_H */
diff --git a/mac/include/net/fira_region_params.h b/mac/include/net/fira_region_params.h
new file mode 100644
index 0000000..cf98038
--- /dev/null
+++ b/mac/include/net/fira_region_params.h
@@ -0,0 +1,417 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_FIRA_REGION_PARAMS_H
+#define NET_FIRA_REGION_PARAMS_H
+
+#include <linux/types.h>
+
+#define FIRA_VUPPER64_SIZE 8
+#define FIRA_STS_VUPPER64_OFFSET 8
+#define FIRA_KEY_SIZE_MAX 16
+#define FIRA_KEY_SIZE_MIN 16
+#define FIRA_CONTROLEES_MAX 8
+#define FIRA_RX_ANTENNA_PAIR_INVALID 0xff
+/*
+ * In BPRF, frame is at most 127
+ * 127 - (MHR + HIE + HT + PIE_Header + V_OUI + MIC + CRC)
+ */
+#define FIRA_DATA_PAYLOAD_SIZE_MAX 84
+
+/* From UCI spec v1.1.0 (converted to mm).
+ * Need a 2mm margin to avoid errors when converting to and from RCTU */
+#define FIRA_RANGE_DATA_NTF_PROXIMITY_FAR_MM_DEFAULT 200002
+
+/*
+ * FIRA_SESSION_DATA_NTF_LOWER_/UPPER_BOUND_AOA default values :
+ * Azimuth in rad_2pi_q16 : -32768 / 32767 (equal to -180 / ~180 degrees)
+ * Elevation in rad_2pi_q16 : -16384 / 16384 (equal to -90 / 90 degrees)
+ */
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_DEFAULT -32768
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_DEFAULT 32767
+#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_DEFAULT -16384
+#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_DEFAULT 16384
+
+/**
+ * enum fira_device_type - Type of a device.
+ * @FIRA_DEVICE_TYPE_CONTROLEE: The device is a controlee.
+ * @FIRA_DEVICE_TYPE_CONTROLLER: The device is a controller.
+ */
+enum fira_device_type {
+ FIRA_DEVICE_TYPE_CONTROLEE,
+ FIRA_DEVICE_TYPE_CONTROLLER,
+};
+
+/**
+ * enum fira_device_role - **[NOT IMPLEMENTED]** Role played by a device.
+ * @FIRA_DEVICE_ROLE_RESPONDER: The device acts as a responder.
+ * @FIRA_DEVICE_ROLE_INITIATOR: The device acts as an initiator.
+ *
+ * Current implementation does not support decorrelation between the
+ * device's role and the device's type. The controller is always
+ * the initiator and the controlee is always the responder.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum fira_device_role {
+ FIRA_DEVICE_ROLE_RESPONDER,
+ FIRA_DEVICE_ROLE_INITIATOR,
+};
+
+/**
+ * enum fira_ranging_round_usage - Ranging mode.
+ * @FIRA_RANGING_ROUND_USAGE_OWR: One Way Ranging mode (unused, not in FiRa 1.1).
+ * @FIRA_RANGING_ROUND_USAGE_SSTWR: Single-Sided Two Way Ranging mode.
+ * @FIRA_RANGING_ROUND_USAGE_DSTWR: Dual-Sided Two Way Ranging mode.
+ */
+enum fira_ranging_round_usage {
+ FIRA_RANGING_ROUND_USAGE_OWR,
+ FIRA_RANGING_ROUND_USAGE_SSTWR,
+ FIRA_RANGING_ROUND_USAGE_DSTWR,
+};
+
+/**
+ * enum fira_multi_node_mode - Multi-node mode.
+ * @FIRA_MULTI_NODE_MODE_UNICAST: Ranging between one initiator and one
+ * responder.
+ * @FIRA_MULTI_NODE_MODE_ONE_TO_MANY: Ranging between one initiator and
+ * multiple responders.
+ * @FIRA_MULTI_NODE_MODE_MANY_TO_MANY: Ranging between multiple initiators
+ * and multiple responders.
+ */
+enum fira_multi_node_mode {
+ FIRA_MULTI_NODE_MODE_UNICAST,
+ FIRA_MULTI_NODE_MODE_ONE_TO_MANY,
+ FIRA_MULTI_NODE_MODE_MANY_TO_MANY,
+};
+
+/**
+ * enum fira_measurement_report - **[NOT IMPLEMENTED]** Transmission of a
+ * Ranging Measurement Report Message (MRM) option.
+ * @FIRA_MEASUREMENT_REPORT_AT_RESPONDER: The initiator emits a MRM.
+ * @FIRA_MEASUREMENT_REPORT_AT_INITIATOR: The responder emits a MRM.
+ *
+ * In the current implementation measurement report is always available at
+ * responder.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum fira_measurement_report {
+ FIRA_MEASUREMENT_REPORT_AT_RESPONDER,
+ FIRA_MEASUREMENT_REPORT_AT_INITIATOR,
+};
+
+/**
+ * enum fira_embedded_mode - **[NOT IMPLEMENTED]** Message embedding
+ * behaviour.
+ * @FIRA_EMBEDDED_MODE_DEFERRED: Ranging messages do not embed control messages.
+ * Additional messages are required.
+ * @FIRA_EMBEDDED_MODE_NON_DEFERRED: Ranging messages embed control messages.
+ *
+ * The current implementation only supports deferred mode.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum fira_embedded_mode {
+ FIRA_EMBEDDED_MODE_DEFERRED,
+ FIRA_EMBEDDED_MODE_NON_DEFERRED,
+};
+
+/**
+ * enum fira_rframe_config - Rframe configuration used to transmit/receive
+ * ranging messages.
+ * @FIRA_RFRAME_CONFIG_SP0: Use SP0 mode.
+ * @FIRA_RFRAME_CONFIG_SP1: Use SP1 mode.
+ * @FIRA_RFRAME_CONFIG_SP2: RFU
+ * @FIRA_RFRAME_CONFIG_SP3: Use SP3 mode.
+ */
+enum fira_rframe_config {
+ FIRA_RFRAME_CONFIG_SP0,
+ FIRA_RFRAME_CONFIG_SP1,
+ FIRA_RFRAME_CONFIG_SP2,
+ FIRA_RFRAME_CONFIG_SP3,
+};
+
+/**
+ * enum fira_prf_mode - Pulse Repetition Frequency mode
+ * @FIRA_PRF_MODE_BPRF: Base Pulse Repetition Frequency.
+ * @FIRA_PRF_MODE_HPRF: Higher Pulse Repetition Frequency.
+ * @FIRA_PRF_MODE_HPRF_HIGH_RATE: Higher Pulse Repetition Frequency allows
+ * high data rate (27.2 Mbps and 31.2 Mbps).
+ *
+ * This enum is not used in the current implementation.
+ */
+enum fira_prf_mode {
+ FIRA_PRF_MODE_BPRF,
+ FIRA_PRF_MODE_HPRF,
+ FIRA_PRF_MODE_HPRF_HIGH_RATE,
+};
+
+/**
+ * enum fira_preambule_duration - Duration of preamble in symbols.
+ * @FIRA_PREAMBULE_DURATION_32: 32 symbols duration.
+ * @FIRA_PREAMBULE_DURATION_64: 64 symbols duration.
+ */
+enum fira_preambule_duration {
+ FIRA_PREAMBULE_DURATION_32,
+ FIRA_PREAMBULE_DURATION_64,
+};
+
+/**
+ * enum fira_sfd_id - Start-of-frame delimiter.
+ * @FIRA_SFD_ID_0: Delimiter is [0 +1 0 –1 +1 0 0 –1]
+ * @FIRA_SFD_ID_1: Delimiter is [ –1 –1 +1 –1 ]
+ * @FIRA_SFD_ID_2: Delimiter is [ –1 –1 –1 +1 –1 –1 +1 –1 ]
+ * @FIRA_SFD_ID_3: Delimiter is
+ * [ –1 –1 –1 –1 –1 +1 +1 –1 –1 +1 –1 +1 –1 –1 +1 –1 ]
+ * @FIRA_SFD_ID_4: Delimiter is
+ * [ –1 –1 –1 –1 –1 –1 –1 +1 –1 –1 +1 –1 –1 +1 –1 +1 –1 +1
+ * –1 –1 –1 +1 +1 –1 –1 –1 +1 –1 +1 +1 –1 –1 ]
+ */
+enum fira_sfd_id {
+ FIRA_SFD_ID_0,
+ FIRA_SFD_ID_1,
+ FIRA_SFD_ID_2,
+ FIRA_SFD_ID_3,
+ FIRA_SFD_ID_4,
+};
+
+/**
+ * enum fira_sts_segments - Number of STS segments.
+ * @FIRA_STS_SEGMENTS_0: No STS Segment (Rframe config SP0).
+ * @FIRA_STS_SEGMENTS_1: 1 STS Segment.
+ * @FIRA_STS_SEGMENTS_2: 2 STS Segments.
+ * @FIRA_STS_SEGMENTS_3: 3 STS Segments.
+ * @FIRA_STS_SEGMENTS_4: 4 STS Segments.
+ */
+enum fira_sts_segments {
+ FIRA_STS_SEGMENTS_0,
+ FIRA_STS_SEGMENTS_1,
+ FIRA_STS_SEGMENTS_2,
+ FIRA_STS_SEGMENTS_3,
+ FIRA_STS_SEGMENTS_4,
+};
+
+/**
+ * enum fira_psdu_data_rate - Data rate used to exchange PSDUs.
+ * @FIRA_PSDU_DATA_RATE_6M81: 6.8Mb/s rate.
+ * @FIRA_PSDU_DATA_RATE_7M80: 7.8Mb/s rate.
+ * @FIRA_PSDU_DATA_RATE_27M2: 27.2Mb/s rate.
+ * @FIRA_PSDU_DATA_RATE_31M2: 31.2Mb/s rate.
+ */
+enum fira_psdu_data_rate {
+ FIRA_PSDU_DATA_RATE_6M81,
+ FIRA_PSDU_DATA_RATE_7M80,
+ FIRA_PSDU_DATA_RATE_27M2,
+ FIRA_PSDU_DATA_RATE_31M2,
+};
+
+/**
+ * enum fira_phr_data_rate - Data rate used to exchange PHR.
+ * @FIRA_PHR_DATA_RATE_850K: 850kb/s rate.
+ * @FIRA_PHR_DATA_RATE_6M81: 6.8Mb/s rate.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum fira_phr_data_rate {
+ FIRA_PHR_DATA_RATE_850K,
+ FIRA_PHR_DATA_RATE_6M81,
+};
+
+/**
+ * enum fira_mac_fcs_type - Length of Frame Check Sequence.
+ * @FIRA_MAC_FCS_TYPE_CRC_16: 2 bytes sequence.
+ * @FIRA_MAC_FCS_TYPE_CRC_32: 4 bytes sequence.
+ */
+enum fira_mac_fcs_type {
+ FIRA_MAC_FCS_TYPE_CRC_16,
+ FIRA_MAC_FCS_TYPE_CRC_32,
+};
+
+/**
+ * enum fira_rssi_report_type - Mode used to sum up individual frames RSSI
+ * in report.
+ * @FIRA_RSSI_REPORT_NONE: No RSSI value in report.
+ * @FIRA_RSSI_REPORT_MINIMUM: Report minimum RSSI
+ * @FIRA_RSSI_REPORT_AVERAGE: Report average RSSI
+ */
+enum fira_rssi_report_type {
+ FIRA_RSSI_REPORT_NONE,
+ FIRA_RSSI_REPORT_MINIMUM,
+ FIRA_RSSI_REPORT_AVERAGE,
+};
+
+/**
+ * enum fira_sts_mode - Scrambled Timestamp Sequence modes.
+ *
+ * @FIRA_STS_MODE_STATIC: Static STS mode.
+ * @FIRA_STS_MODE_DYNAMIC: Use a dynamic STS mode.
+ * @FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY: Use a dynamic STS mode
+ * with individual controlee key.
+ * @FIRA_STS_MODE_PROVISIONED: Use a provisioned STS mode.
+ * @FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY: Use a provisioned STS
+ * mode with individual controlee key.
+ */
+enum fira_sts_mode {
+ FIRA_STS_MODE_STATIC = 0,
+ FIRA_STS_MODE_DYNAMIC = 1,
+ FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY = 2,
+ FIRA_STS_MODE_PROVISIONED = 3,
+ FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY = 4,
+};
+
+/*
+ * Get the capabilities bitfield value corresponding to given STS mode.
+ */
+#define STS_CAP(mode) (1 << (FIRA_STS_MODE_##mode))
+
+/**
+ * enum fira_ranging_status - The ranging status.
+ * @FIRA_STATUS_RANGING_INTERNAL_ERROR: Implementation specific error.
+ * @FIRA_STATUS_RANGING_SUCCESS: Ranging info are valid.
+ * @FIRA_STATUS_RANGING_TX_FAILED: Failed to transmit UWB packet.
+ * @FIRA_STATUS_RANGING_RX_TIMEOUT: No UWB packet detected by the receiver.
+ * @FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED: UWB packet channel decoding error.
+ * @FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED: Failed to detect time of arrival of
+ * the UWB packet from CIR samples.
+ * @FIRA_STATUS_RANGING_RX_PHY_STS_FAILED: UWB packet STS segment mismatch.
+ * @FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED: MAC CRC or syntax error.
+ * @FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED: IE syntax error.
+ * @FIRA_STATUS_RANGING_RX_MAC_IE_MISSING: Expected IE missing in the packet.
+ */
+enum fira_ranging_status {
+ FIRA_STATUS_RANGING_INTERNAL_ERROR = -1,
+ FIRA_STATUS_RANGING_SUCCESS = 0,
+ FIRA_STATUS_RANGING_TX_FAILED = 1,
+ FIRA_STATUS_RANGING_RX_TIMEOUT = 2,
+ FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED = 3,
+ FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED = 4,
+ FIRA_STATUS_RANGING_RX_PHY_STS_FAILED = 5,
+ FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED = 6,
+ FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 7,
+ FIRA_STATUS_RANGING_RX_MAC_IE_MISSING = 8,
+};
+
+/**
+ * enum fira_session_state - Session state.
+ * @FIRA_SESSION_STATE_INIT: Initial state, session is not ready yet.
+ * @FIRA_SESSION_STATE_DEINIT: Session does not exist.
+ * @FIRA_SESSION_STATE_ACTIVE: Session is currently active.
+ * @FIRA_SESSION_STATE_IDLE: Session is ready to start, but not currently
+ * active.
+ */
+enum fira_session_state {
+ FIRA_SESSION_STATE_INIT,
+ FIRA_SESSION_STATE_DEINIT,
+ FIRA_SESSION_STATE_ACTIVE,
+ FIRA_SESSION_STATE_IDLE,
+};
+
+/*
+ * The maximum number of steps a measurement sequence can contain.
+ */
+#define FIRA_MEASUREMENT_SEQUENCE_STEP_MAX 10
+
+/**
+ * enum fira_measurement_type - The different type of available measurements.
+ * @FIRA_MEASUREMENT_TYPE_RANGE: Measure only range.
+ * @FIRA_MEASUREMENT_TYPE_AOA: Measure range + unspecified AoA.
+ * @FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH: Measure range + azimuth.
+ * @FIRA_MEASUREMENT_TYPE_AOA_ELEVATION: Measure range + elevation.
+ * @FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION: Measure range+azimuth+elevation.
+ * @__FIRA_MEASUREMENT_TYPE_AFTER_LAST: Internal use.
+ */
+enum fira_measurement_type {
+ FIRA_MEASUREMENT_TYPE_RANGE = 0,
+ FIRA_MEASUREMENT_TYPE_AOA,
+ FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH,
+ FIRA_MEASUREMENT_TYPE_AOA_ELEVATION,
+ FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION,
+ __FIRA_MEASUREMENT_TYPE_AFTER_LAST,
+};
+
+/**
+ * enum fira_ranging_diagnostics_frame_report_flags - Activation flags for different frame diagnostics information.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_NONE: No specific frame diagnostic report requested.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS: Report RSSI in frame diagnostics.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS: Report AOA in frame diagnostics.
+ * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS: Report CIR in frame diagnostics.
+ * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AFTER_LAST: Internal use.
+ */
+enum fira_ranging_diagnostics_frame_report_flags {
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_NONE = 0,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS = 1 << 0,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS = 1 << 1,
+ FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS = 1 << 2,
+ __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AFTER_LAST = 1 << 31,
+};
+
+/**
+ * enum fira_sts_length - Number of symbols in a STS segment.
+ * @FIRA_STS_LENGTH_32: The STS length is 32 symbols.
+ * @FIRA_STS_LENGTH_64: The STS length is 64 symbols.
+ * @FIRA_STS_LENGTH_128: The STS length is 128 symbols.
+ */
+enum fira_sts_length {
+ FIRA_STS_LENGTH_32 = 0,
+ FIRA_STS_LENGTH_64 = 1,
+ FIRA_STS_LENGTH_128 = 2,
+};
+
+/**
+ * enum fira_range_data_ntf_config - Configure range data notification.
+ * @FIRA_RANGE_DATA_NTF_DISABLED: Do not report range data.
+ * @FIRA_RANGE_DATA_NTF_ALWAYS: Report range data.
+ * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY: Report range data if it is within
+ * proximity range defined by proximity parameters
+ * defined by proximity parameters (RANGE_DATA_NTF_PROXIMITY_NEAR/FAR).
+ * @FIRA_RANGE_DATA_NTF_CONFIG_AOA: Report range data in AoA upper and lower bound.
+ * defined by AOA parameters (FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER/
+ * LOWER_BOUND_AOA_AZIMUTH/ELEVATION)
+ * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA: Report range data in AoA upper
+ * and lower bound as well as in proximity range.
+ * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_CROSSING: Same as
+ * FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY, but issues notification on crossing of
+ * boundaries. As for now, same notif is sent for "enter" and "exit" events.
+ * @FIRA_RANGE_DATA_NTF_CONFIG_AOA_CROSSING: Same as
+ * FIRA_RANGE_DATA_NTF_CONFIG_AOA, but issues notification on crossing of boundaries.
+ * As for now, same notif is sent for "enter" and "exit" events.
+ * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA_CROSSING: Same as
+ * FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA, but issues notification on crossing of
+ * As for now, same notif is sent for "enter" and "exit" events.
+ * @FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: Same as
+ * FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA, but issues notification on crossing of
+ * boundaries. As for now, same notif is sent for "enter" and "exit" events.
+ */
+enum fira_range_data_ntf_config {
+ FIRA_RANGE_DATA_NTF_DISABLED = 0x00,
+ FIRA_RANGE_DATA_NTF_ALWAYS = 0x01,
+ FIRA_RANGE_DATA_NTF_PROXIMITY = 0x02,
+ FIRA_RANGE_DATA_NTF_AOA = 0x03,
+ FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA = 0x04,
+ FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING = 0x05,
+ FIRA_RANGE_DATA_NTF_AOA_CROSSING = 0x06,
+ FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING = 0x07,
+};
+
+#endif /* NET_FIRA_REGION_PARAMS_H */
diff --git a/mac/include/net/idle_region_nl.h b/mac/include/net/idle_region_nl.h
new file mode 100644
index 0000000..03a7e05
--- /dev/null
+++ b/mac/include/net/idle_region_nl.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef IDLE_REGION_NL_H
+#define IDLE_REGION_NL_H
+
+/**
+ * enum idle_param_attrs - Idle parameters attributes.
+ *
+ * @IDLE_PARAM_ATTR_MIN_DURATION_DTU:
+ * Minimum duration of an access.
+ * @IDLE_PARAM_ATTR_MAX_DURATION_DTU:
+ * Maximum duration of an access.
+ *
+ * @IDLE_PARAM_ATTR_UNSPEC: Invalid command.
+ * @__IDLE_PARAM_ATTR_AFTER_LAST: Internal use.
+ * @IDLE_PARAM_ATTR_MAX: Internal use.
+ */
+enum idle_param_attrs {
+ IDLE_PARAM_ATTR_UNSPEC,
+
+ IDLE_PARAM_ATTR_MIN_DURATION_DTU,
+ IDLE_PARAM_ATTR_MAX_DURATION_DTU,
+
+ __IDLE_PARAM_ATTR_AFTER_LAST,
+ IDLE_PARAM_ATTR_MAX = __IDLE_PARAM_ATTR_AFTER_LAST - 1
+};
+
+#endif /* IDLE_REGION_NL_H */
diff --git a/mac/include/net/mcps802154.h b/mac/include/net/mcps802154.h
new file mode 100644
index 0000000..77ef002
--- /dev/null
+++ b/mac/include/net/mcps802154.h
@@ -0,0 +1,1487 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_H
+#define NET_MCPS802154_H
+
+#include <net/mac802154.h>
+#include <crypto/aes.h>
+
+/* Antennas set id to use for transmission by default. */
+#define TX_ANT_SET_ID_DEFAULT 0
+/* Antennas set id to use for reception by default. */
+#define RX_ANT_SET_ID_DEFAULT 0
+
+/** Maximum number of STS segments. */
+#define MCPS802154_STS_N_SEGS_MAX 4
+
+/** Maximum number of RSSI values. */
+#define MCPS802154_RSSIS_N_MAX 2
+
+/** Maximum number of angle of arrival measurements. */
+#define MCPS802154_RX_AOA_MEASUREMENTS_MAX 3
+
+/**
+ * struct mcps802154_channel - Channel parameters.
+ */
+struct mcps802154_channel {
+ /**
+ * @page: Channel page used in conjunction with channel to uniquely
+ * identify the channel.
+ */
+ int page;
+ /**
+ * @channel: RF channel to use for all transmissions and receptions.
+ */
+ int channel;
+ /**
+ * @preamble_code: Preamble code index for HRP UWB. Must be zero for
+ * other PHYs.
+ */
+ int preamble_code;
+};
+
+/**
+ * enum mcps802154_llhw_flags - Low-level hardware without MCPS flags.
+ * @MCPS802154_LLHW_RDEV:
+ * Support for ranging (RDEV). TODO: move to &ieee802154_hw.
+ * @MCPS802154_LLHW_ERDEV:
+ * Support for enhanced ranging (ERDEV). TODO: move to &ieee802154_hw.
+ * @MCPS802154_LLHW_BPRF:
+ * Support for BPRF.
+ * @MCPS802154_LLHW_HPRF:
+ * Support for HPRF.
+ * @MCPS802154_LLHW_DATA_RATE_850K:
+ * Support for data rate 110 kpbs.
+ * @MCPS802154_LLHW_DATA_RATE_6M81:
+ * Support for data rate 6.81 Mpbs.
+ * @MCPS802154_LLHW_DATA_RATE_7M80:
+ * Support for data rate 7.8 Mpbs.
+ * @MCPS802154_LLHW_DATA_RATE_27M2:
+ * Support for data rate 27.2 Mpbs.
+ * @MCPS802154_LLHW_DATA_RATE_31M2:
+ * Support for data rate 31.2 Mpbs.
+ * @MCPS802154_LLHW_DATA_RATE_CUSTOM:
+ * Support for custom data rate, When presents extra data rate are
+ * possible to set.
+ * @MCPS802154_LLHW_PHR_DATA_RATE_850K:
+ * Support PHR data rate 850 kpbs.
+ * @MCPS802154_LLHW_PHR_DATA_RATE_6M81:
+ * Support PHR data rate 6.81 Mpbs.
+ * @MCPS802154_LLHW_PRF_4:
+ * Support Pulse Repetition Frequency 4 MHz.
+ * @MCPS802154_LLHW_PRF_16:
+ * Support Pulse Repetition Frequency 16 MHz.
+ * @MCPS802154_LLHW_PRF_64:
+ * Support Pulse Repetition Frequency 64 MHz.
+ * @MCPS802154_LLHW_PRF_125:
+ * Support Pulse Repetition Frequency 125 MHz.
+ * @MCPS802154_LLHW_PRF_250:
+ * Support Pulse Repetition Frequency 250 MHz.
+ * @MCPS802154_LLHW_PSR_16:
+ * Support 16 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_24:
+ * Support 24 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_32:
+ * Support 32 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_48:
+ * Support 48 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_64:
+ * Support 64 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_96:
+ * Support 96 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_128:
+ * Support 128 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_256:
+ * Support 256 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_1024:
+ * Support 1024 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_PSR_4096:
+ * Support 4096 symbols in preamble symbol repetitions.
+ * @MCPS802154_LLHW_SFD_4A:
+ * Support SFD defined in 4a.
+ * @MCPS802154_LLHW_SFD_4Z_4:
+ * Support SFD defined in 4z with length of 4 symbols.
+ * @MCPS802154_LLHW_SFD_4Z_8:
+ * Support SFD defined in 4z with length of 8 symbols.
+ * @MCPS802154_LLHW_SFD_4Z_16:
+ * Support SFD defined in 4z with length of 16 symbols.
+ * @MCPS802154_LLHW_SFD_4Z_32:
+ * Support SFD defined in 4z with length of 32 symbols.
+ * @MCPS802154_LLHW_STS_SEGMENT_1:
+ * Support one STS segment.
+ * @MCPS802154_LLHW_STS_SEGMENT_2:
+ * Support two STS segments.
+ * @MCPS802154_LLHW_STS_SEGMENT_3:
+ * Support three STS segments.
+ * @MCPS802154_LLHW_STS_SEGMENT_4:
+ * Support four STS segments.
+ * @MCPS802154_LLHW_AOA_AZIMUTH:
+ * Support AOA azimuth [-90°,+90°].
+ * @MCPS802154_LLHW_AOA_AZIMUTH_FULL:
+ * Support AOA full azimuth [-180°,+180°].
+ * @MCPS802154_LLHW_AOA_ELEVATION:
+ * Support AOA elevation [-90°,+90°].
+ * @MCPS802154_LLHW_AOA_FOM:
+ * Support AOA figure of merit.
+ */
+enum mcps802154_llhw_flags {
+ MCPS802154_LLHW_RDEV = BIT(0),
+ MCPS802154_LLHW_ERDEV = BIT(1),
+ MCPS802154_LLHW_BPRF = BIT(2),
+ MCPS802154_LLHW_HPRF = BIT(3),
+ MCPS802154_LLHW_DATA_RATE_850K = BIT(4),
+ MCPS802154_LLHW_DATA_RATE_6M81 = BIT(5),
+ MCPS802154_LLHW_DATA_RATE_7M80 = BIT(6),
+ MCPS802154_LLHW_DATA_RATE_27M2 = BIT(7),
+ MCPS802154_LLHW_DATA_RATE_31M2 = BIT(8),
+ MCPS802154_LLHW_DATA_RATE_CUSTOM = BIT(9),
+ MCPS802154_LLHW_PHR_DATA_RATE_850K = BIT(10),
+ MCPS802154_LLHW_PHR_DATA_RATE_6M81 = BIT(11),
+ MCPS802154_LLHW_PRF_4 = BIT(12),
+ MCPS802154_LLHW_PRF_16 = BIT(13),
+ MCPS802154_LLHW_PRF_64 = BIT(14),
+ MCPS802154_LLHW_PRF_125 = BIT(15),
+ MCPS802154_LLHW_PRF_250 = BIT(16),
+ MCPS802154_LLHW_PSR_16 = BIT(17),
+ MCPS802154_LLHW_PSR_24 = BIT(18),
+ MCPS802154_LLHW_PSR_32 = BIT(19),
+ MCPS802154_LLHW_PSR_48 = BIT(20),
+ MCPS802154_LLHW_PSR_64 = BIT(21),
+ MCPS802154_LLHW_PSR_96 = BIT(22),
+ MCPS802154_LLHW_PSR_128 = BIT(23),
+ MCPS802154_LLHW_PSR_256 = BIT(24),
+ MCPS802154_LLHW_PSR_1024 = BIT(25),
+ MCPS802154_LLHW_PSR_4096 = BIT(26),
+ MCPS802154_LLHW_SFD_4A = BIT(27),
+ MCPS802154_LLHW_SFD_4Z_4 = BIT(28),
+ MCPS802154_LLHW_SFD_4Z_8 = BIT(29),
+ MCPS802154_LLHW_SFD_4Z_16 = BIT(30),
+ MCPS802154_LLHW_SFD_4Z_32 = BIT(31),
+ MCPS802154_LLHW_STS_SEGMENT_1 = BIT_ULL(32),
+ MCPS802154_LLHW_STS_SEGMENT_2 = BIT_ULL(33),
+ MCPS802154_LLHW_STS_SEGMENT_3 = BIT_ULL(34),
+ MCPS802154_LLHW_STS_SEGMENT_4 = BIT_ULL(35),
+ MCPS802154_LLHW_AOA_AZIMUTH = BIT_ULL(36),
+ MCPS802154_LLHW_AOA_AZIMUTH_FULL = BIT_ULL(37),
+ MCPS802154_LLHW_AOA_ELEVATION = BIT_ULL(38),
+ MCPS802154_LLHW_AOA_FOM = BIT_ULL(39),
+};
+
+/**
+ * struct mcps802154_llhw - Low-level hardware without MCPS.
+ *
+ * This must be allocated with mcps802154_alloc_llhw(). The low-level driver
+ * should then initialize it.
+ */
+struct mcps802154_llhw {
+ /**
+ * @dtu_freq_hz: Inverse of device time unit duration, in Hz.
+ */
+ int dtu_freq_hz;
+ /**
+ * @symbol_dtu: Symbol duration in device time unit, can change if radio
+ * parameters are changed. Can be set to 1 if device time unit is the
+ * symbol.
+ */
+ int symbol_dtu;
+ /**
+ * @cca_dtu: CCA duration in device time unit, can change if radio
+ * parameters or CCA modes are changed.
+ */
+ int cca_dtu;
+ /**
+ * @shr_dtu: Synchronisation header duration in device time unit, can
+ * change if radio parameters are changed. If ranging is supported, this
+ * is the difference between the RMARKER and the first frame symbol.
+ */
+ int shr_dtu;
+ /**
+ * @dtu_rctu: Duration of one device time unit in ranging counter time
+ * unit (RDEV only).
+ */
+ int dtu_rctu;
+ /**
+ * @rstu_dtu: Duration of ranging slot time unit in device time unit
+ * (ERDEV only).
+ */
+ int rstu_dtu;
+ /**
+ * @anticip_dtu: Reasonable delay between reading the current timestamp
+ * and doing an operation in device time unit.
+ */
+ int anticip_dtu;
+ /**
+ * @idle_dtu: Duration long enough to prefer entering the idle mode
+ * rather than trying to find a valid access.
+ */
+ int idle_dtu;
+ /**
+ * @current_preamble_code: Current value of preamble code index for HRP
+ * UWB. Must be zero for other PHYs.
+ */
+ int current_preamble_code;
+ /**
+ * @rx_antenna_pairs: Number of antenna pairs for RX.
+ */
+ u32 rx_antenna_pairs;
+ /**
+ * @tx_antennas: Number of antennas for TX.
+ */
+ u32 tx_antennas;
+ /**
+ * @flags: Low-level hardware flags, see &enum mcps802154_llhw_flags.
+ */
+ u64 flags;
+ /**
+ * @hw: Pointer to IEEE802154 hardware exposed by MCPS. The low-level
+ * driver needs to update this and hw->phy according to supported
+ * hardware features and radio parameters. More specifically:
+ *
+ * * &ieee802154_hw.extra_tx_headroom
+ * * in &ieee802154_hw.flags:
+ *
+ * * IEEE802154_HW_TX_OMIT_CKSUM
+ * * IEEE802154_HW_RX_OMIT_CKSUM
+ * * IEEE802154_HW_RX_DROP_BAD_CKSUM
+ *
+ * * &wpan_phy.flags
+ * * &wpan_phy_supported
+ * * &wpan_phy.symbol_duration
+ */
+ struct ieee802154_hw *hw;
+ /**
+ * @priv: Driver private data.
+ */
+ void *priv;
+ /**
+ * @rx_ctx_size: size of the context.
+ */
+ u32 rx_ctx_size;
+};
+
+/**
+ * enum mcps802154_tx_frame_config_flags - Flags for transmitting a frame.
+ * @MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU:
+ * Start transmission at given timestamp in device time unit.
+ * @MCPS802154_TX_FRAME_CONFIG_CCA:
+ * Use CCA before transmission using the programmed mode.
+ * @MCPS802154_TX_FRAME_CONFIG_RANGING:
+ * Enable precise timestamping for the transmitted frame and its response
+ * (RDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK:
+ * Request that the ranging clock be kept valid after the transmission of
+ * this frame (RDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA:
+ * Enable phase difference of arrival measurement for the response frame
+ * (RDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_SP1:
+ * Enable STS for the transmitted frame and its response, mode 1 (STS after
+ * SFD and before PHR, ERDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_SP2:
+ * Enable STS for the transmitted frame and its response, mode 2 (STS after
+ * the payload, ERDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_SP3:
+ * Enable STS for the transmitted frame and its response, mode 3 (STS after
+ * SFD, no PHR, no payload, ERDEV only).
+ * @MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK:
+ * Mask covering all the STS mode configuration values.
+ * @MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND:
+ * Inform low-level driver the transmitted frame is the start of a ranging
+ * round (RDEV only).
+ *
+ * If no timestamp flag is given, transmit as soon as possible.
+ */
+enum mcps802154_tx_frame_config_flags {
+ MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU = BIT(0),
+ MCPS802154_TX_FRAME_CONFIG_CCA = BIT(1),
+ MCPS802154_TX_FRAME_CONFIG_RANGING = BIT(2),
+ MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK = BIT(3),
+ MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA = BIT(4),
+ MCPS802154_TX_FRAME_CONFIG_SP1 = BIT(5),
+ MCPS802154_TX_FRAME_CONFIG_SP2 = BIT(6),
+ MCPS802154_TX_FRAME_CONFIG_SP3 = BIT(5) | BIT(6),
+ MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK = BIT(5) | BIT(6),
+ MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND = BIT(7),
+};
+
+/**
+ * struct mcps802154_tx_frame_config - Information for transmitting a frame.
+ */
+struct mcps802154_tx_frame_config {
+ /**
+ * @timestamp_dtu: If timestamped, date of transmission start.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @rx_enable_after_tx_dtu: If positive, enable receiver this number of
+ * device time unit after the end of the transmitted frame.
+ */
+ int rx_enable_after_tx_dtu;
+ /**
+ * @rx_enable_after_tx_timeout_dtu: When receiver is enabled after the
+ * end of the transmitted frame: if negative, no timeout, if zero, use
+ * a default timeout value, else this is the timeout value in device
+ * time unit.
+ */
+ int rx_enable_after_tx_timeout_dtu;
+ /**
+ * @flags: See &enum mcps802154_tx_frame_config_flags.
+ */
+ u8 flags;
+ /**
+ * @ant_set_id : antenna set index to use for transmit.
+ */
+ int ant_set_id;
+};
+
+/**
+ * enum mcps802154_rx_frame_config_flags - Flags for enabling the receiver.
+ * @MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU:
+ * Enable receiver at given timestamp in device time unit.
+ * @MCPS802154_RX_FRAME_CONFIG_AACK:
+ * Enable automatic acknowledgment.
+ * @MCPS802154_RX_FRAME_CONFIG_RANGING:
+ * Enable precise timestamping for the received frame (RDEV only).
+ * @MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK:
+ * Request that the ranging clock be kept valid after the reception of the
+ * frame (RDEV only).
+ * @MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA:
+ * Enable phase difference of arrival measurement (RDEV only).
+ * @MCPS802154_RX_FRAME_CONFIG_SP1:
+ * Enable STS for the received frame, mode 1 (STS after SFD and before PHR,
+ * ERDEV only).
+ * @MCPS802154_RX_FRAME_CONFIG_SP2:
+ * Enable STS for the received frame, mode 2 (STS after the payload, ERDEV
+ * only).
+ * @MCPS802154_RX_FRAME_CONFIG_SP3:
+ * Enable STS for the received frame, mode 3 (STS after SFD, no PHR, no
+ * payload, ERDEV only).
+ * @MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK:
+ * Mask covering all the STS mode configuration values.
+ * @MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND:
+ * Inform low-level driver the expected received frame is the start of a
+ * ranging round (RDEV only).
+ *
+ * If no timestamp flag is given, enable receiver as soon as possible.
+ */
+enum mcps802154_rx_frame_config_flags {
+ MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU = BIT(0),
+ MCPS802154_RX_FRAME_CONFIG_AACK = BIT(1),
+ MCPS802154_RX_FRAME_CONFIG_RANGING = BIT(2),
+ MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK = BIT(3),
+ MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA = BIT(4),
+ MCPS802154_RX_FRAME_CONFIG_SP1 = BIT(5),
+ MCPS802154_RX_FRAME_CONFIG_SP2 = BIT(6),
+ MCPS802154_RX_FRAME_CONFIG_SP3 = BIT(5) | BIT(6),
+ MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK = BIT(5) | BIT(6),
+ MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND = BIT(7),
+};
+
+/**
+ * struct mcps802154_rx_frame_config - Information for enabling the receiver.
+ */
+struct mcps802154_rx_frame_config {
+ /**
+ * @timestamp_dtu: If timestamped, date to enable the receiver.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @timeout_dtu: If negative, no timeout, if zero, use a default timeout
+ * value, else this is the timeout value in device time unit.
+ */
+ int timeout_dtu;
+ /**
+ * @frame_timeout_dtu: If no zero, timeout value for the full frame
+ * reception. This allow limiting the length of accepted frame. The
+ * timeout starts after &mcps802154_rx_frame_config.timeout_dtu value.
+ */
+ int frame_timeout_dtu;
+ /**
+ * @flags: See &enum mcps802154_rx_frame_config_flags.
+ */
+ u8 flags;
+ /**
+ * @ant_set_id: Antenna set index to use for reception.
+ */
+ int ant_set_id;
+};
+
+/**
+ * enum mcps802154_rx_frame_info_flags - Flags for a received frame.
+ * @MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU:
+ * Set by MCPS to request timestamp in device time unit.
+ * @MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU:
+ * Set by MCPS to request RMARKER timestamp in ranging counter time unit
+ * (RDEV only).
+ * @MCPS802154_RX_FRAME_INFO_LQI:
+ * Set by MCPS to request link quality indicator (LQI).
+ * @MCPS802154_RX_FRAME_INFO_RSSI:
+ * Set by MCPS to request RSSI.
+ * @MCPS802154_RX_FRAME_INFO_RANGING_FOM:
+ * Set by MCPS to request ranging figure of merit (FoM, RDEV only).
+ * @MCPS802154_RX_FRAME_INFO_RANGING_OFFSET:
+ * Set by MCPS to request clock characterization data (RDEV only).
+ * @MCPS802154_RX_FRAME_INFO_RANGING_PDOA:
+ * Set by MCPS to request phase difference of arrival (RDEV only).
+ * @MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM:
+ * Set by MCPS to request phase difference of arrival figure of merit (FoM,
+ * RDEV only).
+ * @MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU:
+ * Set by MCPS to request SRMARKERx timestamps for each STS segments in
+ * ranging counter time unit (ERDEV only).
+ * @MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM:
+ * Set by MCPS to request STS segments figure of merit measuring the
+ * correlation strength between the received STS segment and the expected
+ * one (FoM, ERDEV only).
+ * @MCPS802154_RX_FRAME_INFO_AACK:
+ * Set by low-level driver if an automatic acknowledgment was sent or is
+ * being sent.
+ *
+ * The low-level driver must clear the corresponding flag if the information is
+ * not available.
+ */
+enum mcps802154_rx_frame_info_flags {
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU = BIT(0),
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU = BIT(1),
+ MCPS802154_RX_FRAME_INFO_LQI = BIT(2),
+ MCPS802154_RX_FRAME_INFO_RSSI = BIT(3),
+ MCPS802154_RX_FRAME_INFO_RANGING_FOM = BIT(4),
+ MCPS802154_RX_FRAME_INFO_RANGING_OFFSET = BIT(5),
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA = BIT(6),
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM = BIT(7),
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU = BIT(8),
+ MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM = BIT(9),
+ MCPS802154_RX_FRAME_INFO_AACK = BIT(10),
+};
+
+/**
+ * struct mcps802154_rx_frame_info - Information on a received frame.
+ */
+struct mcps802154_rx_frame_info {
+ /**
+ * @timestamp_dtu: Timestamp of start of frame in device time unit.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @timestamp_rctu: Timestamp of RMARKER in ranging count time unit
+ * (RDEV only).
+ */
+ u64 timestamp_rctu;
+ /**
+ * @frame_duration_dtu: Duration of the whole frame in device time unit
+ * or 0 if unknown.
+ */
+ int frame_duration_dtu;
+ /**
+ * @rssi: Received signal strength indication (RSSI),
+ * absolute value in Q1 fixed point format.
+ */
+ int rssi;
+ /**
+ * @ranging_tracking_interval_rctu: Interval on which tracking offset
+ * was measured (RDEV only).
+ */
+ int ranging_tracking_interval_rctu;
+ /**
+ * @ranging_offset_rctu: Difference between the transmitter and the
+ * receiver clock measure over the tracking interval, if positive, the
+ * transmitter operates at a higher frequency (RDEV only).
+ */
+ int ranging_offset_rctu;
+ /**
+ * @ranging_sts_timestamp_diffs_rctu: For each SRMARKERx, difference
+ * between the measured timestamp and the expected timestamp relative to
+ * RMARKER in ranging count time unit (ERDEV only). When STS mode is
+ * 1 or 3, SRMARKER0 is the same as RMARKER and difference is always 0.
+ */
+ s16 ranging_sts_timestamp_diffs_rctu[MCPS802154_STS_N_SEGS_MAX + 1];
+ /**
+ * @lqi: Link quality indicator (LQI).
+ */
+ u8 lqi;
+ /**
+ * @ranging_fom: Ranging figure of merit (FoM, RDEV only). Should be
+ * formatted according to 802.15.4.
+ */
+ u8 ranging_fom;
+ /**
+ * @ranging_pdoa_fom: Phase difference of arrival figure of merit (FoM,
+ * RDEV only). Range is 0 to 255, with 0 being an invalid measure and
+ * 255 being a 100% confidence.
+ */
+ u8 ranging_pdoa_fom;
+ /**
+ * @ranging_sts_fom: Table of figures of merit measuring the correlation
+ * strength between the received STS segment and the expected one (FoM,
+ * ERDEV only). Range is 0 to 255, with 0 being an invalid measure and
+ * 255 being a 100% confidence.
+ */
+ u8 ranging_sts_fom[MCPS802154_STS_N_SEGS_MAX];
+ /**
+ * @flags: See &enum mcps802154_rx_frame_info_flags.
+ */
+ u16 flags;
+};
+
+/**
+ * enum mcps802154_rx_measurement_info_flags - Flags for measurements on a received
+ * frame.
+ * @MCPS802154_RX_MEASUREMENTS_TIMESTAMP:
+ * Set by MCPS to request time of arrival measurement and associated figure
+ * of merit (RDEV only).
+ * @MCPS802154_RX_MEASUREMENTS_CLOCK_OFFSET:
+ * Set by MCPS to request clock characterization data (RDEV only).
+ * @MCPS802154_RX_MEASUREMENTS_STS_SEGS_TIMESTAMPS:
+ * Set by MCPS to request time of arrival measurement on STS segments and
+ * associated figure of merit (ERDEV only).
+ * @MCPS802154_RX_MEASUREMENTS_RSSIS:
+ * Set by MCPS to request RSSI values.
+ * @MCPS802154_RX_MEASUREMENTS_AOAS:
+ * Set by MCPS to request angle of arrival measurements, time difference of
+ * arrival, phase difference of arrival and associated figure of merit
+ * (RDEV only).
+ * @MCPS802154_RX_MEASUREMENTS_CIRS:
+ * Set by MCPS to request CIR samples (RDEV only).
+ * @MCPS802154_RX_MEASUREMENTS_VENDOR0:
+ * Set by MCPS to request first set of vendor specific measurements.
+ * @MCPS802154_RX_MEASUREMENTS_VENDOR1:
+ * Set by MCPS to request second set of vendor specific measurements.
+ * @MCPS802154_RX_MEASUREMENTS_VENDOR2:
+ * Set by MCPS to request third set of vendor specific measurements.
+ * @MCPS802154_RX_MEASUREMENTS_VENDOR3:
+ * Set by MCPS to request fourth set of vendor specific measurements.
+ *
+ * The low-level driver must clear the corresponding flag if the information is
+ * not available.
+ */
+enum mcps802154_rx_measurement_info_flags {
+ MCPS802154_RX_MEASUREMENTS_TIMESTAMP = BIT(0),
+ MCPS802154_RX_MEASUREMENTS_CLOCK_OFFSET = BIT(1),
+ MCPS802154_RX_MEASUREMENTS_STS_SEGS_TIMESTAMPS = BIT(2),
+ MCPS802154_RX_MEASUREMENTS_RSSIS = BIT(3),
+ MCPS802154_RX_MEASUREMENTS_AOAS = BIT(4),
+ MCPS802154_RX_MEASUREMENTS_CIRS = BIT(5),
+ MCPS802154_RX_MEASUREMENTS_VENDOR0 = BIT(12),
+ MCPS802154_RX_MEASUREMENTS_VENDOR1 = BIT(13),
+ MCPS802154_RX_MEASUREMENTS_VENDOR2 = BIT(14),
+ MCPS802154_RX_MEASUREMENTS_VENDOR3 = BIT(15),
+};
+
+/**
+ * struct mcps802154_rx_aoa_measurements - Angle of arrival measurements on a
+ * received frame (RDEV only).
+ */
+struct mcps802154_rx_aoa_measurements {
+ /**
+ * @tdoa_rctu: Time difference of arrival, in ranging count time unit.
+ */
+ s16 tdoa_rctu;
+ /**
+ * @pdoa_rad_q11: Phase difference of arrival, unit is radian multiplied
+ * by 2048.
+ */
+ s16 pdoa_rad_q11;
+ /**
+ * @aoa_rad_q11: Angle of arrival, unit is radian multiplied by 2048.
+ */
+ s16 aoa_rad_q11;
+ /**
+ * @fom: Measurements figure of merit (FoM). Range is 0 to 255, with 0
+ * being an invalid measure and 255 being a 100% confidence.
+ */
+ u8 fom;
+ /**
+ * @type: Measurement type (azimuth, elevation...). Actual value is
+ * driver dependant.
+ */
+ u8 type;
+};
+
+/**
+ * struct mcps802154_rx_cir_sample_window - Window of CIR samples.
+ */
+struct mcps802154_rx_cir_sample_window {
+ /**
+ * @n_samples: The number of samples contained in the window.
+ */
+ u16 n_samples;
+ /**
+ * @sizeof_sample: The size of a single sample.
+ */
+ u16 sizeof_sample;
+ /**
+ * @samples: CIR samples values.
+ *
+ * Each sample is composed of the real and imaginary part which are
+ * signed numbers. Each sample is encoded using the platform endianness
+ * with @mcps802154_rx_cir_sample_window.sizeof_sample bytes, first half
+ * is the real part, second half is the imaginary part.
+ *
+ * Must be kept valid until next received frame
+ */
+ void *samples;
+};
+
+/**
+ * struct mcps802154_rx_cir - CIR measurements.
+ */
+struct mcps802154_rx_cir {
+ /**
+ * @fp_index: The absolute index of the sample considered as first path.
+ */
+ u16 fp_index;
+ /**
+ * @fp_snr: The SNR of the sample considered as first path.
+ */
+ s16 fp_snr;
+ /**
+ * @fp_ns_q6: (Q10.6) Time in nanosecond of the first path index
+ */
+ u16 fp_ns_q6;
+ /**
+ * @pp_index: The absolute index of the sample considered as peak path.
+ */
+ u16 pp_index;
+ /**
+ * @pp_snr: The SNR of the sample considered as peak path.
+ */
+ s16 pp_snr;
+ /**
+ * @pp_ns_q6: (Q10.6) Time in nanosecond of the peak path index
+ */
+ u16 pp_ns_q6;
+ /**
+ * @fp_sample_offset: The offset of the first path in the sample window.
+ */
+ u16 fp_sample_offset;
+ /**
+ * @sample_window: CIR samples.
+ */
+ struct mcps802154_rx_cir_sample_window sample_window;
+};
+
+/**
+ * struct mcps802154_rx_measurement_info - Measurements on a received frame.
+ */
+struct mcps802154_rx_measurement_info {
+ /**
+ * @n_rssis: The number of RSSI computed for this frame. Depends on the
+ * antenna set used to receive.
+ *
+ * Set by low-level driver.
+ */
+ int n_rssis;
+ /**
+ * @rssis_q1: Received signal strength indication (RSSI), array of
+ * absolute values in Q7.1 fixed point format, unit is dBm.
+ */
+ u8 rssis_q1[MCPS802154_RSSIS_N_MAX];
+ /**
+ * @n_aoas: Number of angle of arrival measurements.
+ *
+ * Set by low-level driver.
+ */
+ int n_aoas;
+ /**
+ * @aoas: Angle of arrival measurements, ordered by increasing
+ * measurement type.
+ */
+ struct mcps802154_rx_aoa_measurements
+ aoas[MCPS802154_RX_AOA_MEASUREMENTS_MAX];
+ /**
+ * @n_cirs: Number of parts of CIR measurements.
+ *
+ * Set by low-level driver.
+ */
+ int n_cirs;
+ /**
+ * @cirs: CIR measurements for different parts of the frame.
+ *
+ * Set by low-level driver, must be kept valid until next received
+ * frame.
+ */
+ struct mcps802154_rx_cir *cirs;
+ /**
+ * @flags: See &enum mcps802154_rx_measurement_info_flags.
+ */
+ int flags;
+};
+
+/**
+ * struct mcps802154_sts_params - STS parameters for HRP UWB.
+ */
+struct mcps802154_sts_params {
+ /**
+ * @v: Value V used in DRBG for generating the STS. The 32 LSB are the
+ * VCounter which is incremented every 128 generated pulse.
+ */
+ u8 v[AES_BLOCK_SIZE];
+ /**
+ * @key: STS AES key used in DRBG for generating the STS.
+ */
+ u8 key[AES_KEYSIZE_128];
+ /**
+ * @n_segs: Number of STS segments.
+ */
+ int n_segs;
+ /**
+ * @seg_len: Length of STS segments.
+ */
+ int seg_len;
+ /**
+ * @sp2_tx_gap_4chips: For SP2 frame format, additional gap in unit of
+ * 4 chips between the end of the payload and the start of the STS, used
+ * for TX.
+ */
+ int sp2_tx_gap_4chips;
+ /**
+ * @sp2_rx_gap_4chips: For SP2 frame format, additional gap in unit of
+ * 4 chips between the end of the payload and the start of the STS, used
+ * for RX. A0 and A1 bits in PHR are used to index the array.
+ */
+ int sp2_rx_gap_4chips[MCPS802154_STS_N_SEGS_MAX];
+};
+
+/**
+ * enum mcps802154_prf - Pulse repetition frequency.
+ * @MCPS802154_PRF_16:
+ * 16 MHz, only used in 4a.
+ * @MCPS802154_PRF_64:
+ * 64 MHz, used for 4a and 4z BPRF.
+ * @MCPS802154_PRF_125:
+ * 125 MHz, used for 4z HPRF.
+ * @MCPS802154_PRF_250:
+ * 250 MHz, used for 4z HPRF.
+ */
+enum mcps802154_prf {
+ MCPS802154_PRF_16 = 16,
+ MCPS802154_PRF_64 = 64,
+ MCPS802154_PRF_125 = 125,
+ MCPS802154_PRF_250 = 250,
+};
+
+/**
+ * enum mcps802154_psr - Number of preamble symbol repetitions in the SYNC
+ * sequence.
+ * @MCPS802154_PSR_16:
+ * 16 symbols, used in 4a and 4z HPRF.
+ * @MCPS802154_PSR_24:
+ * 24 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_32:
+ * 32 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_48:
+ * 48 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_64:
+ * 64 symbols, used 4a and 4z BPRF and HPRF.
+ * @MCPS802154_PSR_96:
+ * 96 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_128:
+ * 128 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_256:
+ * 256 symbols, used only in 4z HPRF.
+ * @MCPS802154_PSR_1024:
+ * 1024 symbols, used only in 4a.
+ * @MCPS802154_PSR_4096:
+ * 4096 symbols, used only in 4a.
+ */
+enum mcps802154_psr {
+ MCPS802154_PSR_16 = 16,
+ MCPS802154_PSR_24 = 24,
+ MCPS802154_PSR_32 = 32,
+ MCPS802154_PSR_48 = 48,
+ MCPS802154_PSR_64 = 64,
+ MCPS802154_PSR_96 = 96,
+ MCPS802154_PSR_128 = 128,
+ MCPS802154_PSR_256 = 256,
+ MCPS802154_PSR_1024 = 1024,
+ MCPS802154_PSR_4096 = 4096,
+};
+
+/**
+ * enum mcps802154_sfd - sfd type selector.
+ * @MCPS802154_SFD_4A:
+ * SFD defined in 4a, length of 8 symbols.
+ * @MCPS802154_SFD_4Z_4:
+ * SFD defined in 4z, length of 4 symbols.
+ * @MCPS802154_SFD_4Z_8:
+ * SFD defined in 4z, length of 8 symbols.
+ * @MCPS802154_SFD_4Z_16:
+ * SFD defined in 4z, length of 16 symbols.
+ * @MCPS802154_SFD_4Z_32:
+ * SFD defined in 4z, length of 32 symbols.
+ */
+enum mcps802154_sfd {
+ MCPS802154_SFD_4A,
+ MCPS802154_SFD_4Z_4,
+ MCPS802154_SFD_4Z_8,
+ MCPS802154_SFD_4Z_16,
+ MCPS802154_SFD_4Z_32,
+};
+
+/**
+ * enum mcps802154_data_rate - Data rate.
+ * @MCPS802154_DATA_RATE_850K:
+ * 850 kbps, used only for 4a.
+ * @MCPS802154_DATA_RATE_6M81:
+ * 6.81 Mbps, used for 4a and 4z (PRF must be 125MHz).
+ * @MCPS802154_DATA_RATE_7M80:
+ * 7.80 Mbps, only used for 4z (PRF must be 125MHz).
+ * @MCPS802154_DATA_RATE_27M2:
+ * 27.2 Mbps, used for 4a and 4z (PRF must be 250MHz).
+ * @MCPS802154_DATA_RATE_31M2:
+ * 31.2 Mbps, used for 4z (PRF must be 250MHz).
+ * NOTE: device specific values can be set to use a custom data rate.
+ */
+enum mcps802154_data_rate {
+ MCPS802154_DATA_RATE_850K = 0,
+ MCPS802154_DATA_RATE_6M81 = 6,
+ MCPS802154_DATA_RATE_7M80 = 7,
+ MCPS802154_DATA_RATE_27M2 = 27,
+ MCPS802154_DATA_RATE_31M2 = 31,
+};
+
+/**
+ * enum mcps802154_hrp_uwb_psdu_size - PSDU size in HPRF.
+ * @MCPS802154_HRP_UWB_PSDU_SIZE_1023:
+ * 1023-bytes PSDU.
+ * @MCPS802154_HRP_UWB_PSDU_SIZE_2047:
+ * 2047-bytes PSDU.
+ * @MCPS802154_HRP_UWB_PSDU_SIZE_4095:
+ * 4095-bytes PSDU.
+ */
+enum mcps802154_hrp_uwb_psdu_size {
+ MCPS802154_HRP_UWB_PSDU_SIZE_1023 = 0,
+ MCPS802154_HRP_UWB_PSDU_SIZE_2047 = 1,
+ MCPS802154_HRP_UWB_PSDU_SIZE_4095 = 2,
+};
+
+/**
+ * struct mcps802154_hrp_uwb_params - Parameters for HRP UWB.
+ *
+ * Parameters are given directly to driver without checking. The driver needs to
+ * check the parameters for supported values, but it can accept non-standard
+ * values.
+ */
+struct mcps802154_hrp_uwb_params {
+ /**
+ * @prf: Nominal mean Pulse Repetition Frequency.
+ *
+ * For 4a, one of MCPS802154_PRF_16 or MCPS802154_PRF_64.
+ *
+ * For 4z BPRF, must be MCPS802154_PRF_64.
+ *
+ * For 4z HPRF, one of MCPS802154_PRF_125 or MCPS802154_PRF_250.
+ */
+ enum mcps802154_prf prf;
+ /**
+ * @psr: Number of preamble symbol repetitions in the SYNC sequence, or
+ * preamble length.
+ *
+ * For 4a, one of 16, 64, 1024 or 4096.
+ *
+ * For 4z BPRF, must be 64.
+ *
+ * For 4z HPRF, one of 16, 24, 32, 48, 64, 96, 128 or 256.
+ */
+ enum mcps802154_psr psr;
+ /**
+ * @sfd_selector: SFD type selector.
+ *
+ * When MCPS802154_SFD_4A, use short SFD defined in 802.15.4a.
+ *
+ * When MCPS802154_SFD_4Z_*, use SFD defined in 802.15.4z, with length
+ * 4, 8, 16 or 32.
+ *
+ * For 4a, must be MCPS802154_SFD_4A.
+ *
+ * For 4z BPRF, one of MCPS802154_SFD_4A or MCPS802154_SFD_4Z_8.
+ *
+ * For 4z HPRF, one of MCPS802154_SFD_4Z_{4,8,16,32}.
+ */
+ enum mcps802154_sfd sfd_selector;
+ /**
+ * @data_rate: Data rate.
+ *
+ * For 4a, one of 850 kbps, 6.81 Mbps or 27.2 Mbps.
+ *
+ * For 4z BPRF, must be 6.81 Mbps.
+ *
+ * For 4z HPRF at 125 MHz, use 6.81 Mbps or 7.8 Mbps.
+ *
+ * For 4z HPRF at 250 MHz, use 27.2 Mbps or 31.2 Mbps.
+ */
+ int data_rate;
+ /**
+ * @phr_hi_rate: Use high PHR data rate, for 4z BPRF only.
+ *
+ * For 4a and 4z HPRF, this parameter is ignored.
+ *
+ * For 4z BPRF, when enabled use 6.81 Mbps, otherwise use 850 kbps.
+ */
+ bool phr_hi_rate;
+ /**
+ * @psdu_size: PSDU size in HPRF.
+ */
+ enum mcps802154_hrp_uwb_psdu_size psdu_size;
+};
+
+/**
+ * enum mcps802154_antenna_caps - Antenna set capabilities
+ * @MCPS802154_AOA_X_AXIS:
+ * Antenna can report azimuth
+ * @MCPS802154_AOA_Y_AXIS:
+ * Antenna can report elevation
+ */
+enum mcps802154_antenna_caps {
+ MCPS802154_AOA_X_AXIS = BIT(0),
+ MCPS802154_AOA_Y_AXIS = BIT(1),
+};
+
+/**
+ * enum mcps802154_power_state - Power states
+ * @MCPS802154_PWR_STATE_OFF:
+ * Power off state.
+ * @MCPS802154_PWR_STATE_SLEEP:
+ * Deep sleep state.
+ * @MCPS802154_PWR_STATE_IDLE:
+ * Idle state, ready to transmit or receive.
+ * @MCPS802154_PWR_STATE_RX:
+ * Receive state.
+ * @MCPS802154_PWR_STATE_TX:
+ * Transmit state.
+ * @MCPS802154_PWR_STATE_MAX:
+ * Total power states count.
+ */
+enum mcps802154_power_state {
+ MCPS802154_PWR_STATE_OFF,
+ MCPS802154_PWR_STATE_SLEEP,
+ MCPS802154_PWR_STATE_IDLE,
+ MCPS802154_PWR_STATE_RX,
+ MCPS802154_PWR_STATE_TX,
+ MCPS802154_PWR_STATE_MAX
+};
+
+/**
+ * struct mcps802154_power_state_stats - Statistics for a power state.
+ * @dur: Duration in this power state in ns.
+ * @count: Count of transitions in this power state.
+ */
+struct mcps802154_power_state_stats {
+ u64 dur;
+ u64 count;
+};
+
+/**
+ * struct mcps802154_power_stats - Global power statistics.
+ * @power_state_stats: Array of power statistics for each power state.
+ * @interrupts: Hardware interrupts count on the device.
+ */
+struct mcps802154_power_stats {
+ struct mcps802154_power_state_stats
+ power_state_stats[MCPS802154_PWR_STATE_MAX];
+ u64 interrupts;
+};
+
+/**
+ * struct mcps802154_ops - Callback from MCPS to the driver.
+ */
+struct mcps802154_ops {
+ /**
+ * @start: Initialize device. Reception should not be activated.
+ *
+ * Return: 0 or error.
+ */
+ int (*start)(struct mcps802154_llhw *llhw);
+ /**
+ * @stop: Stop device. Should stop any transmission or reception and put
+ * the device in a low power mode.
+ */
+ void (*stop)(struct mcps802154_llhw *llhw);
+ /**
+ * @tx_frame: Transmit a frame. skb contains the buffer starting from
+ * the IEEE 802.15.4 header. The low-level driver should send the frame
+ * as specified in config. Receiver should be disabled automatically
+ * unless a frame is being received.
+ *
+ * The &frame_idx parameter gives the index of the frame in a "block".
+ * Frames from the same block (aka frame_idx > 0) should maintain the
+ * same synchronization.
+ *
+ * The &next_delay_dtu parameter gives the expected delay between the
+ * start of the transmitted frame and the next action.
+ *
+ * Return: 0, -ETIME if frame can not be sent at specified timestamp,
+ * -EBUSY if a reception is happening right now, or any other error.
+ */
+ int (*tx_frame)(struct mcps802154_llhw *llhw, struct sk_buff *skb,
+ const struct mcps802154_tx_frame_config *config,
+ int frame_idx, int next_delay_dtu);
+ /**
+ * @rx_enable: Enable receiver.
+ *
+ * The &frame_idx parameter gives the index of the frame in a "block".
+ * Frames from the same block (aka frame_idx > 0) should maintain the
+ * same synchronization.
+ *
+ * The &next_delay_dtu parameter gives the expected delay between the
+ * start of the received frame or timeout event and the next action.
+ *
+ * Return: 0, -ETIME if receiver can not be enabled at specified
+ * timestamp, or any other error.
+ */
+ int (*rx_enable)(struct mcps802154_llhw *llhw,
+ const struct mcps802154_rx_frame_config *config,
+ int frame_idx, int next_delay_dtu);
+ /**
+ * @rx_disable: Disable receiver, or a programmed receiver enabling,
+ * unless a frame reception is happening right now.
+ *
+ * Return: 0, -EBUSY if a reception is happening right now, or any other
+ * error.
+ */
+ int (*rx_disable)(struct mcps802154_llhw *llhw);
+ /**
+ * @rx_get_frame: Get previously received frame. MCPS calls this handler
+ * after a frame reception has been signaled by the low-level driver.
+ *
+ * The received buffer is owned by MCPS after this call. Only the
+ * requested information need to be filled in the information structure.
+ *
+ * Return: 0, -EBUSY if no longer available, or any other error.
+ */
+ int (*rx_get_frame)(struct mcps802154_llhw *llhw, struct sk_buff **skb,
+ struct mcps802154_rx_frame_info *info);
+ /**
+ * @rx_get_error_frame: Get information on rejected frame. MCPS can call
+ * this handler after a frame rejection has been signaled by the
+ * low-level driver.
+ *
+ * In case of error, info flags must be cleared by this callback.
+ *
+ * Return: 0, -EBUSY if no longer available, or any other error.
+ */
+ int (*rx_get_error_frame)(struct mcps802154_llhw *llhw,
+ struct mcps802154_rx_frame_info *info);
+ /**
+ * @rx_get_measurement: Get measurement associated with a received
+ * frame.
+ *
+ * Return: 0, -EBUSY if no longer available, or any other error.
+ */
+ int (*rx_get_measurement)(struct mcps802154_llhw *llhw, void *rx_ctx,
+ struct mcps802154_rx_measurement_info *info);
+ /**
+ * @idle: Put the device into idle mode without time limit or until the
+ * given timestamp. The driver should call &mcps802154_timer_expired()
+ * before the given timestamp so that an action can be programmed at the
+ * given timestamp.
+ *
+ * The &mcps802154_timer_expired() function must not be called
+ * immediately from this callback, but should be scheduled to be called
+ * later.
+ *
+ * If the driver is late, the regular handling of late actions will take
+ * care of the situation.
+ *
+ * Return: 0 or error.
+ */
+ int (*idle)(struct mcps802154_llhw *llhw, bool timestamp,
+ u32 timestamp_dtu);
+ /**
+ * @reset: Reset device after an unrecoverable error.
+ *
+ * Return: 0 or error.
+ */
+ int (*reset)(struct mcps802154_llhw *llhw);
+ /**
+ * @get_current_timestamp_dtu: Get current timestamp in device time
+ * unit.
+ *
+ * If the device is currently in a low power state, the eventual wake up
+ * delay should be added to the returned timestamp.
+ *
+ * If the current timestamp can not be determined precisely, it should
+ * return a pessimistic value, i.e. rounded up.
+ *
+ * Return: 0 or error.
+ */
+ int (*get_current_timestamp_dtu)(struct mcps802154_llhw *llhw,
+ u32 *timestamp_dtu);
+ /**
+ * @tx_timestamp_dtu_to_rmarker_rctu: Compute the RMARKER timestamp in
+ * ranging counter time unit for a frame transmitted at given timestamp
+ * in device time unit (RDEV only).
+ *
+ * Return: The RMARKER timestamp.
+ */
+ u64 (*tx_timestamp_dtu_to_rmarker_rctu)(
+ struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params,
+ int ant_set_id);
+ /**
+ * @difference_timestamp_rctu: Compute the difference between two
+ * timestamp values.
+ *
+ * Return: The difference between A and B.
+ */
+ s64 (*difference_timestamp_rctu)(struct mcps802154_llhw *llhw,
+ u64 timestamp_a_rctu,
+ u64 timestamp_b_rctu);
+ /**
+ * @compute_frame_duration_dtu: Compute the duration of a frame with
+ * given payload length (header and checksum included) using the current
+ * radio parameters.
+ *
+ * Return: The duration in device time unit.
+ */
+ int (*compute_frame_duration_dtu)(struct mcps802154_llhw *llhw,
+ int payload_bytes);
+ /**
+ * @set_channel: Set channel parameters.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_channel)(struct mcps802154_llhw *llhw, u8 page, u8 channel,
+ u8 preamble_code);
+ /**
+ * @set_hrp_uwb_params: Set radio parameters for HRP UWB.
+ *
+ * The parameters in &mcps802154_llhw can change according to radio
+ * parameters.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_hrp_uwb_params)(
+ struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *params);
+ /**
+ * @check_hrp_uwb_params: Check that the HRP parameters are compatible
+ * with the hardware capabilities.
+ *
+ * Return: 0 or error.
+ */
+ int (*check_hrp_uwb_params)(
+ struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *params);
+ /**
+ * @set_sts_params: Set STS parameters (ERDEV only).
+ *
+ * Return: 0 or error.
+ */
+ int (*set_sts_params)(struct mcps802154_llhw *llhw,
+ const struct mcps802154_sts_params *params);
+ /**
+ * @set_hw_addr_filt: Set hardware filter parameters.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_hw_addr_filt)(struct mcps802154_llhw *llhw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed);
+ /**
+ * @set_txpower: Set transmission power.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_txpower)(struct mcps802154_llhw *llhw, s32 mbm);
+ /**
+ * @set_cca_mode: Set CCA mode.
+ *
+ * The CCA duration in &mcps802154_llhw can change according to CCA
+ * mode.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_cca_mode)(struct mcps802154_llhw *llhw,
+ const struct wpan_phy_cca *cca);
+ /**
+ * @set_cca_ed_level: Set CCA energy detection threshold.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_cca_ed_level)(struct mcps802154_llhw *llhw, s32 mbm);
+ /**
+ * @set_promiscuous_mode: Set promiscuous mode.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_promiscuous_mode)(struct mcps802154_llhw *llhw, bool on);
+ /**
+ * @set_scanning_mode: Set SW scanning mode.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_scanning_mode)(struct mcps802154_llhw *llhw, bool on);
+ /**
+ * @set_calibration: Set calibration value.
+ *
+ * Set the calibration parameter specified by the key string with the
+ * value specified in the provided buffer. The provided length must
+ * match the length returned by the @get_calibration() callback.
+ *
+ * Return: 0 or error.
+ */
+ int (*set_calibration)(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length);
+ /**
+ * @get_calibration: Get calibration value.
+ *
+ * Get the calibration parameter specified by the key string into the
+ * provided buffer.
+ *
+ * Return: size of parameter written in buffer or error.
+ */
+ int (*get_calibration)(struct mcps802154_llhw *llhw, const char *key,
+ void *value, size_t length);
+ /**
+ * @list_calibration: Returns list of accepted calibration key strings
+ *
+ * Return: NULL terminated strings pointer array.
+ */
+ const char *const *(*list_calibration)(struct mcps802154_llhw *llhw);
+ /**
+ * @vendor_cmd: Run a vendor specific command.
+ *
+ * Do not (ab)use this feature to implement features that could be
+ * openly shared across drivers.
+ *
+ * Return: 0 or error.
+ */
+ int (*vendor_cmd)(struct mcps802154_llhw *llhw, u32 vendor_id,
+ u32 subcmd, void *data, size_t data_len);
+ /**
+ * @get_antenna_caps: Return antenna set capabilites.
+ *
+ * Return: 0 or error.
+ */
+ int (*get_antenna_caps)(struct mcps802154_llhw *llhw, int ant_idx,
+ u32 *caps);
+ /**
+ * @get_power_stats: Get the power statistics.
+ *
+ * Return: 0 or error.
+ */
+ int (*get_power_stats)(struct mcps802154_llhw *llhw,
+ struct mcps802154_power_stats *pwr_stats);
+#ifdef CONFIG_MCPS802154_TESTMODE
+ /**
+ * @testmode_cmd: Run a testmode command.
+ *
+ * Return: 0 or error.
+ */
+ int (*testmode_cmd)(struct mcps802154_llhw *llhw, void *data, int len);
+#endif
+};
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+#define MCPS802154_TESTMODE_CMD(cmd) .testmode_cmd = (cmd),
+#else
+#define MCPS802154_TESTMODE_CMD(cmd)
+#endif
+
+/**
+ * enum mcps802154_rx_error_type - Type of reception errors.
+ * @MCPS802154_RX_ERROR_NONE:
+ * RX successful.
+ * @MCPS802154_RX_ERROR_TIMEOUT:
+ * RX timeout.
+ * @MCPS802154_RX_ERROR_BAD_CKSUM:
+ * Checksum is not correct.
+ * @MCPS802154_RX_ERROR_UNCORRECTABLE:
+ * During reception, the error correction code detected an uncorrectable
+ * error.
+ * @MCPS802154_RX_ERROR_FILTERED:
+ * A received frame was rejected due to frame filter.
+ * @MCPS802154_RX_ERROR_SFD_TIMEOUT:
+ * A preamble has been detected but without SFD.
+ * @MCPS802154_RX_ERROR_OTHER:
+ * Other error, frame reception is aborted.
+ * @MCPS802154_RX_ERROR_PHR_DECODE:
+ * the preamble and SFD have been detected but without PHR.
+ * @MCPS802154_RX_ERROR_HPDWARN:
+ * Too late to program RX operation.
+ */
+enum mcps802154_rx_error_type {
+ MCPS802154_RX_ERROR_NONE = 0,
+ MCPS802154_RX_ERROR_TIMEOUT = 1,
+ MCPS802154_RX_ERROR_BAD_CKSUM = 2,
+ MCPS802154_RX_ERROR_UNCORRECTABLE = 3,
+ MCPS802154_RX_ERROR_FILTERED = 4,
+ MCPS802154_RX_ERROR_SFD_TIMEOUT = 5,
+ MCPS802154_RX_ERROR_OTHER = 6,
+ MCPS802154_RX_ERROR_PHR_DECODE = 7,
+ MCPS802154_RX_ERROR_HPDWARN = 8,
+};
+
+/**
+ * mcps802154_alloc_llhw() - Allocate a new low-level hardware device.
+ * @priv_data_len: Length of private data.
+ * @ops: Callbacks for this device.
+ *
+ * Return: A pointer to the new low-level hardware device, or %NULL on error.
+ */
+struct mcps802154_llhw *mcps802154_alloc_llhw(size_t priv_data_len,
+ const struct mcps802154_ops *ops);
+
+/**
+ * mcps802154_free_llhw() - Free low-level hardware descriptor.
+ * @llhw: Low-level device pointer.
+ *
+ * You must call mcps802154_unregister_hw() before calling this function.
+ */
+void mcps802154_free_llhw(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_register_llhw() - Register low-level hardware device.
+ * @llhw: Low-level device pointer.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_register_llhw(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_unregister_llhw() - Unregister low-level hardware device.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_unregister_llhw(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_rx_frame() - Signal a frame reception.
+ * @llhw: Low-level device this frame came in on.
+ *
+ * The MCPS will call the &mcps802154_ops.rx_get_frame() handler to retrieve
+ * frame.
+ */
+void mcps802154_rx_frame(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_rx_timeout() - Signal a reception timeout.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_rx_timeout(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_rx_too_late() - Signal a problem programing a RX.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_rx_too_late(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_rx_error() - Signal a reception error.
+ * @llhw: Low-level device pointer.
+ * @error: Type of detected error.
+ *
+ * In case of filtered frame, the MCPS can call the
+ * &mcps802154_ops.rx_get_error_frame() handler to retrieve frame information.
+ */
+void mcps802154_rx_error(struct mcps802154_llhw *llhw,
+ enum mcps802154_rx_error_type error);
+
+/**
+ * mcps802154_tx_done() - Signal the end of an MCPS transmission.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_tx_done(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_tx_too_late() - Signal a problem programing a TX.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_tx_too_late(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_broken() - Signal an unrecoverable error, device needs to be
+ * reset.
+ * @llhw: Low-level device pointer.
+ */
+void mcps802154_broken(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_timer_expired() - Signal that a programmed timer expired.
+ * @llhw: Low-level device pointer.
+ *
+ * To be called before the timestamp given to &mcps802154_ops.idle() callback.
+ */
+void mcps802154_timer_expired(struct mcps802154_llhw *llhw);
+
+/**
+ * is_before_dtu() - Check if timestamp A is before timestamp B.
+ * @a_dtu: A timestamp in device time unit.
+ * @b_dtu: B timestamp in device time unit.
+ *
+ * Return: true if A timestamp is before B timestamp.
+ */
+static inline bool is_before_dtu(u32 a_dtu, u32 b_dtu)
+{
+ return (s32)(a_dtu - b_dtu) < 0;
+}
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+/**
+ * mcps802154_testmode_alloc_reply_skb() - Allocate testmode reply.
+ * @llhw: Low-level device pointer.
+ * @approxlen: an upper bound of the length of the data that will
+ * be put into the skb.
+ *
+ * This function allocates and pre-fills an skb for a reply to
+ * the testmode command. Since it is intended for a reply, calling
+ * it outside of the @testmode_cmd operation is invalid.
+ *
+ * The returned skb is pre-filled with the netlink message's header
+ * and attribute's data and set up in a way that any data that is
+ * put into the skb (with skb_put(), nla_put() or similar) will end up
+ * being within the %MCPS802154_ATTR_TESTDATA attribute, so all
+ * that needs to be done with the skb is adding data for
+ * the corresponding userspace tool which can then read that data
+ * out of the testdata attribute. You must not modify the skb
+ * in any other way.
+ *
+ * When done, call mcps802154_testmode_reply() with the skb and return
+ * its error code as the result of the @testmode_cmd operation.
+ *
+ * Return: An allocated and pre-filled skb. %NULL if any errors happen.
+ */
+struct sk_buff *
+mcps802154_testmode_alloc_reply_skb(struct mcps802154_llhw *llhw,
+ int approxlen);
+
+/**
+ * mcps802154_testmode_reply() - Send the reply skb.
+ * @llhw: Low-level device pointer.
+ * @skb: The skb, must have been allocated with
+ * mcps802154_testmode_alloc_reply_skb().
+ *
+ * Since calling this function will usually be the last thing
+ * before returning from the @testmode_cmd you should return
+ * the error code. Note that this function consumes the skb
+ * regardless of the return value.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_testmode_reply(struct mcps802154_llhw *llhw,
+ struct sk_buff *skb);
+#endif
+
+#endif /* NET_MCPS802154_H */
diff --git a/mac/include/net/mcps802154_frame.h b/mac/include/net/mcps802154_frame.h
new file mode 100644
index 0000000..2bc8105
--- /dev/null
+++ b/mac/include/net/mcps802154_frame.h
@@ -0,0 +1,334 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_FRAME_H
+#define NET_MCPS802154_FRAME_H
+
+#include <linux/skbuff.h>
+#include "mcps802154.h"
+
+#define IEEE802154_FC_NO_SEQ_SHIFT 8
+#define IEEE802154_FC_NO_SEQ (1 << IEEE802154_FC_NO_SEQ_SHIFT)
+
+#define IEEE802154_FC_IE_PRESENT_SHIFT 9
+#define IEEE802154_FC_IE_PRESENT (1 << IEEE802154_FC_IE_PRESENT_SHIFT)
+
+#define IEEE802154_SCF_LEN 1
+#define IEEE802154_SCF_NO_FRAME_COUNTER (1 << 5)
+
+#define IEEE802154_IE_HEADER_LEN 2
+#define IEEE802154_IE_HEADER_TYPE_SHIFT 15
+#define IEEE802154_IE_HEADER_TYPE (1 << IEEE802154_IE_HEADER_TYPE_SHIFT)
+
+#define IEEE802154_HEADER_IE_HEADER_LENGTH 0x7f
+#define IEEE802154_HEADER_IE_HEADER_ELEMENT_ID (0xff << 7)
+#define IEEE802154_HEADER_IE_HEADER_TYPE (0 << IEEE802154_IE_HEADER_TYPE_SHIFT)
+
+#define IEEE802154_PAYLOAD_IE_HEADER_LENGTH 0x7ff
+#define IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID (0xf << 11)
+#define IEEE802154_PAYLOAD_IE_HEADER_TYPE (1 << IEEE802154_IE_HEADER_TYPE_SHIFT)
+
+#define IEEE802154_LONG_NESTED_IE_HEADER_LENGTH 0x7ff
+#define IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID (0xf << 11)
+#define IEEE802154_LONG_NESTED_IE_HEADER_TYPE \
+ (1 << IEEE802154_IE_HEADER_TYPE_SHIFT)
+
+#define IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH 0xff
+#define IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID (0x7f << 8)
+#define IEEE802154_SHORT_NESTED_IE_HEADER_TYPE \
+ (0 << IEEE802154_IE_HEADER_TYPE_SHIFT)
+
+#define IEEE802154_IE_NESTED_SHORT_MIN_SID 0x10
+
+#define IEEE802154_IE_HEADER_VENDOR_ID 0x00
+#define IEEE802154_IE_HEADER_TERMINATION_1_ID 0x7e
+#define IEEE802154_IE_HEADER_TERMINATION_2_ID 0x7f
+
+#define IEEE802154_IE_PAYLOAD_MLME_GID 0x1
+#define IEEE802154_IE_PAYLOAD_VENDOR_GID 0x2
+#define IEEE802154_IE_PAYLOAD_TERMINATION_GID 0xf
+
+struct mcps802154_llhw;
+
+/**
+ * enum mcps802154_ie_get_kind - Kind of IE, or none.
+ * @MCPS802154_IE_GET_KIND_NONE: No IE decoded (at end of frame).
+ * @MCPS802154_IE_GET_KIND_HEADER: Header IE decoded.
+ * @MCPS802154_IE_GET_KIND_PAYLOAD: Payload IE decoded.
+ * @MCPS802154_IE_GET_KIND_MLME_NESTED:
+ * Nested IE inside a MLME payload IE decoded.
+ */
+enum mcps802154_ie_get_kind {
+ MCPS802154_IE_GET_KIND_NONE,
+ MCPS802154_IE_GET_KIND_HEADER,
+ MCPS802154_IE_GET_KIND_PAYLOAD,
+ MCPS802154_IE_GET_KIND_MLME_NESTED,
+};
+
+/**
+ * struct mcps802154_ie_get_context - Context for IE decoding, to be used with
+ * mcps802154_ie_get(). Initialize to zero.
+ */
+struct mcps802154_ie_get_context {
+ /**
+ * @in_payload: If true, next decoding is in payload.
+ */
+ bool in_payload;
+ /**
+ * @kind: Kind of decoded IE.
+ */
+ enum mcps802154_ie_get_kind kind;
+ /**
+ * @id: Element identifier, group identifier or sub identifier of the
+ * decoded IE.
+ */
+ int id;
+ /**
+ * @len: Length of the decoded IE.
+ */
+ unsigned int len;
+ /**
+ * @mlme_len: While an MLME IE is decoded, length of data still in the
+ * frame buffer for this IE. Set this to 0 if you pulled all the MLME
+ * payload.
+ */
+ unsigned int mlme_len;
+};
+
+/**
+ * mcps802154_frame_alloc() - Allocate a buffer for TX.
+ * @llhw: Low-level device pointer.
+ * @size: Header and payload size.
+ * @flags: Allocation mask.
+ *
+ * This is to allocate a buffer for sending a frame to the low-level driver
+ * directly. Additional space is reserved for low-level driver headroom and for
+ * checksum.
+ *
+ * Return: Allocated buffer, or NULL.
+ */
+struct sk_buff *mcps802154_frame_alloc(struct mcps802154_llhw *llhw,
+ unsigned int size, gfp_t flags);
+
+/**
+ * mcps802154_ie_put_begin() - Begin writing information elements.
+ * @skb: Frame buffer.
+ *
+ * Prepare a frame buffer for writing IEs. The buffer control buffer is used to
+ * store state information.
+ */
+void mcps802154_ie_put_begin(struct sk_buff *skb);
+
+/**
+ * mcps802154_ie_put_end() - End writing information elements.
+ * @skb: Frame buffer.
+ * @data_payload: True if data will be appended to the buffer after the IEs. In
+ * this case, a terminator IE may be needed.
+ *
+ * This function appends a terminator IE if needed.
+ *
+ * Return: Length of frame header or -ENOBUFS in case of error.
+ */
+int mcps802154_ie_put_end(struct sk_buff *skb, bool data_payload);
+
+/**
+ * mcps802154_ie_put_header_ie() - Add a header IE.
+ * @skb: Frame buffer.
+ * @element_id: Header IE element identifier.
+ * @len: Header IE payload length.
+ *
+ * This adds the IE header and reserves room to write your payload. This works
+ * like skb_put, you must write at the returned address.
+ *
+ * Return: Address of reserved space to write payload, or NULL in case of error.
+ */
+void *mcps802154_ie_put_header_ie(struct sk_buff *skb, int element_id,
+ unsigned int len);
+
+/**
+ * mcps802154_ie_put_payload_ie() - Add a payload IE.
+ * @skb: Frame buffer.
+ * @group_id: Payload IE group identifier.
+ * @len: Payload IE payload length.
+ *
+ * This adds the IE header and reserves room to write your payload. This works
+ * like skb_put, you must write at the returned address.
+ *
+ * Return: Address of reserved space to write payload, or NULL in case of error.
+ */
+void *mcps802154_ie_put_payload_ie(struct sk_buff *skb, int group_id,
+ unsigned int len);
+
+/**
+ * mcps802154_ie_put_nested_mlme_ie() - Add a nested IE, inside a MLME IE.
+ * @skb: Frame buffer.
+ * @sub_id: Nested IE element identifier.
+ * @len: Nested IE payload length.
+ *
+ * This adds the IE header and reserves room to write your payload. This works
+ * like skb_put, you must write at the returned address.
+ *
+ * The MLME payload IE is added automatically if needed and its length is
+ * incremented if present yet.
+ *
+ * Return: Address of reserved space to write payload, or NULL in case of error.
+ */
+void *mcps802154_ie_put_nested_mlme_ie(struct sk_buff *skb, int sub_id,
+ unsigned int len);
+
+/**
+ * mcps802154_ie_get() - Parse one IE and fill context.
+ * @skb: Frame buffer.
+ * @context: Parse context, should be zero initialized at first call.
+ *
+ * This should only be called if the buffer contains IEs. This can be determined
+ * using the IE_PRESENT bit in the frame control.
+ *
+ * On successful parsing, the context structure is filled with information about
+ * the read IE. The IE payload can be read at the head of the frame buffer,
+ * headers are consumed.
+ *
+ * On last return, 1 is returned and a termination IE can be present in the
+ * context, it usually can be ignored.
+ *
+ * When an MLME IE is found, you have two options:
+ * - ignore it and call again to parse nested IE.
+ * - pull nested payload from the frame buffer, in this case you should set
+ * mlme_len to zero to proceed with the next IE.
+ *
+ * Return: 1 if last IE, 0 on successfully decoded IE, else negative error code.
+ */
+int mcps802154_ie_get(struct sk_buff *skb,
+ struct mcps802154_ie_get_context *context);
+
+/**
+ * mcps802154_get_extended_addr() - Get current extended address.
+ * @llhw: Low-level device pointer.
+ *
+ * Return: Extended address.
+ */
+__le64 mcps802154_get_extended_addr(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_get_pan_id() - Get current PAN identifier.
+ * @llhw: Low-level device pointer.
+ *
+ * Return: PAN ID.
+ */
+__le16 mcps802154_get_pan_id(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_get_short_addr() - Get current short address.
+ * @llhw: Low-level device pointer.
+ *
+ * Return: Short address.
+ */
+__le16 mcps802154_get_short_addr(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_get_current_channel() - Get current channel.
+ * @llhw: Low-level device pointer.
+ *
+ * Return: Channel parameters.
+ */
+const struct mcps802154_channel *
+mcps802154_get_current_channel(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_get_current_timestamp_dtu() - Get current timestamp in device time
+ * unit.
+ * @llhw: Low-level device pointer.
+ * @timestamp_dtu: Pointer to timestamp to write.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_get_current_timestamp_dtu(struct mcps802154_llhw *llhw,
+ u32 *timestamp_dtu);
+
+/**
+ * mcps802154_tx_timestamp_dtu_to_rmarker_rctu() - Compute the RMARKER timestamp
+ * in ranging counter time unit for a frame transmitted at given timestamp in
+ * device time unit (RDEV only).
+ * @llhw: Low-level device pointer.
+ * @tx_timestamp_dtu: TX timestamp in device time unit.
+ * @hrp_uwb_params: HRP UWB parameters.
+ * @channel_params: Channel parameters.
+ * @ant_set_id: Antennas set id used to transmit.
+ *
+ * Return: RMARKER timestamp in ranging count time unit.
+ */
+u64 mcps802154_tx_timestamp_dtu_to_rmarker_rctu(
+ struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params, int ant_set_id);
+
+/**
+ * mcps802154_difference_timestamp_rctu() - Compute the difference between two
+ * timestamp values.
+ * @llhw: Low-level device pointer.
+ * @timestamp_a_rctu: Timestamp A value.
+ * @timestamp_b_rctu: Timestamp B value.
+ *
+ * Return: Difference between A and B.
+ */
+s64 mcps802154_difference_timestamp_rctu(struct mcps802154_llhw *llhw,
+ u64 timestamp_a_rctu,
+ u64 timestamp_b_rctu);
+
+/**
+ * mcps802154_compute_frame_duration_dtu() - Compute the duration of a frame.
+ * @llhw: Low-level device pointer.
+ * @payload_bytes: Payload length (header and checksum included).
+ *
+ * Return: The duration in device time unit.
+ */
+int mcps802154_compute_frame_duration_dtu(struct mcps802154_llhw *llhw,
+ int payload_bytes);
+
+/**
+ * mcps802154_vendor_cmd() - Run a driver vendor specific command.
+ * @llhw: Low-level device pointer.
+ * @vendor_id: Vendor identifier as an OUI.
+ * @subcmd: Command identifier.
+ * @data: Data to be passed to driver, can be in/out.
+ * @data_len: Data length.
+ *
+ * The valid moment to call this function is driver and command specific.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id,
+ u32 subcmd, void *data, size_t data_len);
+
+/**
+ * mcps802154_rx_get_measurement() - Get measurement.
+ * @llhw: Low-level device pointer.
+ * @rx_ctx: Rx context (can be NULL).
+ * @info: Measurements updated by the llhw.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx,
+ struct mcps802154_rx_measurement_info *info);
+
+#endif /* NET_MCPS802154_FRAME_H */
diff --git a/mac/include/net/mcps802154_nl.h b/mac/include/net/mcps802154_nl.h
new file mode 100644
index 0000000..9aa5a9b
--- /dev/null
+++ b/mac/include/net/mcps802154_nl.h
@@ -0,0 +1,254 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_NL_H
+#define NET_MCPS802154_NL_H
+
+#define MCPS802154_GENL_NAME "mcps802154"
+
+/**
+ * enum mcps802154_commands - MCPS net link commands.
+ *
+ * @MCPS802154_CMD_GET_HW:
+ * Request information about a device, or dump request to get a list of all
+ * devices.
+ * @MCPS802154_CMD_NEW_HW:
+ * Result from information request.
+ * @MCPS802154_CMD_SET_SCHEDULER:
+ * Set the scheduler used to manage the schedule.
+ * @MCPS802154_CMD_SET_SCHEDULER_PARAMS:
+ * Set the parameters of the current scheduler.
+ * @MCPS802154_CMD_CALL_SCHEDULER:
+ * Call specific scheduler procedure.
+ * @MCPS802154_CMD_SET_SCHEDULER_REGIONS:
+ * Set and configure the scheduler regions.
+ * @MCPS802154_CMD_SET_REGIONS_PARAMS:
+ * Set the parameters of a specific region.
+ * @MCPS802154_CMD_CALL_REGION:
+ * Call specific region procedure.
+ * @MCPS802154_CMD_SET_CALIBRATIONS:
+ * Attempts to write the given value to the indicated calibration item.
+ * @MCPS802154_CMD_GET_CALIBRATIONS:
+ * Requests information about a given calibration items.
+ * @MCPS802154_CMD_LIST_CALIBRATIONS:
+ * Reports all calibrations existing.
+ * @MCPS802154_CMD_TESTMODE:
+ * Run a testmode command with TESTDATA blob attribute to pass through
+ * to the driver.
+ * @MCPS802154_CMD_CLOSE_SCHEDULER:
+ * Close current scheduler and its regions.
+ * @MCPS802154_CMD_GET_PWR_STATS:
+ * Get the power statistics.
+ *
+ * @MCPS802154_CMD_UNSPEC: Invalid command.
+ * @__MCPS802154_CMD_AFTER_LAST: Internal use.
+ * @MCPS802154_CMD_MAX: Internal use.
+ */
+enum mcps802154_commands {
+ MCPS802154_CMD_UNSPEC,
+
+ MCPS802154_CMD_GET_HW, /* can dump */
+ MCPS802154_CMD_NEW_HW,
+ MCPS802154_CMD_SET_SCHEDULER,
+ MCPS802154_CMD_SET_SCHEDULER_PARAMS,
+ MCPS802154_CMD_CALL_SCHEDULER,
+ MCPS802154_CMD_SET_SCHEDULER_REGIONS,
+ MCPS802154_CMD_SET_REGIONS_PARAMS,
+ MCPS802154_CMD_CALL_REGION,
+ MCPS802154_CMD_SET_CALIBRATIONS,
+ MCPS802154_CMD_GET_CALIBRATIONS,
+ MCPS802154_CMD_LIST_CALIBRATIONS,
+ MCPS802154_CMD_TESTMODE,
+ MCPS802154_CMD_CLOSE_SCHEDULER,
+ MCPS802154_CMD_GET_PWR_STATS,
+ __MCPS802154_CMD_AFTER_LAST,
+ MCPS802154_CMD_MAX = __MCPS802154_CMD_AFTER_LAST - 1
+};
+
+/**
+ * enum mcps802154_attrs - Top level MCPS net link attributes.
+ *
+ * @MCPS802154_ATTR_HW:
+ * Hardware device index, internal to MCPS.
+ * @MCPS802154_ATTR_WPAN_PHY_NAME:
+ * Name of corresponding wpan_phy device.
+ * @MCPS802154_ATTR_SCHEDULER_NAME:
+ * Name of the scheduler.
+ * @MCPS802154_ATTR_SCHEDULER_PARAMS:
+ * Parameters of the scheduler.
+ * @MCPS802154_ATTR_SCHEDULER_REGIONS:
+ * Parameters of the regions.
+ * @MCPS802154_ATTR_SCHEDULER_CALL:
+ * Call id of the scheduler's procedure, scheduler specific.
+ * @MCPS802154_ATTR_SCHEDULER_CALL_PARAMS:
+ * Parameters of the scheduler's procedure, scheduler specific.
+ * @MCPS802154_ATTR_SCHEDULER_REGION_CALL:
+ * Parameters of the region call, scheduler specific.
+ * @MCPS802154_ATTR_TESTDATA:
+ * Testmode's data blob, passed through to the driver. It contains
+ * driver-specific attributes.
+ * @MCPS802154_ATTR_CALIBRATIONS:
+ * Nested array of calibrations.
+ * @MCPS802154_ATTR_PWR_STATS:
+ * Nested power statistics data.
+ *
+ * @MCPS802154_ATTR_UNSPEC: Invalid command.
+ * @__MCPS802154_ATTR_AFTER_LAST: Internal use.
+ * @MCPS802154_ATTR_MAX: Internal use.
+ */
+enum mcps802154_attrs {
+ MCPS802154_ATTR_UNSPEC,
+
+ MCPS802154_ATTR_HW,
+ MCPS802154_ATTR_WPAN_PHY_NAME,
+
+ MCPS802154_ATTR_SCHEDULER_NAME,
+ MCPS802154_ATTR_SCHEDULER_PARAMS,
+ MCPS802154_ATTR_SCHEDULER_REGIONS,
+
+ MCPS802154_ATTR_SCHEDULER_CALL,
+ MCPS802154_ATTR_SCHEDULER_CALL_PARAMS,
+
+ MCPS802154_ATTR_SCHEDULER_REGION_CALL,
+
+ MCPS802154_ATTR_TESTDATA,
+
+ MCPS802154_ATTR_CALIBRATIONS,
+
+ MCPS802154_ATTR_PWR_STATS,
+
+ __MCPS802154_ATTR_AFTER_LAST,
+ MCPS802154_ATTR_MAX = __MCPS802154_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum mcps802154_region_attrs - Regions attributes.
+ *
+ * @MCPS802154_REGION_ATTR_ID:
+ * ID of the region, scheduler specific.
+ * @MCPS802154_REGION_ATTR_NAME:
+ * Name of the region, caller specific.
+ * @MCPS802154_REGION_ATTR_PARAMS:
+ * Parameters of the region.
+ * @MCPS802154_REGION_ATTR_CALL:
+ * Call id of the region's procedure, scheduler specific.
+ * @MCPS802154_REGION_ATTR_CALL_PARAMS:
+ * Parameters of the region's procedure, scheduler specific.
+ *
+ * @MCPS802154_REGION_UNSPEC: Invalid command.
+ * @__MCPS802154_REGION_AFTER_LAST: Internal use.
+ * @MCPS802154_REGION_MAX: Internal use.
+ */
+enum mcps802154_region_attrs {
+ MCPS802154_REGION_UNSPEC,
+
+ MCPS802154_REGION_ATTR_ID,
+ MCPS802154_REGION_ATTR_NAME,
+ MCPS802154_REGION_ATTR_PARAMS,
+ MCPS802154_REGION_ATTR_CALL,
+ MCPS802154_REGION_ATTR_CALL_PARAMS,
+
+ __MCPS802154_REGION_AFTER_LAST,
+ MCPS802154_REGION_MAX = __MCPS802154_REGION_AFTER_LAST - 1
+};
+
+/**
+ * enum mcps802154_calibrations_attrs - Calibration item.
+ *
+ * @MCPS802154_CALIBRATIONS_ATTR_KEY:
+ * Calibration name.
+ * @MCPS802154_CALIBRATIONS_ATTR_VALUE:
+ * Calibration value.
+ * @MCPS802154_CALIBRATIONS_ATTR_STATUS:
+ * Status in case of error on write or read.
+ *
+ * @MCPS802154_CALIBRATIONS_ATTR_UNSPEC: Invalid command.
+ * @__MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST: Internal use.
+ * @MCPS802154_CALIBRATIONS_ATTR_MAX: Internal use.
+ */
+enum mcps802154_calibrations_attrs {
+ MCPS802154_CALIBRATIONS_ATTR_UNSPEC,
+
+ MCPS802154_CALIBRATIONS_ATTR_KEY,
+ MCPS802154_CALIBRATIONS_ATTR_VALUE,
+ MCPS802154_CALIBRATIONS_ATTR_STATUS,
+
+ __MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST,
+ MCPS802154_CALIBRATIONS_ATTR_MAX =
+ __MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum mcps802154_nl_pwr_stats_state_attrs - Power state item.
+ *
+ * @MCPS802154_PWR_STATS_STATE_ATTR_TIME:
+ * Time spent in this state.
+ * @MCPS802154_PWR_STATS_STATE_ATTR_COUNT:
+ * Number of transitions to this state.
+ * @MCPS802154_PWR_STATS_STATE_ATTR_UNSPEC: Invalid command.
+ * @__MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST: Internal use.
+ * @MCPS802154_PWR_STATS_STATE_ATTR_MAX: Internal use.
+ */
+enum mcps802154_nl_pwr_stats_state_attrs {
+ MCPS802154_PWR_STATS_STATE_ATTR_UNSPEC,
+
+ MCPS802154_PWR_STATS_STATE_ATTR_TIME,
+ MCPS802154_PWR_STATS_STATE_ATTR_COUNT,
+
+ __MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST,
+ MCPS802154_PWR_STATS_STATE_ATTR_MAX =
+ __MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum mcps802154_pwr_stats_attrs - Power statistics item.
+ *
+ * @MCPS802154_PWR_STATS_ATTR_SLEEP:
+ * Sleep state nested attribute.
+ * @MCPS802154_PWR_STATS_ATTR_IDLE:
+ * Idle state nested attribute.
+ * @MCPS802154_PWR_STATS_ATTR_RX:
+ * Rx state nested attribute.
+ * @MCPS802154_PWR_STATS_ATTR_TX:
+ * Tx state nested attribute.
+ * @MCPS802154_PWR_STATS_ATTR_INTERRUPTS:
+ * Interrupts count attribute.
+ * @MCPS802154_PWR_STATS_ATTR_UNSPEC: Invalid command.
+ * @__MCPS802154_PWR_STATS_ATTR_AFTER_LAST: Internal use.
+ * @MCPS802154_PWR_STATS_ATTR_MAX: Internal use.
+ */
+enum mcps802154_pwr_stats_attrs {
+ MCPS802154_PWR_STATS_ATTR_UNSPEC,
+
+ MCPS802154_PWR_STATS_ATTR_SLEEP,
+ MCPS802154_PWR_STATS_ATTR_IDLE,
+ MCPS802154_PWR_STATS_ATTR_RX,
+ MCPS802154_PWR_STATS_ATTR_TX,
+ MCPS802154_PWR_STATS_ATTR_INTERRUPTS,
+
+ __MCPS802154_PWR_STATS_ATTR_AFTER_LAST,
+ MCPS802154_PWR_STATS_ATTR_MAX =
+ __MCPS802154_PWR_STATS_ATTR_AFTER_LAST - 1
+};
+
+#endif /* NET_MCPS802154_NL_H */
diff --git a/mac/include/net/mcps802154_schedule.h b/mac/include/net/mcps802154_schedule.h
new file mode 100644
index 0000000..7355742
--- /dev/null
+++ b/mac/include/net/mcps802154_schedule.h
@@ -0,0 +1,859 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_SCHEDULE_H
+#define NET_MCPS802154_SCHEDULE_H
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
+
+#include <net/mcps802154.h>
+
+struct mcps802154_nl_ranging_request;
+
+/**
+ * MCPS802154_DURATION_NO_CHANGE - Do not change duration.
+ */
+#define MCPS802154_DURATION_NO_CHANGE (-1)
+
+/**
+ * enum mcps802154_access_method - Method to implement an access.
+ * @MCPS802154_ACCESS_METHOD_NOTHING:
+ * Nothing to do, wait for end of region, or a schedule change. Internal,
+ * region handlers must return a NULL access if no access is possible.
+ * @MCPS802154_ACCESS_METHOD_IDLE:
+ * Nothing to do, wait for end of region, or a schedule change.
+ * Trust the access duration to not get the current time.
+ * @MCPS802154_ACCESS_METHOD_IMMEDIATE_RX:
+ * RX as soon as possible, without timeout, with auto-ack.
+ * @MCPS802154_ACCESS_METHOD_IMMEDIATE_TX:
+ * TX as soon as possible. Could be with or without ack request (AR).
+ * @MCPS802154_ACCESS_METHOD_MULTI:
+ * Multiple frames described in frames table.
+ * @MCPS802154_ACCESS_METHOD_VENDOR:
+ * Route all signals to access callbacks for vendor specific handling.
+ */
+enum mcps802154_access_method {
+ MCPS802154_ACCESS_METHOD_NOTHING,
+ MCPS802154_ACCESS_METHOD_IDLE,
+ MCPS802154_ACCESS_METHOD_IMMEDIATE_RX,
+ MCPS802154_ACCESS_METHOD_IMMEDIATE_TX,
+ MCPS802154_ACCESS_METHOD_MULTI,
+ MCPS802154_ACCESS_METHOD_VENDOR,
+};
+
+/**
+ * enum mcps802154_access_tx_return_reason - Reason of TX buffer return.
+ * @MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED:
+ * Frame was sent successfully.
+ * @MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE:
+ * An attempt was done to deliver the frame, but it failed.
+ * @MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL:
+ * No attempt was done to deliver the frame, or there was an unexpected
+ * error doing it.
+ */
+enum mcps802154_access_tx_return_reason {
+ MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED,
+ MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE,
+ MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL,
+ MCPS802154_TX_ERROR_HPDWARN,
+};
+
+/**
+ * struct mcps802154_access_frame - Information for a single frame for multiple
+ * frames method.
+ */
+struct mcps802154_access_frame {
+ /**
+ * @is_tx: True if frame is TX, else RX.
+ */
+ bool is_tx;
+ union {
+ /**
+ * @tx_frame_config: Information for transmitting a frame. Should
+ * have rx_enable_after_tx_dtu == 0.
+ */
+ struct mcps802154_tx_frame_config tx_frame_config;
+ /**
+ * @rx: Information for receiving a frame.
+ */
+ struct {
+ /**
+ * @rx.frame_config: Information for enabling the
+ * receiver.
+ */
+ struct mcps802154_rx_frame_config frame_config;
+ /**
+ * @rx.frame_info_flags_request: Information to request
+ * when a frame is received, see
+ * &enum mcps802154_rx_frame_info_flags.
+ */
+ u16 frame_info_flags_request;
+ } rx;
+ };
+ /**
+ * @sts_params: Pointer to STS parameters for this frame and all
+ * following frames. STS is still only used if requested in flags. For
+ * TX, this is read after mcps802154_access::tx_get_frame() is called,
+ * so it can be changed by the callback. For RX, this is read earlier,
+ * so it needs to be valid after mcps802154_access_ops::get_access(), or
+ * after the previous callback.
+ */
+ const struct mcps802154_sts_params *sts_params;
+};
+
+/**
+ * struct mcps802154_region_demand - Access information for on demand
+ * schedulers.
+ */
+struct mcps802154_region_demand {
+ /**
+ * @timestamp_dtu: Start of the demand.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @max_duration_dtu: Maximum duration of the demand, 0 for endless.
+ */
+ int max_duration_dtu;
+};
+
+/**
+ * struct mcps802154_access - Single medium access.
+ *
+ * This structure gives MCPS all the information needed to perform a single
+ * access.
+ */
+struct mcps802154_access {
+ /**
+ * @method: Method of access, see &enum mcps802154_access_method.
+ */
+ enum mcps802154_access_method method;
+ union {
+ /**
+ * @common_ops: Callbacks to implement the access, common part
+ * to all access methods.
+ */
+ struct mcps802154_access_common_ops *common_ops;
+ /**
+ * @ops: Callbacks to implement access with frames, unless
+ * a more specific structure is defined.
+ */
+ struct mcps802154_access_ops *ops;
+ /**
+ * @vendor_ops: Callbacks to implement the vendor specific
+ * access.
+ */
+ struct mcps802154_access_vendor_ops *vendor_ops;
+ };
+ /**
+ * @timestamp_dtu: Start of the access, only valid when the access has
+ * a duration. Invalid for immediate accesses.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @duration_dtu: Duration of the access, or 0 if unknown (this is the
+ * case if the first frame is a RX with no timeout and the frame has not
+ * been received yet).
+ */
+ int duration_dtu;
+ /**
+ * @n_frames: Number of frames in an access using multiple frames
+ * method. This can be changed by the &mcps802154_access_ops.rx_frame()
+ * callback.
+ */
+ size_t n_frames;
+ /**
+ * @frames: Table of information for each frames in an access using
+ * multiple frames method. This can be changed by the
+ * &mcps802154_access_ops.rx_frame() callback.
+ */
+ struct mcps802154_access_frame *frames;
+ /**
+ * @promiscuous: If true, promiscuous mode is activated for this access.
+ */
+ bool promiscuous;
+ /**
+ * @hw_addr_filt: Hardware address filter parameters. This is used at the
+ * start of multiple frames access to change the filter for this access.
+ * The filter is restored after the access.
+ */
+ struct ieee802154_hw_addr_filt hw_addr_filt;
+ /**
+ * @hw_addr_filt_changed: Hardware address filter parameters flags,
+ * see &enum ieee802154_hw_addr_filt_flags.
+ */
+ unsigned long hw_addr_filt_changed;
+ /**
+ * @channel: If not %NULL, channel parameters for this access. This is
+ * set at the start of multiple frames access. Parameters are restored
+ * after the access. If %NULL, use default parameters set through
+ * ieee802154 interface.
+ */
+ const struct mcps802154_channel *channel;
+ /**
+ * @hrp_uwb_params: If not NULL, parameters for a HRP UWB Phy set at the
+ * start of a multiple frames access.
+ */
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params;
+ /**
+ * @error: contain the error from the llhw in order to propagate it to upper regions.
+ */
+ int error;
+};
+
+/**
+ * struct mcps802154_access_common_ops - Callbacks to implement an access,
+ * common part to all access methods.
+ */
+struct mcps802154_access_common_ops {
+ /**
+ * @access_done: Called when the access is done, successfully or
+ * not, ignored if NULL.
+ * @error: Boolean used to signal internal MAC/driver/config error.
+ */
+ void (*access_done)(struct mcps802154_access *access, bool error);
+};
+
+/**
+ * struct mcps802154_access_ops - Callbacks to implement an access.
+ *
+ * This is used for all accesses with frames unless a more specific structure is
+ * defined.
+ */
+struct mcps802154_access_ops {
+ /**
+ * @common: Common part of callbacks.
+ */
+ struct mcps802154_access_common_ops common;
+ /**
+ * @rx_frame: Once a frame is received, it is given to this function.
+ * Buffer ownership is transferred to the callee.
+ *
+ * For multiple frames access method, the error parameter is used to
+ * inform of RX timeout or error. In case of RX timeout, info is NULL.
+ * In case of RX error, info can give some information about the error
+ * frame.
+ *
+ * The skb parameter can be NULL for frame without data.
+ */
+ void (*rx_frame)(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info,
+ enum mcps802154_rx_error_type error);
+ /**
+ * @tx_get_frame: Return a frame to send, the buffer is lend to caller
+ * and should be returned with &mcps802154_access_ops.tx_return().
+ *
+ * The return value can be NULL for frames without data. In this case,
+ * &mcps802154_access_ops.tx_return() will be called anyway, with a NULL
+ * pointer.
+ */
+ struct sk_buff *(*tx_get_frame)(struct mcps802154_access *access,
+ int frame_idx);
+ /**
+ * @tx_return: Give back an unmodified buffer.
+ */
+ void (*tx_return)(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ enum mcps802154_access_tx_return_reason reason);
+ /**
+ * @access_extend: Extend the current access with new frames.
+ *
+ * Current frame index and n_frames are reset before call.
+ * The region can override the frames array to extend the current
+ * access.
+ */
+ void (*access_extend)(struct mcps802154_access *access);
+};
+
+/**
+ * struct mcps802154_access_vendor_ops - Callbacks to implement a vendor
+ * specific access.
+ *
+ * Each callback can return 0 to continue the access, 1 to stop it or an error.
+ *
+ * If access is stopped, the &mcps802154_access.timestamp_dtu and
+ * &mcps802154_access.duration_dtu are used to compute the next access, unless
+ * duration is 0, in this case the current date is requested from the driver.
+ *
+ * In case of error, the devices transition to the broken state.
+ *
+ * If the callback is missing this is treated like an error, except for
+ * &mcps802154_access_vendor_ops.handle,
+ * &mcps802154_access_vendor_ops.timer_expired and
+ * &mcps802154_access_vendor_ops.schedule_change which are ignored.
+ */
+struct mcps802154_access_vendor_ops {
+ /**
+ * @common: Common part of callbacks.
+ */
+ struct mcps802154_access_common_ops common;
+ /**
+ * @handle: Called once to start the access, ignored if NULL.
+ */
+ int (*handle)(struct mcps802154_access *access);
+ /**
+ * @rx_frame: Called when a frame reception is signaled, error if NULL.
+ */
+ int (*rx_frame)(struct mcps802154_access *access);
+ /**
+ * @rx_timeout: Called when a reception timeout is signaled, error if NULL.
+ */
+ int (*rx_timeout)(struct mcps802154_access *access);
+ /**
+ * @rx_error: Called when a reception error is signaled, error if NULL.
+ */
+ int (*rx_error)(struct mcps802154_access *access,
+ enum mcps802154_rx_error_type error);
+ /**
+ * @tx_done: Called when end of transmission is signaled, error if NULL.
+ */
+ int (*tx_done)(struct mcps802154_access *access);
+ /**
+ * @broken: Called when a unrecoverable error is signaled, error if NULL.
+ */
+ int (*broken)(struct mcps802154_access *access);
+ /**
+ * @timer_expired: Called when a timer expired event is signaled,
+ * ignored if NULL.
+ */
+ int (*timer_expired)(struct mcps802154_access *access);
+ /**
+ * @schedule_change: Called to handle schedule change, ignored if NULL.
+ */
+ int (*schedule_change)(struct mcps802154_access *access);
+};
+
+/**
+ * struct mcps802154_region - An open region instance. Region handlers can have
+ * private data appended after this structure.
+ */
+struct mcps802154_region {
+ /**
+ * @ops: Callbacks for the region.
+ */
+ const struct mcps802154_region_ops *ops;
+ /**
+ * @ca_entry: Entry in list of CA regions.
+ */
+ struct list_head ca_entry;
+ /**
+ * @id: Assigned region ID.
+ */
+ int id;
+};
+
+/**
+ * struct mcps802154_region_ops - Region callbacks, handle access for a specific
+ * region in schedule.
+ */
+struct mcps802154_region_ops {
+ /**
+ * @owner: Module owning this region, should be THIS_MODULE in most
+ * cases.
+ */
+ struct module *owner;
+ /**
+ * @name: Region name.
+ */
+ const char *name;
+ /**
+ * @registered_entry: Entry in list of registered regions.
+ */
+ struct list_head registered_entry;
+ /**
+ * @open: Open an instance of this region, return a new region instance,
+ * or NULL in case of error.
+ */
+ struct mcps802154_region *(*open)(struct mcps802154_llhw *llhw);
+ /**
+ * @close: Close a region instance.
+ */
+ void (*close)(struct mcps802154_region *region);
+ /**
+ * @notify_stop: Notify a region that device has been stopped.
+ */
+ void (*notify_stop)(struct mcps802154_region *region);
+ /**
+ * @set_parameters: Set region parameters, may be NULL.
+ */
+ int (*set_parameters)(struct mcps802154_region *region,
+ const struct nlattr *attrs,
+ struct netlink_ext_ack *extack);
+ /**
+ * @call: Call region procedure, may be NULL.
+ */
+ int (*call)(struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *attrs, const struct genl_info *info);
+ /**
+ * @get_demand: Get access demand from a region, may be NULL.
+ */
+ int (*get_demand)(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *demand);
+ /**
+ * @get_access: Get access for a given region at the given timestamp.
+ * Access is valid until &mcps802154_access_ops.access_done() callback
+ * is called. Return NULL if access is not possible.
+ */
+ struct mcps802154_access *(*get_access)(
+ struct mcps802154_region *region, u32 next_timestamp_dtu,
+ int next_in_region_dtu, int region_duration_dtu);
+ /**
+ * @xmit_skb: Transmit buffer. Warning: Bypass the FSM lock mechanism.
+ * Return 1 if the region accepted to transmit the buffer, 0 otherwise.
+ */
+ int (*xmit_skb)(struct mcps802154_region *region, struct sk_buff *skb);
+ /**
+ * @deferred: Called at the end of event processing on request. See
+ * mcps802154_region_deferred.
+ */
+ void (*deferred)(struct mcps802154_region *region);
+};
+
+/**
+ * struct mcps802154_schedule_update - Context valid during a schedule
+ * update.
+ */
+struct mcps802154_schedule_update {
+ /**
+ * @expected_start_timestamp_dtu: Expected start timestamp, based on the
+ * current access date and having the new schedule put right after the
+ * old one.
+ */
+ u32 expected_start_timestamp_dtu;
+ /**
+ * @start_timestamp_dtu: Date of the schedule start, might be too far in
+ * the past for endless schedule.
+ */
+ u32 start_timestamp_dtu;
+ /**
+ * @duration_dtu: Schedule duration or 0 for endless schedule. This is
+ * also 0 when the schedule is empty.
+ */
+ int duration_dtu;
+ /**
+ * @n_regions: Number of regions in the schedule.
+ */
+ size_t n_regions;
+};
+
+/**
+ * struct mcps802154_scheduler - An open scheduler instance. Schedulers can have
+ * private data appended after this structure.
+ */
+struct mcps802154_scheduler {
+ /**
+ * @n_regions: Number of regions possible to add in the scheduler,
+ * zero means infinite number of regions can be added to the scheduler.
+ */
+ u32 n_regions;
+ /**
+ * @ops: Callbacks for the scheduler.
+ */
+ const struct mcps802154_scheduler_ops *ops;
+};
+
+/**
+ * struct mcps802154_scheduler_ops - Callbacks for schedulers. A scheduler
+ * provides a schedule to MCPS and updates it when specific frames are
+ * received or schedule is no longer valid.
+ */
+struct mcps802154_scheduler_ops {
+ /**
+ * @owner: Module owning this scheduler, should be THIS_MODULE in most
+ * cases.
+ */
+ struct module *owner;
+ /**
+ * @name: Scheduler name.
+ */
+ const char *name;
+ /**
+ * @registered_entry: Entry in list of registered schedulers.
+ */
+ struct list_head registered_entry;
+ /**
+ * @open: Attach a scheduler to a device.
+ */
+ struct mcps802154_scheduler *(*open)(struct mcps802154_llhw *llhw);
+ /**
+ * @close: Detach and close a scheduler.
+ */
+ void (*close)(struct mcps802154_scheduler *scheduler);
+ /**
+ * @notify_stop: Notify a scheduler that device has been stopped.
+ */
+ void (*notify_stop)(struct mcps802154_scheduler *scheduler);
+ /**
+ * @set_parameters: Configure the scheduler.
+ */
+ int (*set_parameters)(struct mcps802154_scheduler *scheduler,
+ const struct nlattr *attrs,
+ struct netlink_ext_ack *extack);
+ /**
+ * @call: Call scheduler specific procedure.
+ */
+ int (*call)(struct mcps802154_scheduler *scheduler, u32 call_id,
+ const struct nlattr *attrs, const struct genl_info *info);
+ /**
+ * @update_schedule: Called to initialize and update the schedule.
+ */
+ int (*update_schedule)(
+ struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_schedule_update *schedule_update,
+ u32 next_timestamp_dtu);
+ /**
+ * @ranging_setup: Called to configure ranging. This is a temporary
+ * interface.
+ */
+ int (*ranging_setup)(
+ struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_nl_ranging_request *requests,
+ unsigned int n_requests);
+ /**
+ * @get_next_demands: Called to get an aggregated demand for the specified
+ * region.
+ */
+ int (*get_next_demands)(struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_region *region,
+ u32 timestamp_dtu, int duration_dtu,
+ int delta_dtu,
+ struct mcps802154_region_demand *demands);
+};
+
+/**
+ * mcps802154_region_register() - Register a region, to be called when your
+ * module is loaded.
+ * @region_ops: Region to register.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_region_register(struct mcps802154_region_ops *region_ops);
+
+/**
+ * mcps802154_region_unregister() - Unregister a region, to be called at module
+ * unloading.
+ * @region_ops: Region to unregister.
+ */
+void mcps802154_region_unregister(struct mcps802154_region_ops *region_ops);
+
+/**
+ * mcps802154_region_open() - Open a region, and set parameters.
+ * @llhw: Low-level device pointer.
+ * @name: Name of region to open.
+ * @params_attr: Nested attribute containing region parameters, may be NULL.
+ * @extack: Extended ACK report structure.
+ *
+ * Return: The open region or NULL on error.
+ */
+struct mcps802154_region *
+mcps802154_region_open(struct mcps802154_llhw *llhw, const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_region_close() - Close a region.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ */
+void mcps802154_region_close(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region);
+
+/**
+ * mcps802154_region_notify_stop() - Notify a region that device has been
+ * stopped.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ */
+void mcps802154_region_notify_stop(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region);
+
+/**
+ * mcps802154_region_set_parameters() - Set parameters of an open region.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @params_attr: Nested attribute containing region parameters, may be NULL.
+ * @extack: Extended ACK report structure.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_region_set_parameters(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_region_call() - Call specific procedure in this region.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @call_id: Identifier of the procedure, region specific.
+ * @params_attr: Nested attribute containing region parameters, may be NULL.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_region_call(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info);
+
+/**
+ * mcps802154_region_get_demand() - Get access demand from a region in order to
+ * help building a schedule.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ * @demand: Where to write the demand.
+ *
+ * If the region does not implement access demand, this function will request an
+ * endless demand starting at the next access opportunity.
+ *
+ * Return: 1 if data is available, 0 if no demand (do not want access) or error.
+ */
+int mcps802154_region_get_demand(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *demand);
+
+/**
+ * mcps802154_region_call_alloc_reply_skb() - Allocate buffer to send
+ * a response for a region call.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @call_id: Identifier of the procedure, region specific.
+ * @approx_len: Upper bound of the data to be put into the buffer.
+ *
+ * Return: An allocated and pre-filled buffer, or NULL on error.
+ */
+struct sk_buff *
+mcps802154_region_call_alloc_reply_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ u32 call_id, int approx_len);
+
+/**
+ * mcps802154_region_call_reply() - Send a previously allocated and filled
+ * reply buffer.
+ * @llhw: Low-level device pointer.
+ * @skb: Buffer to send.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_region_call_reply(struct mcps802154_llhw *llhw,
+ struct sk_buff *skb);
+
+/**
+ * mcps802154_region_event_alloc_skb() - Allocate buffer to send a notification
+ * for a region.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @call_id: Identifier of the procedure, region specific.
+ * @portid: Port identifier of the receiver.
+ * @approx_len: Upper bound of the data to be put into the buffer.
+ * @gfp: Allocation flags.
+ *
+ * Return: An allocated and pre-filled buffer, or NULL on error.
+ */
+struct sk_buff *
+mcps802154_region_event_alloc_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region, u32 call_id,
+ u32 portid, int approx_len, gfp_t gfp);
+
+/**
+ * mcps802154_region_event() - Send a previously allocated and filled
+ * buffer.
+ * @llhw: Low-level device pointer.
+ * @skb: Buffer to send.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_region_event(struct mcps802154_llhw *llhw, struct sk_buff *skb);
+
+/**
+ * mcps802154_region_xmit_resume() - Signal buffer transmit can resume.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @queue_index: Queue index.
+ */
+void mcps802154_region_xmit_resume(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ int queue_index);
+
+/**
+ * mcps802154_region_xmit_done() - Signal buffer transmit completion.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @skb: Buffer.
+ * @ok: True if the buffer was successfully transmitted.
+ */
+void mcps802154_region_xmit_done(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ struct sk_buff *skb, bool ok);
+
+/**
+ * mcps802154_region_rx_skb() - Signal the reception of a buffer.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ * @skb: Received buffer.
+ * @lqi: Link Quality Indicator (LQI).
+ */
+void mcps802154_region_rx_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ struct sk_buff *skb, u8 lqi);
+
+/**
+ * mcps802154_region_deferred() - Request to call the deferred callback at the
+ * end of event processing.
+ * @llhw: Low-level device pointer.
+ * @region: Pointer to the open region.
+ *
+ * Event is coming from the low-level device. The region must be the one which
+ * triggered the event (region must not call this after a get_access). If this
+ * is not respected, this call can return -EINVAL in case two regions request
+ * the deferred callback at the same time.
+ *
+ * Return: 0 or -EINVAL.
+ */
+int mcps802154_region_deferred(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region);
+
+/**
+ * mcps802154_scheduler_register() - Register a scheduler, to be called when
+ * your module is loaded.
+ * @scheduler_ops: Scheduler to register.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_scheduler_register(
+ struct mcps802154_scheduler_ops *scheduler_ops);
+
+/**
+ * mcps802154_scheduler_unregister() - Unregister a scheduler, to be called at
+ * module unloading.
+ * @scheduler_ops: Scheduler to unregister.
+ */
+void mcps802154_scheduler_unregister(
+ struct mcps802154_scheduler_ops *scheduler_ops);
+
+/**
+ * mcps802154_schedule_set_start() - Change the currently updated schedule start
+ * timestamp.
+ * @schedule_update: Schedule update context.
+ * @start_timestamp_dtu: New start timestamp.
+ *
+ * Return: 0 or -EINVAL if arguments are garbage.
+ */
+int mcps802154_schedule_set_start(
+ const struct mcps802154_schedule_update *schedule_update,
+ u32 start_timestamp_dtu);
+
+/**
+ * mcps802154_schedule_recycle() - Purge or recycle the current schedule.
+ * @schedule_update: Schedule update context.
+ * @n_keeps: Number of regions to keep from the previous schedule.
+ * @last_region_duration_dtu:
+ * Duration of the last region, or MCPS802154_DURATION_NO_CHANGE to keep it
+ * unchanged.
+ *
+ * Return: 0 or -EINVAL if arguments are garbage.
+ */
+int mcps802154_schedule_recycle(
+ const struct mcps802154_schedule_update *schedule_update,
+ size_t n_keeps, int last_region_duration_dtu);
+
+/**
+ * mcps802154_schedule_add_region() - Add a new region to the currently updated
+ * schedule.
+ * @schedule_update: Schedule update context.
+ * @region: Region to add.
+ * @start_dtu: Region start from the start of the schedule.
+ * @duration_dtu: Region duration, or 0 for endless region.
+ * @once: Schedule the region once, ignoring the remaining region duration.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_schedule_add_region(
+ const struct mcps802154_schedule_update *schedule_update,
+ struct mcps802154_region *region, int start_dtu, int duration_dtu,
+ bool once);
+
+/**
+ * mcps802154_reschedule() - Request to change access as possible.
+ * @llhw: Low-level device pointer.
+ *
+ * Use this to reevaluate the current access as new data is available. For
+ * example, the device may be sleeping, or waiting to receive a frame, and you
+ * have a fresh frame to send.
+ *
+ * Request may be ignored if the device is busy, in which case the current
+ * access will be done before the new access is examined.
+ */
+void mcps802154_reschedule(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_schedule_invalidate() - Request to invalidate the schedule.
+ * @llhw: Low-level device pointer.
+ *
+ * FSM mutex should be locked.
+ *
+ * Invalidate the current schedule, which will result on a schedule change.
+ * This API should be called from external modules to force schedule change,
+ * when for example some parameters changed.
+ */
+void mcps802154_schedule_invalidate(struct mcps802154_llhw *llhw);
+
+/**
+ * mcps802154_schedule_get_regions() - Get opened regions.
+ * @llhw: Low-level device pointer.
+ * @regions: pointer to list of regions.
+ *
+ * FSM mutex should be locked.
+ *
+ * Get the list of current regions opened.
+ *
+ * Return: Number of regions.
+ */
+int mcps802154_schedule_get_regions(struct mcps802154_llhw *llhw,
+ struct list_head **regions);
+
+/**
+ * mcps802154_schedule_get_next_demands() - Get an aggregated demand for the
+ * specified region.
+ * @llhw: Low-level device pointer.
+ * @region: Region.
+ * @timestamp_dtu: Timestamp from which demands must be computed.
+ * @duration_dtu: Duration for which demands are considered.
+ * @delta_dtu: Maximum gap between two demands.
+ * @demands: Aggregated demand.
+ *
+ * Return: 1 if demand is returned, 0 if no demand or error.
+ */
+int mcps802154_schedule_get_next_demands(
+ struct mcps802154_llhw *llhw, const struct mcps802154_region *region,
+ u32 timestamp_dtu, int duration_dtu, int delta_dtu,
+ struct mcps802154_region_demand *demands);
+
+#endif /* NET_MCPS802154_SCHEDULE_H */
diff --git a/mac/include/net/mcps_skb_frag.h b/mac/include/net/mcps_skb_frag.h
new file mode 100644
index 0000000..78418f0
--- /dev/null
+++ b/mac/include/net/mcps_skb_frag.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/skbuff.h>
+
+/**
+ * mcps_skb_frags_len() - Return the total length of the fragments attached to this buffer.
+ * @skb: Pointer to buffer.
+ *
+ * Return: Attached fragments length.
+ *
+ * NOTE: The parent length is NOT included in the computed value
+ */
+int mcps_skb_frags_len(struct sk_buff *skb);
diff --git a/mac/include/net/nfcc_coex_region_nl.h b/mac/include/net/nfcc_coex_region_nl.h
new file mode 100644
index 0000000..169691a
--- /dev/null
+++ b/mac/include/net/nfcc_coex_region_nl.h
@@ -0,0 +1,96 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NFCC_COEX_REGION_NL_H
+#define NFCC_COEX_REGION_NL_H
+
+/**
+ * enum nfcc_coex_call - NFCC coexistence calls identifiers.
+ *
+ * @NFCC_COEX_CALL_CCC_SESSION_START:
+ * Start CCC session.
+ * @NFCC_COEX_CALL_CCC_SESSION_STOP:
+ * Stop CCC session.
+ * @NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION:
+ * Notify session reports.
+ * @NFCC_COEX_CALL_MAX: Internal use.
+ */
+enum nfcc_coex_call {
+ NFCC_COEX_CALL_CCC_SESSION_START,
+ NFCC_COEX_CALL_CCC_SESSION_STOP,
+ NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION,
+ NFCC_COEX_CALL_MAX,
+};
+
+/**
+ * enum nfcc_coex_call_attrs - NFCC coexistence call attributes.
+ *
+ * @NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS:
+ * Session parameters.
+ * @NFCC_COEX_CALL_ATTR_CCC_WATCHDOG_TIMEOUT:
+ * Watchdog trigged.
+ * @NFCC_COEX_CALL_ATTR_CCC_STOPPED:
+ * Session stopped.
+ *
+ * @NFCC_COEX_CALL_ATTR_UNSPEC: Invalid command.
+ * @__NFCC_COEX_CALL_ATTR_AFTER_LAST: Internal use.
+ * @NFCC_COEX_CALL_ATTR_MAX: Internal use.
+ */
+enum nfcc_coex_call_attrs {
+ NFCC_COEX_CALL_ATTR_UNSPEC,
+
+ NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS,
+ NFCC_COEX_CALL_ATTR_CCC_WATCHDOG_TIMEOUT,
+ NFCC_COEX_CALL_ATTR_CCC_STOPPED,
+
+ __NFCC_COEX_CALL_ATTR_AFTER_LAST,
+ NFCC_COEX_CALL_ATTR_MAX = __NFCC_COEX_CALL_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nfcc_coex_ccc_session_param_attrs - NFCC coexistence session parameters attributes.
+ *
+ * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS:
+ * Initiation time in unit of ns, default 0.
+ * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER:
+ * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14.
+ * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION:
+ * Protocol version to be used.
+ *
+ * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_UNSPEC: Invalid command.
+ * @__NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST: Internal use.
+ * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX: Internal use.
+ */
+enum nfcc_coex_ccc_session_param_attrs {
+ NFCC_COEX_CCC_SESSION_PARAM_ATTR_UNSPEC,
+
+ NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS,
+ NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER,
+ NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION,
+
+ __NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST,
+ NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX =
+ __NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST - 1
+};
+
+#endif /* NFCC_COEX_REGION_NL_H */
diff --git a/mac/include/net/pctt_region_nl.h b/mac/include/net/pctt_region_nl.h
new file mode 100644
index 0000000..f9a674a
--- /dev/null
+++ b/mac/include/net/pctt_region_nl.h
@@ -0,0 +1,241 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_PCTT_REGION_NL_H
+#define NET_PCTT_REGION_NL_H
+
+/**
+ * enum pctt_call - PCTT calls identifiers.
+ *
+ * @PCTT_CALL_SESSION_INIT:
+ * Initialize PCTT session.
+ * @PCTT_CALL_SESSION_CMD:
+ * Identifier of command to do: start test, stop.
+ * TODO: Could be move in session parameters.
+ * @PCTT_CALL_SESSION_DEINIT:
+ * Deinit PCTT session.
+ * @PCTT_CALL_SESSION_GET_STATE:
+ * Get session state.
+ * @PCTT_CALL_SESSION_GET_PARAMS:
+ * Request parameters.
+ * @PCTT_CALL_SESSION_SET_PARAMS:
+ * Set session parameters.
+ * @PCTT_CALL_SESSION_NOTIFICATION:
+ * Notify session reports.
+ * @PCTT_CALL_MAX: Internal use.
+ */
+enum pctt_call {
+ PCTT_CALL_SESSION_INIT,
+ PCTT_CALL_SESSION_CMD,
+ PCTT_CALL_SESSION_DEINIT,
+ PCTT_CALL_SESSION_GET_STATE,
+ PCTT_CALL_SESSION_GET_PARAMS,
+ PCTT_CALL_SESSION_SET_PARAMS,
+ PCTT_CALL_SESSION_NOTIFICATION,
+ PCTT_CALL_MAX,
+};
+
+enum pctt_call_attrs {
+ PCTT_CALL_ATTR_UNSPEC,
+ PCTT_CALL_ATTR_CMD_ID,
+ PCTT_CALL_ATTR_RESULT_DATA,
+ PCTT_CALL_ATTR_SESSION_ID,
+ PCTT_CALL_ATTR_SESSION_STATE,
+ PCTT_CALL_ATTR_SESSION_PARAMS,
+
+ __PCTT_CALL_ATTR_AFTER_LAST,
+ PCTT_CALL_ATTR_MAX = __PCTT_CALL_ATTR_AFTER_LAST - 1,
+};
+
+/**
+ * enum pctt_session_param_attrs - Pctt session parameters attributes.
+ *
+ * @PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE:
+ * Responder (0) or initiator (1)
+ * @PCTT_SESSION_PARAM_ATTR_SHORT_ADDR:
+ * Override device address for this session (UCI: DEVICE_MAC_ADDRESS)
+ * @PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR:
+ * Controller short addresses (UCI: DST_MAC_ADDRESS) [controlee only]
+ * @PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION:
+ * Antenna set to use during RX phases
+ * @PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION:
+ * Antenna set to use during Tx phases
+ * @PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU:
+ * Duration of a slot in RSTU, default 2400. (2 ms)
+ * @PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER:
+ * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14
+ * @PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX:
+ * Override preamble code for this session, BPRF (9-24),
+ * HPRF (25-32, not supported)
+ * @PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG:
+ * SP0 (0), SP1 (1), SP2 (2, unused, not in PcTt 1.1) or SP3 (3, default)
+ * @PCTT_SESSION_PARAM_ATTR_PRF_MODE:
+ * BPRF (0, default) or HPRF (1, not supported)
+ * @PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION:
+ * 64 (1, default) or 32 (0, only for HPRF)
+ * @PCTT_SESSION_PARAM_ATTR_SFD_ID:
+ * BPRF (0 or 2), HPRF (1-4, not supported), default 2
+ * @PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS:
+ * 0-2, default to 0 for SP0, default to 1 for SP1 & SP3, 2 not supported
+ * @PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE:
+ * 6.81 Mbps (0, default), 7.80 Mbps (1, not supported),
+ * 27.2 Mbps (2, not supported), 31.2 Mbps (3, not supported)
+ * @PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE:
+ * 850 kbps (0, default) or 6.81 Mbps (1)
+ * @PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE:
+ * CRC16 (0, default) or CRC32 (1, not supported)
+ * @PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER:
+ * Disable adaptive payload power for TX (0, default) or enable (1)
+ * @PCTT_SESSION_PARAM_ATTR_STS_INDEX:
+ * STS index initialization value
+ * @PCTT_SESSION_PARAM_ATTR_STS_LENGTH:
+ * Number of symbols in a STS segment. 32 (0x00), 64 (0x01, default) or 128
+ * symbols (0x02)
+ * @PCTT_SESSION_PARAM_ATTR_NUM_PACKETS:
+ * Number of packets (default 1000).
+ * @PCTT_SESSION_PARAM_ATTR_T_GAP:
+ * Gap between start of one packet to the next in µs (default 2000).
+ * @PCTT_SESSION_PARAM_ATTR_T_START:
+ * Max. time from the start of T_GAP to SFD found state in µs (default
+ * 450us).
+ * @PCTT_SESSION_PARAM_ATTR_T_WIN:
+ * Max. time for which RX is looking for a packet from the start of T_GAP
+ * in µs (default 750).
+ * @PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU:
+ * Disable (0, default) or enable (1) PSDU randomization.
+ * @PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT:
+ * Disable (0, default) or enable (1) ranging bit field of PHR in both BPRF
+ * and HPRF.
+ * @PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START:
+ * Start time of TX in 1/(128*499.2MHz) units.
+ * @PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START:
+ * Start time of RX in 1/(128*499.2MHz) units.
+ * @PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR:
+ * Disable (0, default) or enable (1) incrementation of STS_INDEX config
+ * value for every frame in PER Rx/Periodic TX test.
+ * @PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD:
+ * PSDU Data.
+ * @PCTT_SESSION_PARAM_ATTR_UNSPEC: Invalid command.
+ * @__PCTT_SESSION_PARAM_ATTR_AFTER_LAST: Internal use.
+ * @PCTT_SESSION_PARAM_ATTR_MAX: Internal use.
+ */
+enum pctt_session_param_attrs {
+ PCTT_SESSION_PARAM_ATTR_UNSPEC,
+ /* Main session parameters */
+ PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE,
+ PCTT_SESSION_PARAM_ATTR_SHORT_ADDR,
+ PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR,
+ PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION,
+ PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION,
+ /* Timings */
+ PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU,
+ /* Radio */
+ PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER,
+ PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX,
+ PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG,
+ PCTT_SESSION_PARAM_ATTR_PRF_MODE,
+ PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION,
+ PCTT_SESSION_PARAM_ATTR_SFD_ID,
+ PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS,
+ PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE,
+ PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE,
+ PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE,
+ PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER,
+ /* STS and crypto */
+ PCTT_SESSION_PARAM_ATTR_STS_INDEX,
+ PCTT_SESSION_PARAM_ATTR_STS_LENGTH,
+ /* Test configuration parameters */
+ PCTT_SESSION_PARAM_ATTR_NUM_PACKETS,
+ PCTT_SESSION_PARAM_ATTR_T_GAP,
+ PCTT_SESSION_PARAM_ATTR_T_START,
+ PCTT_SESSION_PARAM_ATTR_T_WIN,
+ PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU,
+ PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT,
+ PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START,
+ PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START,
+ PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR,
+ /* Payload */
+ PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD,
+
+ __PCTT_SESSION_PARAM_ATTR_AFTER_LAST,
+ PCTT_SESSION_PARAM_ATTR_MAX = __PCTT_SESSION_PARAM_ATTR_AFTER_LAST - 1
+};
+
+enum pctt_id_attrs {
+ PCTT_ID_ATTR_UNSPEC,
+
+ PCTT_ID_ATTR_PERIODIC_TX,
+ PCTT_ID_ATTR_PER_RX,
+ PCTT_ID_ATTR_RX,
+ PCTT_ID_ATTR_LOOPBACK,
+ PCTT_ID_ATTR_SS_TWR,
+ PCTT_ID_ATTR_STOP_TEST,
+
+ __PCTT_ID_ATTR_AFTER_LAST,
+ PCTT_ID_ATTR_MAX = __PCTT_ID_ATTR_AFTER_LAST - 1
+};
+
+enum pctt_result_data_attrs {
+ PCTT_RESULT_DATA_ATTR_UNSPEC,
+
+ PCTT_RESULT_DATA_ATTR_STATUS,
+ PCTT_RESULT_DATA_ATTR_ATTEMPTS,
+ PCTT_RESULT_DATA_ATTR_ACQ_DETECT,
+ PCTT_RESULT_DATA_ATTR_ACQ_REJECT,
+ PCTT_RESULT_DATA_ATTR_RX_FAIL,
+ PCTT_RESULT_DATA_ATTR_SYNC_CIR_READY,
+ PCTT_RESULT_DATA_ATTR_SFD_FAIL,
+ PCTT_RESULT_DATA_ATTR_SFD_FOUND,
+ PCTT_RESULT_DATA_ATTR_PHR_DEC_ERROR,
+ PCTT_RESULT_DATA_ATTR_PHR_BIT_ERROR,
+ PCTT_RESULT_DATA_ATTR_PSDU_DEC_ERROR,
+ PCTT_RESULT_DATA_ATTR_PSDU_BIT_ERROR,
+ PCTT_RESULT_DATA_ATTR_STS_FOUND,
+ PCTT_RESULT_DATA_ATTR_EOF,
+
+ PCTT_RESULT_DATA_ATTR_RX_DONE_TS_INT,
+ PCTT_RESULT_DATA_ATTR_RX_DONE_TS_FRAC,
+ PCTT_RESULT_DATA_ATTR_AOA_AZIMUTH,
+ PCTT_RESULT_DATA_ATTR_AOA_ELEVATION,
+ PCTT_RESULT_DATA_ATTR_TOA_GAP,
+ PCTT_RESULT_DATA_ATTR_PHR,
+ PCTT_RESULT_DATA_ATTR_PSDU_DATA_LEN,
+ PCTT_RESULT_DATA_ATTR_PSDU_DATA,
+ PCTT_RESULT_DATA_ATTR_TX_TS_INT,
+ PCTT_RESULT_DATA_ATTR_TX_TS_FRAC,
+ PCTT_RESULT_DATA_ATTR_RX_TS_INT,
+ PCTT_RESULT_DATA_ATTR_RX_TS_FRAC,
+
+ PCTT_RESULT_DATA_ATTR_MEASUREMENT,
+
+ PCTT_RESULT_DATA_ATTR_PDOA_AZIMUTH_DEG_Q7,
+ PCTT_RESULT_DATA_ATTR_PDOA_ELEVATION_DEG_Q7,
+ PCTT_RESULT_DATA_ATTR_RSSI,
+ PCTT_RESULT_DATA_ATTR_AOA_AZIMUTH_DEG_Q7,
+ PCTT_RESULT_DATA_ATTR_AOA_ELEVATION_DEG_Q7,
+
+ __PCTT_RESULT_DATA_ATTR_AFTER_LAST,
+ PCTT_RESULT_DATA_ATTR_MAX = __PCTT_RESULT_DATA_ATTR_AFTER_LAST - 1,
+};
+
+#endif /* NET_PCTT_REGION_NL_H */
diff --git a/mac/include/net/pctt_region_params.h b/mac/include/net/pctt_region_params.h
new file mode 100644
index 0000000..0a0f745
--- /dev/null
+++ b/mac/include/net/pctt_region_params.h
@@ -0,0 +1,220 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo.
+ * Please contact Qorvo to inquire about licensing terms.
+ *
+ * 802.15.4 mac common part sublayer, pctt ranging region.
+ *
+ */
+
+#ifndef NET_PCTT_REGION_PARAMS_H
+#define NET_PCTT_REGION_PARAMS_H
+
+#include <linux/types.h>
+
+#define PCTT_VUPPER64_SIZE 8
+#define PCTT_KEY_SIZE_MAX 32
+#define PCTT_KEY_SIZE_MIN 16
+#define PCTT_CONTROLEES_MAX 16
+#define PCTT_RX_ANTENNA_PAIR_INVALID 0xff
+/*
+ * In BPRF, frame is at most 127
+ * 127 - (MHR + HIE + HT + PIE_Header + V_OUI + MIC + CRC)
+ */
+#define PCTT_DATA_PAYLOAD_SIZE_MAX 84
+
+/**
+ * enum pctt_device_role - **[NOT IMPLEMENTED]** Role played by a device.
+ * @PCTT_DEVICE_ROLE_RESPONDER: The device acts as a responder.
+ * @PCTT_DEVICE_ROLE_INITIATOR: The device acts as an initiator.
+ *
+ * Current implementation does not support decorrelation between the
+ * device's role and the device's type. The controller is always
+ * the initiator and the controlee is always the responder.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum pctt_device_role {
+ PCTT_DEVICE_ROLE_RESPONDER,
+ PCTT_DEVICE_ROLE_INITIATOR,
+};
+
+/**
+ * enum pctt_rframe_config - Rframe configuration used to transmit/receive
+ * ranging messages.
+ * @PCTT_RFRAME_CONFIG_SP0: Use SP0 mode.
+ * @PCTT_RFRAME_CONFIG_SP1: Use SP1 mode.
+ * @PCTT_RFRAME_CONFIG_SP2: RFU
+ * @PCTT_RFRAME_CONFIG_SP3: Use SP3 mode.
+ */
+enum pctt_rframe_config {
+ PCTT_RFRAME_CONFIG_SP0,
+ PCTT_RFRAME_CONFIG_SP1,
+ PCTT_RFRAME_CONFIG_SP2,
+ PCTT_RFRAME_CONFIG_SP3,
+};
+
+/**
+ * enum pctt_prf_mode - Pulse Repetition Frequency mode.
+ * @PCTT_PRF_MODE_BPRF: Base Pulse Repetition Frequency.
+ * @PCTT_PRF_MODE_HPRF: Higher Pulse Repetition Frequency.
+ * @PCTT_PRF_MODE_HPRF_HIGH_RATE: Higher Pulse Repetition Frequency allowing
+ * higher data rates (27M2 and 31M2).
+ *
+ * This enum is not used in the current implementation.
+ */
+enum pctt_prf_mode {
+ PCTT_PRF_MODE_BPRF,
+ PCTT_PRF_MODE_HPRF,
+ PCTT_PRF_MODE_HPRF_HIGH_RATE,
+};
+
+/**
+ * enum pctt_preamble_duration - Duration of preamble in symbols.
+ * @PCTT_PREAMBLE_DURATION_32: 32 symbols duration.
+ * @PCTT_PREAMBLE_DURATION_64: 64 symbols duration.
+ */
+enum pctt_preamble_duration {
+ PCTT_PREAMBLE_DURATION_32,
+ PCTT_PREAMBLE_DURATION_64,
+};
+
+/**
+ * enum pctt_sfd_id - Start-of-frame delimiter.
+ * @PCTT_SFD_ID_0: Delimiter is [0 +1 0 –1 +1 0 0 –1]
+ * @PCTT_SFD_ID_1: Delimiter is [ –1 –1 +1 –1 ]
+ * @PCTT_SFD_ID_2: Delimiter is [ –1 –1 –1 +1 –1 –1 +1 –1 ]
+ * @PCTT_SFD_ID_3: Delimiter is
+ * [ –1 –1 –1 –1 –1 +1 +1 –1 –1 +1 –1 +1 –1 –1 +1 –1 ]
+ * @PCTT_SFD_ID_4: Delimiter is
+ * [ –1 –1 –1 –1 –1 –1 –1 +1 –1 –1 +1 –1 –1 +1 –1 +1 –1 +1
+ * –1 –1 –1 +1 +1 –1 –1 –1 +1 –1 +1 +1 –1 –1 ]
+ */
+enum pctt_sfd_id {
+ PCTT_SFD_ID_0,
+ PCTT_SFD_ID_1,
+ PCTT_SFD_ID_2,
+ PCTT_SFD_ID_3,
+ PCTT_SFD_ID_4,
+};
+
+/**
+ * enum pctt_number_of_sts_segments - Number of STS segments.
+ * @PCTT_NUMBER_OF_STS_SEGMENTS_NONE: No STS Segment (Rframe config SP0).
+ * @PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT: 1 STS Segment.
+ * @PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS: 2 STS Segments.
+ * @PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS: 3 STS Segments.
+ * @PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS: 4 STS Segments.
+ */
+enum pctt_number_of_sts_segments {
+ PCTT_NUMBER_OF_STS_SEGMENTS_NONE,
+ PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT,
+ PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS,
+ PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS,
+ PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS,
+};
+
+/**
+ * enum pctt_psdu_data_rate - Data rate used to exchange PSDUs.
+ * @PCTT_PSDU_DATA_RATE_6M81: 6.8Mb/s rate.
+ * @PCTT_PSDU_DATA_RATE_7M80: 7.8Mb/s rate.
+ * @PCTT_PSDU_DATA_RATE_27M2: 27.2Mb/s rate.
+ * @PCTT_PSDU_DATA_RATE_31M2: 31.2Mb/s rate.
+ */
+enum pctt_psdu_data_rate {
+ PCTT_PSDU_DATA_RATE_6M81,
+ PCTT_PSDU_DATA_RATE_7M80,
+ PCTT_PSDU_DATA_RATE_27M2,
+ PCTT_PSDU_DATA_RATE_31M2,
+};
+
+/**
+ * enum pctt_phr_data_rate - Data rate used to exchange PHR.
+ * @PCTT_PHR_DATA_RATE_850K: 850kb/s rate.
+ * @PCTT_PHR_DATA_RATE_6M81: 6.8Mb/s rate.
+ *
+ * This enum is not used in the current implementation.
+ */
+enum pctt_phr_data_rate {
+ PCTT_PHR_DATA_RATE_850K,
+ PCTT_PHR_DATA_RATE_6M81,
+};
+
+enum pctt_mac_fcs_type {
+ PCTT_MAC_FCS_TYPE_CRC_16,
+ PCTT_MAC_FCS_TYPE_CRC_32,
+};
+
+/**
+ * enum pctt_status_ranging - Ranging status: success or failure reason.
+ * @PCTT_STATUS_RANGING_INTERNAL_ERROR: Implementation specific error.
+ * @PCTT_STATUS_RANGING_SUCCESS: Ranging info are valid.
+ * @PCTT_STATUS_RANGING_TX_FAILED: Failed to transmit UWB packet.
+ * @PCTT_STATUS_RANGING_RX_TIMEOUT: No UWB packet detected by the receiver.
+ * @PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED: UWB packet channel decoding error.
+ * @PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED: Failed to detect time of arrival of
+ * the UWB packet from CIR samples.
+ * @PCTT_STATUS_RANGING_RX_PHY_STS_FAILED: UWB packet STS segment mismatch.
+ * @PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED: MAC CRC or syntax error.
+ * @PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED: IE syntax error.
+ * @PCTT_STATUS_RANGING_RX_MAC_IE_MISSING: Expected IE missing in the packet.
+ */
+enum pctt_status_ranging {
+ PCTT_STATUS_RANGING_INTERNAL_ERROR = -1,
+ PCTT_STATUS_RANGING_SUCCESS = 0,
+ PCTT_STATUS_RANGING_TX_FAILED = 1,
+ PCTT_STATUS_RANGING_RX_TIMEOUT = 2,
+ PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED = 3,
+ PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED = 4,
+ PCTT_STATUS_RANGING_RX_PHY_STS_FAILED = 5,
+ PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED = 6,
+ PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 7,
+ PCTT_STATUS_RANGING_RX_MAC_IE_MISSING = 8,
+};
+
+/**
+ * enum pctt_session_state - Session state.
+ * @PCTT_SESSION_STATE_INIT: Initial state, session is not ready yet.
+ * @PCTT_SESSION_STATE_DEINIT: Session does not exist.
+ * @PCTT_SESSION_STATE_ACTIVE: Session is currently active.
+ * @PCTT_SESSION_STATE_IDLE: Session is ready to start, but not currently
+ * active.
+ */
+enum pctt_session_state {
+ PCTT_SESSION_STATE_INIT,
+ PCTT_SESSION_STATE_DEINIT,
+ PCTT_SESSION_STATE_ACTIVE,
+ PCTT_SESSION_STATE_IDLE,
+};
+
+/**
+ * enum pctt_sts_length - Number of symbols in a STS segment.
+ * @PCTT_STS_LENGTH_32: The STS length is 32 symbols.
+ * @PCTT_STS_LENGTH_64: The STS length is 64 symbols.
+ * @PCTT_STS_LENGTH_128: The STS length is 128 symbols.
+ */
+enum pctt_sts_length {
+ PCTT_STS_LENGTH_32 = 0,
+ PCTT_STS_LENGTH_64 = 1,
+ PCTT_STS_LENGTH_128 = 2,
+};
+
+#endif /* NET_PCTT_REGION_PARAMS_H */
diff --git a/mac/include/net/vendor_cmd.h b/mac/include/net/vendor_cmd.h
new file mode 100644
index 0000000..38a6224
--- /dev/null
+++ b/mac/include/net/vendor_cmd.h
@@ -0,0 +1,243 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_VENDOR_CMD_H
+#define NET_VENDOR_CMD_H
+
+#include <linux/types.h>
+#include <net/mcps802154.h>
+
+/**
+ * enum llhw_vendor_cmd - Vendor command identifiers.
+ * @LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS:
+ * NFCC Coex: Handle access.
+ * @LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION:
+ * NFCC Coex: Get access information.
+ * @LLHW_VENDOR_CMD_NFCC_COEX_STOP:
+ * NFCC Coex: Stop.
+ * @LLHW_VENDOR_CMD_PCTT_SETUP_HW:
+ * PCTT: Setup hardware access.
+ * @LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK:
+ * PCTT: Handle loop-back test.
+ * @LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO:
+ * PCTT: Get loop-back information.
+ * @LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO:
+ * PCTT: Get the last received frame information.
+ */
+enum llhw_vendor_cmd {
+ LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS,
+ LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION,
+ LLHW_VENDOR_CMD_NFCC_COEX_STOP,
+ LLHW_VENDOR_CMD_PCTT_SETUP_HW,
+ LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK,
+ LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO,
+ LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO,
+};
+
+/**
+ * struct llhw_vendor_cmd_nfcc_coex_handle_access - NFCC Coex: handle access
+ * vendor command.
+ */
+struct llhw_vendor_cmd_nfcc_coex_handle_access {
+ /**
+ * @start: True to start a new session.
+ */
+ bool start;
+ /**
+ * @timestamp_dtu:
+ * Access start date. If this is a new session, this also defines
+ * TIME0.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @duration_dtu:
+ * Duration of the access, or 0 if unknown (this is the case when
+ * starting a new session).
+ */
+ int duration_dtu;
+ /**
+ * @chan: Channel number, 5 or 9.
+ */
+ int chan;
+ /**
+ * @version: Protocol version.
+ */
+ int version;
+};
+
+/**
+ * struct llhw_vendor_cmd_nfcc_coex_get_access_info - NFCC Coex: get access
+ * info vendor command.
+ */
+struct llhw_vendor_cmd_nfcc_coex_get_access_info {
+ /**
+ * @stop: If true, the NFCC did not give a next access.
+ */
+ bool stop;
+ /**
+ * @watchdog_timeout:
+ * If true, Watchdog triggered before NFCC released the SPI.
+ */
+ bool watchdog_timeout;
+ /**
+ * @duration_dtu:
+ * Effective duration of the access. If 0, the current date will be
+ * read to continue.
+ */
+ int duration_dtu;
+ /**
+ * @next_timestamp_dtu: Next access date.
+ */
+ u32 next_timestamp_dtu;
+ /**
+ * @next_duration_dtu: Next access duration, or 0 if unknown.
+ */
+ int next_duration_dtu;
+};
+
+/**
+ * struct llhw_vendor_cmd_nfcc_coex_stop - NFCC Coex: stop
+ * vendor command.
+ */
+struct llhw_vendor_cmd_nfcc_coex_stop {
+ /**
+ * @timestamp_dtu:
+ * Access date when the stop must be sent.
+ */
+ u32 timestamp_dtu;
+ /**
+ * @duration_dtu:
+ * Duration of the access.
+ */
+ int duration_dtu;
+ /**
+ * @version: Protocol version.
+ */
+ int version;
+};
+
+/**
+ * struct llhw_vendor_cmd_pctt_setup_hw - PCTT: direct HW access
+ * vendor command.
+ */
+struct llhw_vendor_cmd_pctt_setup_hw {
+ /**
+ * @chan: Channel number, 5 or 9.
+ */
+ int chan;
+ /**
+ * @rframe_config: STS packet configuration for ranging frame.
+ */
+ u8 rframe_config;
+ /**
+ * @preamble_code_index: Specifies code index according to Table 16-7 in
+ * IEEE Std 802.15.4-2020 and Table 42 in IEEE Std 802.15.4z-2020
+ */
+ u8 preamble_code_index;
+ /**
+ * @sfd_id: Start of frame delimiter.
+ */
+ u8 sfd_id;
+ /**
+ * @psdu_data_rate: PSDU data rate.
+ */
+ u8 psdu_data_rate;
+ /**
+ * @preamble_duration: Preamble duration.
+ */
+ u8 preamble_duration;
+};
+
+/**
+ * struct llhw_vendor_cmd_pctt_handle_loopback - PCTT: handle loopback access.
+ */
+struct llhw_vendor_cmd_pctt_handle_loopback {
+ /**
+ * @ant_set_id : antenna set index to use for transmit/receive.
+ */
+ int ant_set_id;
+ /**
+ * @rx_timeout_dtu: If negative, no timeout, if zero, use a default timeout
+ * value, else this is the timeout value in device time unit.
+ */
+ int rx_timeout_dtu;
+ /**
+ * @rx_frame_timeout_dtu: If no zero, timeout value for the full frame
+ * reception. This allow limiting the length of accepted frame. The
+ * timeout starts after rx_timeout_dtu value.
+ */
+ int rx_frame_timeout_dtu;
+ /**
+ * @data_payload: Array of payload to send during loopback test.
+ */
+ const u8 *data_payload;
+ /**
+ * @data_payload_len: Length of the payload array in byte.
+ */
+ size_t data_payload_len;
+};
+
+/**
+ * struct llhw_vendor_cmd_pctt_get_loopback_info - PCTT: get access
+ * info vendor command.
+ */
+struct llhw_vendor_cmd_pctt_get_loopback_info {
+ /**
+ * @skb: sk buffer containing received data.
+ */
+ struct sk_buff *skb;
+ /**
+ * @success: True when data sent match with received.
+ */
+ bool success;
+ /**
+ * @rssi: Received signal strength indication (RSSI),
+ * absolute value in Q1 fixed point format.
+ */
+ int rssi;
+ /**
+ * @rx_timestamp_rctu: RX timestamp in RCTU units.
+ */
+ u64 rx_timestamp_rctu;
+ /**
+ * @tx_timestamp_rctu: TX timestamp in RCTU units.
+ */
+ u64 tx_timestamp_rctu;
+};
+
+/**
+ * struct llhw_vendor_cmd_pctt_get_frame_info - PCTT: last received frame
+ * information.
+ */
+struct llhw_vendor_cmd_pctt_get_frame_info {
+ /**
+ * @skb: sk buffer containing received data.
+ */
+ struct sk_buff *skb;
+ /**
+ * @info: frame information.
+ */
+ struct mcps802154_rx_frame_info info;
+};
+
+#endif /* NET_VENDOR_CMD_H */
diff --git a/mac/llhw-ops.h b/mac/llhw-ops.h
new file mode 100644
index 0000000..1dfcf54
--- /dev/null
+++ b/mac/llhw-ops.h
@@ -0,0 +1,359 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef LLHW_OPS_H
+#define LLHW_OPS_H
+
+#include <linux/errno.h>
+
+#include "mcps802154_i.h"
+#include "trace.h"
+
+static inline int llhw_start(struct mcps802154_local *local)
+{
+ int r;
+
+ trace_llhw_start(local);
+ r = local->ops->start(&local->llhw);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline void llhw_stop(struct mcps802154_local *local)
+{
+ trace_llhw_stop(local);
+ local->ops->stop(&local->llhw);
+ trace_llhw_return_void(local);
+}
+
+static inline int llhw_tx_frame(struct mcps802154_local *local,
+ struct sk_buff *skb,
+ const struct mcps802154_tx_frame_config *config,
+ int frame_idx, int next_delay_dtu)
+{
+ int r;
+
+ trace_llhw_tx_frame(local, config, frame_idx, next_delay_dtu);
+ r = local->ops->tx_frame(&local->llhw, skb, config, frame_idx,
+ next_delay_dtu);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_rx_enable(struct mcps802154_local *local,
+ const struct mcps802154_rx_frame_config *info,
+ int frame_idx, int next_delay_dtu)
+{
+ int r;
+
+ trace_llhw_rx_enable(local, info, frame_idx, next_delay_dtu);
+ r = local->ops->rx_enable(&local->llhw, info, frame_idx,
+ next_delay_dtu);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_rx_disable(struct mcps802154_local *local)
+{
+ int r;
+
+ trace_llhw_rx_disable(local);
+ r = local->ops->rx_disable(&local->llhw);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_rx_get_frame(struct mcps802154_local *local,
+ struct sk_buff **skb,
+ struct mcps802154_rx_frame_info *info)
+{
+ int r;
+
+ trace_llhw_rx_get_frame(local, info);
+ r = local->ops->rx_get_frame(&local->llhw, skb, info);
+ trace_llhw_return_rx_frame(local, r, info);
+ return r;
+}
+
+static inline int llhw_rx_get_error_frame(struct mcps802154_local *local,
+ struct mcps802154_rx_frame_info *info)
+{
+ int r;
+
+ trace_llhw_rx_get_error_frame(local, info);
+ r = local->ops->rx_get_error_frame(&local->llhw, info);
+ trace_llhw_return_rx_frame(local, r, info);
+ return r;
+}
+
+static inline int llhw_idle(struct mcps802154_local *local, bool timestamp,
+ u32 timestamp_dtu)
+{
+ int r;
+
+ if (timestamp)
+ trace_llhw_idle_timestamp(local, timestamp_dtu);
+ else
+ trace_llhw_idle(local);
+ r = local->ops->idle(&local->llhw, timestamp, timestamp_dtu);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_reset(struct mcps802154_local *local)
+{
+ int r;
+
+ trace_llhw_reset(local);
+ r = local->ops->reset(&local->llhw);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_get_current_timestamp_dtu(struct mcps802154_local *local,
+ u32 *timestamp_dtu)
+{
+ int r;
+
+ trace_llhw_get_current_timestamp_dtu(local);
+ r = local->ops->get_current_timestamp_dtu(&local->llhw, timestamp_dtu);
+ trace_llhw_return_timestamp_dtu(local, r, *timestamp_dtu);
+ return r;
+}
+
+static inline u64 llhw_tx_timestamp_dtu_to_rmarker_rctu(
+ struct mcps802154_local *local, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params, int ant_set_id)
+{
+ return local->ops->tx_timestamp_dtu_to_rmarker_rctu(
+ &local->llhw, tx_timestamp_dtu, hrp_uwb_params, channel_params,
+ ant_set_id);
+}
+
+static inline s64 llhw_difference_timestamp_rctu(struct mcps802154_local *local,
+ u64 timestamp_a_rctu,
+ u64 timestamp_b_rctu)
+{
+ return local->ops->difference_timestamp_rctu(
+ &local->llhw, timestamp_a_rctu, timestamp_b_rctu);
+}
+
+static inline int
+llhw_compute_frame_duration_dtu(struct mcps802154_local *local,
+ int payload_bytes)
+{
+ return local->ops->compute_frame_duration_dtu(&local->llhw,
+ payload_bytes);
+}
+
+static inline int llhw_set_channel(struct mcps802154_local *local, u8 page,
+ u8 channel, u8 preamble_code)
+{
+ int r;
+
+ trace_llhw_set_channel(local, page, channel, preamble_code);
+ r = local->ops->set_channel(&local->llhw, page, channel, preamble_code);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int __nocfi
+llhw_set_hrp_uwb_params(struct mcps802154_local *local,
+ const struct mcps802154_hrp_uwb_params *params)
+{
+ int r;
+
+ trace_llhw_set_hrp_uwb_params(local, params);
+ r = local->ops->set_hrp_uwb_params(&local->llhw, params);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int
+llhw_set_sts_params(struct mcps802154_local *local,
+ const struct mcps802154_sts_params *params)
+{
+ int r;
+
+ trace_llhw_set_sts_params(local, params);
+ if (local->ops->set_sts_params)
+ r = local->ops->set_sts_params(&local->llhw, params);
+ else
+ r = -EOPNOTSUPP;
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_hw_addr_filt(struct mcps802154_local *local,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ int r;
+
+ trace_llhw_set_hw_addr_filt(local, filt, changed);
+ r = local->ops->set_hw_addr_filt(&local->llhw, filt, changed);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_txpower(struct mcps802154_local *local, s32 mbm)
+{
+ int r;
+
+ trace_llhw_set_txpower(local, mbm);
+ r = local->ops->set_txpower(&local->llhw, mbm);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_cca_ed_level(struct mcps802154_local *local, s32 mbm)
+{
+ int r;
+
+ trace_llhw_set_cca_ed_level(local, mbm);
+ r = local->ops->set_cca_ed_level(&local->llhw, mbm);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_promiscuous_mode(struct mcps802154_local *local,
+ bool on)
+{
+ int r;
+
+ trace_llhw_set_promiscuous_mode(local, on);
+ r = local->ops->set_promiscuous_mode(&local->llhw, on);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_scanning_mode(struct mcps802154_local *local,
+ bool on)
+{
+ int r;
+
+ trace_llhw_set_scanning_mode(local, on);
+ r = local->ops->set_scanning_mode(&local->llhw, on);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_set_calibration(struct mcps802154_local *local,
+ const char *key, void *value,
+ size_t length)
+{
+ int r;
+
+ trace_llhw_set_calibration(local, key);
+ r = local->ops->set_calibration(&local->llhw, key, value, length);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_get_calibration(struct mcps802154_local *local,
+ const char *key, void *value,
+ size_t length)
+{
+ int r;
+
+ trace_llhw_get_calibration(local, key);
+ r = local->ops->get_calibration(&local->llhw, key, value, length);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline const char *const *
+llhw_list_calibration(struct mcps802154_local *local)
+{
+ const char *const *r;
+
+ trace_llhw_list_calibration(local);
+ if (local->ops->list_calibration) {
+ r = local->ops->list_calibration(&local->llhw);
+ } else {
+ r = NULL;
+ }
+ trace_llhw_return_void(local);
+ return r;
+}
+
+static inline int llhw_vendor_cmd(struct mcps802154_local *local, u32 vendor_id,
+ u32 subcmd, void *data, size_t data_len)
+{
+ int r;
+
+ trace_llhw_vendor_cmd(local, vendor_id, subcmd, data_len);
+ if (local->ops->vendor_cmd)
+ r = local->ops->vendor_cmd(&local->llhw, vendor_id, subcmd,
+ data, data_len);
+ else
+ r = -EOPNOTSUPP;
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int llhw_check_hrp_uwb_params(
+ struct mcps802154_local *local,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params)
+{
+ int r;
+
+ trace_llhw_check_hrp_uwb_params(local, hrp_uwb_params);
+ if (local->ops->check_hrp_uwb_params)
+ r = local->ops->check_hrp_uwb_params(&local->llhw,
+ hrp_uwb_params);
+ else
+ r = -EOPNOTSUPP;
+ trace_llhw_return_int(local, r);
+ return r;
+}
+
+static inline int
+llhw_rx_get_measurement(struct mcps802154_local *local, void *rx_ctx,
+ struct mcps802154_rx_measurement_info *info)
+{
+ int r;
+ trace_llhw_rx_get_measurement(local, rx_ctx);
+ if (local->ops->rx_get_measurement)
+ r = local->ops->rx_get_measurement(&local->llhw, rx_ctx, info);
+ else
+ r = -EOPNOTSUPP;
+ trace_llhw_return_measurement(local, r, info);
+ return r;
+}
+
+#ifdef CONFIG_MCPS802154_TESTMODE
+static inline int llhw_testmode_cmd(struct mcps802154_local *local, void *data,
+ int len)
+{
+ int r;
+
+ trace_llhw_testmode_cmd(local);
+ r = local->ops->testmode_cmd(&local->llhw, data, len);
+ trace_llhw_return_int(local, r);
+ return r;
+}
+#endif /* CONFIG_MCPS802154_TESTMODE */
+
+#endif /* LLHW_OPS_H */
diff --git a/mac/mcps802154_fproc.h b/mac/mcps802154_fproc.h
new file mode 100644
index 0000000..fc6e871
--- /dev/null
+++ b/mac/mcps802154_fproc.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef __MCPS802154_FPROC_H__
+#define __MCPS802154_FPROC_H__
+
+#include <net/mcps802154_schedule.h>
+
+bool mcps802154_fproc_is_non_recoverable_error(struct mcps802154_access *access);
+
+#endif /* MCPS802154_FPROC_H */
+
diff --git a/mac/mcps802154_i.h b/mac/mcps802154_i.h
new file mode 100644
index 0000000..311ee3a
--- /dev/null
+++ b/mac/mcps802154_i.h
@@ -0,0 +1,161 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_MCPS802154_I_H
+#define NET_MCPS802154_MCPS802154_I_H
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <net/mac802154.h>
+#include <net/mcps802154.h>
+
+#include "ca.h"
+#include "fproc.h"
+
+/**
+ * struct mcps802154_pib - PIB (PAN Information Base): this is a database of
+ * 802.15.4 settings.
+ */
+struct mcps802154_pib {
+ /**
+ * @mac_extended_addr: Current extended address.
+ */
+ __le64 mac_extended_addr;
+ /**
+ * @mac_pan_id: The identifier of the PAN on which the device is
+ * operating. 0xffff if the device is not associated.
+ */
+ __le16 mac_pan_id;
+ /**
+ * @mac_short_addr: The address the device uses to communicate inside
+ * its PAN. 0xffff if the device is not associated, 0xfffe if the device
+ * is associated but has no short address.
+ */
+ __le16 mac_short_addr;
+ /**
+ * @mac_promiscuous: Indicate whether the promiscuous mode is enabled.
+ */
+ bool mac_promiscuous;
+ /**
+ * @mac_max_frame_retries: Number of retries on TX.
+ */
+ s8 mac_max_frame_retries;
+ /**
+ * @phy_current_channel: Current channel parameters.
+ */
+ struct mcps802154_channel phy_current_channel;
+};
+
+/**
+ * struct mcps802154_local - MCPS private data.
+ */
+struct mcps802154_local {
+ /**
+ * @llhw: Low-level hardware.
+ */
+ struct mcps802154_llhw llhw;
+ /**
+ * @hw: Pointer to MCPS hw instance.
+ */
+ struct ieee802154_hw *hw;
+ /**
+ * @ops: Low-level driver operations.
+ */
+ const struct mcps802154_ops *ops;
+ /**
+ * @hw_idx: Index of hardware.
+ */
+ int hw_idx;
+ /**
+ * @cur_cmd_info: Current netlink command.
+ */
+ struct genl_info *cur_cmd_info;
+ /**
+ * @registered_entry: Entry in list of registered low-level driver.
+ */
+ struct list_head registered_entry;
+ /**
+ * @wq: Wake queue for synchronous operation with an asynchronous
+ * implementation.
+ */
+ wait_queue_head_t wq;
+ /**
+ * @ca: Channel access context.
+ */
+ struct mcps802154_ca ca;
+ /**
+ * @fproc: Frame processing context.
+ */
+ struct mcps802154_fproc fproc;
+ /**
+ * @fsm_lock: FSM lock to avoid multiple access.
+ */
+ struct mutex fsm_lock;
+ /**
+ * @tx_work: Transmit work to schedule async actions.
+ */
+ struct work_struct tx_work;
+ /**
+ * @start_stop_request: Request to start (true) or stop (false) from
+ * mac802154 layer.
+ */
+ bool start_stop_request;
+ /**
+ * @started: Current started state.
+ */
+ bool started;
+ /**
+ * @broken: Currently broken.
+ */
+ bool broken;
+ /**
+ * @pib: PAN Information Base.
+ */
+ struct mcps802154_pib pib;
+ /**
+ * @mac_pan_coord: Indicate whether the hardware filtering should operate as
+ * coordinator.
+ */
+ bool mac_pan_coord;
+};
+
+static inline struct mcps802154_local *
+llhw_to_local(struct mcps802154_llhw *llhw)
+{
+ return container_of(llhw, struct mcps802154_local, llhw);
+}
+
+static inline struct mcps802154_local *
+txwork_to_local(struct work_struct *tx_work)
+{
+ return container_of(tx_work, struct mcps802154_local, tx_work);
+}
+
+struct mcps802154_local *mcps802154_get_first_by_idx(int hw_idx);
+
+extern const struct ieee802154_ops mcps802154_ops;
+
+#endif /* NET_MCPS802154_MCPS802154_I_H */
diff --git a/mac/mcps802154_qorvo.h b/mac/mcps802154_qorvo.h
new file mode 100644
index 0000000..170ea06
--- /dev/null
+++ b/mac/mcps802154_qorvo.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef MCPS802154_QORVO_H
+#define MCPS802154_QORVO_H
+
+/* Qorvo OUI in big endian.
+ * The define can't be declared in include/net/vendor_cmd.h because
+ * it's not generic to other mac provider. */
+#define VENDOR_QORVO_OUI 0xc8b1ee00
+
+#endif /* MCPS802154_QORVO_H */
diff --git a/mac/mcps_main.c b/mac/mcps_main.c
new file mode 100644
index 0000000..f3c2590
--- /dev/null
+++ b/mac/mcps_main.c
@@ -0,0 +1,324 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/atomic.h>
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <net/rtnetlink.h>
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+#include "default_region.h"
+#include "idle_region.h"
+#include "endless_scheduler.h"
+#include "on_demand_scheduler.h"
+#include "nl.h"
+#include "warn_return.h"
+
+static LIST_HEAD(registered_llhw);
+static DEFINE_MUTEX(registered_llhw_lock);
+
+static void mcps802154_tx_event(struct work_struct *work)
+{
+ struct mcps802154_local *local = txwork_to_local(work);
+
+ mutex_lock(&local->fsm_lock);
+ if (likely(local->started))
+ mcps802154_ca_may_reschedule(local);
+ mutex_unlock(&local->fsm_lock);
+}
+
+struct mcps802154_llhw *mcps802154_alloc_llhw(size_t priv_data_len,
+ const struct mcps802154_ops *ops)
+{
+ static atomic_t llhw_counter = ATOMIC_INIT(0);
+ int idx;
+ struct ieee802154_hw *hw;
+ struct mcps802154_local *local;
+ size_t priv_size;
+
+ if (WARN_ON(!ops || !ops->start || !ops->stop || !ops->tx_frame ||
+ !ops->rx_enable || !ops->rx_disable || !ops->rx_get_frame ||
+ !ops->rx_get_error_frame))
+ return NULL;
+
+ priv_size = ALIGN(sizeof(*local), NETDEV_ALIGN) + priv_data_len;
+ hw = ieee802154_alloc_hw(priv_size, &mcps802154_ops);
+ if (!hw)
+ return NULL;
+
+ idx = atomic_inc_return(&llhw_counter);
+ if (idx < 0) {
+ /* Wrapped! */
+ atomic_dec(&llhw_counter);
+ ieee802154_free_hw(hw);
+ return NULL;
+ }
+
+ local = hw->priv;
+ local->hw = hw;
+ local->llhw.hw = hw;
+ local->llhw.priv = (char *)local + ALIGN(sizeof(*local), NETDEV_ALIGN);
+ local->ops = ops;
+ local->hw_idx = idx - 1;
+ init_waitqueue_head(&local->wq);
+ mutex_init(&local->fsm_lock);
+ INIT_WORK(&local->tx_work, mcps802154_tx_event);
+ mutex_lock(&local->fsm_lock);
+ mcps802154_ca_init(local);
+ mcps802154_fproc_init(local);
+ mutex_unlock(&local->fsm_lock);
+
+ return &local->llhw;
+}
+EXPORT_SYMBOL(mcps802154_alloc_llhw);
+
+void mcps802154_free_llhw(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&local->fsm_lock);
+ mcps802154_fproc_uninit(local);
+ mcps802154_ca_uninit(local);
+ mutex_unlock(&local->fsm_lock);
+ mutex_destroy(&local->fsm_lock);
+
+ WARN_ON(waitqueue_active(&local->wq));
+#ifndef __KERNEL__
+ destroy_waitqueue_head(&local->wq);
+#endif
+
+ ieee802154_free_hw(local->hw);
+}
+EXPORT_SYMBOL(mcps802154_free_llhw);
+
+int mcps802154_register_llhw(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ int r;
+
+ llhw->hw->flags |= IEEE802154_HW_FRAME_RETRIES;
+
+ r = ieee802154_register_hw(local->hw);
+ if (r)
+ return r;
+
+ local->pib.mac_extended_addr = local->hw->phy->perm_extended_addr;
+ local->pib.mac_pan_id = IEEE802154_PAN_ID_BROADCAST;
+ local->pib.mac_promiscuous = false;
+ local->pib.mac_short_addr = IEEE802154_ADDR_SHORT_BROADCAST;
+ local->pib.phy_current_channel.page = local->hw->phy->current_page;
+ local->pib.phy_current_channel.channel =
+ local->hw->phy->current_channel;
+ local->pib.phy_current_channel.preamble_code =
+ llhw->current_preamble_code;
+ local->mac_pan_coord = false;
+
+ mutex_lock(&registered_llhw_lock);
+ list_add(&local->registered_entry, &registered_llhw);
+ mutex_unlock(&registered_llhw_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(mcps802154_register_llhw);
+
+void mcps802154_unregister_llhw(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mutex_lock(&registered_llhw_lock);
+ list_del(&local->registered_entry);
+ mutex_unlock(&registered_llhw_lock);
+ ieee802154_unregister_hw(local->hw);
+ mutex_lock(&local->fsm_lock);
+ mcps802154_ca_close(local);
+ mutex_unlock(&local->fsm_lock);
+}
+EXPORT_SYMBOL(mcps802154_unregister_llhw);
+
+__le64 mcps802154_get_extended_addr(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return local->pib.mac_extended_addr;
+}
+EXPORT_SYMBOL(mcps802154_get_extended_addr);
+
+__le16 mcps802154_get_pan_id(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return local->pib.mac_pan_id;
+}
+EXPORT_SYMBOL(mcps802154_get_pan_id);
+
+__le16 mcps802154_get_short_addr(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return local->pib.mac_short_addr;
+}
+EXPORT_SYMBOL(mcps802154_get_short_addr);
+
+const struct mcps802154_channel *
+mcps802154_get_current_channel(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return &local->pib.phy_current_channel;
+}
+EXPORT_SYMBOL(mcps802154_get_current_channel);
+
+int mcps802154_get_current_timestamp_dtu(struct mcps802154_llhw *llhw,
+ u32 *timestamp_dtu)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ if (!local->started)
+ return -ENETDOWN;
+
+ return llhw_get_current_timestamp_dtu(local, timestamp_dtu);
+}
+EXPORT_SYMBOL(mcps802154_get_current_timestamp_dtu);
+
+u64 mcps802154_tx_timestamp_dtu_to_rmarker_rctu(
+ struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params,
+ const struct mcps802154_channel *channel_params, int ant_set_id)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_tx_timestamp_dtu_to_rmarker_rctu(local, tx_timestamp_dtu,
+ hrp_uwb_params,
+ channel_params,
+ ant_set_id);
+}
+EXPORT_SYMBOL(mcps802154_tx_timestamp_dtu_to_rmarker_rctu);
+
+s64 mcps802154_difference_timestamp_rctu(struct mcps802154_llhw *llhw,
+ u64 timestamp_a_rctu,
+ u64 timestamp_b_rctu)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_difference_timestamp_rctu(local, timestamp_a_rctu,
+ timestamp_b_rctu);
+}
+EXPORT_SYMBOL(mcps802154_difference_timestamp_rctu);
+
+int mcps802154_rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx,
+ struct mcps802154_rx_measurement_info *info)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_rx_get_measurement(local, rx_ctx, info);
+}
+EXPORT_SYMBOL(mcps802154_rx_get_measurement);
+
+int mcps802154_compute_frame_duration_dtu(struct mcps802154_llhw *llhw,
+ int payload_bytes)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_compute_frame_duration_dtu(local, payload_bytes);
+}
+EXPORT_SYMBOL(mcps802154_compute_frame_duration_dtu);
+
+int mcps802154_vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id,
+ u32 subcmd, void *data, size_t data_len)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_vendor_cmd(local, vendor_id, subcmd, data, data_len);
+}
+EXPORT_SYMBOL(mcps802154_vendor_cmd);
+
+int mcps802154_check_hrp_uwb_params(
+ struct mcps802154_llhw *llhw,
+ const struct mcps802154_hrp_uwb_params *hrp_uwb_params)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ return llhw_check_hrp_uwb_params(local, hrp_uwb_params);
+}
+EXPORT_SYMBOL(mcps802154_check_hrp_uwb_params);
+
+struct mcps802154_local *mcps802154_get_first_by_idx(int hw_idx)
+{
+ struct mcps802154_local *result = NULL, *local;
+
+ ASSERT_RTNL();
+
+ mutex_lock(&registered_llhw_lock);
+ list_for_each_entry (local, &registered_llhw, registered_entry) {
+ if (local->hw_idx >= hw_idx) {
+ result = local;
+ break;
+ }
+ }
+ mutex_unlock(&registered_llhw_lock);
+
+ return result;
+}
+
+int __init mcps802154_init(void)
+{
+ int r;
+
+ r = mcps802154_nl_init();
+ if (r)
+ return r;
+ r = mcps802154_default_region_init();
+ WARN_RETURN(r);
+ r = mcps802154_idle_region_init();
+ WARN_RETURN(r);
+ r = mcps802154_endless_scheduler_init();
+ WARN_ON(r);
+ r = mcps802154_default_scheduler_init();
+ WARN_ON(r);
+ r = mcps802154_on_demand_scheduler_init();
+ WARN_ON(r);
+
+ return r;
+}
+
+void __exit mcps802154_exit(void)
+{
+ mcps802154_on_demand_scheduler_exit();
+ mcps802154_default_scheduler_exit();
+ mcps802154_endless_scheduler_exit();
+ mcps802154_idle_region_exit();
+ mcps802154_default_region_exit();
+ mcps802154_nl_exit();
+}
+
+module_init(mcps802154_init);
+module_exit(mcps802154_exit);
+
+MODULE_DESCRIPTION("IEEE 802.15.4 MAC common part sublayer");
+MODULE_AUTHOR("Nicolas Schodet <nicolas.schodet@qorvo.com>");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
diff --git a/mac/mcps_skb_frag.c b/mac/mcps_skb_frag.c
new file mode 100644
index 0000000..6657612
--- /dev/null
+++ b/mac/mcps_skb_frag.c
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/skbuff.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+
+int mcps_skb_frags_len(struct sk_buff *skb)
+{
+ /* No fragmentation on Linux. */
+ return 0;
+}
+EXPORT_SYMBOL(mcps_skb_frags_len);
diff --git a/mac/nfcc_coex_access.c b/mac/nfcc_coex_access.c
new file mode 100644
index 0000000..60d6bae
--- /dev/null
+++ b/mac/nfcc_coex_access.c
@@ -0,0 +1,179 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "nfcc_coex_access.h"
+#include "nfcc_coex_session.h"
+#include "nfcc_coex_region.h"
+#include "nfcc_coex_trace.h"
+#include "llhw-ops.h"
+#include "mcps802154_qorvo.h"
+
+#include <linux/string.h>
+#include <linux/ieee802154.h>
+#include <net/mcps802154_frame.h>
+#include <net/vendor_cmd.h>
+
+#include "warn_return.h"
+
+static void nfcc_coex_access_done(struct mcps802154_access *access, bool error)
+{
+ struct nfcc_coex_local *local = access_to_local(access);
+ struct nfcc_coex_session *session = &local->session;
+
+ /* Stop on error because the next timestamps is unknown.
+ * Stop in V2, because the vendor stop is not supported by NFC. */
+ if ((error || (session->state == NFCC_COEX_STATE_STOPPING &&
+ session->params.version == 2)) &&
+ !session->get_access_info.watchdog_timeout) {
+ const struct llhw_vendor_cmd_nfcc_coex_get_access_info stop = {
+ .stop = true,
+ };
+
+ local->session.get_access_info = stop;
+ }
+
+ if (session->get_access_info.stop ||
+ session->get_access_info.watchdog_timeout)
+ nfcc_coex_set_state(local, NFCC_COEX_STATE_IDLE);
+
+ nfcc_coex_report(local);
+}
+
+static int nfcc_coex_handle(struct mcps802154_access *access)
+{
+ struct nfcc_coex_local *local = access_to_local(access);
+ struct nfcc_coex_session *session = &local->session;
+ struct llhw_vendor_cmd_nfcc_coex_handle_access handle_access = {};
+
+ handle_access.start = session->first_access;
+ handle_access.timestamp_dtu = access->timestamp_dtu;
+ handle_access.duration_dtu = access->duration_dtu;
+ handle_access.chan = session->params.channel_number;
+ handle_access.version = session->params.version;
+
+ if (session->state == NFCC_COEX_STATE_STOPPING &&
+ session->params.version == 3) {
+ /* Stop processing : stop the nfcc coex */
+ if (local->session.first_access) {
+ struct mcps802154_region_demand *rd =
+ &session->region_demand;
+ struct llhw_vendor_cmd_nfcc_coex_stop stop = {
+ .timestamp_dtu = rd->timestamp_dtu,
+ .duration_dtu = rd->max_duration_dtu,
+ .version = session->params.version,
+ };
+ return mcps802154_vendor_cmd(
+ local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_NFCC_COEX_STOP, &stop,
+ sizeof(stop));
+ } else
+ return mcps802154_vendor_cmd(
+ local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_NFCC_COEX_STOP, NULL, 0);
+ }
+
+ return mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS,
+ &handle_access, sizeof(handle_access));
+}
+
+static int nfcc_coex_tx_done(struct mcps802154_access *access)
+{
+ struct nfcc_coex_local *local = access_to_local(access);
+ struct nfcc_coex_session *session = &local->session;
+ struct llhw_vendor_cmd_nfcc_coex_get_access_info *get_access_info =
+ &session->get_access_info;
+ struct mcps802154_region_demand *rd = &session->region_demand;
+ int r;
+
+ session->first_access = false;
+
+ r = mcps802154_vendor_cmd(
+ local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION,
+ get_access_info, sizeof(*get_access_info));
+ if (r)
+ return r;
+
+ /* Update region demand for next access. */
+ rd->timestamp_dtu = get_access_info->next_timestamp_dtu;
+ rd->max_duration_dtu = get_access_info->next_duration_dtu;
+ /* Request end of current access. */
+ return 1;
+}
+
+static int nfcc_coex_broken(struct mcps802154_access *access)
+{
+ struct nfcc_coex_local *local = access_to_local(access);
+ const struct llhw_vendor_cmd_nfcc_coex_get_access_info
+ watchdog_timeout = {
+ .watchdog_timeout = true,
+ };
+
+ local->session.get_access_info = watchdog_timeout;
+ /* Request end of current access. */
+ return -ETIME;
+}
+
+struct mcps802154_access_vendor_ops nfcc_coex_ops = {
+ .common = {
+ .access_done = nfcc_coex_access_done,
+ },
+ .handle = nfcc_coex_handle,
+ .tx_done = nfcc_coex_tx_done,
+ .broken = nfcc_coex_broken,
+};
+
+static struct mcps802154_access *
+nfcc_coex_access_controller(struct nfcc_coex_local *local,
+ struct nfcc_coex_session *session)
+{
+ struct mcps802154_access *access = &local->access;
+ const struct mcps802154_region_demand *rd = &session->region_demand;
+
+ access->method = MCPS802154_ACCESS_METHOD_VENDOR;
+ access->vendor_ops = &nfcc_coex_ops;
+ access->duration_dtu = rd->max_duration_dtu;
+ access->timestamp_dtu = rd->timestamp_dtu;
+ access->n_frames = 0;
+ access->frames = NULL;
+
+ return access;
+}
+
+struct mcps802154_access *nfcc_coex_get_access(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ int next_in_region_dtu,
+ int region_duration_dtu)
+{
+ struct nfcc_coex_local *local = region_to_local(region);
+ struct nfcc_coex_session *session = &local->session;
+
+ if (session->state == NFCC_COEX_STATE_STARTED ||
+ session->state == NFCC_COEX_STATE_STOPPING) {
+ nfcc_coex_session_update(local, session, next_timestamp_dtu,
+ region_duration_dtu);
+ return nfcc_coex_access_controller(local, session);
+ }
+ return NULL;
+}
diff --git a/mac/nfcc_coex_access.h b/mac/nfcc_coex_access.h
new file mode 100644
index 0000000..de4d103
--- /dev/null
+++ b/mac/nfcc_coex_access.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NFCC_COEX_ACCESS_H
+#define NFCC_COEX_ACCESS_H
+
+#include <net/mcps802154_schedule.h>
+
+/**
+ * nfcc_coex_get_access() - NFCC coexitence compute and return access.
+ * @region: Region context.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ * @next_in_region_dtu: Region start from the start of the access opportunity.
+ * @region_duration_dtu: Region duration, or 0 for endless region.
+ *
+ * Return: A pointer to current access or NULL if none.
+ */
+struct mcps802154_access *nfcc_coex_get_access(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ int next_in_region_dtu,
+ int region_duration_dtu);
+
+#endif /* NFCC_COEX_ACCESS_H */
diff --git a/mac/nfcc_coex_region.c b/mac/nfcc_coex_region.c
new file mode 100644
index 0000000..1e26b64
--- /dev/null
+++ b/mac/nfcc_coex_region.c
@@ -0,0 +1,197 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/math64.h>
+#include <linux/printk.h>
+
+#include <linux/netdevice.h>
+
+#include <net/mcps802154_schedule.h>
+#include "net/nfcc_coex_region_nl.h"
+
+#include "nfcc_coex_region.h"
+#include "nfcc_coex_region_call.h"
+#include "nfcc_coex_access.h"
+#include "nfcc_coex_session.h"
+#include "nfcc_coex_trace.h"
+
+static struct mcps802154_region_ops nfcc_coex_region_ops;
+
+static struct mcps802154_region *nfcc_coex_open(struct mcps802154_llhw *llhw)
+{
+ struct nfcc_coex_local *local;
+
+ local = kzalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return NULL;
+
+ local->llhw = llhw;
+ local->region.ops = &nfcc_coex_region_ops;
+ local->session.state = NFCC_COEX_STATE_IDLE;
+
+ return &local->region;
+}
+
+static void nfcc_coex_close(struct mcps802154_region *region)
+{
+ struct nfcc_coex_local *local = region_to_local(region);
+
+ kfree(local);
+}
+
+static void nfcc_coex_notify_stop(struct mcps802154_region *region)
+{
+ struct nfcc_coex_local *local = region_to_local(region);
+
+ trace_region_nfcc_coex_notify_stop(local);
+}
+
+static int nfcc_coex_call(struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *attrs,
+ const struct genl_info *info)
+{
+ struct nfcc_coex_local *local = region_to_local(region);
+
+ trace_region_nfcc_coex_call(local, call_id);
+ switch (call_id) {
+ case NFCC_COEX_CALL_CCC_SESSION_START:
+ case NFCC_COEX_CALL_CCC_SESSION_STOP:
+ return nfcc_coex_session_control(local, call_id, attrs, info);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int nfcc_coex_get_demand(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *demand)
+{
+ struct nfcc_coex_local *local = region_to_local(region);
+ const struct nfcc_coex_session *session = &local->session;
+ const struct mcps802154_region_demand *rd = &session->region_demand;
+
+ demand->max_duration_dtu = 0;
+
+ switch (session->state) {
+ case NFCC_COEX_STATE_STARTED:
+ if (is_before_dtu(rd->timestamp_dtu, next_timestamp_dtu))
+ demand->timestamp_dtu = next_timestamp_dtu;
+ else
+ demand->timestamp_dtu = rd->timestamp_dtu;
+ return 1;
+
+ case NFCC_COEX_STATE_STOPPING:
+ if (session->first_access) {
+ if (is_before_dtu(rd->timestamp_dtu,
+ next_timestamp_dtu))
+ demand->timestamp_dtu = next_timestamp_dtu;
+ else
+ demand->timestamp_dtu = rd->timestamp_dtu;
+ } else
+ demand->timestamp_dtu = next_timestamp_dtu;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+void nfcc_coex_set_state(struct nfcc_coex_local *local,
+ enum nfcc_coex_state new_state)
+{
+ struct nfcc_coex_session *session = &local->session;
+
+ trace_region_nfcc_coex_set_state(local, new_state);
+ session->state = new_state;
+}
+
+void nfcc_coex_report(struct nfcc_coex_local *local)
+{
+ struct nfcc_coex_session *session = &local->session;
+ const struct llhw_vendor_cmd_nfcc_coex_get_access_info *get_access_info =
+ &session->get_access_info;
+ struct sk_buff *msg;
+ int r;
+
+ trace_region_nfcc_coex_report(local, get_access_info);
+ msg = mcps802154_region_event_alloc_skb(
+ local->llhw, &local->region,
+ NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION, session->event_portid,
+ NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+#define P(attr, type, value) \
+ do { \
+ if (nla_put_##type(msg, NFCC_COEX_CALL_ATTR_CCC_##attr, \
+ value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+
+ P(WATCHDOG_TIMEOUT, u8, get_access_info->watchdog_timeout);
+ P(STOPPED, u8, get_access_info->stop);
+#undef P
+
+ r = mcps802154_region_event(local->llhw, msg);
+ if (r == -ECONNREFUSED)
+ /* TODO stop. */
+ ;
+ return;
+
+nla_put_failure:
+ trace_region_nfcc_coex_report_nla_put_failure(local);
+ kfree_skb(msg);
+}
+
+static struct mcps802154_region_ops nfcc_coex_region_ops = {
+ /* clang-format off */
+ .owner = THIS_MODULE,
+ .name = "nfcc_coex",
+ .open = nfcc_coex_open,
+ .close = nfcc_coex_close,
+ .notify_stop = nfcc_coex_notify_stop,
+ .call = nfcc_coex_call,
+ .get_access = nfcc_coex_get_access,
+ .get_demand = nfcc_coex_get_demand,
+ /* clang-format on */
+};
+
+int __init nfcc_coex_region_init(void)
+{
+ return mcps802154_region_register(&nfcc_coex_region_ops);
+}
+
+void __exit nfcc_coex_region_exit(void)
+{
+ mcps802154_region_unregister(&nfcc_coex_region_ops);
+}
+
+module_init(nfcc_coex_region_init);
+module_exit(nfcc_coex_region_exit);
+
+MODULE_DESCRIPTION("Vendor Region for IEEE 802.15.4 MCPS");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
diff --git a/mac/nfcc_coex_region.h b/mac/nfcc_coex_region.h
new file mode 100644
index 0000000..b9828df
--- /dev/null
+++ b/mac/nfcc_coex_region.h
@@ -0,0 +1,79 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_NFCC_COEX_REGION_H
+#define NET_NFCC_COEX_REGION_H
+
+#include <net/mcps802154_schedule.h>
+#include <net/vendor_cmd.h>
+#include "nfcc_coex_session.h"
+
+/**
+ * struct nfcc_coex_local - Local context.
+ */
+struct nfcc_coex_local {
+ /**
+ * @region: Region instance returned to MCPS.
+ */
+ struct mcps802154_region region;
+ /**
+ * @llhw: Low-level device pointer.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @access: Access returned to MCPS.
+ */
+ struct mcps802154_access access;
+ /**
+ * @session: Unique session on the NFCC controller.
+ */
+ struct nfcc_coex_session session;
+};
+
+static inline struct nfcc_coex_local *
+region_to_local(struct mcps802154_region *region)
+{
+ return container_of(region, struct nfcc_coex_local, region);
+}
+
+static inline struct nfcc_coex_local *
+access_to_local(struct mcps802154_access *access)
+{
+ return container_of(access, struct nfcc_coex_local, access);
+}
+
+/**
+ * nfcc_coex_set_state() - Set the new state.
+ * @local: NFCC coex context.
+ * @new_state: New nfcc_coex state.
+ */
+void nfcc_coex_set_state(struct nfcc_coex_local *local,
+ enum nfcc_coex_state new_state);
+
+/**
+ * nfcc_coex_report() - Send notification to upper layer.
+ * @local: Local nfcc coex context.
+ */
+void nfcc_coex_report(struct nfcc_coex_local *local);
+
+#endif /* NET_NFCC_COEX_REGION_H */
diff --git a/mac/nfcc_coex_region_call.c b/mac/nfcc_coex_region_call.c
new file mode 100644
index 0000000..bf8fb9b
--- /dev/null
+++ b/mac/nfcc_coex_region_call.c
@@ -0,0 +1,221 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/limits.h>
+
+#include <net/nfcc_coex_region_nl.h>
+#include <net/mcps802154_frame.h>
+
+#include "mcps802154_qorvo.h"
+#include "nfcc_coex_session.h"
+#include "nfcc_coex_region_call.h"
+#include "nfcc_coex_trace.h"
+
+static const struct nla_policy nfcc_coex_call_nla_policy[NFCC_COEX_CALL_ATTR_MAX +
+ 1] = {
+ [NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy nfcc_coex_session_param_nla_policy
+ [NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1] = {
+ [NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS] = { .type = NLA_U64 },
+ [NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 },
+ [NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION] =
+ NLA_POLICY_RANGE(NLA_U8, 2, 3),
+ };
+
+/**
+ * nfcc_coex_session_set_parameters() - Set NFCC coexistence session parameters.
+ * @local: NFCC coexistence context.
+ * @params: Nested attribute containing session parameters.
+ * @info: Request information.
+ * @now_ns: current kernel time.
+ *
+ * Return: 0 or error.
+ */
+static int nfcc_coex_session_set_parameters(struct nfcc_coex_local *local,
+ const struct nlattr *params,
+ const struct genl_info *info,
+ u64 now_ns)
+{
+ struct nlattr *attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1];
+ struct nfcc_coex_session *session = &local->session;
+ struct nfcc_coex_session_params *p = &session->params;
+ /* Maximum dtu duration is INT32_MAX. */
+ const s64 max_time0_ns =
+ (S32_MAX * NS_PER_SECOND) / local->llhw->dtu_freq_hz;
+ int r;
+
+ if (!params)
+ return -EINVAL;
+
+ r = nla_parse_nested(attrs, NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX,
+ params, nfcc_coex_session_param_nla_policy,
+ info->extack);
+ if (r)
+ return r;
+
+#define P(attr, member, type, conv) \
+ do { \
+ type x; \
+ if (attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]) { \
+ x = nla_get_##type( \
+ attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]); \
+ p->member = conv; \
+ } \
+ } while (0)
+
+ P(TIME0_NS, time0_ns, u64, x);
+ P(CHANNEL_NUMBER, channel_number, u8, x);
+ P(VERSION, version, u8, x);
+
+#undef P
+
+ if (!attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS]) {
+ p->time0_ns = (NS_PER_SECOND * local->llhw->anticip_dtu) /
+ local->llhw->dtu_freq_hz + now_ns;
+ }
+
+ if ((s64)(p->time0_ns - now_ns) > max_time0_ns)
+ return -ERANGE;
+ return 0;
+}
+
+/**
+ * nfcc_coex_session_start() - Start NFCC coex session.
+ * @local: NFCC coexistence context.
+ * @info: Request information.
+ * @now_ns: current kernel time.
+ *
+ * Return: 0 or error.
+ */
+static int nfcc_coex_session_start(struct nfcc_coex_local *local,
+ const struct genl_info *info, u64 now_ns)
+{
+ struct nfcc_coex_session *session = &local->session;
+ const struct nfcc_coex_session_params *p = &session->params;
+ u32 now_dtu;
+ s64 diff_ns;
+ s64 diff_dtu;
+ int r;
+
+ WARN_ON(session->state == NFCC_COEX_STATE_STARTED);
+
+ trace_region_nfcc_coex_session_start(local, p);
+ r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu);
+ if (r)
+ return r;
+
+ diff_ns = p->time0_ns - now_ns;
+ diff_dtu = div64_s64(diff_ns * local->llhw->dtu_freq_hz, NS_PER_SECOND);
+ /* If the requested start date is in the past, start immediately */
+ if (diff_dtu < local->llhw->anticip_dtu) {
+ pr_warn("dw3000: Computed start date is in the past, scheduling"
+ " to anticip_dtu instead");
+ diff_dtu = local->llhw->anticip_dtu;
+ }
+
+ session->region_demand.timestamp_dtu = now_dtu + diff_dtu;
+ session->region_demand.max_duration_dtu = 0;
+ session->event_portid = info->snd_portid;
+ session->first_access = true;
+ nfcc_coex_set_state(local, NFCC_COEX_STATE_STARTED);
+
+ mcps802154_reschedule(local->llhw);
+ return 0;
+}
+
+/**
+ * nfcc_coex_session_start_all() - Start all for a NFCC coex session.
+ * @local: NFCC coexistence context.
+ * @params: Call parameters.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+static int nfcc_coex_session_start_all(struct nfcc_coex_local *local,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ struct nlattr *attrs[NFCC_COEX_CALL_ATTR_MAX + 1];
+ int r;
+ u64 now_ns;
+
+ if (!params)
+ return -EINVAL;
+
+ r = nla_parse_nested(attrs, NFCC_COEX_CALL_ATTR_MAX, params,
+ nfcc_coex_call_nla_policy, info->extack);
+ if (r)
+ return r;
+
+ if (local->session.state == NFCC_COEX_STATE_STARTED)
+ return -EBUSY;
+
+ nfcc_coex_session_init(local);
+ now_ns = ktime_to_ns(ktime_get_boottime());
+ r = nfcc_coex_session_set_parameters(
+ local, attrs[NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS], info,
+ now_ns);
+ if (r)
+ return r;
+
+ r = nfcc_coex_session_start(local, info, now_ns);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+/**
+ * nfcc_coex_session_stop() - Stop NFCC coex session.
+ * @local: NFCC coexistence context.
+ *
+ * Return: 0 or error.
+ */
+static int nfcc_coex_session_stop(struct nfcc_coex_local *local)
+{
+ struct nfcc_coex_session *session = &local->session;
+
+ trace_region_nfcc_coex_session_stop(local);
+ if (session->state == NFCC_COEX_STATE_STARTED) {
+ nfcc_coex_set_state(local, NFCC_COEX_STATE_STOPPING);
+ mcps802154_schedule_invalidate(local->llhw);
+ }
+ return 0;
+}
+
+int nfcc_coex_session_control(struct nfcc_coex_local *local, u32 call_id,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ switch (call_id) {
+ case NFCC_COEX_CALL_CCC_SESSION_START:
+ return nfcc_coex_session_start_all(local, params, info);
+ default:
+ case NFCC_COEX_CALL_CCC_SESSION_STOP:
+ return nfcc_coex_session_stop(local);
+ }
+}
diff --git a/mac/nfcc_coex_region_call.h b/mac/nfcc_coex_region_call.h
new file mode 100644
index 0000000..7720d75
--- /dev/null
+++ b/mac/nfcc_coex_region_call.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef NET_MCPS802154_NFCC_COEX_REGION_CALL_H
+#define NET_MCPS802154_NFCC_COEX_REGION_CALL_H
+
+#include "nfcc_coex_region.h"
+
+/**
+ * nfcc_coex_session_control() - Control nfcc_coex session.
+ * @local: Vendor context.
+ * @call_id: Identifier of the nfcc_coex procedure.
+ * @params: Call parameters.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+int nfcc_coex_session_control(struct nfcc_coex_local *local, u32 call_id,
+ const struct nlattr *params,
+ const struct genl_info *info);
+
+#endif /* NET_MCPS802154_NFCC_COEX_REGION_CALL_H */
diff --git a/mac/nfcc_coex_session.c b/mac/nfcc_coex_session.c
new file mode 100644
index 0000000..6247365
--- /dev/null
+++ b/mac/nfcc_coex_session.c
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include "nfcc_coex_session.h"
+#include "nfcc_coex_region.h"
+#include "nfcc_coex_trace.h"
+
+void nfcc_coex_session_init(struct nfcc_coex_local *local)
+{
+ struct nfcc_coex_session_params *p = &local->session.params;
+
+ memset(p, 0, sizeof(*p));
+
+ /* Default protocol version is V2 */
+ p->version = 3;
+}
+
+void nfcc_coex_session_update(struct nfcc_coex_local *local,
+ struct nfcc_coex_session *session,
+ u32 next_timestamp_dtu, int region_duration_dtu)
+{
+ struct mcps802154_region_demand *rd = &session->region_demand;
+
+ if (is_before_dtu(rd->timestamp_dtu, next_timestamp_dtu)) {
+ int shift_dtu = next_timestamp_dtu - rd->timestamp_dtu;
+
+ /* Date is late. */
+ trace_region_nfcc_coex_session_update_late(local, shift_dtu, 0);
+ rd->timestamp_dtu = next_timestamp_dtu;
+ rd->max_duration_dtu = 0;
+ }
+}
diff --git a/mac/nfcc_coex_session.h b/mac/nfcc_coex_session.h
new file mode 100644
index 0000000..49de0b3
--- /dev/null
+++ b/mac/nfcc_coex_session.h
@@ -0,0 +1,117 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_NFCC_COEX_SESSION_H
+#define NET_MCPS802154_NFCC_COEX_SESSION_H
+
+#include <linux/kernel.h>
+#include <net/mcps802154_schedule.h>
+#include <net/vendor_cmd.h>
+
+#define NS_PER_SECOND 1000000000ull
+
+/**
+ * struct nfcc_coex_session_params - Session parameters.
+ */
+struct nfcc_coex_session_params {
+ /**
+ * @time0_ns: Timestamp in nanoseconds in the ``CLOCK_MONOTONIC`` time.
+ */
+ u64 time0_ns;
+ /**
+ * @channel_number: Channel to use for the session, 5 or 9.
+ */
+ u8 channel_number;
+ /**
+ * @version: Protocol version to use.
+ */
+ u8 version;
+};
+
+/**
+ * enum nfcc_coex_state - State of the unique session.
+ * @NFCC_COEX_STATE_IDLE:
+ * Session is not used by access right now.
+ * @NFCC_COEX_STATE_STARTED:
+ * Session is started.
+ * @NFCC_COEX_STATE_STOPPING:
+ * Session is currently used for the last access.
+ */
+enum nfcc_coex_state {
+ NFCC_COEX_STATE_IDLE,
+ NFCC_COEX_STATE_STARTED,
+ NFCC_COEX_STATE_STOPPING,
+};
+
+/**
+ * struct nfcc_coex_session - Session information.
+ */
+struct nfcc_coex_session {
+ /**
+ * @params: Session parameters, mostly read only while the session is
+ * active.
+ */
+ struct nfcc_coex_session_params params;
+ /**
+ * @event_portid: Port identifier to use for notifications.
+ */
+ u32 event_portid;
+ /**
+ * @get_access_info: Next access feedback get through a vendor command.
+ */
+ struct llhw_vendor_cmd_nfcc_coex_get_access_info get_access_info;
+ /**
+ * @region_demand: Region access demand which contains start and duration.
+ */
+ struct mcps802154_region_demand region_demand;
+ /**
+ * @first_access: True on the first access.
+ */
+ bool first_access;
+ /**
+ * @state: State of the unique session.
+ */
+ enum nfcc_coex_state state;
+};
+
+/* Forward declaration. */
+struct nfcc_coex_local;
+
+/**
+ * nfcc_coex_session_init() - Initialize session parameters to default value.
+ * @local: NFCC coex context.
+ */
+void nfcc_coex_session_init(struct nfcc_coex_local *local);
+
+/**
+ * nfcc_coex_session_update() - Update session timestamps.
+ * @local: NFCC coex context.
+ * @session: Session context.
+ * @next_timestamp_dtu: Next start access opportunity.
+ * @region_duration_dtu: Region duration, or 0 for endless region.
+ */
+void nfcc_coex_session_update(struct nfcc_coex_local *local,
+ struct nfcc_coex_session *session,
+ u32 next_timestamp_dtu, int region_duration_dtu);
+
+#endif /* NET_MCPS802154_NFCC_COEX_SESSION_H */
diff --git a/mac/nfcc_coex_trace.h b/mac/nfcc_coex_trace.h
new file mode 100644
index 0000000..3d24f81
--- /dev/null
+++ b/mac/nfcc_coex_trace.h
@@ -0,0 +1,208 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mcps802154_region_nfcc_coex
+
+#if !defined(NFCC_COEX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define NFCC_COEX_TRACE_H
+
+#include <linux/tracepoint.h>
+#include <net/nfcc_coex_region_nl.h>
+#include "mcps802154_i.h"
+#include "nfcc_coex_region.h"
+#include "nfcc_coex_session.h"
+
+/* clang-format off */
+#define nfcc_coex_call_name(name) \
+ { \
+ NFCC_COEX_CALL_##name, #name \
+ }
+#define NFCC_COEX_CALL_SYMBOLS \
+ nfcc_coex_call_name(CCC_SESSION_START), \
+ nfcc_coex_call_name(CCC_SESSION_STOP), \
+ nfcc_coex_call_name(CCC_SESSION_NOTIFICATION)
+TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_START);
+TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_STOP);
+TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION);
+
+#define nfcc_coex_state_name(name) \
+ { \
+ NFCC_COEX_STATE_##name, #name \
+ }
+#define NFCC_COEX_STATE_SYMBOLS \
+ nfcc_coex_state_name(IDLE), \
+ nfcc_coex_state_name(STARTED), \
+ nfcc_coex_state_name(STOPPING)
+TRACE_DEFINE_ENUM(NFCC_COEX_STATE_IDLE);
+TRACE_DEFINE_ENUM(NFCC_COEX_STATE_STARTED);
+TRACE_DEFINE_ENUM(NFCC_COEX_STATE_STOPPING);
+
+#define NFCC_COEX_LOCAL_ENTRY __field(enum nfcc_coex_state, state)
+#define NFCC_COEX_LOCAL_ASSIGN __entry->state = local->session.state
+#define NFCC_COEX_LOCAL_PR_FMT "state=%s"
+#define NFCC_COEX_LOCAL_PR_ARG \
+ __print_symbolic(__entry->state, NFCC_COEX_STATE_SYMBOLS)
+
+DECLARE_EVENT_CLASS(
+ local_only_evt_nfcc,
+ TP_PROTO(const struct nfcc_coex_local *local),
+ TP_ARGS(local),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT, NFCC_COEX_LOCAL_PR_ARG)
+);
+
+TRACE_EVENT(
+ region_nfcc_coex_session_start,
+ TP_PROTO(const struct nfcc_coex_local *local,
+ const struct nfcc_coex_session_params *p),
+ TP_ARGS(local, p),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ __field(u64, time0_ns)
+ __field(u8, channel_number)
+ __field(u8, version)
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ __entry->time0_ns = p->time0_ns;
+ __entry->channel_number = p->channel_number;
+ __entry->version = p->version;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT " time0_ns=%llu channel_number=%d version=%d",
+ NFCC_COEX_LOCAL_PR_ARG, __entry->time0_ns,
+ __entry->channel_number, __entry->version)
+);
+
+DEFINE_EVENT(
+ local_only_evt_nfcc, region_nfcc_coex_session_stop,
+ TP_PROTO(const struct nfcc_coex_local *local),
+ TP_ARGS(local)
+);
+
+DEFINE_EVENT(
+ local_only_evt_nfcc, region_nfcc_coex_notify_stop,
+ TP_PROTO(const struct nfcc_coex_local *local),
+ TP_ARGS(local)
+);
+
+TRACE_EVENT(
+ region_nfcc_coex_call,
+ TP_PROTO(const struct nfcc_coex_local *local,
+ enum nfcc_coex_call call_id),
+ TP_ARGS(local, call_id),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ __field(enum nfcc_coex_call, call_id)
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ __entry->call_id = call_id;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT " call_id=%s",
+ NFCC_COEX_LOCAL_PR_ARG,
+ __print_symbolic(__entry->call_id, NFCC_COEX_CALL_SYMBOLS))
+);
+
+TRACE_EVENT(
+ region_nfcc_coex_session_update_late,
+ TP_PROTO(const struct nfcc_coex_local *local,
+ int shift_dtu, int new_duration_dtu),
+ TP_ARGS(local, shift_dtu, new_duration_dtu),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ __field(int, shift_dtu)
+ __field(int, new_duration_dtu)
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ __entry->shift_dtu = shift_dtu;
+ __entry->new_duration_dtu = new_duration_dtu;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT " shift_dtu=0x%08x "
+ "new_duration_dtu=0x%08x",
+ NFCC_COEX_LOCAL_PR_ARG,
+ __entry->shift_dtu,
+ __entry->new_duration_dtu)
+);
+
+TRACE_EVENT(
+ region_nfcc_coex_set_state,
+ TP_PROTO(const struct nfcc_coex_local *local,
+ enum nfcc_coex_state new_state),
+ TP_ARGS(local, new_state),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ __field(enum nfcc_coex_state, new_state)
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ __entry->new_state = new_state;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT " new_state=%s",
+ NFCC_COEX_LOCAL_PR_ARG,
+ __print_symbolic(__entry->new_state,
+ NFCC_COEX_STATE_SYMBOLS))
+);
+
+TRACE_EVENT(
+ region_nfcc_coex_report,
+ TP_PROTO(const struct nfcc_coex_local *local,
+ const struct llhw_vendor_cmd_nfcc_coex_get_access_info *info),
+ TP_ARGS(local, info),
+ TP_STRUCT__entry(
+ NFCC_COEX_LOCAL_ENTRY
+ __field(bool, watchdog_timeout)
+ __field(bool, stop)
+ ),
+ TP_fast_assign(
+ NFCC_COEX_LOCAL_ASSIGN;
+ __entry->watchdog_timeout = info->watchdog_timeout;
+ __entry->stop = info->stop;
+ ),
+ TP_printk(NFCC_COEX_LOCAL_PR_FMT " watchdog_timeout=%s stop=%s",
+ NFCC_COEX_LOCAL_PR_ARG,
+ __entry->watchdog_timeout ? "true": "false",
+ __entry->stop ? "true": "false")
+);
+
+DEFINE_EVENT(
+ local_only_evt_nfcc, region_nfcc_coex_report_nla_put_failure,
+ TP_PROTO(const struct nfcc_coex_local *local),
+ TP_ARGS(local)
+);
+
+/* clang-format on */
+
+#endif /* !NFCC_COEX_TRACE_H || TRACE_HEADER_MULTI_READ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE nfcc_coex_trace
+#include <trace/define_trace.h>
diff --git a/mac/on_demand_scheduler.c b/mac/on_demand_scheduler.c
new file mode 100644
index 0000000..852901e
--- /dev/null
+++ b/mac/on_demand_scheduler.c
@@ -0,0 +1,287 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo.
+ * Please contact Qorvo to inquire about licensing terms.
+ *
+ * 802.15.4 mac common part sublayer, on_demand scheduler.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <net/mcps802154_schedule.h>
+
+#include "mcps802154_i.h"
+#include "on_demand_scheduler.h"
+#include "warn_return.h"
+
+/**
+ * struct mcps802154_on_demand_local - local context for on demand scheduler.
+ */
+struct mcps802154_on_demand_local {
+ /**
+ * @scheduler: Common scheduler context.
+ */
+ struct mcps802154_scheduler scheduler;
+ /**
+ * @llhw: Low layer hardware attached.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @idle_region: Idle region to delay start of region selected.
+ */
+ struct mcps802154_region *idle_region;
+};
+
+static inline struct mcps802154_on_demand_local *
+scheduler_to_plocal(const struct mcps802154_scheduler *scheduler)
+{
+ return container_of(scheduler, struct mcps802154_on_demand_local,
+ scheduler);
+}
+
+static struct mcps802154_scheduler *
+mcps802154_on_demand_scheduler_open(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_on_demand_local *plocal;
+
+ plocal = kmalloc(sizeof(*plocal), GFP_KERNEL);
+ if (!plocal)
+ goto open_failure;
+
+ plocal->idle_region = mcps802154_region_open(llhw, "idle", NULL, NULL);
+ if (!plocal->idle_region) {
+ goto open_failure;
+ }
+
+ plocal->llhw = llhw;
+ plocal->scheduler.n_regions = 0;
+ return &plocal->scheduler;
+
+open_failure:
+ kfree(plocal);
+ return NULL;
+}
+
+static void
+mcps802154_on_demand_scheduler_close(struct mcps802154_scheduler *scheduler)
+{
+ struct mcps802154_on_demand_local *plocal =
+ scheduler_to_plocal(scheduler);
+
+ kfree(plocal->idle_region);
+ kfree(plocal);
+}
+
+static int mcps802154_on_demand_scheduler_get_next_region(
+ struct mcps802154_on_demand_local *plocal, struct list_head *regions,
+ const struct mcps802154_region *first_region, u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *next_demand,
+ struct mcps802154_region **next_region)
+{
+ struct mcps802154_region *region;
+ int max_duration_dtu = 0;
+ int r;
+
+ *next_region = NULL;
+ list_for_each_entry (region, regions, ca_entry) {
+ struct mcps802154_region_demand candidate = {};
+
+ if (first_region && region == first_region)
+ continue;
+
+ r = mcps802154_region_get_demand(
+ plocal->llhw, region, next_timestamp_dtu, &candidate);
+ switch (r) {
+ case 0:
+ /* The region doesn't have a demand. */
+ continue;
+ case 1:
+ /* The region have a demand. */
+ break;
+ default:
+ return r;
+ }
+
+ /* Reduce duration of candidate region with less priority. */
+ if (max_duration_dtu &&
+ (!candidate.max_duration_dtu ||
+ is_before_dtu(next_timestamp_dtu + max_duration_dtu,
+ candidate.timestamp_dtu +
+ candidate.max_duration_dtu)))
+ candidate.max_duration_dtu = max_duration_dtu -
+ candidate.timestamp_dtu +
+ next_timestamp_dtu;
+
+ /* Arbitrate between regions. */
+ if (!*next_region ||
+ is_before_dtu(candidate.timestamp_dtu,
+ next_demand->timestamp_dtu)) {
+ *next_region = region;
+ *next_demand = candidate;
+ /* Is there some time remaining for a region with
+ * less priority? */
+ if (!is_before_dtu(next_timestamp_dtu,
+ next_demand->timestamp_dtu))
+ break;
+ else
+ max_duration_dtu = next_demand->timestamp_dtu -
+ next_timestamp_dtu;
+ }
+ }
+
+ return *next_region ? 1 : 0;
+}
+
+static int mcps802154_on_demand_scheduler_update_schedule(
+ struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_schedule_update *schedule_update,
+ u32 next_timestamp_dtu)
+{
+ struct mcps802154_on_demand_local *plocal =
+ scheduler_to_plocal(scheduler);
+ struct list_head *regions;
+ struct mcps802154_region_demand next_demand;
+ struct mcps802154_region *next_region = NULL;
+ u32 start_in_schedule_dtu;
+ int r;
+
+ mcps802154_schedule_get_regions(plocal->llhw, &regions);
+ r = mcps802154_on_demand_scheduler_get_next_region(
+ plocal, regions, NULL, next_timestamp_dtu, &next_demand,
+ &next_region);
+ if (r < 0)
+ return r;
+
+ if (!next_region)
+ return -ENOENT;
+
+ start_in_schedule_dtu = next_demand.timestamp_dtu - next_timestamp_dtu;
+
+ r = mcps802154_schedule_set_start(schedule_update, next_timestamp_dtu);
+ WARN_RETURN(r);
+
+ r = mcps802154_schedule_recycle(schedule_update, 0,
+ MCPS802154_DURATION_NO_CHANGE);
+ /* Can not fail, only possible error is invalid parameters. */
+ WARN_RETURN(r);
+
+ if (next_demand.max_duration_dtu)
+ next_demand.max_duration_dtu += start_in_schedule_dtu;
+ start_in_schedule_dtu = 0;
+
+ if (start_in_schedule_dtu)
+ /* Don't give the access to the region too early.
+ * And provide advantages:
+ * - to have a region inserted with a CA invalidate schedule.
+ * - Reduce latency with TX frame prepared close to region
+ * start date. */
+ r = mcps802154_schedule_add_region(schedule_update,
+ plocal->idle_region, 0,
+ start_in_schedule_dtu,
+ false);
+ r = mcps802154_schedule_add_region(schedule_update, next_region,
+ start_in_schedule_dtu,
+ next_demand.max_duration_dtu, true);
+
+ return r;
+}
+
+static int mcps802154_on_demand_scheduler_get_next_demands(
+ struct mcps802154_scheduler *scheduler,
+ const struct mcps802154_region *region, u32 timestamp_dtu,
+ int duration_dtu, int delta_dtu,
+ struct mcps802154_region_demand *demands)
+{
+ struct mcps802154_on_demand_local *plocal =
+ scheduler_to_plocal(scheduler);
+ struct list_head *regions;
+ bool is_demands_set = false;
+ u32 next_timestamp_dtu = timestamp_dtu;
+ int r;
+
+ mcps802154_schedule_get_regions(plocal->llhw, &regions);
+
+ while (true) {
+ struct mcps802154_region_demand next_demand;
+ struct mcps802154_region *next_region = NULL;
+
+ r = mcps802154_on_demand_scheduler_get_next_region(
+ plocal, regions, region, next_timestamp_dtu,
+ &next_demand, &next_region);
+ if (r < 0)
+ return r;
+ if (!r || !next_demand.max_duration_dtu ||
+ !is_before_dtu(next_demand.timestamp_dtu,
+ timestamp_dtu + duration_dtu))
+ break;
+ if (!is_demands_set) {
+ *demands = next_demand;
+ is_demands_set = true;
+ } else if (!is_before_dtu(demands->timestamp_dtu +
+ demands->max_duration_dtu +
+ delta_dtu,
+ next_demand.timestamp_dtu)) {
+ demands->max_duration_dtu =
+ next_demand.timestamp_dtu +
+ next_demand.max_duration_dtu -
+ demands->timestamp_dtu;
+ } else {
+ break;
+ }
+
+ if (!is_before_dtu(demands->timestamp_dtu +
+ demands->max_duration_dtu,
+ timestamp_dtu + duration_dtu))
+ break;
+
+ next_timestamp_dtu =
+ demands->timestamp_dtu + demands->max_duration_dtu;
+ }
+ return is_demands_set ? 1 : 0;
+}
+
+static struct mcps802154_scheduler_ops
+ mcps802154_on_demand_scheduler_scheduler = {
+ .owner = THIS_MODULE,
+ .name = "on_demand",
+ .open = mcps802154_on_demand_scheduler_open,
+ .close = mcps802154_on_demand_scheduler_close,
+ .set_parameters = NULL, /* No scheduler parameters for now. */
+ .update_schedule =
+ mcps802154_on_demand_scheduler_update_schedule,
+ .get_next_demands =
+ mcps802154_on_demand_scheduler_get_next_demands,
+ };
+
+int __init mcps802154_on_demand_scheduler_init(void)
+{
+ return mcps802154_scheduler_register(
+ &mcps802154_on_demand_scheduler_scheduler);
+}
+
+void __exit mcps802154_on_demand_scheduler_exit(void)
+{
+ mcps802154_scheduler_unregister(
+ &mcps802154_on_demand_scheduler_scheduler);
+}
diff --git a/mac/on_demand_scheduler.h b/mac/on_demand_scheduler.h
new file mode 100644
index 0000000..1568568
--- /dev/null
+++ b/mac/on_demand_scheduler.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo.
+ * Please contact Qorvo to inquire about licensing terms.
+ *
+ * 802.15.4 mac common part sublayer, default data path regions definitions.
+ */
+#ifndef NET_MCPS802154_ON_DEMAND_SCHEDULER_H
+#define NET_MCPS802154_ON_DEMAND_SCHEDULER_H
+
+int mcps802154_on_demand_scheduler_init(void);
+void mcps802154_on_demand_scheduler_exit(void);
+
+#endif /* NET_MCPS802154_ON_DEMAND_SCHEDULER_H */
diff --git a/mac/ops.c b/mac/ops.c
new file mode 100644
index 0000000..bfeb3a2
--- /dev/null
+++ b/mac/ops.c
@@ -0,0 +1,215 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <net/rtnetlink.h>
+#include <linux/jiffies.h>
+
+#include "mcps802154_i.h"
+#include "llhw-ops.h"
+
+#define DW3000_MAX_STOP_WAIT 10000
+
+static int mcps802154_start(struct ieee802154_hw *hw)
+{
+ struct mcps802154_local *local = hw->priv;
+ int r;
+
+ ASSERT_RTNL();
+ WARN_ON(local->started);
+
+ mutex_lock(&local->fsm_lock);
+ local->pib.phy_current_channel.page = local->hw->phy->current_page;
+ local->pib.phy_current_channel.channel =
+ local->hw->phy->current_channel;
+ local->pib.phy_current_channel.preamble_code =
+ local->llhw.current_preamble_code;
+ r = llhw_set_channel(local, local->pib.phy_current_channel.page,
+ local->pib.phy_current_channel.channel,
+ local->pib.phy_current_channel.preamble_code);
+ if (!r)
+ r = mcps802154_ca_start(local);
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+static void mcps802154_stop(struct ieee802154_hw *hw)
+{
+ struct mcps802154_local *local = hw->priv;
+ int rc;
+
+ ASSERT_RTNL();
+ WARN_ON(!local->started);
+
+ mutex_lock(&local->fsm_lock);
+ mcps802154_ca_stop(local);
+ mutex_unlock(&local->fsm_lock);
+
+ rc = wait_event_timeout(local->wq, !local->started, msecs_to_jiffies(DW3000_MAX_STOP_WAIT));
+ if (!rc)
+ pr_err("%s timeout elapsed, event !local->started = false\n", __func__);
+}
+
+static int mcps802154_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
+{
+ struct mcps802154_local *local = hw->priv;
+ int r;
+
+ if (unlikely(!local->started)) {
+ r = -EPIPE;
+ } else {
+ r = mcps802154_ca_xmit_skb(local, skb);
+ }
+
+ schedule_work(&local->tx_work);
+ return r;
+}
+
+static int mcps802154_ed(struct ieee802154_hw *hw, u8 *level)
+{
+ /* Not supported for the moment (and not used in Linux SoftMAC). */
+ return -EOPNOTSUPP;
+}
+
+static int mcps802154_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel)
+{
+ struct mcps802154_local *local = hw->priv;
+ int r;
+
+ if (!local->ops->set_channel)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&local->fsm_lock);
+ r = llhw_set_channel(local, page, channel,
+ local->pib.phy_current_channel.preamble_code);
+ if (!r) {
+ local->pib.phy_current_channel.page = page;
+ local->pib.phy_current_channel.channel = channel;
+ }
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+static int mcps802154_set_hw_addr_filt(struct ieee802154_hw *hw,
+ struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed)
+{
+ struct mcps802154_local *local = hw->priv;
+ int r;
+
+ if (!local->ops->set_hw_addr_filt)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&local->fsm_lock);
+ if (local->started) {
+ r = -EBUSY;
+ } else {
+ r = llhw_set_hw_addr_filt(local, filt, changed);
+ if (!r) {
+ if (changed & IEEE802154_AFILT_PANID_CHANGED)
+ local->pib.mac_pan_id = filt->pan_id;
+ if (changed & IEEE802154_AFILT_SADDR_CHANGED)
+ local->pib.mac_short_addr = filt->short_addr;
+ if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED)
+ local->pib.mac_extended_addr = filt->ieee_addr;
+ if (changed & IEEE802154_AFILT_PANC_CHANGED)
+ local->mac_pan_coord = filt->pan_coord;
+ }
+ }
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+static int mcps802154_set_frame_retries(struct ieee802154_hw *hw, s8 retries)
+{
+ struct mcps802154_local *local = hw->priv;
+
+ if (retries < 0 || retries > 7)
+ return -EINVAL;
+ local->pib.mac_max_frame_retries = retries;
+
+ return 0;
+}
+
+static int mcps802154_set_promiscuous_mode(struct ieee802154_hw *hw, bool on)
+{
+ struct mcps802154_local *local = hw->priv;
+ int r;
+
+ if (!local->ops->set_promiscuous_mode)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&local->fsm_lock);
+ r = llhw_set_promiscuous_mode(local, on);
+ if (!r)
+ local->pib.mac_promiscuous = on;
+ mutex_unlock(&local->fsm_lock);
+
+ return r;
+}
+
+#ifdef CONFIG_HAVE_IEEE802154_SCANNING
+static void mcps802154_sw_scan_start(struct ieee802154_hw *hw, __le64 addr)
+{
+ struct mcps802154_local *local = hw->priv;
+
+ if (!local->ops->set_scanning_mode)
+ return;
+
+ mutex_lock(&local->fsm_lock);
+ llhw_set_scanning_mode(local, true);
+ mutex_unlock(&local->fsm_lock);
+}
+
+static void mcps802154_sw_scan_complete(struct ieee802154_hw *hw)
+{
+ struct mcps802154_local *local = hw->priv;
+
+ if (!local->ops->set_scanning_mode)
+ return;
+
+ mutex_lock(&local->fsm_lock);
+ llhw_set_scanning_mode(local, false);
+ mutex_unlock(&local->fsm_lock);
+}
+#endif
+
+const struct ieee802154_ops mcps802154_ops = {
+ .owner = THIS_MODULE,
+ .start = mcps802154_start,
+ .stop = mcps802154_stop,
+ .xmit_async = mcps802154_xmit_async,
+ .ed = mcps802154_ed,
+ .set_channel = mcps802154_set_channel,
+ .set_hw_addr_filt = mcps802154_set_hw_addr_filt,
+ .set_frame_retries = mcps802154_set_frame_retries,
+ .set_promiscuous_mode = mcps802154_set_promiscuous_mode,
+#ifdef CONFIG_HAVE_IEEE802154_SCANNING
+ .sw_scan_start = mcps802154_sw_scan_start,
+ .sw_scan_complete = mcps802154_sw_scan_complete,
+#endif
+};
diff --git a/mac/pctt_access.c b/mac/pctt_access.c
new file mode 100644
index 0000000..ae7b994
--- /dev/null
+++ b/mac/pctt_access.c
@@ -0,0 +1,786 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/math64.h>
+#include "pctt_access.h"
+#include "pctt_region.h"
+#include "pctt_region_call.h"
+#include "llhw-ops.h"
+#include "mcps802154_qorvo.h"
+
+#include <net/mcps802154_frame.h>
+#include <net/pctt_region_nl.h>
+#include <net/pctt_region_params.h>
+#include <asm/unaligned.h>
+
+#include "warn_return.h"
+
+#define PCTT_STS_FOM_THRESHOLD 153
+/* The FC-PHY shall have a block timing tolerance of +/-100 ppm as
+ specified in IEEE Std 802.15.4z-2020, subclause 6.9.7.2. */
+#define PCTT_MARGIN_PPM 200
+
+static inline int pctt_rx_margin(int duration)
+{
+ return duration / (1000000 / PCTT_MARGIN_PPM);
+}
+
+static void
+pctt_set_sts_params(struct mcps802154_sts_params *sts_params,
+ const struct pctt_session_params *session_params)
+{
+ const u8 key[AES_KEYSIZE_128] = { 0x14, 0x14, 0x86, 0x74, 0xd1, 0xd3,
+ 0x36, 0xaa, 0xf8, 0x60, 0x50, 0xa8,
+ 0x14, 0xeb, 0x22, 0xf };
+ u8 *iv = sts_params->v;
+ u8 seg_len = session_params->sts_length == PCTT_STS_LENGTH_128 ?
+ 128 :
+ session_params->sts_length == PCTT_STS_LENGTH_32 ?
+ 32 :
+ 64;
+
+ sts_params->n_segs = session_params->number_of_sts_segments;
+ sts_params->seg_len = seg_len;
+ sts_params->sp2_tx_gap_4chips = 0;
+ sts_params->sp2_rx_gap_4chips[0] = 0;
+ sts_params->sp2_rx_gap_4chips[1] = 0;
+ sts_params->sp2_rx_gap_4chips[2] = 0;
+ sts_params->sp2_rx_gap_4chips[3] = 0;
+
+ /* Overflow is not propagated to the next IV */
+ put_unaligned_be32(0x362eeb34u, &iv[0]);
+ put_unaligned_be32(0xc44fa8fbu + session_params->sts_index,
+ &iv[sizeof(u32)]);
+ put_unaligned_be64(0xd37ec3ca1f9a3de4ull, &iv[sizeof(u64)]);
+ memcpy(sts_params->key, key, AES_KEYSIZE_128);
+}
+
+static void pctt_randomize_psdu(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+ struct pctt_session_params *p = &session->params;
+
+ if (p->randomize_psdu && session->first_access) {
+ const int A = 1664525, B = 1013904223;
+ /* First byte of data is used as seed. */
+ u32 state = p->data_payload[0];
+ u8 *buf = p->data_payload;
+ int size = p->data_payload_len;
+ int i;
+ for (i = 0; i < size; i++) {
+ state = A * state + B;
+ buf[i] = state >> 8;
+ }
+ }
+}
+
+/**
+ * pctt_access_setup_frame() - Fill an access frame from a PCTT slot.
+ * @local: PCTT context.
+ * @slot: Corresponding slot.
+ * @frame_dtu: Frame transmission or reception date.
+ * @frame: Access frame.
+ * @sts_params: Where to store STS parameters.
+ */
+static void pctt_access_setup_frame(struct pctt_local *local,
+ const struct pctt_slot *slot,
+ const u32 frame_dtu,
+ struct mcps802154_access_frame *frame,
+ struct mcps802154_sts_params *sts_params)
+{
+ struct mcps802154_sts_params *sts_params_for_access = NULL;
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ bool is_rframe = p->rframe_config != PCTT_RFRAME_CONFIG_SP0;
+
+ if (is_rframe) {
+ pctt_set_sts_params(sts_params, p);
+ sts_params_for_access = sts_params;
+ }
+
+ if (slot->is_tx) {
+ u8 flags = slot->is_immediate ?
+ 0 :
+ MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU;
+
+ if (is_rframe) {
+ if (p->rframe_config == PCTT_RFRAME_CONFIG_SP3)
+ flags |= MCPS802154_TX_FRAME_CONFIG_SP3;
+ else if (p->rframe_config == PCTT_RFRAME_CONFIG_SP2)
+ flags |= MCPS802154_TX_FRAME_CONFIG_SP2;
+ else
+ flags |= MCPS802154_TX_FRAME_CONFIG_SP1;
+ }
+ *frame = (struct mcps802154_access_frame){
+ .is_tx = true,
+ .tx_frame_config = {
+ .timestamp_dtu = frame_dtu,
+ .flags = flags,
+ .ant_set_id = p->tx_antenna_selection,
+ },
+ .sts_params = sts_params_for_access,
+ };
+ } else {
+ u8 flags = slot->is_immediate ?
+ 0 :
+ MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU;
+ u16 request = MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU |
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU |
+ MCPS802154_RX_FRAME_INFO_RSSI;
+
+ if (is_rframe) {
+ request |= MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM;
+ flags |= MCPS802154_RX_FRAME_CONFIG_RANGING;
+ if (session->cmd_id == PCTT_ID_ATTR_SS_TWR) {
+ flags |=
+ MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA;
+ request |=
+ MCPS802154_RX_FRAME_INFO_RANGING_PDOA;
+ }
+ if (p->rframe_config == PCTT_RFRAME_CONFIG_SP3)
+ flags |= MCPS802154_RX_FRAME_CONFIG_SP3;
+ else if (p->rframe_config == PCTT_RFRAME_CONFIG_SP2)
+ flags |= MCPS802154_RX_FRAME_CONFIG_SP2;
+ else
+ flags |= MCPS802154_RX_FRAME_CONFIG_SP1;
+ }
+ *frame = (struct mcps802154_access_frame){
+ .is_tx = false,
+ .rx = {
+ .frame_config = {
+ .timestamp_dtu = frame_dtu,
+ .flags = flags,
+ .timeout_dtu = slot->timeout_dtu,
+ .ant_set_id = p->rx_antenna_selection,
+ },
+ .frame_info_flags_request = request,
+ },
+ .sts_params = sts_params_for_access,
+ };
+ }
+}
+
+static struct sk_buff *pctt_tx_get_frame(struct mcps802154_access *access,
+ int frame_idx)
+{
+ struct pctt_local *local = access_to_local(access);
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct sk_buff *skb = NULL;
+
+ if (p->data_payload_len) {
+ skb = mcps802154_frame_alloc(local->llhw, p->data_payload_len,
+ GFP_KERNEL);
+ if (skb)
+ skb_put_data(skb, p->data_payload, p->data_payload_len);
+ }
+
+ return skb;
+}
+
+static void pctt_tx_return(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ enum mcps802154_access_tx_return_reason reason)
+{
+ struct pctt_local *local = access_to_local(access);
+
+ kfree_skb(skb);
+
+ /* Error on TX. */
+ if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL)
+ local->results.status = PCTT_STATUS_RANGING_TX_FAILED;
+ else
+ local->frames_remaining_nb--;
+}
+
+static bool pctt_rx_sts_good(const struct mcps802154_rx_frame_info *i)
+{
+ int idx;
+ if (!(i->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM))
+ return false;
+ for (idx = 0; idx < MCPS802154_STS_N_SEGS_MAX; idx++) {
+ if (i->ranging_sts_fom[idx] < PCTT_STS_FOM_THRESHOLD)
+ return false;
+ }
+ return true;
+}
+
+static void pctt_rx_frame_ss_twr(struct pctt_local *local,
+ const struct mcps802154_rx_frame_info *info)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct pctt_test_ss_twr_results *ss_twr = &local->results.tests.ss_twr;
+ bool is_responser = p->device_role == PCTT_DEVICE_ROLE_RESPONDER;
+
+ if (info) {
+ if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU)) {
+ local->results.status =
+ PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED;
+ return;
+ }
+ if (!pctt_rx_sts_good(info)) {
+ local->results.status =
+ PCTT_STATUS_RANGING_RX_PHY_STS_FAILED;
+ return;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA) {
+ struct mcps802154_rx_measurement_info info = {};
+ int r;
+
+ info.flags |= MCPS802154_RX_MEASUREMENTS_AOAS;
+ r = mcps802154_rx_get_measurement(local->llhw, NULL,
+ &info);
+ if (!r &&
+ info.flags & MCPS802154_RX_MEASUREMENTS_AOAS &&
+ info.n_aoas) {
+ /* TODO: Find which aoas index to use */
+ ss_twr->pdoa_azimuth_deg_q7 =
+ map_rad_q11_to_deg_q7(
+ info.aoas[0].pdoa_rad_q11);
+ ss_twr->aoa_azimuth_deg_q7 =
+ map_rad_q11_to_deg_q7(
+ info.aoas[0].aoa_rad_q11);
+ }
+ }
+
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI) {
+ ss_twr->rssi = info->rssi;
+ }
+
+ ss_twr->rx_timestamps_rctu = info->timestamp_rctu;
+
+ /* Resync timestamps. */
+ if (is_responser) {
+ struct mcps802154_access *access = &local->access;
+ struct mcps802154_access_frame *frame =
+ &local->frames[1];
+ struct mcps802154_sts_params *sts_params =
+ &local->sts_params[1];
+ struct pctt_slot *s = &local->slots[1];
+ u32 frame_dtu;
+
+ if (!(info->flags &
+ MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) {
+ local->results.status =
+ PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED;
+ return;
+ }
+
+ frame_dtu = info->timestamp_dtu + p->slot_duration_dtu;
+
+ ss_twr->tx_timestamps_rctu =
+ mcps802154_tx_timestamp_dtu_to_rmarker_rctu(
+ local->llhw, frame_dtu,
+ access->hrp_uwb_params, access->channel,
+ p->tx_antenna_selection);
+
+ pctt_access_setup_frame(local, s, frame_dtu, frame,
+ sts_params);
+
+ frame_dtu += p->slot_duration_dtu;
+
+ access->timestamp_dtu = info->timestamp_dtu;
+ access->duration_dtu = frame_dtu - info->timestamp_dtu;
+ access->n_frames = 2;
+ }
+
+ /* Tround time of Initiator or Treply time of Responder. */
+ ss_twr->measurement_rctu = mcps802154_difference_timestamp_rctu(
+ local->llhw,
+ is_responser ? ss_twr->tx_timestamps_rctu :
+ ss_twr->rx_timestamps_rctu,
+ is_responser ? ss_twr->rx_timestamps_rctu :
+ ss_twr->tx_timestamps_rctu);
+ }
+}
+
+static void pctt_rx_frame_per_rx(struct pctt_local *local, struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info,
+ enum mcps802154_rx_error_type error)
+{
+ struct pctt_test_per_rx_results *per_rx = &local->results.tests.per_rx;
+
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ bool has_sts = p->rframe_config != PCTT_RFRAME_CONFIG_SP0;
+
+ if (info) {
+ if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU) {
+ session->next_timestamp_dtu = info->timestamp_dtu;
+ session->first_rx_synchronized = true;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI) {
+ if (!per_rx->rssi || per_rx->rssi > info->rssi)
+ per_rx->rssi = info->rssi;
+ }
+ }
+ session->next_timestamp_dtu +=
+ p->gap_duration_dtu - pctt_rx_margin(p->gap_duration_dtu);
+
+ switch (error) {
+ case MCPS802154_RX_ERROR_NONE:
+ case MCPS802154_RX_ERROR_BAD_CKSUM:
+ per_rx->acq_detect++;
+ per_rx->sync_cir_ready++;
+ per_rx->sfd_found++;
+ per_rx->eof++;
+ if (has_sts && pctt_rx_sts_good(info))
+ per_rx->sts_found++;
+ if (skb && (skb->len != p->data_payload_len ||
+ (!p->randomize_psdu &&
+ memcmp(skb->data, p->data_payload, skb->len))))
+ per_rx->psdu_bit_error++;
+ if (error == MCPS802154_RX_ERROR_BAD_CKSUM)
+ per_rx->psdu_dec_error++;
+ break;
+ case MCPS802154_RX_ERROR_SFD_TIMEOUT:
+ per_rx->acq_detect++;
+ per_rx->sfd_fail++;
+ break;
+ case MCPS802154_RX_ERROR_UNCORRECTABLE:
+ case MCPS802154_RX_ERROR_FILTERED:
+ case MCPS802154_RX_ERROR_HPDWARN:
+ case MCPS802154_RX_ERROR_OTHER:
+ case MCPS802154_RX_ERROR_PHR_DECODE:
+ per_rx->acq_detect++;
+ per_rx->sync_cir_ready++;
+ per_rx->sfd_found++;
+ if (error == MCPS802154_RX_ERROR_OTHER) {
+ per_rx->psdu_dec_error++;
+ } else if (error == MCPS802154_RX_ERROR_PHR_DECODE) {
+ per_rx->phr_dec_error++;
+ }
+ break;
+ case MCPS802154_RX_ERROR_TIMEOUT:
+ per_rx->rx_fail++;
+ break;
+ }
+}
+
+static void pctt_rx_frame_rx(struct pctt_local *local, struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info)
+{
+ struct pctt_test_rx_results *rx = &local->results.tests.rx;
+
+ if (skb) {
+ int len = min(skb->len, (unsigned int)PCTT_PAYLOAD_MAX_LEN);
+
+ rx->psdu_data_len = len;
+ memcpy(rx->psdu_data, skb->data, len);
+ }
+
+ if (info) {
+ if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU) {
+ /* 8ns unit for both stats. */
+ rx->rx_done_ts_int = (info->timestamp_rctu >> 32) &
+ 0xfffffffe;
+ rx->rx_done_ts_frac = info->timestamp_rctu & 0xffff;
+ } else {
+ local->results.status =
+ PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED;
+ }
+ if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI)
+ rx->rssi = info->rssi;
+ } else
+ local->results.status = PCTT_STATUS_RANGING_RX_TIMEOUT;
+}
+
+static void pctt_rx_frame(struct mcps802154_access *access, int frame_idx,
+ struct sk_buff *skb,
+ const struct mcps802154_rx_frame_info *info,
+ enum mcps802154_rx_error_type error)
+{
+ struct pctt_local *local = access_to_local(access);
+ struct pctt_session *session = &local->session;
+ struct llhw_vendor_cmd_pctt_get_frame_info frame_info = {};
+
+ local->frames_remaining_nb--;
+
+ if (error == MCPS802154_RX_ERROR_BAD_CKSUM) {
+ struct mcps802154_access_frame *frame =
+ &access->frames[frame_idx];
+ int r;
+
+ frame_info.info.flags = frame->rx.frame_info_flags_request;
+ r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO,
+ &frame_info, sizeof(frame_info));
+ if (!r) {
+ skb = frame_info.skb;
+ info = &frame_info.info;
+ }
+ }
+
+ if (session->cmd_id == PCTT_ID_ATTR_SS_TWR)
+ pctt_rx_frame_ss_twr(local, info);
+ else if (session->cmd_id == PCTT_ID_ATTR_PER_RX)
+ pctt_rx_frame_per_rx(local, skb, info, error);
+ else
+ pctt_rx_frame_rx(local, skb, info);
+
+ switch (error) {
+ case MCPS802154_RX_ERROR_NONE:
+ break;
+ case MCPS802154_RX_ERROR_SFD_TIMEOUT:
+ case MCPS802154_RX_ERROR_TIMEOUT:
+ local->results.status = PCTT_STATUS_RANGING_RX_TIMEOUT;
+ break;
+ case MCPS802154_RX_ERROR_FILTERED:
+ case MCPS802154_RX_ERROR_BAD_CKSUM:
+ local->results.status = PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED;
+ break;
+ case MCPS802154_RX_ERROR_UNCORRECTABLE:
+ case MCPS802154_RX_ERROR_HPDWARN:
+ case MCPS802154_RX_ERROR_OTHER:
+ case MCPS802154_RX_ERROR_PHR_DECODE:
+ local->results.status = PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED;
+ break;
+ }
+ if (skb)
+ kfree_skb(skb);
+}
+
+static void pctt_access_done(struct mcps802154_access *access, bool error)
+{
+ struct pctt_local *local = access_to_local(access);
+ struct pctt_session *session = &local->session;
+ bool end_of_test = false;
+
+ if (session->cmd_id == PCTT_ID_ATTR_PER_RX)
+ local->results.tests.per_rx.attempts++;
+
+ if (error && !local->results.status)
+ local->results.status = PCTT_STATUS_RANGING_INTERNAL_ERROR;
+
+ switch (session->cmd_id) {
+ case PCTT_ID_ATTR_LOOPBACK:
+ case PCTT_ID_ATTR_SS_TWR:
+ case PCTT_ID_ATTR_RX:
+ end_of_test = true;
+ break;
+ default:
+ /* Only stop rx tests when all packets are received. */
+ if (local->results.status != PCTT_STATUS_RANGING_SUCCESS &&
+ session->cmd_id != PCTT_ID_ATTR_PER_RX)
+ end_of_test = true;
+ if (session->stop_request || !local->frames_remaining_nb)
+ end_of_test = true;
+ break;
+ }
+
+ if (end_of_test) {
+ int r;
+
+ r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_PCTT_SETUP_HW, NULL,
+ 0);
+
+ if (r)
+ local->results.status =
+ PCTT_STATUS_RANGING_INTERNAL_ERROR;
+ pctt_report(local);
+ pctt_session_set_state(local, PCTT_SESSION_STATE_IDLE);
+ session->test_on_going = false;
+ session->stop_request = false;
+ }
+}
+
+struct mcps802154_access_ops pctt_access_ops = {
+ .common = {
+ .access_done = pctt_access_done,
+ },
+ .tx_get_frame = pctt_tx_get_frame,
+ .tx_return = pctt_tx_return,
+ .rx_frame = pctt_rx_frame,
+};
+
+static struct mcps802154_access *
+pctt_get_access_periodic_tx(struct pctt_local *local, u32 next_timestamp_dtu)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct mcps802154_access *access = &local->access;
+ struct pctt_slot *s = local->slots;
+ u32 frame_dtu;
+ access->hrp_uwb_params = &session->hrp_uwb_params;
+
+ /* Unique frame in this access. */
+ *s = (struct pctt_slot){
+ .is_tx = true,
+ };
+ frame_dtu = session->first_access ? next_timestamp_dtu :
+ session->next_timestamp_dtu;
+
+ pctt_access_setup_frame(local, s, frame_dtu, &local->frames[0],
+ &local->sts_params[0]);
+
+ access->method = MCPS802154_ACCESS_METHOD_MULTI;
+ access->ops = &pctt_access_ops;
+ access->duration_dtu = 0;
+ access->n_frames = 1;
+ access->frames = local->frames;
+ access->timestamp_dtu = frame_dtu;
+ /* Compute next transmit date. */
+ session->next_timestamp_dtu = frame_dtu + p->gap_duration_dtu;
+
+ pctt_randomize_psdu(local);
+
+ return access;
+}
+
+static struct mcps802154_access *
+pctt_get_access_per_rx(struct pctt_local *local, u32 next_timestamp_dtu)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct mcps802154_access *access = &local->access;
+ struct pctt_slot *s = local->slots;
+ u32 frame_timestamp_dtu;
+ access->hrp_uwb_params = &session->hrp_uwb_params;
+
+ /* Unique frame in this access. */
+ *s = (struct pctt_slot){
+ .is_immediate = !session->first_rx_synchronized,
+ .timeout_dtu = session->first_rx_synchronized ?
+ 2 * pctt_rx_margin(p->gap_duration_dtu) :
+ -1,
+ };
+
+ frame_timestamp_dtu = session->first_rx_synchronized ?
+ session->next_timestamp_dtu :
+ next_timestamp_dtu;
+
+ pctt_access_setup_frame(local, s, frame_timestamp_dtu,
+ &local->frames[0], &local->sts_params[0]);
+
+ access->ops = &pctt_access_ops;
+ access->method = MCPS802154_ACCESS_METHOD_MULTI;
+ access->timestamp_dtu = frame_timestamp_dtu;
+ access->duration_dtu =
+ session->first_rx_synchronized ? p->gap_duration_dtu : 0;
+ access->n_frames = 1;
+ access->frames = local->frames;
+
+ return access;
+}
+
+static int pctt_handle_loopback(struct mcps802154_access *access)
+{
+ struct pctt_local *local = access_to_local(access);
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct llhw_vendor_cmd_pctt_handle_loopback handle_loopback = {};
+
+ handle_loopback.ant_set_id = p->tx_antenna_selection;
+ handle_loopback.data_payload = p->data_payload;
+ handle_loopback.data_payload_len = p->data_payload_len;
+
+ return mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK,
+ &handle_loopback, sizeof(handle_loopback));
+}
+
+static int pctt_tx_done_loopback(struct mcps802154_access *access)
+{
+ struct pctt_local *local = access_to_local(access);
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct llhw_vendor_cmd_pctt_get_loopback_info loopback_info = {};
+ int r;
+
+ r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO,
+ &loopback_info, sizeof(loopback_info));
+ if (r)
+ return r;
+
+ local->results.status = loopback_info.success ?
+ PCTT_STATUS_RANGING_SUCCESS :
+ PCTT_STATUS_RANGING_TX_FAILED;
+
+ local->results.tests.loopback.rssi = loopback_info.rssi;
+
+ if (loopback_info.success) {
+ /* Compare data received with the one sent. */
+ struct sk_buff *rx_skb = loopback_info.skb;
+ WARN_RETURN_ON(!rx_skb, -EFAULT);
+
+ if ((rx_skb->len != p->data_payload_len) ||
+ memcmp(rx_skb->data, p->data_payload, rx_skb->len)) {
+ local->results.status = PCTT_STATUS_RANGING_TX_FAILED;
+ }
+
+ /* Free rx_frame skb. */
+ kfree_skb(rx_skb);
+ }
+
+ local->results.tests.loopback.rx_ts_int =
+ (u32)(loopback_info.rx_timestamp_rctu >> PCTT_TIMESTAMP_SHIFT);
+ local->results.tests.loopback.rx_ts_frac =
+ (u16)(loopback_info.rx_timestamp_rctu &
+ (((unsigned long)1 << PCTT_TIMESTAMP_SHIFT) - 1));
+ local->results.tests.loopback.tx_ts_int =
+ (u32)(loopback_info.tx_timestamp_rctu >> PCTT_TIMESTAMP_SHIFT);
+ local->results.tests.loopback.tx_ts_frac =
+ (u16)(loopback_info.tx_timestamp_rctu &
+ (((unsigned long)1 << PCTT_TIMESTAMP_SHIFT) - 1));
+
+ /* Request end of current access. */
+ return 1;
+}
+
+struct mcps802154_access_vendor_ops pctt_access_ops_loopback = {
+ .common = {
+ .access_done = pctt_access_done,
+ },
+ .handle = pctt_handle_loopback,
+ .tx_done = pctt_tx_done_loopback,
+};
+
+static struct mcps802154_access *
+pctt_get_access_loopback(struct pctt_local *local, u32 next_timestamp_dtu)
+{
+ struct mcps802154_access *access = &local->access;
+
+ access->method = MCPS802154_ACCESS_METHOD_VENDOR;
+ access->vendor_ops = &pctt_access_ops_loopback;
+ access->duration_dtu = 0;
+ access->timestamp_dtu = next_timestamp_dtu;
+ access->n_frames = 0;
+ access->frames = NULL;
+ return access;
+}
+
+static struct mcps802154_access *
+pctt_get_access_ss_twr(struct pctt_local *local, u32 next_timestamp_dtu)
+{
+ struct mcps802154_access *access = &local->access;
+ struct pctt_session *session = &local->session;
+ struct pctt_slot *s = local->slots;
+ const struct pctt_session_params *p = &session->params;
+ const bool is_initiator = p->device_role == PCTT_DEVICE_ROLE_INITIATOR;
+ int nb_frames;
+ u32 frame_dtu;
+ int i;
+ access->hrp_uwb_params = &session->hrp_uwb_params;
+
+ /* First frames. */
+ *s = (struct pctt_slot){
+ .is_tx = is_initiator,
+ .timeout_dtu = -1,
+ };
+ s++;
+ /* Second frames. */
+ *s = (struct pctt_slot){
+ .is_tx = !is_initiator,
+ };
+ s++;
+
+ if (is_initiator) {
+ struct pctt_test_ss_twr_results *ss_twr =
+ &local->results.tests.ss_twr;
+
+ ss_twr->tx_timestamps_rctu =
+ mcps802154_tx_timestamp_dtu_to_rmarker_rctu(
+ local->llhw, next_timestamp_dtu,
+ access->hrp_uwb_params, access->channel,
+ p->tx_antenna_selection);
+ }
+
+ frame_dtu = next_timestamp_dtu;
+ nb_frames = is_initiator ? 2 : 1;
+
+ for (i = 0; i < nb_frames; i++) {
+ struct mcps802154_access_frame *frame = &local->frames[i];
+ struct mcps802154_sts_params *sts_params =
+ &local->sts_params[i];
+
+ s = &local->slots[i];
+ pctt_access_setup_frame(local, s, frame_dtu, frame, sts_params);
+ frame_dtu += p->slot_duration_dtu;
+ }
+
+ access->method = MCPS802154_ACCESS_METHOD_MULTI;
+ access->ops = &pctt_access_ops;
+ access->timestamp_dtu = next_timestamp_dtu;
+ access->frames = local->frames;
+ access->n_frames = nb_frames;
+ access->duration_dtu = frame_dtu - next_timestamp_dtu;
+
+ return access;
+}
+
+struct mcps802154_access *pctt_get_access(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ int next_in_region_dtu,
+ int region_duration_dtu)
+{
+ struct pctt_local *local = region_to_local(region);
+ struct pctt_session *session = &local->session;
+ struct mcps802154_access *access = NULL;
+
+ if (!session->test_on_going)
+ return NULL;
+
+ switch (session->cmd_id) {
+ case PCTT_ID_ATTR_PERIODIC_TX:
+ access = pctt_get_access_periodic_tx(local, next_timestamp_dtu);
+ break;
+ case PCTT_ID_ATTR_PER_RX:
+ case PCTT_ID_ATTR_RX:
+ access = pctt_get_access_per_rx(local, next_timestamp_dtu);
+ break;
+ case PCTT_ID_ATTR_LOOPBACK:
+ access = pctt_get_access_loopback(local, next_timestamp_dtu);
+ break;
+ case PCTT_ID_ATTR_SS_TWR:
+ access = pctt_get_access_ss_twr(local, next_timestamp_dtu);
+ break;
+ default: /* LCOV_EXCL_START */
+ /* Impossible to cover with unit test.
+ * The only way is a memory corruption on the cmd_id. */
+ break;
+ /* LCOV_EXCL_STOP */
+ }
+
+ WARN_ON(!access);
+ if (session->first_access) {
+ int r;
+
+ r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI,
+ LLHW_VENDOR_CMD_PCTT_SETUP_HW,
+ &session->setup_hw,
+ sizeof(session->setup_hw));
+ if (r) {
+ local->results.status =
+ PCTT_STATUS_RANGING_INTERNAL_ERROR;
+ pctt_report(local);
+ pctt_session_set_state(local, PCTT_SESSION_STATE_IDLE);
+ return NULL;
+ }
+ }
+
+ session->first_access = false;
+ return access;
+}
diff --git a/mac/pctt_access.h b/mac/pctt_access.h
new file mode 100644
index 0000000..d61f8bf
--- /dev/null
+++ b/mac/pctt_access.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef PCTT_ACCESS_H
+#define PCTT_ACCESS_H
+
+#include <net/mcps802154_schedule.h>
+
+/**
+ * pctt_get_access() - Get access for a given region at the given timestamp.
+ * @region: Region.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ * @next_in_region_dtu: Region start from the start of the access opportunity.
+ * @region_duration_dtu: Region duration, or 0 for endless region.
+ *
+ * Return: The access.
+ */
+struct mcps802154_access *pctt_get_access(struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ int next_in_region_dtu,
+ int region_duration_dtu);
+
+#endif /* PCTT_ACCESS_H */
diff --git a/mac/pctt_region.c b/mac/pctt_region.c
new file mode 100644
index 0000000..4c11f5b
--- /dev/null
+++ b/mac/pctt_region.c
@@ -0,0 +1,310 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+
+#include <net/pctt_region_nl.h>
+#include <net/mcps802154_frame.h>
+
+#include "pctt_trace.h"
+#include "pctt_region.h"
+#include "pctt_region_call.h"
+#include "pctt_access.h"
+#include "llhw-ops.h"
+
+static struct mcps802154_region_ops pctt_region_ops;
+
+static struct mcps802154_region *pctt_open(struct mcps802154_llhw *llhw)
+{
+ struct pctt_local *local;
+
+ local = kzalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return NULL;
+ local->llhw = llhw;
+ local->region.ops = &pctt_region_ops;
+ local->session.state = PCTT_SESSION_STATE_DEINIT;
+
+ return &local->region;
+}
+
+static void pctt_close(struct mcps802154_region *region)
+{
+ struct pctt_local *local = region_to_local(region);
+
+ kfree_sensitive(local);
+}
+
+static int pctt_call(struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *attrs, const struct genl_info *info)
+{
+ struct pctt_local *local = region_to_local(region);
+
+ switch (call_id) {
+ case PCTT_CALL_SESSION_INIT:
+ return pctt_session_init(local);
+ case PCTT_CALL_SESSION_DEINIT:
+ return pctt_session_deinit(local);
+ case PCTT_CALL_SESSION_GET_STATE:
+ return pctt_call_session_get_state(local);
+ case PCTT_CALL_SESSION_GET_PARAMS:
+ return pctt_call_session_get_params(local);
+ case PCTT_CALL_SESSION_SET_PARAMS:
+ case PCTT_CALL_SESSION_CMD:
+ return pctt_call_session_control(local, call_id, attrs, info);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pctt_report_periodic_tx(struct pctt_local *local,
+ struct sk_buff *msg)
+{
+ trace_region_pctt_report_periodic_tx(local->results.status);
+
+ if (nla_put_u8(msg, PCTT_RESULT_DATA_ATTR_STATUS,
+ local->results.status))
+ return -EMSGSIZE;
+ return 0;
+}
+
+static int pctt_report_per_rx(struct pctt_local *local, struct sk_buff *msg)
+{
+ const struct pctt_test_per_rx_results *per_rx =
+ &local->results.tests.per_rx;
+
+ trace_region_pctt_report_per_rx(local->results.status, per_rx);
+
+#define P(attr, type, value) \
+ do { \
+ if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \
+ value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+ P(STATUS, u8, PCTT_STATUS_RANGING_SUCCESS);
+ P(ATTEMPTS, u32, per_rx->attempts);
+ P(ACQ_DETECT, u32, per_rx->acq_detect);
+ P(ACQ_REJECT, u32, per_rx->acq_reject);
+ P(RX_FAIL, u32, per_rx->rx_fail);
+ P(SYNC_CIR_READY, u32, per_rx->sync_cir_ready);
+ P(SFD_FAIL, u32, per_rx->sfd_fail);
+ P(SFD_FOUND, u32, per_rx->sfd_found);
+ P(PHR_DEC_ERROR, u32, per_rx->phr_dec_error);
+ P(PHR_BIT_ERROR, u32, per_rx->phr_bit_error);
+ P(PSDU_DEC_ERROR, u32, per_rx->psdu_dec_error);
+ P(PSDU_BIT_ERROR, u32, per_rx->psdu_bit_error);
+ P(STS_FOUND, u32, per_rx->sts_found);
+ P(EOF, u32, per_rx->eof);
+ P(RSSI, u8, per_rx->rssi);
+#undef P
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int pctt_report_rx(struct pctt_local *local, struct sk_buff *msg)
+{
+ const struct pctt_test_rx_results *rx = &local->results.tests.rx;
+
+ trace_region_pctt_report_rx(local->results.status, rx);
+
+#define P(attr, type, value) \
+ do { \
+ if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \
+ value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+ P(STATUS, u8, local->results.status);
+ P(RX_DONE_TS_INT, u32, rx->rx_done_ts_int);
+ P(RX_DONE_TS_FRAC, u16, rx->rx_done_ts_frac);
+ P(AOA_AZIMUTH, s16, rx->aoa_azimuth);
+ P(AOA_ELEVATION, s16, rx->aoa_elevation);
+ P(TOA_GAP, u8, rx->toa_gap);
+ P(PHR, u16, rx->phr);
+ P(RSSI, u8, rx->rssi);
+ P(PSDU_DATA_LEN, u16, rx->psdu_data_len);
+ if (rx->psdu_data_len > 0 &&
+ nla_put(msg, PCTT_RESULT_DATA_ATTR_PSDU_DATA, rx->psdu_data_len,
+ rx->psdu_data))
+ goto nla_put_failure;
+#undef P
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int pctt_report_loopback(struct pctt_local *local, struct sk_buff *msg)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ trace_region_pctt_report_loopback(local->results.status);
+
+#define P(attr, type, value) \
+ do { \
+ if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \
+ value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+
+ P(STATUS, u8, local->results.status);
+ P(RSSI, u8, local->results.tests.loopback.rssi);
+ P(RX_TS_INT, u32, local->results.tests.loopback.rx_ts_int);
+ P(RX_TS_FRAC, u16, local->results.tests.loopback.rx_ts_frac);
+ P(TX_TS_INT, u32, local->results.tests.loopback.tx_ts_int);
+ P(TX_TS_FRAC, u16, local->results.tests.loopback.tx_ts_frac);
+
+ /* If test succeeded, return data that was sent (and received) as
+ * PSDU payload. */
+ if (!local->results.status) {
+ P(PSDU_DATA_LEN, u16, p->data_payload_len);
+ if (nla_put(msg, PCTT_RESULT_DATA_ATTR_PSDU_DATA,
+ p->data_payload_len, p->data_payload)) {
+ goto nla_put_failure;
+ }
+ } else {
+ P(PSDU_DATA_LEN, u16, 0);
+ }
+#undef P
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int pctt_report_ss_twr(struct pctt_local *local, struct sk_buff *msg)
+{
+ const struct pctt_test_ss_twr_results *ss_twr =
+ &local->results.tests.ss_twr;
+
+ trace_region_pctt_report_ss_twr(local->results.status, ss_twr);
+#define P(attr, type, value) \
+ do { \
+ if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \
+ value)) { \
+ goto nla_put_failure; \
+ } \
+ } while (0)
+ P(STATUS, u8, local->results.status);
+ P(MEASUREMENT, u32, ss_twr->measurement_rctu);
+ P(PDOA_AZIMUTH_DEG_Q7, s16, ss_twr->pdoa_azimuth_deg_q7);
+ P(PDOA_ELEVATION_DEG_Q7, s16, ss_twr->pdoa_elevation_deg_q7);
+ P(AOA_AZIMUTH_DEG_Q7, s16, ss_twr->aoa_azimuth_deg_q7);
+ P(AOA_ELEVATION_DEG_Q7, s16, ss_twr->aoa_elevation_deg_q7);
+ P(RSSI, u8, ss_twr->rssi);
+#undef P
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+void pctt_report(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+ struct sk_buff *msg;
+ struct nlattr *data;
+
+ msg = mcps802154_region_event_alloc_skb(local->llhw, &local->region,
+ PCTT_CALL_SESSION_NOTIFICATION,
+ session->event_portid,
+ NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ if (nla_put_u8(msg, PCTT_CALL_ATTR_CMD_ID, session->cmd_id))
+ goto nla_put_failure;
+
+ data = nla_nest_start(msg, PCTT_CALL_ATTR_RESULT_DATA);
+ if (!data)
+ goto nla_put_failure;
+
+ switch (session->cmd_id) {
+ case PCTT_ID_ATTR_PERIODIC_TX:
+ if (pctt_report_periodic_tx(local, msg))
+ goto nla_put_failure;
+ break;
+ case PCTT_ID_ATTR_PER_RX:
+ if (pctt_report_per_rx(local, msg))
+ goto nla_put_failure;
+ break;
+ case PCTT_ID_ATTR_RX:
+ if (pctt_report_rx(local, msg))
+ goto nla_put_failure;
+ break;
+ case PCTT_ID_ATTR_LOOPBACK:
+ if (pctt_report_loopback(local, msg))
+ goto nla_put_failure;
+ break;
+ case PCTT_ID_ATTR_SS_TWR:
+ if (pctt_report_ss_twr(local, msg))
+ goto nla_put_failure;
+ break;
+ default: /* LCOV_EXCL_START */
+ /* Impossible to cover with unit test.
+ * The only way is a memory corruption on the cmd_id. */
+ goto nla_put_failure;
+ /* LCOV_EXCL_STOP */
+ }
+
+ nla_nest_end(msg, data);
+ mcps802154_region_event(local->llhw, msg);
+ return;
+
+nla_put_failure:
+ trace_region_pctt_report_nla_put_failure(session->cmd_id);
+ kfree_skb(msg);
+}
+
+static struct mcps802154_region_ops pctt_region_ops = {
+ /* clang-format off */
+ .owner = THIS_MODULE,
+ .name = "pctt",
+ .open = pctt_open,
+ .close = pctt_close,
+ .call = pctt_call,
+ .get_access = pctt_get_access,
+ /* clang-format on */
+};
+
+int __init pctt_region_init(void)
+{
+ return mcps802154_region_register(&pctt_region_ops);
+}
+
+void __exit pctt_region_exit(void)
+{
+ mcps802154_region_unregister(&pctt_region_ops);
+}
+
+module_init(pctt_region_init);
+module_exit(pctt_region_exit);
+
+MODULE_DESCRIPTION("PCTT Region for IEEE 802.15.4 MCPS");
+MODULE_AUTHOR("Clement Calmels <clement.calmels@qorvo.com>");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
diff --git a/mac/pctt_region.h b/mac/pctt_region.h
new file mode 100644
index 0000000..8e7830c
--- /dev/null
+++ b/mac/pctt_region.h
@@ -0,0 +1,343 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef PCTT_REGION_H
+#define PCTT_REGION_H
+
+#include <linux/kernel.h>
+#include <net/mcps802154_schedule.h>
+#include <net/vendor_cmd.h>
+#include <linux/types.h>
+#include <net/pctt_region_params.h>
+#include <net/pctt_region_nl.h>
+
+#include "pctt_session.h"
+
+#define PCTT_SESSION_ID 0
+#define PCTT_BOOLEAN_MAX 1
+#define PCTT_FRAMES_MAX 2
+
+#define PCTT_TIMESTAMP_SHIFT 9
+/**
+ * map_rad_q11_to_deg_q7() - Map a Fixed Point angle to a signed 16-bit integer
+ * @ang_rad_q11: angle as Q11 fixed_point value in range [-PI, PI]
+ *
+ * Return: the angle mapped to deg q7
+ */
+static inline s16 map_rad_q11_to_deg_q7(int ang_rad_q11)
+{
+ /* 180 / (pi * (1 << 4)) => ~3,581. */
+ return ang_rad_q11 * 3581 / 1000;
+}
+
+/**
+ * struct pctt_test_per_rx_results - PER_RX result for report.
+ */
+struct pctt_test_per_rx_results {
+ /**
+ * @attempts: No. of RX attempts.
+ */
+ u32 attempts;
+ /**
+ * @acq_detect: No. of times signal was detected.
+ */
+ u32 acq_detect;
+ /**
+ * @acq_reject: No. of times signal was rejected.
+ */
+ u32 acq_reject;
+ /**
+ * @rx_fail: No. of times RX did not go beyond ACQ stage.
+ */
+ u32 rx_fail;
+ /**
+ * @sync_cir_ready: No. of times sync CIR ready event was received.
+ */
+ u32 sync_cir_ready;
+ /**
+ * @sfd_fail: No. of time RX was stuck at either ACQ detect or sync CIR ready.
+ */
+ u32 sfd_fail;
+ /**
+ * @sfd_found: No. of times SFD was found.
+ */
+ u32 sfd_found;
+ /**
+ * @phr_dec_error: No. of times PHR decode failed.
+ */
+ u32 phr_dec_error;
+ /**
+ * @phr_bit_error: No. of times PHR bits in error.
+ */
+ u32 phr_bit_error;
+ /**
+ * @psdu_dec_error: No. of times payload decode failed.
+ */
+ u32 psdu_dec_error;
+ /**
+ * @psdu_bit_error: No. of times payload bits in error.
+ */
+ u32 psdu_bit_error;
+ /**
+ * @sts_found: No. of times STS detection was successful.
+ */
+ u32 sts_found;
+ /**
+ * @eof: No. of times end of frame event was triggered.
+ */
+ u32 eof;
+ /**
+ * @rssi: Received signal strength indication (RSSI).
+ */
+ u8 rssi;
+};
+
+/**
+ * struct pctt_test_rx_results - RX result for report.
+ */
+struct pctt_test_rx_results {
+ /**
+ * @rx_done_ts_int: Integer part of timestamp 1/124.8Mhz ticks.
+ */
+ u32 rx_done_ts_int;
+ /**
+ * @rx_done_ts_frac: Fractional part of timestamp in 1/(128 * 499.2Mhz) ticks.
+ */
+ u16 rx_done_ts_frac;
+ /**
+ * @aoa_azimuth: AoA azimuth in degrees and it is a signed value in Q9.7 format.
+ */
+ s16 aoa_azimuth;
+ /**
+ * @aoa_elevation: AoA elevation in degrees and it is a signed value in Q9.7 format.
+ */
+ s16 aoa_elevation;
+ /**
+ * @phr: Received PHR (bits 0-12 as per IEEE spec).
+ */
+ u16 phr;
+ /**
+ * @psdu_data_len: Length of PSDU Data(N).
+ */
+ u16 psdu_data_len;
+ /**
+ * @toa_gap: ToA of main path minus ToA of first path in nanosecond.
+ */
+ u8 toa_gap;
+ /**
+ * @rssi: Received signal strength indication (RSSI).
+ */
+ u8 rssi;
+ /**
+ * @psdu_data: Received PSDU Data[0:N] bytes.
+ */
+ u8 psdu_data[PCTT_PAYLOAD_MAX_LEN];
+};
+
+/**
+ * struct pctt_test_ss_twr_results - SS_TWR result for report.
+ */
+struct pctt_test_ss_twr_results {
+ /**
+ * @tx_timestamps_rctu: Timestamps of transmitted frame.
+ */
+ u64 tx_timestamps_rctu;
+ /**
+ * @rx_timestamps_rctu: Timestamps of received frame.
+ */
+ u64 rx_timestamps_rctu;
+ /**
+ * @measurement_rctu: Contains Tround time of Initiator or
+ * Treply time of Responder depending on DEVICE_ROLE option.
+ */
+ u32 measurement_rctu;
+ /**
+ * @pdoa_azimuth_deg_q7: Phase Difference of Arrival Azimuth in deg Q7
+ */
+ s16 pdoa_azimuth_deg_q7;
+ /**
+ * @aoa_azimuth_deg_q7: AoA Azimuth in deg Q7
+ */
+ s16 aoa_azimuth_deg_q7;
+ /**
+ * @pdoa_elevation_deg_q7: Phase Difference of Arrival Elevation in deg Q7
+ */
+ s16 pdoa_elevation_deg_q7;
+ /**
+ * @aoa_elevation_deg_q7: AoA Elevation in deg Q7
+ */
+ s16 aoa_elevation_deg_q7;
+ /**
+ * @rssi: Received signal strength indication (RSSI).
+ */
+ u8 rssi;
+};
+
+/**
+ * struct pctt_test_loopback_results - LOOPBACK result for report.
+ */
+struct pctt_test_loopback_results {
+ /**
+ * @rssi: Received signal strength indication (RSSI).
+ */
+ u8 rssi;
+ /**
+ * @tx_ts_int: Integer part of TX timestamp in 1/124.8 us. resolution.
+ */
+ u32 tx_ts_int;
+ /**
+ * @tx_ts_frac: Fractional part of TX timestamp in 1/124.8/512 us. resolution.
+ */
+ u16 tx_ts_frac;
+ /**
+ * @rx_ts_int: Integer part of Rx timestamp in 1/124.8 us. resolution.
+ */
+ u32 rx_ts_int;
+ /**
+ * @rx_ts_frac: Fractional part of RX timestamp in 1/124.8/512 us. resolution.
+ */
+ u16 rx_ts_frac;
+};
+
+/**
+ * union pctt_tests_results - All commands notifications.
+ */
+union pctt_tests_results {
+ /**
+ * @per_rx: Result of the PER_RX command.
+ */
+ struct pctt_test_per_rx_results per_rx;
+ /**
+ * @rx: Result of the RX command.
+ */
+ struct pctt_test_rx_results rx;
+ /**
+ * @ss_twr: Result of the SS_TWR command.
+ */
+ struct pctt_test_ss_twr_results ss_twr;
+ /**
+ * @loopback: Result of the LOOPBACK command.
+ */
+ struct pctt_test_loopback_results loopback;
+};
+
+/**
+ * union pctt_results - Main notification for all commands.
+ */
+struct pctt_results {
+ /**
+ * @status: Result of the command done.
+ */
+ enum pctt_status_ranging status;
+ /**
+ * @tests: Result detail.
+ */
+ union pctt_tests_results tests;
+};
+
+/**
+ * struct pctt_slot - Information on an active slot.
+ */
+struct pctt_slot {
+ /**
+ * @is_tx: Is transmit frame?
+ */
+ bool is_tx;
+ /**
+ * @is_rframe: Is ranging frame?
+ */
+ bool is_rframe;
+ /**
+ * @is_immediate: True when the frame is immediate.
+ */
+ bool is_immediate;
+ /**
+ * @timeout_dtu: see (mcps802154_rx_frame_config).timeout_dtu.
+ */
+ int timeout_dtu;
+};
+
+struct pctt_local {
+ /**
+ * @region: Region instance returned to MCPS.
+ */
+ struct mcps802154_region region;
+ /**
+ * @llhw: Low-level device pointer.
+ */
+ struct mcps802154_llhw *llhw;
+ /**
+ * @access: Access returned to MCPS.
+ */
+ struct mcps802154_access access;
+ /**
+ * @session: Unique session on the PCTT.
+ */
+ struct pctt_session session;
+ /**
+ * @frames: Access frames referenced from access.
+ */
+ struct mcps802154_access_frame frames[PCTT_FRAMES_MAX];
+ /**
+ * @sts_params: STS parameters for access frames.
+ */
+ struct mcps802154_sts_params sts_params[PCTT_FRAMES_MAX];
+ /**
+ * @slots: Descriptions of each active slots for the current session.
+ */
+ struct pctt_slot slots[PCTT_FRAMES_MAX];
+ /**
+ * @results: Test result used for notification at the end of test.
+ */
+ struct pctt_results results;
+ /**
+ * @frames_remaining_nb: Number of frame remaining to do for the current test.
+ */
+ int frames_remaining_nb;
+};
+
+static inline struct pctt_local *
+region_to_local(struct mcps802154_region *region)
+{
+ return container_of(region, struct pctt_local, region);
+}
+
+static inline struct pctt_local *
+access_to_local(struct mcps802154_access *access)
+{
+ return container_of(access, struct pctt_local, access);
+}
+
+/**
+ * pctt_report() - PCTT Report.
+ * @local: pctt context.
+ */
+void pctt_report(struct pctt_local *local);
+
+/**
+ * pctt_session_notify_state_change() - Notify session state change to upper layers.
+ * @local: context.
+ */
+void pctt_session_notify_state_change(struct pctt_local *local);
+
+#endif /* PCTT_REGION_H */
diff --git a/mac/pctt_region_call.c b/mac/pctt_region_call.c
new file mode 100644
index 0000000..65b60f3
--- /dev/null
+++ b/mac/pctt_region_call.c
@@ -0,0 +1,339 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+
+#include <net/pctt_region_nl.h>
+#include <net/mcps802154_frame.h>
+
+#include "pctt_access.h"
+#include "pctt_region.h"
+#include "pctt_region_call.h"
+#include "pctt_session.h"
+#include "pctt_trace.h"
+
+static const struct nla_policy pctt_call_nla_policy[PCTT_CALL_ATTR_MAX + 1] = {
+ [PCTT_CALL_ATTR_CMD_ID] = { .type = NLA_U8 },
+ [PCTT_CALL_ATTR_RESULT_DATA] = { .type = NLA_NESTED },
+ [PCTT_CALL_ATTR_SESSION_ID] = { .type = NLA_U32 },
+ [PCTT_CALL_ATTR_SESSION_STATE] = { .type = NLA_U8 },
+ [PCTT_CALL_ATTR_SESSION_PARAMS] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy pctt_session_param_nla_policy[PCTT_SESSION_PARAM_ATTR_MAX +
+ 1] = {
+ [PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_DEVICE_ROLE_INITIATOR,
+ },
+ [PCTT_SESSION_PARAM_ATTR_SHORT_ADDR] = { .type = NLA_U16 },
+ [PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR] = { .type = NLA_U16 },
+ [PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_RFRAME_CONFIG_SP3,
+ },
+ [PCTT_SESSION_PARAM_ATTR_PRF_MODE] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_PRF_MODE_HPRF_HIGH_RATE,
+ },
+ [PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_PREAMBLE_DURATION_64,
+ },
+ [PCTT_SESSION_PARAM_ATTR_SFD_ID] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_SFD_ID_4,
+ },
+ [PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS,
+ },
+ [PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_PSDU_DATA_RATE_31M2,
+ },
+ [PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_PHR_DATA_RATE_6M81,
+ },
+ [PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_MAC_FCS_TYPE_CRC_32,
+ },
+ [PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_BOOLEAN_MAX,
+ },
+ [PCTT_SESSION_PARAM_ATTR_STS_INDEX] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_STS_LENGTH] = {
+ .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX,
+ .max = PCTT_STS_LENGTH_128,
+ },
+ [PCTT_SESSION_PARAM_ATTR_NUM_PACKETS] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_T_GAP] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_T_START] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_T_WIN] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START] = { .type = NLA_U32 },
+ [PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR] = { .type = NLA_U8 },
+ [PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD] = {
+ .type = NLA_BINARY,
+ .len = PCTT_PAYLOAD_MAX_LEN
+ },
+};
+
+int pctt_call_session_get_state(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+ struct sk_buff *msg;
+
+ msg = mcps802154_region_call_alloc_reply_skb(
+ local->llhw, &local->region, PCTT_CALL_SESSION_GET_STATE,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, PCTT_CALL_ATTR_SESSION_ID, PCTT_SESSION_ID))
+ goto nla_put_failure;
+
+ if (nla_put_u8(msg, PCTT_CALL_ATTR_SESSION_STATE, session->state))
+ goto nla_put_failure;
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+int pctt_call_session_get_params(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ struct nlattr *params;
+ struct sk_buff *msg;
+
+ msg = mcps802154_region_call_alloc_reply_skb(
+ local->llhw, &local->region, PCTT_CALL_SESSION_GET_PARAMS,
+ NLMSG_DEFAULT_SIZE);
+ if (!msg)
+ return -ENOMEM;
+
+ if (nla_put_u32(msg, PCTT_CALL_ATTR_SESSION_ID, PCTT_SESSION_ID))
+ goto nla_put_failure;
+
+ params = nla_nest_start(msg, PCTT_CALL_ATTR_SESSION_PARAMS);
+ if (!params)
+ goto nla_put_failure;
+
+#define P(attr, member, type, conv) \
+ do { \
+ type x = p->member; \
+ if (nla_put_##type(msg, PCTT_SESSION_PARAM_ATTR_##attr, conv)) \
+ goto nla_put_failure; \
+ } while (0)
+ P(DEVICE_ROLE, device_role, u8, x);
+ P(SHORT_ADDR, short_addr, u16, x);
+ P(DESTINATION_SHORT_ADDR, dst_short_addr, u16, x);
+ P(RX_ANTENNA_SELECTION, rx_antenna_selection, u8, x);
+ P(TX_ANTENNA_SELECTION, tx_antenna_selection, u8, x);
+ P(SLOT_DURATION_RSTU, slot_duration_dtu, u32,
+ x / local->llhw->rstu_dtu);
+ P(CHANNEL_NUMBER, channel_number, u8, x);
+ P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x);
+ P(RFRAME_CONFIG, rframe_config, u8, x);
+ P(PREAMBLE_DURATION, preamble_duration, u8, x);
+ P(SFD_ID, sfd_id, u8, x);
+ P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x);
+ P(PSDU_DATA_RATE, psdu_data_rate, u8, x);
+ P(MAC_FCS_TYPE, mac_fcs_type, u8, x);
+ P(PRF_MODE, prf_mode, u8, x);
+ P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x);
+ P(TX_ADAPTIVE_PAYLOAD_POWER, tx_adaptive_payload_power, u8, x);
+ P(STS_INDEX, sts_index, u32, x);
+ P(STS_LENGTH, sts_length, u8, x);
+ P(NUM_PACKETS, num_packets, u32, x);
+ P(T_GAP, gap_duration_dtu, u32,
+ (((u64)x * 1000) / (local->llhw->dtu_freq_hz / 1000)));
+ P(T_START, t_start, u32, x);
+ P(T_WIN, t_win, u32, x);
+ P(RANDOMIZE_PSDU, randomize_psdu, u8, x);
+ P(PHR_RANGING_BIT, phr_ranging_bit, u8, x);
+ P(RMARKER_TX_START, rmarker_tx_start, u32, x);
+ P(RMARKER_RX_START, rmarker_rx_start, u32, x);
+ P(STS_INDEX_AUTO_INCR, sts_index_auto_incr, u8, x);
+#undef P
+ nla_nest_end(msg, params);
+
+ return mcps802154_region_call_reply(local->llhw, msg);
+nla_put_failure:
+ kfree_skb(msg);
+ return -ENOBUFS;
+}
+
+static int pctt_call_session_set_params(struct pctt_local *local,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ struct nlattr *attrs[PCTT_SESSION_PARAM_ATTR_MAX + 1];
+ struct pctt_session *session = &local->session;
+ struct pctt_session_params *p = &session->params;
+ int r;
+
+ if (!params)
+ return -EINVAL;
+ if (session->test_on_going)
+ return -EBUSY;
+
+ r = nla_parse_nested(attrs, PCTT_SESSION_PARAM_ATTR_MAX, params,
+ pctt_session_param_nla_policy, info->extack);
+ if (r)
+ return r;
+
+#define P(attr, member, type, conv) \
+ do { \
+ int x; \
+ if (attrs[PCTT_SESSION_PARAM_ATTR_##attr]) { \
+ x = nla_get_##type( \
+ attrs[PCTT_SESSION_PARAM_ATTR_##attr]); \
+ p->member = conv; \
+ } \
+ } while (0)
+#define PMEMNCPY(attr, member, size) \
+ do { \
+ if (attrs[PCTT_SESSION_PARAM_ATTR_##attr]) { \
+ struct nlattr *attr = \
+ attrs[PCTT_SESSION_PARAM_ATTR_##attr]; \
+ int len = nla_len(attr); \
+ memcpy(p->member, nla_data(attr), len); \
+ p->size = len; \
+ } \
+ } while (0)
+
+ P(DEVICE_ROLE, device_role, u8, x);
+ P(SHORT_ADDR, short_addr, u16, x);
+ P(DESTINATION_SHORT_ADDR, dst_short_addr, u16, x);
+ P(RX_ANTENNA_SELECTION, rx_antenna_selection, u8, x);
+ P(TX_ANTENNA_SELECTION, tx_antenna_selection, u8, x);
+ P(SLOT_DURATION_RSTU, slot_duration_dtu, u32,
+ x * local->llhw->rstu_dtu);
+ P(CHANNEL_NUMBER, channel_number, u8, x);
+ P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x);
+ P(RFRAME_CONFIG, rframe_config, u8, x);
+ P(PREAMBLE_DURATION, preamble_duration, u8, x);
+ P(SFD_ID, sfd_id, u8, x);
+ P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x);
+ P(PSDU_DATA_RATE, psdu_data_rate, u8, x);
+ P(MAC_FCS_TYPE, mac_fcs_type, u8, x);
+ P(PRF_MODE, prf_mode, u8, x);
+ P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x);
+ P(TX_ADAPTIVE_PAYLOAD_POWER, tx_adaptive_payload_power, u8, x);
+ P(STS_INDEX, sts_index, u32, x);
+ P(STS_LENGTH, sts_length, u8, x);
+ P(NUM_PACKETS, num_packets, u32, x);
+ P(T_GAP, gap_duration_dtu, u32,
+ ((u64)x * (local->llhw->dtu_freq_hz / 1000)) / 1000);
+ P(T_START, t_start, u32, x);
+ P(T_WIN, t_win, u32, x);
+ P(RANDOMIZE_PSDU, randomize_psdu, u8, x);
+ P(PHR_RANGING_BIT, phr_ranging_bit, u8, x);
+ P(RMARKER_TX_START, rmarker_tx_start, u32, x);
+ P(RMARKER_RX_START, rmarker_rx_start, u32, x);
+ P(STS_INDEX_AUTO_INCR, sts_index_auto_incr, u8, x);
+ PMEMNCPY(DATA_PAYLOAD, data_payload, data_payload_len);
+#undef PMEMNCPY
+#undef P
+
+ return 0;
+}
+
+static int pctt_call_cmd(struct pctt_local *local,
+ const struct nlattr *cmd_id_attr,
+ const struct genl_info *info)
+{
+ struct pctt_session *session = &local->session;
+ enum pctt_id_attrs cmd_id;
+
+ if (!cmd_id_attr)
+ return -EINVAL;
+ cmd_id = nla_get_u8(cmd_id_attr);
+
+ if (session->test_on_going) {
+ if (cmd_id == PCTT_ID_ATTR_STOP_TEST &&
+ !session->stop_request) {
+ session->stop_request = true;
+ mcps802154_reschedule(local->llhw);
+ return 0;
+ }
+ return -EBUSY;
+ } else {
+ u32 now_dtu;
+ int r;
+
+ if (cmd_id == PCTT_ID_ATTR_STOP_TEST)
+ return 0;
+
+ /* FIXME: Used only to detect dw3000_is_active. */
+ r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu);
+ if (r)
+ return r;
+ r = pctt_session_start_test(local, cmd_id, info);
+ if (r)
+ return r;
+ }
+
+ mcps802154_reschedule(local->llhw);
+ return 0;
+}
+
+int pctt_call_session_control(struct pctt_local *local, enum pctt_call call_id,
+ const struct nlattr *params,
+ const struct genl_info *info)
+{
+ struct pctt_session *session = &local->session;
+ struct nlattr *attrs[PCTT_CALL_ATTR_MAX + 1];
+ int r;
+
+ if (session->state == PCTT_SESSION_STATE_DEINIT)
+ return -EPERM;
+ if (!params || !info)
+ return -EINVAL;
+ r = nla_parse_nested(attrs, PCTT_CALL_ATTR_MAX, params,
+ pctt_call_nla_policy, info->extack);
+ if (r)
+ return r;
+
+ if (call_id == PCTT_CALL_SESSION_SET_PARAMS)
+ return pctt_call_session_set_params(
+ local, attrs[PCTT_CALL_ATTR_SESSION_PARAMS], info);
+ else
+ return pctt_call_cmd(local, attrs[PCTT_CALL_ATTR_CMD_ID], info);
+}
diff --git a/mac/pctt_region_call.h b/mac/pctt_region_call.h
new file mode 100644
index 0000000..5aa0a7d
--- /dev/null
+++ b/mac/pctt_region_call.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#ifndef PCTT_REGION_CALL_H
+#define PCTT_REGION_CALL_H
+
+#include "pctt_region.h"
+
+/**
+ * pctt_call_session_control() - PCTT specific region call
+ * @local: PCTT region context.
+ * @call_id: Identifier of the call procedure.
+ * @params: Nested attribute containing procedure parameters.
+ * @info: Request information.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_call_session_control(struct pctt_local *local, enum pctt_call call_id,
+ const struct nlattr *params,
+ const struct genl_info *info);
+
+/**
+ * pctt_call_session_get_state() - Return current state on netlink reply.
+ * @local: PCTT region context.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_call_session_get_state(struct pctt_local *local);
+
+/**
+ * pctt_call_session_get_params() - Return session params on netlink reply
+ * @local: PCTT region context.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_call_session_get_params(struct pctt_local *local);
+
+#endif /* PCTT_REGION_CALL_H */
diff --git a/mac/pctt_session.c b/mac/pctt_session.c
new file mode 100644
index 0000000..70beaf1
--- /dev/null
+++ b/mac/pctt_session.c
@@ -0,0 +1,194 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include "pctt_session.h"
+#include "pctt_trace.h"
+#include "pctt_region.h"
+#include "pctt_region_call.h"
+
+#include <linux/errno.h>
+#include <linux/ieee802154.h>
+#include <linux/string.h>
+
+int pctt_session_init(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+ struct pctt_session_params *p = &session->params;
+
+ /* Do the same behavior as get_session_state in FiRa.
+ * INIT state means kzalloc in FiRa once by session_id.
+ * But as pctt have only one static region. Simulate
+ * kzalloc to do or already done with the local state. */
+ if (session->state != PCTT_SESSION_STATE_DEINIT)
+ return -EBUSY;
+
+ memset(p, 0, sizeof(*p));
+ p->rx_antenna_selection = RX_ANT_SET_ID_DEFAULT;
+ p->tx_antenna_selection = TX_ANT_SET_ID_DEFAULT;
+ p->preamble_duration = PCTT_PREAMBLE_DURATION_64;
+ p->preamble_code_index = 9;
+ p->sts_length = PCTT_STS_LENGTH_64;
+ pctt_session_set_state(local, PCTT_SESSION_STATE_INIT);
+ return 0;
+}
+
+int pctt_session_deinit(struct pctt_local *local)
+{
+ struct pctt_session *session = &local->session;
+
+ /* Do the same behavior as get_session_state in FiRa.
+ * DEINIT state means kfree in FiRa.
+ * But as pctt have only one static region. Simulate
+ * kfree to do or already done with the local state. */
+ if (session->state == PCTT_SESSION_STATE_DEINIT)
+ return -ENOENT;
+ if (session->test_on_going)
+ return -EBUSY;
+
+ pctt_session_set_state(local, PCTT_SESSION_STATE_DEINIT);
+ return 0;
+}
+
+void pctt_session_set_state(struct pctt_local *local,
+ enum pctt_session_state new_state)
+{
+ struct pctt_session *session = &local->session;
+ const enum pctt_session_state old_state = session->state;
+
+ trace_region_pctt_session_set_state(old_state, new_state);
+
+ session->state = new_state;
+}
+
+int pctt_session_start_test(struct pctt_local *local, enum pctt_id_attrs cmd_id,
+ const struct genl_info *info)
+{
+ struct pctt_session *session = &local->session;
+ const struct pctt_session_params *p = &session->params;
+ static const enum mcps802154_data_rate pctt_rate_to_mcps_rate[] = {
+ MCPS802154_DATA_RATE_6M81,
+ MCPS802154_DATA_RATE_7M80,
+ MCPS802154_DATA_RATE_27M2,
+ MCPS802154_DATA_RATE_31M2,
+ };
+
+ trace_region_pctt_session_start_test(cmd_id, p);
+
+ switch (cmd_id) {
+ case PCTT_ID_ATTR_PERIODIC_TX:
+ break;
+ case PCTT_ID_ATTR_LOOPBACK:
+ break;
+ case PCTT_ID_ATTR_SS_TWR:
+ if (p->rframe_config != PCTT_RFRAME_CONFIG_SP3)
+ return -EINVAL;
+ if (!p->slot_duration_dtu)
+ return -EINVAL;
+ break;
+ case PCTT_ID_ATTR_RX:
+ break;
+ case PCTT_ID_ATTR_PER_RX:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* check uwb parameters. */
+ if (p->prf_mode == PCTT_PRF_MODE_BPRF) {
+ if (p->preamble_code_index < 9 || p->preamble_code_index > 24)
+ return -EINVAL;
+ if (p->sfd_id != PCTT_SFD_ID_0 && p->sfd_id != PCTT_SFD_ID_2)
+ return -EINVAL;
+ if (p->psdu_data_rate != PCTT_PSDU_DATA_RATE_6M81)
+ return -EINVAL;
+ if (p->preamble_duration != PCTT_PREAMBLE_DURATION_64)
+ return -EINVAL;
+ if (p->number_of_sts_segments >
+ PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT)
+ return -EINVAL;
+ } else {
+ if (p->preamble_code_index < 25 || p->preamble_code_index > 32)
+ return -EINVAL;
+ if (p->sfd_id == PCTT_SFD_ID_0)
+ return -EINVAL;
+ if (p->prf_mode == PCTT_PRF_MODE_HPRF &&
+ p->psdu_data_rate > PCTT_PSDU_DATA_RATE_7M80)
+ return -EINVAL;
+ if (p->prf_mode == PCTT_PRF_MODE_HPRF_HIGH_RATE &&
+ p->psdu_data_rate < PCTT_PSDU_DATA_RATE_27M2)
+ return -EINVAL;
+ }
+ if ((p->rframe_config == PCTT_RFRAME_CONFIG_SP0) &&
+ (p->number_of_sts_segments != PCTT_NUMBER_OF_STS_SEGMENTS_NONE))
+ return -EINVAL;
+ if ((p->rframe_config != PCTT_RFRAME_CONFIG_SP0) &&
+ (p->number_of_sts_segments == PCTT_NUMBER_OF_STS_SEGMENTS_NONE))
+ return -EINVAL;
+ if ((p->rframe_config == PCTT_RFRAME_CONFIG_SP3) &&
+ (p->data_payload_len))
+ return -EINVAL;
+
+ /* Set radio parameters. */
+ switch (p->prf_mode) {
+ case PCTT_PRF_MODE_BPRF:
+ session->hrp_uwb_params.prf = MCPS802154_PRF_64;
+ break;
+ case PCTT_PRF_MODE_HPRF:
+ session->hrp_uwb_params.prf = MCPS802154_PRF_125;
+ break;
+ default:
+ session->hrp_uwb_params.prf = MCPS802154_PRF_250;
+ }
+ session->hrp_uwb_params.psr =
+ p->preamble_duration == PCTT_PREAMBLE_DURATION_64 ?
+ MCPS802154_PSR_64 :
+ MCPS802154_PSR_32;
+ session->hrp_uwb_params.sfd_selector = (enum mcps802154_sfd)(p->sfd_id);
+ session->hrp_uwb_params.phr_hi_rate = !!p->phr_data_rate;
+ session->hrp_uwb_params.data_rate =
+ pctt_rate_to_mcps_rate[p->psdu_data_rate];
+
+ /* Update unique session context. */
+ session->first_access = true;
+ session->first_rx_synchronized = false;
+ /* FIXME: Delete portid_set_once.
+ * See: UWB-2057. */
+ session->portid_set_once = true;
+ session->event_portid = info->snd_portid;
+ /* Set parameters used by the PCTT vendor command. */
+ session->setup_hw = (struct llhw_vendor_cmd_pctt_setup_hw){
+ .chan = p->channel_number,
+ .rframe_config = p->rframe_config,
+ .preamble_code_index = p->preamble_code_index,
+ .sfd_id = p->sfd_id,
+ .psdu_data_rate = p->psdu_data_rate,
+ .preamble_duration = p->preamble_duration,
+ };
+ /* Update region context. */
+ memset(&local->results, 0, sizeof(local->results));
+ local->frames_remaining_nb = session->params.num_packets;
+ session->cmd_id = cmd_id;
+ session->test_on_going = true;
+ /* At the end, update the state. */
+ pctt_session_set_state(local, PCTT_SESSION_STATE_ACTIVE);
+ return 0;
+}
diff --git a/mac/pctt_session.h b/mac/pctt_session.h
new file mode 100644
index 0000000..40058e4
--- /dev/null
+++ b/mac/pctt_session.h
@@ -0,0 +1,166 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021-2022 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_PCTT_SESSION_H
+#define NET_MCPS802154_PCTT_SESSION_H
+
+#include <linux/kernel.h>
+#include <net/mcps802154_schedule.h>
+#include <net/vendor_cmd.h>
+#include <net/pctt_region_params.h>
+#include <net/pctt_region_nl.h>
+
+#define PCTT_PAYLOAD_MAX_LEN 4096
+
+struct pctt_session_params {
+ enum pctt_device_role device_role;
+ __le16 short_addr;
+ __le16 dst_short_addr;
+ u8 rx_antenna_selection;
+ u8 tx_antenna_selection;
+ int slot_duration_dtu;
+ int channel_number;
+ int preamble_code_index;
+ enum pctt_rframe_config rframe_config;
+ enum pctt_preamble_duration preamble_duration;
+ enum pctt_sfd_id sfd_id;
+ enum pctt_number_of_sts_segments number_of_sts_segments;
+ enum pctt_psdu_data_rate psdu_data_rate;
+ enum pctt_mac_fcs_type mac_fcs_type;
+ enum pctt_prf_mode prf_mode;
+ enum pctt_phr_data_rate phr_data_rate;
+ u8 tx_adaptive_payload_power;
+ u32 sts_index;
+ enum pctt_sts_length sts_length;
+ /* Test specific parameters */
+ u32 num_packets;
+ int gap_duration_dtu;
+ u32 t_start;
+ u32 t_win;
+ u8 randomize_psdu;
+ u8 phr_ranging_bit;
+ u32 rmarker_tx_start;
+ u32 rmarker_rx_start;
+ u8 sts_index_auto_incr;
+ /* Data payload to put in TX test frame */
+ u8 data_payload[PCTT_PAYLOAD_MAX_LEN];
+ int data_payload_len;
+};
+
+/**
+ * struct pctt_session - Session information.
+ */
+struct pctt_session {
+ /**
+ * @params: Session parameters, mostly read only while the session is
+ * active.
+ */
+ struct pctt_session_params params;
+ /**
+ * @hrp_uwb_params: HRP UWB parameters, read only while the session is
+ * active.
+ */
+ struct mcps802154_hrp_uwb_params hrp_uwb_params;
+ /**
+ * @event_portid: Port identifier to use for notifications.
+ */
+ u32 event_portid;
+ /**
+ * @portid_set_once: True when portid have been set once.
+ *
+ * FIXME: To be delete with status notification!
+ */
+ bool portid_set_once;
+ /**
+ * @first_access: True on the first access.
+ */
+ bool first_access;
+ /**
+ * @first_rx_synchronized: True after the first successful reception.
+ */
+ bool first_rx_synchronized;
+ /**
+ * @stop_request: True to not start an another access.
+ */
+ bool stop_request;
+ /**
+ * @next_timestamp_dtu: next date for next frame.
+ */
+ u32 next_timestamp_dtu;
+ /**
+ * @setup_hw: setup hardware through a vendor command.
+ */
+ struct llhw_vendor_cmd_pctt_setup_hw setup_hw;
+ /**
+ * @state: UWB session state.
+ */
+ enum pctt_session_state state;
+ /**
+ * @cmd_id: test identifier in progress.
+ */
+ enum pctt_id_attrs cmd_id;
+ /**
+ * @test_on_going: True when test is in progress.
+ */
+ bool test_on_going;
+};
+
+/* Forward declaration. */
+struct pctt_local;
+
+/**
+ * pctt_session_init() - Initialize session parameters to default value.
+ * @local: PCTT context.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_session_init(struct pctt_local *local);
+
+/**
+ * pctt_session_deinit() - Declare session as not ready.
+ * @local: PCTT context.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_session_deinit(struct pctt_local *local);
+
+/**
+ * pctt_session_set_state() - Update PCTT state and send notification on change.
+ * @local: PCTT context.
+ * @new_state: New state to set.
+ */
+void pctt_session_set_state(struct pctt_local *local,
+ enum pctt_session_state new_state);
+
+/**
+ * pctt_session_start_test() - Start PCTT test.
+ * @local: PCTT context.
+ * @id: Test identifier requested.
+ * @info: Request information.
+ *
+ * Return: 0 on success -errno otherwise.
+ */
+int pctt_session_start_test(struct pctt_local *local, enum pctt_id_attrs id,
+ const struct genl_info *info);
+
+#endif /* NET_MCPS802154_PCTT_SESSION_H */
diff --git a/mac/pctt_trace.h b/mac/pctt_trace.h
new file mode 100644
index 0000000..3cba8e7
--- /dev/null
+++ b/mac/pctt_trace.h
@@ -0,0 +1,519 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mcps802154_region_pctt
+
+#if !defined(PCTT_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define PCTT_TRACE_H
+
+#include <linux/tracepoint.h>
+#include <net/pctt_region_nl.h>
+#include <net/pctt_region_params.h>
+#include "pctt_session.h"
+#include "pctt_region.h"
+
+/* clang-format off */
+#define pctt_device_role_name(name) \
+ { PCTT_DEVICE_ROLE_##name, #name }
+#define PCTT_DEVICE_ROLE_SYMBOLS \
+ pctt_device_role_name(RESPONDER), \
+ pctt_device_role_name(INITIATOR)
+TRACE_DEFINE_ENUM(PCTT_DEVICE_ROLE_RESPONDER);
+TRACE_DEFINE_ENUM(PCTT_DEVICE_ROLE_INITIATOR);
+#define PCTT_DEVICE_ROLE_ENTRY __field(enum pctt_device_role, device_role)
+#define PCTT_DEVICE_ROLE_ASSIGN __entry->device_role = params->device_role
+#define PCTT_DEVICE_ROLE_PR_FMT "device_role=%s"
+#define PCTT_DEVICE_ROLE_PR_ARG \
+ __print_symbolic(__entry->device_role, PCTT_DEVICE_ROLE_SYMBOLS)
+
+#define pctt_rframe_config_name(name) \
+ { PCTT_RFRAME_CONFIG_##name, #name }
+#define PCTT_RFRAME_CONFIG_SYMBOLS \
+ pctt_rframe_config_name(SP0), \
+ pctt_rframe_config_name(SP1), \
+ pctt_rframe_config_name(SP2), \
+ pctt_rframe_config_name(SP3)
+TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP0);
+TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP1);
+TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP2);
+TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP3);
+#define PCTT_RFRAME_CONFIG_ENTRY __field(enum pctt_rframe_config, rframe_config)
+#define PCTT_RFRAME_CONFIG_ASSIGN __entry->rframe_config = params->rframe_config
+#define PCTT_RFRAME_CONFIG_PR_FMT "rframe_config=%s"
+#define PCTT_RFRAME_CONFIG_PR_ARG \
+ __print_symbolic(__entry->rframe_config, PCTT_RFRAME_CONFIG_SYMBOLS)
+
+#define pctt_prf_mode_name(name) \
+ { PCTT_PRF_MODE_##name, #name }
+#define PCTT_PRF_MODE_SYMBOLS \
+ pctt_prf_mode_name(BPRF), \
+ pctt_prf_mode_name(HPRF), \
+ pctt_prf_mode_name(HPRF_HIGH_RATE)
+TRACE_DEFINE_ENUM(PCTT_PRF_MODE_BPRF);
+TRACE_DEFINE_ENUM(PCTT_PRF_MODE_HPRF);
+TRACE_DEFINE_ENUM(PCTT_PRF_MODE_HPRF_HIGH_RATE);
+#define PCTT_PRF_MODE_ENTRY __field(enum pctt_prf_mode, prf_mode)
+#define PCTT_PRF_MODE_ASSIGN __entry->prf_mode = params->prf_mode
+#define PCTT_PRF_MODE_PR_FMT "prf_mode=%s"
+#define PCTT_PRF_MODE_PR_ARG \
+ __print_symbolic(__entry->prf_mode, PCTT_PRF_MODE_SYMBOLS)
+
+#define pctt_preamble_duration_name(name) \
+ { PCTT_PREAMBLE_DURATION_##name, #name }
+#define PCTT_PREAMBLE_DURATION_SYMBOLS \
+ pctt_preamble_duration_name(32), \
+ pctt_preamble_duration_name(64)
+TRACE_DEFINE_ENUM(PCTT_PREAMBLE_DURATION_32);
+TRACE_DEFINE_ENUM(PCTT_PREAMBLE_DURATION_64);
+#define PCTT_PREAMBLE_DURATION_ENTRY \
+ __field(enum pctt_preamble_duration, preamble_duration)
+#define PCTT_PREAMBLE_DURATION_ASSIGN \
+ __entry->preamble_duration = params->preamble_duration
+#define PCTT_PREAMBLE_DURATION_PR_FMT "preamble_duration=%s"
+#define PCTT_PREAMBLE_DURATION_PR_ARG \
+ __print_symbolic(__entry->preamble_duration, \
+ PCTT_PREAMBLE_DURATION_SYMBOLS)
+
+#define pctt_sfd_id_name(name) \
+ { PCTT_SFD_ID_##name, #name }
+#define PCTT_SFD_ID_SYMBOLS \
+ pctt_sfd_id_name(0), \
+ pctt_sfd_id_name(1), \
+ pctt_sfd_id_name(2), \
+ pctt_sfd_id_name(3), \
+ pctt_sfd_id_name(4)
+TRACE_DEFINE_ENUM(PCTT_SFD_ID_0);
+TRACE_DEFINE_ENUM(PCTT_SFD_ID_1);
+TRACE_DEFINE_ENUM(PCTT_SFD_ID_2);
+TRACE_DEFINE_ENUM(PCTT_SFD_ID_3);
+TRACE_DEFINE_ENUM(PCTT_SFD_ID_4);
+#define PCTT_SFD_ID_ENTRY __field(enum pctt_sfd_id, sfd_id)
+#define PCTT_SFD_ID_ASSIGN __entry->sfd_id = params->sfd_id
+#define PCTT_SFD_ID_PR_FMT "sfd_id=%s"
+#define PCTT_SFD_ID_PR_ARG \
+ __print_symbolic(__entry->sfd_id, PCTT_SFD_ID_SYMBOLS)
+
+#define pctt_number_of_sts_segments_name(name) \
+ { PCTT_NUMBER_OF_STS_SEGMENTS_##name, #name }
+#define PCTT_NUMBER_OF_STS_SEGMENTS_SYMBOLS \
+ pctt_number_of_sts_segments_name(NONE), \
+ pctt_number_of_sts_segments_name(1_SEGMENT), \
+ pctt_number_of_sts_segments_name(2_SEGMENTS), \
+ pctt_number_of_sts_segments_name(3_SEGMENTS), \
+ pctt_number_of_sts_segments_name(4_SEGMENTS)
+TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_NONE);
+TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT);
+TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS);
+TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS);
+TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS);
+#define PCTT_NUMBER_OF_STS_SEGMENTS_ENTRY \
+ __field(enum pctt_number_of_sts_segments, number_of_sts_segments)
+#define PCTT_NUMBER_OF_STS_SEGMENTS_ASSIGN \
+ __entry->number_of_sts_segments = params->number_of_sts_segments
+#define PCTT_NUMBER_OF_STS_SEGMENTS_PR_FMT "number_of_sts_segments=%s"
+#define PCTT_NUMBER_OF_STS_SEGMENTS_PR_ARG \
+ __print_symbolic(__entry->number_of_sts_segments, \
+ PCTT_NUMBER_OF_STS_SEGMENTS_SYMBOLS)
+
+#define pctt_psdu_data_rate_name(name) \
+ { PCTT_PSDU_DATA_RATE_##name, #name }
+#define PCTT_PSDU_DATA_RATE_SYMBOLS \
+ pctt_psdu_data_rate_name(6M81), \
+ pctt_psdu_data_rate_name(7M80), \
+ pctt_psdu_data_rate_name(27M2), \
+ pctt_psdu_data_rate_name(31M2)
+TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_6M81);
+TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_7M80);
+TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_27M2);
+TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_31M2);
+#define PCTT_PSDU_DATA_RATE_ENTRY \
+ __field(enum pctt_psdu_data_rate, psdu_data_rate)
+#define PCTT_PSDU_DATA_RATE_ASSIGN \
+ __entry->psdu_data_rate = params->psdu_data_rate
+#define PCTT_PSDU_DATA_RATE_PR_FMT "psdu_data_rate=%s"
+#define PCTT_PSDU_DATA_RATE_PR_ARG \
+ __print_symbolic(__entry->psdu_data_rate, PCTT_PSDU_DATA_RATE_SYMBOLS)
+
+#define pctt_phr_data_rate_name(name) \
+ { PCTT_PHR_DATA_RATE##name, #name }
+#define PCTT_PHR_DATA_RATE_SYMBOLS \
+ pctt_phr_data_rate_name(850k), \
+ pctt_phr_data_rate_name(6M81), \
+TRACE_DEFINE_ENUM(PCTT_PHR_DATA_RATE_850k);
+TRACE_DEFINE_ENUM(PCTT_PHR_DATA_RATE_6M81);
+#define PCTT_PHR_DATA_RATE_ENTRY \
+ __field(enum pctt_phr_data_rate, phr_data_rate)
+#define PCTT_PHR_DATA_RATE_ASSIGN \
+ __entry->phr_data_rate = params->phr_data_rate
+#define PCTT_PHR_DATA_RATE_PR_FMT "phr_data_rate=%s"
+#define PCTT_PHR_DATA_RATE_PR_ARG \
+ __print_symbolic(__entry->phr_data_rate, PCTT_PHR_DATA_RATE_SYMBOLS)
+
+#define pctt_mac_fcs_type_name(name) \
+ { PCTT_MAC_FCS_TYPE_##name, #name }
+#define PCTT_MAC_FCS_TYPE_SYMBOLS \
+ pctt_mac_fcs_type_name(CRC_16), \
+ pctt_mac_fcs_type_name(CRC_32)
+TRACE_DEFINE_ENUM(PCTT_MAC_FCS_TYPE_CRC_16);
+TRACE_DEFINE_ENUM(PCTT_MAC_FCS_TYPE_CRC_32);
+#define PCTT_MAC_FCS_TYPE_ENTRY __field(enum pctt_mac_fcs_type, mac_fcs_type)
+#define PCTT_MAC_FCS_TYPE_ASSIGN __entry->mac_fcs_type = params->mac_fcs_type
+#define PCTT_MAC_FCS_TYPE_PR_FMT "mac_fcs_type=%s"
+#define PCTT_MAC_FCS_TYPE_PR_ARG \
+ __print_symbolic(__entry->mac_fcs_type, PCTT_MAC_FCS_TYPE_SYMBOLS)
+
+/* clang-format off */
+#define pctt_status_ranging_name(name) \
+ { PCTT_STATUS_RANGING_##name, #name }
+#define PCTT_STATUS_RANGING_SYMBOLS \
+ pctt_status_ranging_name(INTERNAL_ERROR), \
+ pctt_status_ranging_name(SUCCESS), \
+ pctt_status_ranging_name(TX_FAILED), \
+ pctt_status_ranging_name(RX_TIMEOUT), \
+ pctt_status_ranging_name(RX_PHY_DEC_FAILED), \
+ pctt_status_ranging_name(RX_PHY_TOA_FAILED), \
+ pctt_status_ranging_name(RX_PHY_STS_FAILED), \
+ pctt_status_ranging_name(RX_MAC_DEC_FAILED), \
+ pctt_status_ranging_name(RX_MAC_IE_DEC_FAILED), \
+ pctt_status_ranging_name(RX_MAC_IE_MISSING)
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_INTERNAL_ERROR);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_SUCCESS);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_TX_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_TIMEOUT);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_STS_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED);
+TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_IE_MISSING);
+#define PCTT_STATUS_RANGING_ENTRY \
+ __field(enum pctt_status_ranging, status_ranging)
+#define PCTT_STATUS_RANGING_ASSIGN __entry->status_ranging = status_ranging
+#define PCTT_STATUS_RANGING_PR_FMT "status_ranging=%s"
+#define PCTT_STATUS_RANGING_PR_ARG \
+ __print_symbolic(__entry->status_ranging, PCTT_STATUS_RANGING_SYMBOLS)
+
+#define pctt_session_state_name(name) \
+ { PCTT_SESSION_STATE_##name, #name }
+#define PCTT_SESSION_STATE_SYMBOLS \
+ pctt_session_state_name(INIT), \
+ pctt_session_state_name(DEINIT), \
+ pctt_session_state_name(ACTIVE), \
+ pctt_session_state_name(IDLE)
+TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_INIT);
+TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_DEINIT);
+TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_ACTIVE);
+TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_IDLE);
+#define PCTT_SESSION_STATE_ENTRY(name) __field(enum pctt_session_state, name)
+#define PCTT_SESSION_STATE_ASSIGN(name) __entry->name = name
+#define PCTT_SESSION_STATE_PR_FMT(name) "name=%s"
+#define PCTT_SESSION_STATE_PR_ARG(name) \
+ __print_symbolic(__entry->name, PCTT_SESSION_STATE_SYMBOLS)
+
+#define pctt_id_name(name) \
+ { PCTT_ID_ATTR_##name, #name }
+#define PCTT_ID_SYMBOLS \
+ pctt_id_name(UNSPEC), \
+ pctt_id_name(PERIODIC_TX), \
+ pctt_id_name(PER_RX), \
+ pctt_id_name(RX), \
+ pctt_id_name(LOOPBACK), \
+ pctt_id_name(SS_TWR), \
+ pctt_id_name(RX)
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_UNSPEC);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_PERIODIC_TX);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_PER_RX);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_RX);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_LOOPBACK);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_SS_TWR);
+TRACE_DEFINE_ENUM(PCTT_ID_ATTR_STOP_TEST);
+#define PCTT_ID_ENTRY __field(enum pctt_id_attrs, cmd_id)
+#define PCTT_ID_ASSIGN __entry->cmd_id = cmd_id
+#define PCTT_ID_PR_FMT "cmd_id=%s"
+#define PCTT_ID_PR_ARG \
+ __print_symbolic(__entry->cmd_id, PCTT_ID_SYMBOLS)
+
+TRACE_EVENT(
+ region_pctt_session_start_test,
+ TP_PROTO(enum pctt_id_attrs cmd_id,
+ const struct pctt_session_params *params),
+ TP_ARGS(cmd_id, params),
+ TP_STRUCT__entry(
+ PCTT_ID_ENTRY
+ PCTT_DEVICE_ROLE_ENTRY
+ __field(__le16, short_addr)
+ __field(__le16, dst_short_addr)
+ __field(int, slot_duration_dtu)
+ __field(int, channel_number)
+ __field(int, preamble_code_index)
+ PCTT_RFRAME_CONFIG_ENTRY
+ PCTT_PRF_MODE_ENTRY
+ PCTT_PREAMBLE_DURATION_ENTRY
+ PCTT_SFD_ID_ENTRY
+ PCTT_NUMBER_OF_STS_SEGMENTS_ENTRY
+ PCTT_PSDU_DATA_RATE_ENTRY
+ PCTT_MAC_FCS_TYPE_ENTRY
+ __field(u8, tx_adaptive_payload_power)
+ __field(uint32_t, sts_index)
+ ),
+ TP_fast_assign(
+ PCTT_ID_ASSIGN;
+ PCTT_DEVICE_ROLE_ASSIGN;
+ __entry->short_addr = params->short_addr;
+ __entry->dst_short_addr = params->dst_short_addr;
+ __entry->slot_duration_dtu = params->slot_duration_dtu;
+ __entry->channel_number = params->channel_number;
+ __entry->preamble_code_index = params->preamble_code_index;
+ PCTT_RFRAME_CONFIG_ASSIGN;
+ PCTT_PRF_MODE_ASSIGN;
+ PCTT_PREAMBLE_DURATION_ASSIGN;
+ PCTT_SFD_ID_ASSIGN;
+ PCTT_NUMBER_OF_STS_SEGMENTS_ASSIGN;
+ PCTT_PSDU_DATA_RATE_ASSIGN;
+ PCTT_MAC_FCS_TYPE_ASSIGN;
+ __entry->tx_adaptive_payload_power =
+ params->tx_adaptive_payload_power;
+ __entry->sts_index = params->sts_index;
+ ),
+ TP_printk(
+ PCTT_ID_PR_FMT " " PCTT_DEVICE_ROLE_PR_FMT " "
+ "short_addr=0x%x "
+ "dst_short_addr=0x%x slot_duration_dtu=%d "
+ "channel_number=%d preamble_code_index=%d "
+ PCTT_RFRAME_CONFIG_PR_FMT " "
+ PCTT_PRF_MODE_PR_FMT " "
+ PCTT_PREAMBLE_DURATION_PR_FMT " "
+ PCTT_SFD_ID_PR_FMT " "
+ PCTT_NUMBER_OF_STS_SEGMENTS_PR_FMT " "
+ PCTT_PSDU_DATA_RATE_PR_FMT " "
+ PCTT_MAC_FCS_TYPE_PR_FMT " "
+ "tx_adaptive_payload_power=%u sts_index=%u",
+ PCTT_ID_PR_ARG, PCTT_DEVICE_ROLE_PR_ARG,
+ __entry->short_addr,
+ __entry->dst_short_addr,
+ __entry->slot_duration_dtu,
+ __entry->channel_number,
+ __entry->preamble_code_index,
+ PCTT_RFRAME_CONFIG_PR_ARG,
+ PCTT_PRF_MODE_PR_ARG,
+ PCTT_PREAMBLE_DURATION_PR_ARG,
+ PCTT_SFD_ID_PR_ARG,
+ PCTT_NUMBER_OF_STS_SEGMENTS_PR_ARG,
+ PCTT_PSDU_DATA_RATE_PR_ARG,
+ PCTT_MAC_FCS_TYPE_PR_ARG,
+ __entry->tx_adaptive_payload_power,
+ __entry->sts_index
+ )
+);
+
+TRACE_EVENT(region_pctt_session_set_state,
+ TP_PROTO(enum pctt_session_state old_state,
+ enum pctt_session_state new_state),
+ TP_ARGS(old_state, new_state),
+ TP_STRUCT__entry(
+ PCTT_SESSION_STATE_ENTRY(old_state)
+ PCTT_SESSION_STATE_ENTRY(new_state)
+ ),
+ TP_fast_assign(
+ PCTT_SESSION_STATE_ASSIGN(old_state);
+ PCTT_SESSION_STATE_ASSIGN(new_state);
+ ),
+ TP_printk(PCTT_SESSION_STATE_PR_FMT(old_state) " "
+ PCTT_SESSION_STATE_PR_FMT(new_state),
+ PCTT_SESSION_STATE_PR_ARG(old_state),
+ PCTT_SESSION_STATE_PR_ARG(new_state))
+);
+
+TRACE_EVENT(region_pctt_report_periodic_tx,
+ TP_PROTO(enum pctt_status_ranging status_ranging),
+ TP_ARGS(status_ranging),
+ TP_STRUCT__entry(
+ PCTT_STATUS_RANGING_ENTRY
+ ),
+ TP_fast_assign(
+ PCTT_STATUS_RANGING_ASSIGN;
+ ),
+ TP_printk(PCTT_STATUS_RANGING_PR_FMT,
+ PCTT_STATUS_RANGING_PR_ARG)
+);
+
+TRACE_EVENT(region_pctt_report_per_rx,
+ TP_PROTO(enum pctt_status_ranging status_ranging,
+ const struct pctt_test_per_rx_results *per_rx),
+ TP_ARGS(status_ranging, per_rx),
+ TP_STRUCT__entry(
+ PCTT_STATUS_RANGING_ENTRY
+ __field(u32, attempts)
+ __field(u32, acq_detect)
+ __field(u32, acq_reject)
+ __field(u32, rx_fail)
+ __field(u32, sync_cir_ready)
+ __field(u32, sfd_fail)
+ __field(u32, sfd_found)
+ __field(u32, phr_dec_error)
+ __field(u32, phr_bit_error)
+ __field(u32, psdu_dec_error)
+ __field(u32, psdu_bit_error)
+ __field(u32, sts_found)
+ __field(u32, eof)
+ __field(u8, rssi)
+ ),
+ TP_fast_assign(
+ PCTT_STATUS_RANGING_ASSIGN;
+ __entry->attempts = per_rx->attempts;
+ __entry->acq_detect = per_rx->acq_detect;
+ __entry->acq_reject = per_rx->acq_reject;
+ __entry->rx_fail = per_rx->rx_fail;
+ __entry->sync_cir_ready = per_rx->sync_cir_ready;
+ __entry->sfd_fail = per_rx->sfd_fail;
+ __entry->sfd_found = per_rx->sfd_found;
+ __entry->phr_dec_error = per_rx->phr_dec_error;
+ __entry->phr_bit_error = per_rx->phr_bit_error;
+ __entry->psdu_dec_error = per_rx->psdu_dec_error;
+ __entry->psdu_bit_error = per_rx->psdu_bit_error;
+ __entry->sts_found = per_rx->sts_found;
+ __entry->eof = per_rx->eof;
+ __entry->rssi = per_rx->rssi;
+ ),
+ TP_printk(PCTT_STATUS_RANGING_PR_FMT " "
+ "attempts=%u acq_detect=%u acq_reject=%u "
+ "rx_fail=%u sync_cir_ready=%u sfd_fail=%u "
+ "sfd_found=%u phr_dec_error=%u phr_bit_error=%u "
+ "psdu_dec_error=%u psdu_bit_error=%u sts_found=%u "
+ "eof=%u rssi=%u ",
+ PCTT_STATUS_RANGING_PR_ARG, __entry->attempts,
+ __entry->acq_detect, __entry->acq_reject,
+ __entry->rx_fail, __entry->sync_cir_ready,
+ __entry->sfd_fail, __entry->sfd_found,
+ __entry->phr_dec_error, __entry->phr_bit_error,
+ __entry->psdu_dec_error, __entry->psdu_bit_error,
+ __entry->sts_found, __entry->eof, __entry->rssi)
+);
+
+TRACE_EVENT(region_pctt_report_rx,
+ TP_PROTO(enum pctt_status_ranging status_ranging,
+ const struct pctt_test_rx_results *rx),
+ TP_ARGS(status_ranging, rx),
+ TP_STRUCT__entry(
+ PCTT_STATUS_RANGING_ENTRY
+ __field(u32, rx_done_ts_int)
+ __field(u16, rx_done_ts_frac)
+ __field(s16, aoa_azimuth)
+ __field(s16, aoa_elevation)
+ __field(u8, toa_gap)
+ __field(u16, phr)
+ __field(u8, rssi)
+ __field(u16, psdu_data_len)
+ __dynamic_array(u8, psdu_data, rx->psdu_data_len)
+ ),
+ TP_fast_assign(
+ PCTT_STATUS_RANGING_ASSIGN;
+ __entry->rx_done_ts_int = rx->rx_done_ts_int;
+ __entry->rx_done_ts_frac = rx->rx_done_ts_frac;
+ __entry->aoa_azimuth = rx->aoa_azimuth;
+ __entry->aoa_elevation = rx->aoa_elevation;
+ __entry->toa_gap = rx->toa_gap;
+ __entry->phr = rx->phr;
+ __entry->rssi = rx->rssi;
+ __entry->psdu_data_len = rx->psdu_data_len;
+ memcpy(__get_dynamic_array(psdu_data), rx->psdu_data,
+ __get_dynamic_array_len(psdu_data));
+
+ ),
+ TP_printk(PCTT_STATUS_RANGING_PR_FMT " "
+ "rx_done_ts_int=%u rx_done_ts_frac=%u "
+ "aoa_azimuth=%d aoa_elevation=%d toa_gap=%u "
+ "phr=%u rssi=%u psdu_data_len=%u psdu_data=%s",
+ PCTT_STATUS_RANGING_PR_ARG, __entry->rx_done_ts_int,
+ __entry->rx_done_ts_frac, __entry->aoa_azimuth,
+ __entry->aoa_elevation, __entry->toa_gap, __entry->phr,
+ __entry->rssi, __entry->psdu_data_len,
+ __print_hex(__get_dynamic_array(psdu_data),
+ __get_dynamic_array_len(psdu_data)))
+);
+
+TRACE_EVENT(region_pctt_report_loopback,
+ TP_PROTO(enum pctt_status_ranging status_ranging),
+ TP_ARGS(status_ranging),
+ TP_STRUCT__entry(
+ PCTT_STATUS_RANGING_ENTRY
+ ),
+ TP_fast_assign(
+ PCTT_STATUS_RANGING_ASSIGN;
+ ),
+ TP_printk(PCTT_STATUS_RANGING_PR_FMT,
+ PCTT_STATUS_RANGING_PR_ARG)
+);
+
+TRACE_EVENT(region_pctt_report_ss_twr,
+ TP_PROTO(enum pctt_status_ranging status_ranging,
+ const struct pctt_test_ss_twr_results *ss_twr),
+ TP_ARGS(status_ranging, ss_twr),
+ TP_STRUCT__entry(
+ PCTT_STATUS_RANGING_ENTRY
+ __field(u32, measurement_rctu)
+ __field(s16, pdoa_azimuth_deg_q7)
+ __field(s16, pdoa_elevation_deg_q7)
+ __field(u8, rssi)
+ __field(s16, aoa_azimuth_deg_q7)
+ __field(s16, aoa_elevation_deg_q7)
+ ),
+ TP_fast_assign(
+ PCTT_STATUS_RANGING_ASSIGN;
+ __entry->measurement_rctu = ss_twr->measurement_rctu;
+ __entry->pdoa_azimuth_deg_q7 = ss_twr->pdoa_azimuth_deg_q7;
+ __entry->pdoa_elevation_deg_q7 = ss_twr->pdoa_elevation_deg_q7;
+ __entry->rssi = ss_twr->rssi;
+ __entry->aoa_azimuth_deg_q7 = ss_twr->aoa_azimuth_deg_q7;
+ __entry->aoa_elevation_deg_q7 = ss_twr->aoa_elevation_deg_q7;
+ ),
+ TP_printk(PCTT_STATUS_RANGING_PR_FMT " measurement_rctu=%u "
+ "pdoa_azimuth_deg_q7=%u pdoa_elevation_deg_q7=%u "
+ "rssi=%u aoa_azimuth_deg_q7=%u aoa_elevation_deg_q7=%u",
+ PCTT_STATUS_RANGING_PR_ARG, __entry->measurement_rctu,
+ __entry->pdoa_azimuth_deg_q7, __entry->pdoa_elevation_deg_q7,
+ __entry->rssi, __entry->aoa_azimuth_deg_q7, __entry->aoa_elevation_deg_q7)
+);
+
+TRACE_EVENT(region_pctt_report_nla_put_failure,
+ TP_PROTO(enum pctt_id_attrs cmd_id),
+ TP_ARGS(cmd_id),
+ TP_STRUCT__entry(
+ PCTT_ID_ENTRY
+ ),
+ TP_fast_assign(
+ PCTT_ID_ASSIGN;
+ ),
+ TP_printk(PCTT_ID_PR_FMT,
+ PCTT_ID_PR_ARG)
+);
+/* clang-format on */
+
+#endif /* !PCTT_TRACE_H || TRACE_HEADER_MULTI_READ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE pctt_trace
+#include <trace/define_trace.h>
diff --git a/mac/regions.c b/mac/regions.c
new file mode 100644
index 0000000..26ef8d8
--- /dev/null
+++ b/mac/regions.c
@@ -0,0 +1,229 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+
+#include "mcps802154_i.h"
+#include "trace.h"
+
+static LIST_HEAD(registered_regions);
+static DEFINE_MUTEX(registered_regions_lock);
+
+int mcps802154_region_register(struct mcps802154_region_ops *region_ops)
+{
+ struct mcps802154_region_ops *ops;
+ int r = 0;
+
+ if (WARN_ON(!region_ops || !region_ops->owner || !region_ops->name ||
+ !region_ops->open || !region_ops->close ||
+ !region_ops->get_access))
+ return -EINVAL;
+
+ mutex_lock(&registered_regions_lock);
+
+ list_for_each_entry (ops, &registered_regions, registered_entry) {
+ if (WARN_ON(strcmp(ops->name, region_ops->name) == 0)) {
+ r = -EBUSY;
+ goto unlock;
+ }
+ }
+
+ list_add(&region_ops->registered_entry, &registered_regions);
+
+unlock:
+ mutex_unlock(&registered_regions_lock);
+
+ return r;
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_register);
+
+void mcps802154_region_unregister(struct mcps802154_region_ops *region_ops)
+{
+ mutex_lock(&registered_regions_lock);
+ list_del(&region_ops->registered_entry);
+ mutex_unlock(&registered_regions_lock);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_unregister);
+
+struct mcps802154_region *
+mcps802154_region_open(struct mcps802154_llhw *llhw, const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_region_ops *ops;
+ struct mcps802154_region *region;
+ bool found = false;
+
+ mutex_lock(&registered_regions_lock);
+
+ list_for_each_entry (ops, &registered_regions, registered_entry) {
+ if (strcmp(ops->name, name) == 0) {
+ if (try_module_get(ops->owner))
+ found = true;
+ break;
+ }
+ }
+
+ mutex_unlock(&registered_regions_lock);
+
+ if (!found)
+ return NULL;
+
+ region = ops->open(llhw);
+ if (!region) {
+ module_put(ops->owner);
+ return NULL;
+ }
+
+ region->ops = ops;
+ if (mcps802154_region_set_parameters(llhw, region, params_attr,
+ extack)) {
+ ops->close(region);
+ return NULL;
+ }
+
+ return region;
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_open);
+
+void mcps802154_region_close(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region)
+{
+ const struct mcps802154_region_ops *ops;
+
+ ops = region->ops;
+ ops->close(region);
+ module_put(ops->owner);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_close);
+
+void mcps802154_region_notify_stop(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region)
+{
+ if (!region->ops->notify_stop)
+ return;
+
+ trace_region_notify_stop(region);
+ region->ops->notify_stop(region);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_notify_stop);
+
+int mcps802154_region_set_parameters(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ if (!params_attr)
+ return 0;
+
+ if (!region->ops->set_parameters)
+ return -EOPNOTSUPP;
+
+ return region->ops->set_parameters(region, params_attr, extack);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_set_parameters);
+
+int mcps802154_region_call(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region, u32 call_id,
+ const struct nlattr *params_attr,
+ const struct genl_info *info)
+{
+ if (!region->ops->call)
+ return -EOPNOTSUPP;
+
+ return region->ops->call(region, call_id, params_attr, info);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_call);
+
+int mcps802154_region_get_demand(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ u32 next_timestamp_dtu,
+ struct mcps802154_region_demand *demand)
+{
+ int r;
+
+ if (!region->ops->get_demand)
+ return -EOPNOTSUPP;
+
+ trace_region_get_demand(region, next_timestamp_dtu);
+ r = region->ops->get_demand(region, next_timestamp_dtu, demand);
+ trace_region_get_demand_return(region, demand, r);
+
+ return r;
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_get_demand);
+
+void mcps802154_region_xmit_resume(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ int queue_index)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ ieee802154_wake_queue(local->hw);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_xmit_resume);
+
+void mcps802154_region_xmit_done(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ struct sk_buff *skb, bool ok)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ if (ok) {
+ ieee802154_xmit_complete(local->hw, skb, false);
+ } else {
+ ieee802154_wake_queue(local->hw);
+ dev_kfree_skb_any(skb);
+ }
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_xmit_done);
+
+void mcps802154_region_rx_skb(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region,
+ struct sk_buff *skb, u8 lqi)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ ieee802154_rx_irqsafe(local->hw, skb, lqi);
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_rx_skb);
+
+int mcps802154_region_deferred(struct mcps802154_llhw *llhw,
+ struct mcps802154_region *region)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ if (local->fproc.deferred && local->fproc.deferred != region)
+ return -EINVAL;
+
+ local->fproc.deferred = region;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mcps802154_region_deferred);
diff --git a/mac/schedule.c b/mac/schedule.c
new file mode 100644
index 0000000..1ca0bfa
--- /dev/null
+++ b/mac/schedule.c
@@ -0,0 +1,252 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+
+#include "mcps802154_i.h"
+#include "trace.h"
+
+void mcps802154_schedule_clear(struct mcps802154_local *local)
+{
+ if (local->ca.schedule.regions) {
+ local->ca.schedule.n_regions = 0;
+ kfree(local->ca.schedule.regions);
+ local->ca.schedule.regions = NULL;
+ }
+}
+
+int mcps802154_schedule_update(struct mcps802154_local *local,
+ u32 next_timestamp_dtu)
+{
+ struct mcps802154_schedule_update_local sulocal;
+ struct mcps802154_schedule_update *su = &sulocal.schedule_update;
+ struct mcps802154_schedule *sched = &local->ca.schedule;
+ struct mcps802154_scheduler *scheduler;
+ u32 expected_start_timestamp_dtu;
+ int r;
+
+ trace_schedule_update(local, next_timestamp_dtu);
+
+ /* If no schedule at all, set sane values. */
+ if (sched->n_regions == 0) {
+ sched->start_timestamp_dtu = next_timestamp_dtu;
+ sched->duration_dtu = 0;
+ expected_start_timestamp_dtu = next_timestamp_dtu;
+ } else if (sched->duration_dtu == 0) {
+ expected_start_timestamp_dtu = next_timestamp_dtu;
+ } else {
+ expected_start_timestamp_dtu =
+ sched->start_timestamp_dtu + sched->duration_dtu;
+ }
+ sched->current_index = 0;
+
+ /* Call scheduler. */
+ su->expected_start_timestamp_dtu = expected_start_timestamp_dtu;
+ su->start_timestamp_dtu = sched->start_timestamp_dtu;
+ su->duration_dtu = sched->duration_dtu;
+ su->n_regions = sched->n_regions;
+ sulocal.local = local;
+ scheduler = local->ca.scheduler;
+ r = scheduler->ops->update_schedule(scheduler, su, next_timestamp_dtu);
+ if (r) {
+ if (r != -ENOENT)
+ trace_update_schedule_error(local, su,
+ next_timestamp_dtu);
+ mcps802154_schedule_clear(local);
+ return r;
+ }
+
+ /* Check we have a valid schedule. */
+ if (sched->n_regions == 0 ||
+ is_before_dtu(sched->start_timestamp_dtu,
+ expected_start_timestamp_dtu)) {
+ mcps802154_schedule_clear(local);
+ return -EOPNOTSUPP;
+ }
+
+ trace_schedule_update_done(local, sched);
+
+ return 1;
+}
+
+int mcps802154_schedule_set_start(
+ const struct mcps802154_schedule_update *schedule_update,
+ u32 start_timestamp_dtu)
+{
+ struct mcps802154_schedule_update_local *sulocal =
+ schedule_update_to_local(schedule_update);
+ struct mcps802154_schedule_update *su = &sulocal->schedule_update;
+ struct mcps802154_schedule *sched = &sulocal->local->ca.schedule;
+
+ if (is_before_dtu(start_timestamp_dtu,
+ schedule_update->expected_start_timestamp_dtu))
+ return -EINVAL;
+
+ su->start_timestamp_dtu = sched->start_timestamp_dtu =
+ start_timestamp_dtu;
+
+ return 0;
+}
+EXPORT_SYMBOL(mcps802154_schedule_set_start);
+
+int mcps802154_schedule_recycle(
+ const struct mcps802154_schedule_update *schedule_update,
+ size_t n_keeps, int last_region_duration_dtu)
+{
+ struct mcps802154_schedule_update_local *sulocal =
+ schedule_update_to_local(schedule_update);
+ struct mcps802154_schedule_update *su = &sulocal->schedule_update;
+ struct mcps802154_schedule *sched = &sulocal->local->ca.schedule;
+ struct mcps802154_schedule_region *last_sched_region;
+
+ if (n_keeps > sched->n_regions)
+ return -EINVAL;
+
+ if (n_keeps == 0 &&
+ last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE)
+ return -EINVAL;
+
+ /* Change the number of currently used regions. */
+ su->n_regions = sched->n_regions = n_keeps;
+
+ /* Update last region. */
+ last_sched_region =
+ sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL;
+ if (last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE)
+ last_sched_region->duration_dtu = last_region_duration_dtu;
+
+ /* Update schedule duration. */
+ if (!last_sched_region || last_sched_region->duration_dtu == 0) {
+ su->duration_dtu = sched->duration_dtu = 0;
+ } else {
+ su->duration_dtu = sched->duration_dtu =
+ last_sched_region->start_dtu +
+ last_sched_region->duration_dtu;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mcps802154_schedule_recycle);
+
+int mcps802154_schedule_add_region(
+ const struct mcps802154_schedule_update *schedule_update,
+ struct mcps802154_region *region, int start_dtu, int duration_dtu,
+ bool once)
+{
+ struct mcps802154_schedule_update_local *sulocal =
+ schedule_update_to_local(schedule_update);
+ struct mcps802154_schedule_update *su = &sulocal->schedule_update;
+ struct mcps802154_schedule *sched = &sulocal->local->ca.schedule;
+ struct mcps802154_schedule_region *last_sched_region =
+ sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL;
+ struct mcps802154_schedule_region *sched_region, *new_sched_regions;
+
+ if (start_dtu < 0 || duration_dtu < 0)
+ return -EINVAL;
+
+ /* Can not add a region after an endless region. */
+ if (last_sched_region && last_sched_region->duration_dtu == 0)
+ return -EINVAL;
+
+ /* Regions can not overlap. */
+ if (last_sched_region &&
+ start_dtu < last_sched_region->start_dtu +
+ last_sched_region->duration_dtu)
+ return -EINVAL;
+
+ if (!region || !region->ops || !region->ops->get_access)
+ return -EINVAL;
+
+ /* Add to schedule. */
+ new_sched_regions = krealloc(
+ sched->regions,
+ sizeof(sched->regions[0]) * (sched->n_regions + 1), GFP_KERNEL);
+ if (!new_sched_regions)
+ return -ENOMEM;
+
+ /* Fill new added schedule region. */
+ sched_region = &new_sched_regions[sched->n_regions];
+ sched_region->start_dtu = start_dtu;
+ sched_region->duration_dtu = duration_dtu;
+ sched_region->region = region;
+ sched_region->once = once;
+
+ sched->regions = new_sched_regions;
+ su->n_regions = sched->n_regions = sched->n_regions + 1;
+
+ /* Update schedule duration. */
+ if (duration_dtu == 0) {
+ su->duration_dtu = sched->duration_dtu = 0;
+ } else {
+ su->duration_dtu = sched->duration_dtu =
+ start_dtu + duration_dtu;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mcps802154_schedule_add_region);
+
+void mcps802154_reschedule(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ mcps802154_ca_may_reschedule(local);
+}
+EXPORT_SYMBOL(mcps802154_reschedule);
+
+void mcps802154_schedule_invalidate(struct mcps802154_llhw *llhw)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ if (likely(local->started))
+ mcps802154_ca_invalidate_schedule(local);
+}
+EXPORT_SYMBOL(mcps802154_schedule_invalidate);
+
+int mcps802154_schedule_get_regions(struct mcps802154_llhw *llhw,
+ struct list_head **regions)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+
+ *regions = &local->ca.regions;
+
+ return local->ca.n_regions;
+}
+EXPORT_SYMBOL(mcps802154_schedule_get_regions);
+
+int mcps802154_schedule_get_next_demands(
+ struct mcps802154_llhw *llhw, const struct mcps802154_region *region,
+ u32 timestamp_dtu, int duration_dtu, int delta_dtu,
+ struct mcps802154_region_demand *demands)
+{
+ struct mcps802154_local *local = llhw_to_local(llhw);
+ struct mcps802154_scheduler *scheduler = local->ca.scheduler;
+
+ if (!scheduler->ops->get_next_demands)
+ return -EOPNOTSUPP;
+ return scheduler->ops->get_next_demands(scheduler, region,
+ timestamp_dtu, duration_dtu,
+ delta_dtu, demands);
+}
+EXPORT_SYMBOL(mcps802154_schedule_get_next_demands);
diff --git a/mac/schedule.h b/mac/schedule.h
new file mode 100644
index 0000000..f17feae
--- /dev/null
+++ b/mac/schedule.h
@@ -0,0 +1,122 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef MCPS802154_SCHEDULE_H
+#define MCPS802154_SCHEDULE_H
+
+#include <linux/kernel.h>
+#include <net/mcps802154_schedule.h>
+
+struct mcps802154_local;
+
+/**
+ * struct mcps802154_schedule_region - Region as defined in the schedule.
+ */
+struct mcps802154_schedule_region {
+ /**
+ * @region: Pointer to the open region.
+ */
+ struct mcps802154_region *region;
+ /**
+ * @start_dtu: Region start from the start of the schedule.
+ */
+ int start_dtu;
+ /**
+ * @duration_dtu: Region duration or 0 for endless region.
+ */
+ int duration_dtu;
+ /**
+ * @once: Schedule the region once, ignoring the remaining region duration.
+ */
+ bool once;
+};
+
+/**
+ * struct mcps802154_schedule - Schedule.
+ */
+struct mcps802154_schedule {
+ /**
+ * @start_timestamp_dtu: Date of the schedule start, might be too far in
+ * the past for endless schedule.
+ */
+ u32 start_timestamp_dtu;
+ /**
+ * @duration_dtu: Schedule duration or 0 for endless schedule.
+ */
+ int duration_dtu;
+ /**
+ * @regions: Table of regions.
+ */
+ struct mcps802154_schedule_region *regions;
+ /**
+ * @n_regions: Number of regions in the schedule.
+ */
+ size_t n_regions;
+ /**
+ * @current_index: Index of the current region.
+ */
+ int current_index;
+};
+
+/**
+ * struct mcps802154_schedule_update_local - Private part of a schedule
+ * update context.
+ */
+struct mcps802154_schedule_update_local {
+ /**
+ * @schedule_update: Public part.
+ */
+ struct mcps802154_schedule_update schedule_update;
+ /**
+ * @local: MCPS private data.
+ */
+ struct mcps802154_local *local;
+};
+
+static inline struct mcps802154_schedule_update_local *schedule_update_to_local(
+ const struct mcps802154_schedule_update *schedule_update)
+{
+ return container_of(schedule_update,
+ struct mcps802154_schedule_update_local,
+ schedule_update);
+}
+
+/**
+ * mcps802154_schedule_clear() - Clear schedule and release regions.
+ * @local: MCPS private data.
+ */
+void mcps802154_schedule_clear(struct mcps802154_local *local);
+
+/**
+ * mcps802154_schedule_update() - Initialize or update the schedule.
+ * @local: MCPS private data.
+ * @next_timestamp_dtu: Date of next access opportunity.
+ *
+ * Request the scheduler to update the schedule.
+ *
+ * Return: 1 or error.
+ */
+int mcps802154_schedule_update(struct mcps802154_local *local,
+ u32 next_timestamp_dtu);
+
+#endif /* NET_MCPS802154_SCHEDULE_H */
diff --git a/mac/schedulers.c b/mac/schedulers.c
new file mode 100644
index 0000000..f938c58
--- /dev/null
+++ b/mac/schedulers.c
@@ -0,0 +1,155 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+
+#include "mcps802154_i.h"
+#include "schedulers.h"
+
+static LIST_HEAD(registered_schedulers);
+static DEFINE_MUTEX(registered_schedulers_lock);
+
+int mcps802154_scheduler_register(struct mcps802154_scheduler_ops *scheduler_ops)
+{
+ struct mcps802154_scheduler_ops *ops;
+ int r = 0;
+
+ if (WARN_ON(!scheduler_ops || !scheduler_ops->owner ||
+ !scheduler_ops->name || !scheduler_ops->open ||
+ !scheduler_ops->close || !scheduler_ops->update_schedule))
+ return -EINVAL;
+
+ mutex_lock(&registered_schedulers_lock);
+
+ list_for_each_entry (ops, &registered_schedulers, registered_entry) {
+ if (WARN_ON(strcmp(ops->name, scheduler_ops->name) == 0)) {
+ r = -EBUSY;
+ goto unlock;
+ }
+ }
+
+ list_add(&scheduler_ops->registered_entry, &registered_schedulers);
+
+unlock:
+ mutex_unlock(&registered_schedulers_lock);
+
+ return r;
+}
+EXPORT_SYMBOL(mcps802154_scheduler_register);
+
+void mcps802154_scheduler_unregister(
+ struct mcps802154_scheduler_ops *scheduler_ops)
+{
+ mutex_lock(&registered_schedulers_lock);
+ list_del(&scheduler_ops->registered_entry);
+ mutex_unlock(&registered_schedulers_lock);
+}
+EXPORT_SYMBOL(mcps802154_scheduler_unregister);
+
+struct mcps802154_scheduler *
+mcps802154_scheduler_open(struct mcps802154_local *local, const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ struct mcps802154_scheduler_ops *ops;
+ struct mcps802154_scheduler *scheduler;
+ bool found = false;
+
+ mutex_lock(&registered_schedulers_lock);
+
+ list_for_each_entry (ops, &registered_schedulers, registered_entry) {
+ if (strcmp(ops->name, name) == 0) {
+ if (try_module_get(ops->owner))
+ found = true;
+ break;
+ }
+ }
+
+ mutex_unlock(&registered_schedulers_lock);
+
+ if (!found)
+ return NULL;
+
+ scheduler = ops->open(&local->llhw);
+ if (!scheduler) {
+ module_put(ops->owner);
+ return NULL;
+ }
+
+ scheduler->ops = ops;
+ if (mcps802154_scheduler_set_parameters(scheduler, params_attr,
+ extack)) {
+ ops->close(scheduler);
+ return NULL;
+ }
+
+ return scheduler;
+}
+
+void mcps802154_scheduler_close(struct mcps802154_scheduler *scheduler)
+{
+ const struct mcps802154_scheduler_ops *ops;
+
+ ops = scheduler->ops;
+ ops->close(scheduler);
+ module_put(ops->owner);
+}
+
+void mcps802154_scheduler_notify_stop(struct mcps802154_scheduler *scheduler)
+{
+ const struct mcps802154_scheduler_ops *ops;
+
+ if (!scheduler)
+ return;
+
+ ops = scheduler->ops;
+ if (ops->notify_stop)
+ ops->notify_stop(scheduler);
+}
+
+int mcps802154_scheduler_set_parameters(struct mcps802154_scheduler *scheduler,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack)
+{
+ if (!params_attr)
+ return 0;
+
+ if (!scheduler->ops->set_parameters)
+ return -EOPNOTSUPP;
+
+ return scheduler->ops->set_parameters(scheduler, params_attr, extack);
+}
+
+int mcps802154_scheduler_call(struct mcps802154_scheduler *scheduler,
+ u32 call_id, const struct nlattr *params_attr,
+ const struct genl_info *info)
+{
+ if (!scheduler->ops->call)
+ return -EOPNOTSUPP;
+
+ return scheduler->ops->call(scheduler, call_id, params_attr, info);
+}
diff --git a/mac/schedulers.h b/mac/schedulers.h
new file mode 100644
index 0000000..af34d07
--- /dev/null
+++ b/mac/schedulers.h
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef NET_MCPS802154_SCHEDULERS_H
+#define NET_MCPS802154_SCHEDULERS_H
+
+#include <net/netlink.h>
+
+struct mcps802154_local;
+struct mcps802154_scheduler;
+
+/**
+ * mcps802154_scheduler_open() - Open a scheduler, and set parameters.
+ * @local: MCPS private data.
+ * @name: Name of scheduler to open.
+ * @params_attr: Nested attribute containing scheduler parameters, may be NULL.
+ * @extack: Extended ACK report structure.
+ *
+ * Return: The open scheduler or NULL on error.
+ */
+struct mcps802154_scheduler *
+mcps802154_scheduler_open(struct mcps802154_local *local, const char *name,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_scheduler_close() - Close a scheduler.
+ * @scheduler: Pointer to the scheduler.
+ */
+void mcps802154_scheduler_close(struct mcps802154_scheduler *scheduler);
+
+/**
+ * mcps802154_scheduler_notify_stop() - Notify a scheduler that device has been
+ * stopped.
+ * @scheduler: Pointer to the scheduler.
+ */
+void mcps802154_scheduler_notify_stop(struct mcps802154_scheduler *scheduler);
+
+/**
+ * mcps802154_scheduler_set_parameters() - Set parameters of an open scheduler.
+ * @scheduler: Pointer to the scheduler.
+ * @params_attr: Nested attribute containing scheduler parameters, may be NULL.
+ * @extack: Extended ACK report structure.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_scheduler_set_parameters(struct mcps802154_scheduler *scheduler,
+ const struct nlattr *params_attr,
+ struct netlink_ext_ack *extack);
+
+/**
+ * mcps802154_scheduler_call() - Call scheduler specific procedure.
+ * @scheduler: Pointer to the scheduler.
+ * @call_id: Identifier of the procedure, scheduler specific.
+ * @params_attr: Nested attribute containing procedure parameters.
+ * @info: Request information.
+ *
+ * Return: 0 or error.
+ */
+int mcps802154_scheduler_call(struct mcps802154_scheduler *scheduler,
+ u32 call_id, const struct nlattr *params_attr,
+ const struct genl_info *info);
+
+#endif /* NET_MCPS802154_SCHEDULERS_H */
diff --git a/mac/trace.h b/mac/trace.h
new file mode 100644
index 0000000..3808554
--- /dev/null
+++ b/mac/trace.h
@@ -0,0 +1,1108 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mcps802154
+
+#if !defined(TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define TRACE_H
+
+#include <linux/tracepoint.h>
+#include "mcps802154_i.h"
+#include "idle_region.h"
+
+/* clang-format off */
+
+#define LOCAL_ENTRY __field(int, hw_idx)
+#define LOCAL_ASSIGN __entry->hw_idx = local->hw_idx
+#define LOCAL_PR_FMT "hw%d"
+#define LOCAL_PR_ARG __entry->hw_idx
+
+#define mcps802154_tx_frame_config_name(name) \
+ { \
+ MCPS802154_TX_FRAME_CONFIG_##name, #name \
+ }
+#define MCPS802154_TX_FRAME_CONFIG_NAMES \
+ mcps802154_tx_frame_config_name(TIMESTAMP_DTU), \
+ mcps802154_tx_frame_config_name(CCA), \
+ mcps802154_tx_frame_config_name(RANGING), \
+ mcps802154_tx_frame_config_name(KEEP_RANGING_CLOCK), \
+ mcps802154_tx_frame_config_name(RANGING_PDOA), \
+ mcps802154_tx_frame_config_name(SP1), \
+ mcps802154_tx_frame_config_name(SP2), \
+ mcps802154_tx_frame_config_name(SP3), \
+ mcps802154_tx_frame_config_name(STS_MODE_MASK), \
+ mcps802154_tx_frame_config_name(RANGING_ROUND)
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_CCA);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP1);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP2);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP3);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK);
+TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND);
+
+#define mcps802154_rx_frame_config_name(name) \
+ { \
+ MCPS802154_RX_FRAME_CONFIG_##name, #name \
+ }
+#define MCPS802154_RX_FRAME_CONFIG_NAMES \
+ mcps802154_rx_frame_config_name(TIMESTAMP_DTU), \
+ mcps802154_rx_frame_config_name(AACK), \
+ mcps802154_rx_frame_config_name(RANGING), \
+ mcps802154_rx_frame_config_name(KEEP_RANGING_CLOCK), \
+ mcps802154_rx_frame_config_name(RANGING_PDOA), \
+ mcps802154_rx_frame_config_name(SP1), \
+ mcps802154_rx_frame_config_name(SP2), \
+ mcps802154_rx_frame_config_name(SP3), \
+ mcps802154_rx_frame_config_name(STS_MODE_MASK), \
+ mcps802154_rx_frame_config_name(RANGING_ROUND)
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_AACK);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP1);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP2);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP3);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND);
+
+#define mcps802154_rx_error_name(name) \
+ { \
+ MCPS802154_RX_ERROR_##name, #name \
+ }
+#define MCPS802154_RX_ERROR_NAMES \
+ mcps802154_rx_error_name(NONE), \
+ mcps802154_rx_error_name(TIMEOUT), \
+ mcps802154_rx_error_name(BAD_CKSUM), \
+ mcps802154_rx_error_name(UNCORRECTABLE), \
+ mcps802154_rx_error_name(FILTERED), \
+ mcps802154_rx_error_name(SFD_TIMEOUT), \
+ mcps802154_rx_error_name(PHR_DECODE), \
+ mcps802154_rx_error_name(OTHER)
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_NONE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_TIMEOUT);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_BAD_CKSUM);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_UNCORRECTABLE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_FILTERED);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_SFD_TIMEOUT);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_PHR_DECODE);
+TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_OTHER);
+
+#define ieee802154_afilt_name(name) \
+ { \
+ IEEE802154_AFILT_##name, #name \
+ }
+#define IEEE802154_AFILT_NAMES \
+ ieee802154_afilt_name(SADDR_CHANGED), \
+ ieee802154_afilt_name(IEEEADDR_CHANGED), \
+ ieee802154_afilt_name(PANID_CHANGED), \
+ ieee802154_afilt_name(PANC_CHANGED)
+TRACE_DEFINE_ENUM(IEEE802154_AFILT_SADDR_CHANGED);
+TRACE_DEFINE_ENUM(IEEE802154_AFILT_IEEEADDR_CHANGED);
+TRACE_DEFINE_ENUM(IEEE802154_AFILT_PANID_CHANGED);
+TRACE_DEFINE_ENUM(IEEE802154_AFILT_PANC_CHANGED);
+
+#define RX_FRAME_INFO_FLAGS_ENTRY __field(u16, flags)
+#define RX_FRAME_INFO_FLAGS_ASSIGN __entry->flags = info->flags
+#define RX_FRAME_INFO_FLAGS_PR_FMT "flags=%s"
+#define rx_frame_info_name(name) \
+ { \
+ MCPS802154_RX_FRAME_INFO_##name, #name \
+ }
+#define RX_FRAME_INFO_FLAGS_NAMES \
+ rx_frame_info_name(TIMESTAMP_DTU), \
+ rx_frame_info_name(TIMESTAMP_RCTU), \
+ rx_frame_info_name(LQI), \
+ rx_frame_info_name(RSSI), \
+ rx_frame_info_name(RANGING_FOM), \
+ rx_frame_info_name(RANGING_OFFSET), \
+ rx_frame_info_name(RANGING_PDOA), \
+ rx_frame_info_name(RANGING_PDOA_FOM), \
+ rx_frame_info_name(RANGING_STS_TIMESTAMP_RCTU), \
+ rx_frame_info_name(RANGING_STS_FOM), \
+ rx_frame_info_name(AACK)
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_LQI);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RSSI);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_FOM);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_OFFSET);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_PDOA);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM);
+TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_AACK);
+#define RX_FRAME_INFO_FLAGS_PR_ARG \
+ __print_flags(__entry->flags, "|", RX_FRAME_INFO_FLAGS_NAMES)
+
+#define RX_FRAME_INFO_ENTRY \
+ __field(u32, timestamp_dtu) \
+ __field(u64, timestamp_rctu) \
+ __field(int, frame_duration_dtu) \
+ __field(u8, ranging_sts_fom0) \
+ RX_FRAME_INFO_FLAGS_ENTRY
+#define RX_FRAME_INFO_ASSIGN \
+ __entry->timestamp_dtu = info->timestamp_dtu; \
+ __entry->timestamp_rctu = info->timestamp_rctu; \
+ __entry->frame_duration_dtu = info->frame_duration_dtu; \
+ __entry->ranging_sts_fom0 = info->ranging_sts_fom[0]; \
+ RX_FRAME_INFO_FLAGS_ASSIGN
+#define RX_FRAME_INFO_PR_FMT \
+ "timestamp_dtu=0x%08x timestamp_rctu=0x%llx frame_duration_dtu=%d " \
+ "ranging_sts_fom[0]=0x%02x " \
+ RX_FRAME_INFO_FLAGS_PR_FMT
+#define RX_FRAME_INFO_PR_ARG \
+ __entry->timestamp_dtu, \
+ __entry->timestamp_rctu, \
+ __entry->frame_duration_dtu, \
+ __entry->ranging_sts_fom0, \
+ RX_FRAME_INFO_FLAGS_PR_ARG
+
+#define KEY_ENTRY __string(key, key)
+#define KEY_ASSIGN __assign_str(key, key)
+#define KEY_PR_FMT "key=%s"
+#define KEY_PR_ARG __get_str(key)
+
+DECLARE_EVENT_CLASS(local_only_evt,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ ),
+ TP_printk(LOCAL_PR_FMT, LOCAL_PR_ARG)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_return_void,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(llhw_return_int,
+ TP_PROTO(const struct mcps802154_local *local, int ret),
+ TP_ARGS(local, ret),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, ret)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->ret = ret;
+ ),
+ TP_printk(LOCAL_PR_FMT " returned=%d", LOCAL_PR_ARG, __entry->ret)
+ );
+
+TRACE_EVENT(llhw_return_rx_frame,
+ TP_PROTO(const struct mcps802154_local *local, int ret,
+ const struct mcps802154_rx_frame_info *info),
+ TP_ARGS(local, ret, info),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, ret)
+ RX_FRAME_INFO_ENTRY
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->ret = ret;
+ RX_FRAME_INFO_ASSIGN;
+ ),
+ TP_printk(LOCAL_PR_FMT " returned=%d " RX_FRAME_INFO_PR_FMT,
+ LOCAL_PR_ARG, __entry->ret, RX_FRAME_INFO_PR_ARG)
+ );
+
+TRACE_EVENT(llhw_return_timestamp_dtu,
+ TP_PROTO(const struct mcps802154_local *local, int ret,
+ u32 timestamp_dtu),
+ TP_ARGS(local, ret, timestamp_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, ret)
+ __field(u32, timestamp_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->ret = ret;
+ __entry->timestamp_dtu = timestamp_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " returned=%d timestamp_dtu=0x%08x",
+ LOCAL_PR_ARG, __entry->ret, __entry->timestamp_dtu)
+ );
+
+TRACE_EVENT(llhw_return_timestamp_rctu,
+ TP_PROTO(const struct mcps802154_local *local, int ret,
+ u64 timestamp_rctu),
+ TP_ARGS(local, ret, timestamp_rctu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, ret)
+ __field(u64, timestamp_rctu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->ret = ret;
+ __entry->timestamp_rctu = timestamp_rctu;
+ ),
+ TP_printk(LOCAL_PR_FMT " returned=%d timestamp_rctu=0x%llx",
+ LOCAL_PR_ARG, __entry->ret, __entry->timestamp_rctu)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_start,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_stop,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(llhw_tx_frame,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_tx_frame_config *config,
+ int frame_idx, int next_delay_dtu),
+ TP_ARGS(local, config, frame_idx, next_delay_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, timestamp_dtu)
+ __field(int, rx_enable_after_tx_dtu)
+ __field(int, rx_enable_after_tx_timeout_dtu)
+ __field(int, ant_set_id)
+ __field(u8, flags)
+ __field(int, frame_idx)
+ __field(int, next_delay_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->timestamp_dtu = config->timestamp_dtu;
+ __entry->rx_enable_after_tx_dtu = config->rx_enable_after_tx_dtu;
+ __entry->rx_enable_after_tx_timeout_dtu = config->rx_enable_after_tx_timeout_dtu;
+ __entry->ant_set_id = config->ant_set_id;
+ __entry->flags = config->flags;
+ __entry->frame_idx = frame_idx;
+ __entry->next_delay_dtu = next_delay_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x"
+ " rx_enable_after_tx_dtu=%d rx_enable_after_tx_timeout_dtu=%d"
+ " ant_set_id=%d flags=%s frame_idx=%d next_delay_dtu=%d",
+ LOCAL_PR_ARG,
+ __entry->timestamp_dtu, __entry->rx_enable_after_tx_dtu,
+ __entry->rx_enable_after_tx_timeout_dtu, __entry->ant_set_id,
+ __print_flags(__entry->flags, "|", MCPS802154_TX_FRAME_CONFIG_NAMES),
+ __entry->frame_idx,
+ __entry->next_delay_dtu
+ )
+ );
+
+TRACE_EVENT(llhw_rx_enable,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_rx_frame_config *config,
+ int frame_idx, int next_delay_dtu),
+ TP_ARGS(local, config, frame_idx, next_delay_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, timestamp_dtu)
+ __field(int, timeout_dtu)
+ __field(int, frame_timeout_dtu)
+ __field(u8, flags)
+ __field(int, ant_set_id)
+ __field(int, frame_idx)
+ __field(int, next_delay_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->timestamp_dtu = config->timestamp_dtu;
+ __entry->timeout_dtu = config->timeout_dtu;
+ __entry->frame_timeout_dtu = config->frame_timeout_dtu;
+ __entry->flags = config->flags;
+ __entry->ant_set_id = config->ant_set_id;
+ __entry->frame_idx = frame_idx;
+ __entry->next_delay_dtu = next_delay_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x timeout_dtu=%d"
+ " frame_timeout_dtu=%d ant_set_id=%d flags=%s frame_idx=%d"
+ " next_delay_dtu=%d",
+ LOCAL_PR_ARG,
+ __entry->timestamp_dtu, __entry->timeout_dtu,
+ __entry->frame_timeout_dtu, __entry->ant_set_id,
+ __print_flags(__entry->flags, "|", MCPS802154_RX_FRAME_CONFIG_NAMES),
+ __entry->frame_idx,
+ __entry->next_delay_dtu
+ )
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_rx_disable,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DECLARE_EVENT_CLASS(rx_frame_info_evt,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_rx_frame_info *info),
+ TP_ARGS(local, info),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ RX_FRAME_INFO_FLAGS_ENTRY
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ RX_FRAME_INFO_FLAGS_ASSIGN;
+ ),
+ TP_printk(LOCAL_PR_FMT " " RX_FRAME_INFO_FLAGS_PR_FMT, LOCAL_PR_ARG,
+ RX_FRAME_INFO_FLAGS_PR_ARG)
+ );
+
+DEFINE_EVENT(rx_frame_info_evt, llhw_rx_get_frame,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_rx_frame_info *info),
+ TP_ARGS(local, info)
+ );
+
+DEFINE_EVENT(rx_frame_info_evt, llhw_rx_get_error_frame,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_rx_frame_info *info),
+ TP_ARGS(local, info)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_idle,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(llhw_idle_timestamp,
+ TP_PROTO(const struct mcps802154_local *local,
+ u32 timestamp_dtu),
+ TP_ARGS(local, timestamp_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, timestamp_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->timestamp_dtu = timestamp_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x",
+ LOCAL_PR_ARG,
+ __entry->timestamp_dtu
+ )
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_reset,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_get_current_timestamp_dtu,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(llhw_set_channel,
+ TP_PROTO(const struct mcps802154_local *local, u8 page, u8 channel,
+ u8 preamble_code),
+ TP_ARGS(local, page, channel, preamble_code),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u8, page)
+ __field(u8, channel)
+ __field(u8, preamble_code)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->page = page;
+ __entry->channel = channel;
+ __entry->preamble_code = preamble_code;
+ ),
+ TP_printk(LOCAL_PR_FMT " page=%d channel=%d preamble_code=%d",
+ LOCAL_PR_ARG, __entry->page, __entry->channel,
+ __entry->preamble_code)
+ );
+
+TRACE_EVENT(llhw_set_hrp_uwb_params,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_hrp_uwb_params *params),
+ TP_ARGS(local, params),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, prf)
+ __field(int, psr)
+ __field(int, sfd_selector)
+ __field(int, phr_hi_rate)
+ __field(int, data_rate)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->prf = params->prf;
+ __entry->psr = params->psr;
+ __entry->sfd_selector = params->sfd_selector;
+ __entry->phr_hi_rate = params->phr_hi_rate;
+ __entry->data_rate = params->data_rate;
+ ),
+ TP_printk(LOCAL_PR_FMT " prf=%d psr=%d sfd_selector=%d phr_hi_rate=%d data_rate=%d",
+ LOCAL_PR_ARG, __entry->prf, __entry->psr,
+ __entry->sfd_selector, __entry->phr_hi_rate, __entry->data_rate)
+ );
+
+TRACE_EVENT(llhw_check_hrp_uwb_params,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_hrp_uwb_params *params),
+ TP_ARGS(local, params),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, prf)
+ __field(int, psr)
+ __field(int, sfd_selector)
+ __field(int, phr_hi_rate)
+ __field(int, data_rate)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->prf = params->prf;
+ __entry->psr = params->psr;
+ __entry->sfd_selector = params->sfd_selector;
+ __entry->phr_hi_rate = params->phr_hi_rate;
+ __entry->data_rate = params->data_rate;
+ ),
+ TP_printk(LOCAL_PR_FMT " prf=%d psr=%d sfd_selector=%d phr_hi_rate=%d data_rate=%d",
+ LOCAL_PR_ARG, __entry->prf, __entry->psr,
+ __entry->sfd_selector, __entry->phr_hi_rate, __entry->data_rate)
+ );
+
+TRACE_EVENT(llhw_set_sts_params,
+ TP_PROTO(const struct mcps802154_local *local, const struct
+ mcps802154_sts_params *params),
+ TP_ARGS(local, params),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, n_segs)
+ __field(int, seg_len)
+ __field(int, sp2_tx_gap_4chips)
+ __array(int, sp2_rx_gap_4chips, MCPS802154_STS_N_SEGS_MAX)
+ __array(u8, key, AES_BLOCK_SIZE)
+ __array(u8, v, AES_BLOCK_SIZE)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->n_segs = params->n_segs;
+ __entry->seg_len = params->seg_len;
+ __entry->sp2_tx_gap_4chips = params->sp2_tx_gap_4chips;
+ memcpy(__entry->sp2_rx_gap_4chips, params->sp2_rx_gap_4chips,
+ sizeof(params->sp2_rx_gap_4chips));
+ memcpy(__entry->key, params->key, sizeof(params->key));
+ memcpy(__entry->v, params->v, sizeof(params->v));
+ ),
+ TP_printk(LOCAL_PR_FMT " n_segs=%d seg_len=%d sp2_tx_gap_4chips=%d"
+ " sp2_rx_gap_4chips=%d,%d,%d,%d"
+ " sts_key=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x"
+ " sts_v=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+ LOCAL_PR_ARG, __entry->n_segs, __entry->seg_len,
+ __entry->sp2_tx_gap_4chips, __entry->sp2_rx_gap_4chips[0],
+ __entry->sp2_rx_gap_4chips[1], __entry->sp2_rx_gap_4chips[2],
+ __entry->sp2_rx_gap_4chips[3], __entry->key[0], __entry->key[1],
+ __entry->key[2], __entry->key[3], __entry->key[4], __entry->key[5],
+ __entry->key[6], __entry->key[7], __entry->key[8], __entry->key[9],
+ __entry->key[10], __entry->key[11], __entry->key[12], __entry->key[13],
+ __entry->key[14], __entry->key[15], __entry->v[0], __entry->v[1],
+ __entry->v[2], __entry->v[3], __entry->v[4], __entry->v[5],
+ __entry->v[6], __entry->v[7], __entry->v[8], __entry->v[9],
+ __entry->v[10], __entry->v[11], __entry->v[12], __entry->v[13],
+ __entry->v[14], __entry->v[15])
+ );
+
+TRACE_EVENT(llhw_rx_get_measurement,
+ TP_PROTO(const struct mcps802154_local *local, const void *rx_ctx),
+ TP_ARGS(local, rx_ctx),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(const void *, rx_ctx)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->rx_ctx = rx_ctx;
+ ),
+ TP_printk(LOCAL_PR_FMT " rx_ctx=%p",
+ LOCAL_PR_ARG,
+ __entry->rx_ctx)
+ );
+
+TRACE_EVENT(llhw_return_measurement,
+ TP_PROTO(const struct mcps802154_local *local, int r,
+ const struct mcps802154_rx_measurement_info *info),
+ TP_ARGS(local, r, info),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, r)
+ __field(int, n_rssis)
+ __field(int, n_aoas)
+ __field(int, n_cirs)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->r = r;
+ __entry->n_rssis = info->n_rssis;
+ __entry->n_aoas = info->n_aoas;
+ __entry->n_cirs = info->n_cirs;
+ ),
+ TP_printk(LOCAL_PR_FMT " r=%d n_rssis=%d n_aoas=%d n_cirs=%d",
+ LOCAL_PR_ARG,
+ __entry->r,
+ __entry->n_rssis,
+ __entry->n_aoas,
+ __entry->n_cirs)
+ );
+
+TRACE_EVENT(llhw_set_hw_addr_filt,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct ieee802154_hw_addr_filt *filt,
+ unsigned long changed),
+ TP_ARGS(local, filt, changed),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(__le16, pan_id)
+ __field(__le16, short_addr)
+ __field(__le64, extended_addr)
+ __field(bool, pan_coord)
+ __field(unsigned long, changed)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->pan_id = filt->pan_id;
+ __entry->short_addr = filt->short_addr;
+ __entry->extended_addr = filt->ieee_addr;
+ __entry->pan_coord = filt->pan_coord;
+ __entry->changed = changed;
+ ),
+ TP_printk(LOCAL_PR_FMT " pan_id=0x%04x short_addr=0x%04x"
+ " extended_addr=0x%016llx pan_coord=%s changed=%s",
+ LOCAL_PR_ARG, __entry->pan_id, __entry->short_addr,
+ __entry->extended_addr,
+ __entry->pan_coord ? "true" : "false",
+ __print_flags(__entry->changed, "|", IEEE802154_AFILT_NAMES))
+ );
+
+TRACE_EVENT(llhw_set_txpower,
+ TP_PROTO(const struct mcps802154_local *local, s32 mbm),
+ TP_ARGS(local, mbm),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(s32, mbm)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->mbm = mbm;
+ ),
+ TP_printk(LOCAL_PR_FMT " mbm=%d", LOCAL_PR_ARG, __entry->mbm)
+ );
+
+TRACE_EVENT(llhw_set_cca_ed_level,
+ TP_PROTO(const struct mcps802154_local *local, s32 mbm),
+ TP_ARGS(local, mbm),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(s32, mbm)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->mbm = mbm;
+ ),
+ TP_printk(LOCAL_PR_FMT " mbm=%d", LOCAL_PR_ARG, __entry->mbm)
+ );
+
+DECLARE_EVENT_CLASS(bool_on_evt,
+ TP_PROTO(const struct mcps802154_local *local, bool on),
+ TP_ARGS(local, on),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(bool, on)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->on = on;
+ ),
+ TP_printk(LOCAL_PR_FMT " %s", LOCAL_PR_ARG, __entry->on ? "on" : "off")
+ );
+
+DEFINE_EVENT(bool_on_evt, llhw_set_promiscuous_mode,
+ TP_PROTO(const struct mcps802154_local *local, bool on),
+ TP_ARGS(local, on)
+ );
+
+DEFINE_EVENT(bool_on_evt, llhw_set_scanning_mode,
+ TP_PROTO(const struct mcps802154_local *local, bool on),
+ TP_ARGS(local, on)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_testmode_cmd,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_rx_frame,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_rx_timeout,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DECLARE_EVENT_CLASS(str_key_evt,
+ TP_PROTO(const struct mcps802154_local *local, const char *const key),
+ TP_ARGS(local, key),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ KEY_ENTRY
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ KEY_ASSIGN;
+ ),
+ TP_printk(LOCAL_PR_FMT " " KEY_PR_FMT, LOCAL_PR_ARG, KEY_PR_ARG)
+ );
+
+DEFINE_EVENT(str_key_evt, llhw_set_calibration,
+ TP_PROTO(const struct mcps802154_local *local, const char *const key),
+ TP_ARGS(local, key)
+ );
+
+DEFINE_EVENT(str_key_evt, llhw_get_calibration,
+ TP_PROTO(const struct mcps802154_local *local, const char *const key),
+ TP_ARGS(local, key)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_list_calibration,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(llhw_vendor_cmd,
+ TP_PROTO(const struct mcps802154_local *local, u32 vendor_id,
+ u32 subcmd, size_t data_len),
+ TP_ARGS(local, vendor_id, subcmd, data_len),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, vendor_id)
+ __field(u32, subcmd)
+ __field(u32, data_len)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->vendor_id = vendor_id;
+ __entry->subcmd = subcmd;
+ __entry->data_len = data_len;
+ ),
+ TP_printk(LOCAL_PR_FMT " vendor_id=0x%06x subcmd=0x%x data_len=%d",
+ LOCAL_PR_ARG, __entry->vendor_id, __entry->subcmd,
+ __entry->data_len)
+ );
+
+TRACE_EVENT(llhw_event_rx_error,
+ TP_PROTO(const struct mcps802154_local *local,
+ enum mcps802154_rx_error_type error),
+ TP_ARGS(local, error),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(enum mcps802154_rx_error_type, error)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->error = error;
+ ),
+ TP_printk(LOCAL_PR_FMT " error=%s", LOCAL_PR_ARG,
+ __print_symbolic(__entry->error, MCPS802154_RX_ERROR_NAMES))
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_tx_done,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_broken,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_timer_expired,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+DEFINE_EVENT(local_only_evt, llhw_event_done,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local)
+ );
+
+TRACE_EVENT(ca_set_scheduler,
+ TP_PROTO(const struct mcps802154_local *local, const char *name),
+ TP_ARGS(local, name),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(name, name)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(name, name);
+ ),
+ TP_printk(LOCAL_PR_FMT " name=%s", LOCAL_PR_ARG, __get_str(name))
+ );
+
+TRACE_EVENT(ca_set_scheduler_parameters,
+ TP_PROTO(const struct mcps802154_local *local, const char *name),
+ TP_ARGS(local, name),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(name, name)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(name, name);
+ ),
+ TP_printk(LOCAL_PR_FMT " name=%s", LOCAL_PR_ARG, __get_str(name))
+ );
+
+TRACE_EVENT(ca_set_region,
+ TP_PROTO(const struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name),
+ TP_ARGS(local, scheduler_name, region_id, region_name),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(scheduler_name, scheduler_name)
+ __field(u32, region_id)
+ __string(region_name, region_name)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(scheduler_name, scheduler_name);
+ __entry->region_id = region_id;
+ __assign_str(region_name, region_name);
+ ),
+ TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s",
+ LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id,
+ __get_str(region_name))
+ );
+
+TRACE_EVENT(ca_scheduler_call,
+ TP_PROTO(const struct mcps802154_local *local,
+ const char *scheduler_name, u32 call_id),
+ TP_ARGS(local, scheduler_name, call_id),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(scheduler_name, scheduler_name)
+ __field(u32, call_id)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(scheduler_name, scheduler_name);
+ __entry->call_id = call_id;
+ ),
+ TP_printk(LOCAL_PR_FMT" scheduler=%s call_id=0x%x",
+ LOCAL_PR_ARG, __get_str(scheduler_name), __entry->call_id)
+ );
+
+TRACE_EVENT(ca_set_region_params,
+ TP_PROTO(const struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name),
+ TP_ARGS(local, scheduler_name, region_id, region_name),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(scheduler_name, scheduler_name)
+ __field(u32, region_id)
+ __string(region_name, region_name)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(scheduler_name, scheduler_name);
+ __entry->region_id = region_id;
+ __assign_str(region_name, region_name);
+ ),
+ TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s",
+ LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id,
+ __get_str(region_name))
+ );
+
+TRACE_EVENT(ca_call_region,
+ TP_PROTO(const struct mcps802154_local *local,
+ const char *scheduler_name, u32 region_id,
+ const char *region_name, u32 call_id),
+ TP_ARGS(local, scheduler_name, region_id, region_name, call_id),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(scheduler_name, scheduler_name)
+ __field(u32, region_id)
+ __string(region_name, region_name)
+ __field(u32, call_id)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(scheduler_name, scheduler_name);
+ __entry->region_id = region_id;
+ __assign_str(region_name, region_name);
+ __entry->call_id = call_id;
+ ),
+ TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s call_id=0x%x",
+ LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id,
+ __get_str(region_name), __entry->call_id)
+ );
+
+TRACE_EVENT(ca_get_access,
+ TP_PROTO(const struct mcps802154_local *local, u32 next_timestamp_dtu),
+ TP_ARGS(local, next_timestamp_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, next_timestamp_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " next_timestamp_dtu=0x%08x", LOCAL_PR_ARG,
+ __entry->next_timestamp_dtu)
+ );
+
+TRACE_EVENT(ca_return_int,
+ TP_PROTO(const struct mcps802154_local *local, int r),
+ TP_ARGS(local, r),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(int, r)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->r = r;
+ ),
+ TP_printk(LOCAL_PR_FMT " r=%d", LOCAL_PR_ARG, __entry->r)
+ );
+
+TRACE_EVENT(fproc_broken_enter,
+ TP_PROTO(const struct mcps802154_local *local),
+ TP_ARGS(local),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ ),
+ TP_printk(LOCAL_PR_FMT, LOCAL_PR_ARG)
+ );
+
+TRACE_EVENT(schedule_update,
+ TP_PROTO(const struct mcps802154_local *local, u32 next_timestamp_dtu),
+ TP_ARGS(local, next_timestamp_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, next_timestamp_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " next_timestamp_dtu=0x%08x", LOCAL_PR_ARG,
+ __entry->next_timestamp_dtu)
+ );
+
+TRACE_EVENT(update_schedule_error,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_schedule_update *su,
+ u32 next_timestamp_dtu),
+ TP_ARGS(local, su, next_timestamp_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, expected_start_timestamp_dtu)
+ __field(u32, start_timestamp_dtu)
+ __field(int, duration_dtu)
+ __field(size_t, n_regions)
+ __field(u32, next_timestamp_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->expected_start_timestamp_dtu = su->expected_start_timestamp_dtu;
+ __entry->start_timestamp_dtu = su->start_timestamp_dtu;
+ __entry->duration_dtu = su->duration_dtu;
+ __entry->n_regions = su->n_regions;
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " expected_start_timestamp_dtu=0x%08x "
+ "start_timestamp_dtu=0x%08x duration_dtu=%d "
+ "n_regions=%lu next_timestamp_dtu=0x%08x",
+ LOCAL_PR_ARG,
+ __entry->expected_start_timestamp_dtu,
+ __entry->start_timestamp_dtu,
+ __entry->duration_dtu,
+ __entry->n_regions,
+ __entry->next_timestamp_dtu)
+ );
+
+TRACE_EVENT(schedule_update_done,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_schedule *sched),
+ TP_ARGS(local, sched),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __field(u32, start_timestamp_dtu)
+ __field(int, duration_dtu)
+ __field(size_t, n_regions)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __entry->start_timestamp_dtu = sched->start_timestamp_dtu;
+ __entry->duration_dtu = sched->duration_dtu;
+ __entry->n_regions = sched->n_regions;
+ ),
+ TP_printk(LOCAL_PR_FMT " start_timestamp_dtu=0x%08x duration_dtu=%d n_regions=%lu",
+ LOCAL_PR_ARG, __entry->start_timestamp_dtu,
+ __entry->duration_dtu, __entry->n_regions)
+ );
+
+TRACE_EVENT(
+ region_notify_stop,
+ TP_PROTO(const struct mcps802154_region *region),
+ TP_ARGS(region),
+ TP_STRUCT__entry(
+ __string(region_name, region->ops->name)
+ ),
+ TP_fast_assign(
+ __assign_str(region_name, region->ops->name);
+ ),
+ TP_printk("region=%s",
+ __get_str(region_name)
+ )
+ );
+
+TRACE_EVENT(
+ region_get_demand,
+ TP_PROTO(const struct mcps802154_region *region,
+ u32 next_timestamp_dtu),
+ TP_ARGS(region, next_timestamp_dtu),
+ TP_STRUCT__entry(
+ __string(region_name, region->ops->name)
+ __field(u32, next_timestamp_dtu)
+ ),
+ TP_fast_assign(
+ __assign_str(region_name, region->ops->name);
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ ),
+ TP_printk("region=%s next_timestamp_dtu=%#x",
+ __get_str(region_name),
+ __entry->next_timestamp_dtu
+ )
+ );
+
+TRACE_EVENT(
+ region_get_demand_return,
+ TP_PROTO(const struct mcps802154_region *region,
+ const struct mcps802154_region_demand *demand,
+ int r),
+ TP_ARGS(region, demand, r),
+ TP_STRUCT__entry(
+ __field(int, r)
+ __field(u32, timestamp_dtu)
+ __field(u32, max_duration_dtu)
+ ),
+ TP_fast_assign(
+ __entry->r = r;
+ __entry->timestamp_dtu = demand->timestamp_dtu;
+ __entry->max_duration_dtu = demand->max_duration_dtu;
+ ),
+ TP_printk("r=%d timestamp_dtu=%#x max_duration_dtu=%d",
+ __entry->r,
+ __entry->timestamp_dtu,
+ __entry->max_duration_dtu
+ )
+ );
+
+TRACE_EVENT(region_get_access,
+ TP_PROTO(const struct mcps802154_local *local,
+ const struct mcps802154_region *region,
+ u32 next_timestamp_dtu, int next_in_region_dtu,
+ int region_duration_dtu),
+ TP_ARGS(local, region, next_timestamp_dtu, next_in_region_dtu,
+ region_duration_dtu),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ __string(region_name, region->ops->name)
+ __field(u32, next_timestamp_dtu)
+ __field(int, next_in_region_dtu)
+ __field(int, region_duration_dtu)
+ ),
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ __assign_str(region_name, region->ops->name);
+ __entry->next_timestamp_dtu = next_timestamp_dtu;
+ __entry->next_in_region_dtu = next_in_region_dtu;
+ __entry->region_duration_dtu = region_duration_dtu;
+ ),
+ TP_printk(LOCAL_PR_FMT " region=%s next_timestamp_dtu=0x%08x next_in_region_dtu=%d region_duration_dtu=%d",
+ LOCAL_PR_ARG,
+ __get_str(region_name), __entry->next_timestamp_dtu,
+ __entry->next_in_region_dtu, __entry->region_duration_dtu)
+ );
+
+TRACE_EVENT(
+ region_idle_params,
+ TP_PROTO(const struct idle_params *params),
+ TP_ARGS(params),
+ TP_STRUCT__entry(
+ __field(s32, min_duration_dtu)
+ __field(s32, max_duration_dtu)
+ ),
+ TP_fast_assign(
+ __entry->min_duration_dtu = params->min_duration_dtu;
+ __entry->max_duration_dtu = params->max_duration_dtu;
+ ),
+ TP_printk("min_duration_dtu=%d max_duration_dtu=%d",
+ __entry->min_duration_dtu,
+ __entry->max_duration_dtu)
+);
+
+TRACE_EVENT(
+ region_idle_get_access,
+ TP_PROTO(u32 timestamp_dtu, int duration_dtu),
+ TP_ARGS(timestamp_dtu, duration_dtu),
+ TP_STRUCT__entry(
+ __field(u32, timestamp_dtu)
+ __field(int, duration_dtu)
+ ),
+ TP_fast_assign(
+ __entry->timestamp_dtu = timestamp_dtu;
+ __entry->duration_dtu = duration_dtu;
+ ),
+ TP_printk("timestamp_dtu=%#x duration_dtu=%d",
+ __entry->timestamp_dtu,
+ __entry->duration_dtu)
+);
+
+#endif /* !TRACE_H || TRACE_HEADER_MULTI_READ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+#include <trace/define_trace.h>
diff --git a/mac/warn_return.h b/mac/warn_return.h
new file mode 100644
index 0000000..66d95dd
--- /dev/null
+++ b/mac/warn_return.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of the UWB stack for linux.
+ *
+ * Copyright (c) 2020-2021 Qorvo US, Inc.
+ *
+ * This software is provided under the GNU General Public License, version 2
+ * (GPLv2), as well as under a Qorvo commercial license.
+ *
+ * You may choose to use this software under the terms of the GPLv2 License,
+ * version 2 ("GPLv2"), as published by the Free Software Foundation.
+ * You should have received a copy of the GPLv2 along with this program. If
+ * not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program is distributed under the GPLv2 in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
+ * details.
+ *
+ * If you cannot meet the requirements of the GPLv2, you may not use this
+ * software for any purpose without first obtaining a commercial license from
+ * Qorvo. Please contact Qorvo to inquire about licensing terms.
+ */
+
+#ifndef WARN_RETURN_H
+#define WARN_RETURN_H
+
+#define WARN_RETURN(r) \
+ do { \
+ if (WARN_ON(r)) \
+ return (r); \
+ } while (0)
+
+#define WARN_RETURN_ON(r, ret) \
+ do { \
+ if (WARN_ON(r)) \
+ return (ret); \
+ } while (0)
+
+#define WARN_RETURN_VOID_ON(r) \
+ do { \
+ if (WARN_ON(r)) \
+ return; \
+ } while (0)
+
+#define WARN_UNREACHABLE_DEFAULT() WARN(1, "unreachable case")
+
+#endif /* WARN_RETURN_H */
diff --git a/tools/calibrations/pdoa_lut_generation b/tools/calibrations/pdoa_lut_generation
new file mode 100755
index 0000000..dc850ee
--- /dev/null
+++ b/tools/calibrations/pdoa_lut_generation
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import math
+import json
+from optparse import OptionParser
+
+# This defaulr value is sync with:
+# dw3000_calib.c:DW3000_CALIBRATION_PDOA_LUT_MAX
+DW3000_CALIBRATION_PDOA_LUT_MAX = 31
+
+usage="""%s [options]
+
+This script aims to dump a binary blob suitable for uwb-stack calibration file.
+It works in two modes :
+ 1. Input JSON LUT
+ 2. Antenna spacing and formula
+
+== Input JSON LUT ==
+It takes a JSON input on stdin and dumps the corresponding blob on stdout.
+The JSON input must be a list of either:
+ * lists containing 2 items of type float, 1st is assumed to be PDoA, 2nd AoA,
+ * maps containing 'aoa' and 'pdoa' keys, and corresponding values of type float.
+ float values are assumed to be in radian unit.
+
+== Antenna spacing and formula ==
+This mode uses the theorical math formula to compute AoA value from PDoA measurement.
+It also uses the antenna spacing parameter, please ensure to provide the right value.
+2 sub modes are possible:
+ * PDoA value are provided in list of float that matches the LUT size
+ * --no-json option is specified or empty input is provided, then the tool will
+ split the [-Pi, Pi] range in %d equal parts and use it as PDoA value list
+"""%(
+ os.path.basename(sys.argv[0]),
+ DW3000_CALIBRATION_PDOA_LUT_MAX,
+ )
+
+
+# ==== Fixed point / math utils ====
+
+Q = 11
+K = 2 ** Q
+S16_MAX = ((2 ** 15) - 1)
+S16_MIN = -(2 ** 15)
+
+speed_of_light_m_per_s = 299702547
+
+def freq_hz(chan):
+ if chan == 5:
+ return 6.5e9
+ elif chan == 9:
+ return 7.9872e9
+ else:
+ raise ValueError("Channel must be either 5 or 9")
+
+def sat_uf(x):
+ return min(S16_MAX, max(S16_MIN, x))
+
+def float_to_q11(x):
+ return sat_uf(round(x * K))
+
+pi_q = float_to_q11(math.pi)
+
+def pdoa_to_aoa_rad(x, chan, antenna_dist_m):
+ L_M = speed_of_light_m_per_s / freq_hz(chan)
+ phase_m = x * L_M / (2.0 * math.pi)
+ rad = phase_m / antenna_dist_m
+ rad = min(1.0, max(-1.0, rad))
+ return math.asin(rad)
+
+# ==================================
+
+def blob_item(pdoa, aoa, fmt, byteorder):
+ pdoa_q11 = float_to_q11(pdoa)
+ aoa_q11 = float_to_q11(aoa)
+ pdoa_bytes = pdoa_q11.to_bytes(2, byteorder=byteorder, signed=True)
+ aoa_bytes = aoa_q11.to_bytes(2, byteorder=byteorder, signed=True)
+ return fmt%(pdoa_bytes[0], pdoa_bytes[1], aoa_bytes[0], aoa_bytes[1])
+
+def input_json_lut(pdoa_lut, options):
+ print("Generating LUT using 'input JSON LUT' mode...")
+ print("LUT = %s"%str(pdoa_lut))
+ if not isinstance(pdoa_lut, list) \
+ or (options.size_check and (len(pdoa_lut) != options.lut_size)):
+ raise ValueError("input JSON must be list of size %d"
+ %(str(options.lut_size)))
+
+ blob = ""
+ first_val = True
+ internal_pdoa_lut = []
+ for item in pdoa_lut:
+ if isinstance(item, list) and len(item) == 2:
+ pdoa = item[0]
+ aoa = item[1]
+ elif isinstance(item, dict):
+ # NB: exception raised if key not provided in input data
+ pdoa = item['pdoa']
+ aoa = item['aoa']
+ else:
+ raise ValueError("input JSON list item must of type list \
+[float, float] (PDoA then AoA) or map {'aoa':float, 'pdoa':float}")
+ internal_pdoa_lut.append((pdoa, aoa))
+
+ internal_pdoa_lut.sort(key=lambda x: x[0])
+
+ for item in internal_pdoa_lut:
+ pdoa = item[0]
+ aoa = item[1]
+ if first_val:
+ first_val = False
+ else:
+ blob += options.sep
+ blob += blob_item(pdoa, aoa, options.fmt, options.byteorder)
+
+ return blob
+
+def antenna_spacing_and_formula(pdoa_values, options):
+ print("Generating LUT using 'antenna spacing and formula' mode...")
+ print("PDoA values = %s"%(str(pdoa_values)))
+ print("Antenna spacing (mm) = %f"%(options.antenna_spacing / 1000.0))
+ if not isinstance(pdoa_values, list) \
+ or (options.size_check and (len(pdoa_values) != options.lut_size)):
+ raise ValueError("input JSON must be list of size %d"
+ %(options.lut_size))
+ blob = ""
+ first_val = True
+
+ for value in pdoa_values:
+ pdoa = float(value) # would raise TypeError
+ aoa = pdoa_to_aoa_rad(pdoa, options.channel,
+ options.antenna_spacing / 1000000.0)
+ print("\t%f\t=>\t%f"%(pdoa, aoa))
+ if first_val:
+ first_val = False
+ else:
+ blob += options.sep
+ blob += blob_item(pdoa, aoa, options.fmt, options.byteorder)
+ return blob
+
+def float_range(start, stop, step):
+ while start < stop:
+ yield float(start)
+ start += step
+
+if __name__ == "__main__":
+ parser = OptionParser(usage=usage)
+ parser.add_option("--channel", dest="channel", type="int", default=5,
+ help="UWB Channel (5 or 9, default is 5)")
+ parser.add_option("--formula-mode", dest="formula_mode",
+ action="store_true", default=False,
+ help="selects formula mode")
+ parser.add_option("--antenna-spacing", dest="antenna_spacing",
+ type="int", default=20800,
+ help="sets the antenna pair spacing used in formula \
+ mode, unit is micrometers (default value 20800 \
+ matches Mona Lisa design).")
+ parser.add_option("--byteorder", dest="byteorder",
+ type="string", default=sys.byteorder,
+ help="Override byte order. Default is the system's \
+ byteorder (%s)"%(sys.byteorder))
+ parser.add_option("--lut-size", dest="lut_size",
+ type="int", default=DW3000_CALIBRATION_PDOA_LUT_MAX,
+ help="Override default lut size (%d)"%(DW3000_CALIBRATION_PDOA_LUT_MAX))
+ parser.add_option("--no-size-check", dest="size_check",
+ action="store_false", default=True,
+ help="skips length of the input map.")
+ parser.add_option("--dump-c-array", dest="dump_c_array",
+ action="store_true", default=False,
+ help="Dumps a C array instead of calib blob")
+ parser.add_option("--no-input", dest="read_stdin",
+ action="store_false", default=True,
+ help="Skips reading input on stdin")
+ (options, args) = parser.parse_args()
+
+ if options.dump_c_array:
+ options.sep = ",\n"
+ options.fmt = "\t{ 0x%02x%02x, 0x%02x%02x }"
+ options.byteorder = 'big'
+ else:
+ options.sep = ":"
+ options.fmt = "%02x:%02x:%02x:%02x"
+
+ txt_input = ""
+ if options.read_stdin:
+ for line in sys.stdin:
+ txt_input += line
+
+ if options.formula_mode:
+ if txt_input:
+ pdoa_values = json.loads(txt_input)
+ else:
+ step = 2 * math.pi / (options.lut_size-1)
+ pdoa_values = list(float_range(-math.pi, math.pi, step))
+ blob = antenna_spacing_and_formula(pdoa_values, options)
+ else:
+ pdoa_lut = json.loads(txt_input)
+ blob = input_json_lut(pdoa_lut, options)
+
+ if options.dump_c_array:
+ print("const dw3000_pdoa_lut_t pdoa_lut = {\n" + blob + "\n};")
+ else:
+ print("antX.antY.chN.pdoa_lut " + blob)