[rdkb][common][app][Add atenl tool for wifi test mode]

[Description]
Add atenl tool for wifi test mode
1. add atenl test mode tool
2. remove iwpriv from wireless tools, because of iwpriv command of wireless-tool is conflict with the iwpriv of atenl

[Release-log]

diff --git a/recipes-devtools/atenl/Makefile b/recipes-devtools/atenl/Makefile
new file mode 100644
index 0000000..d1c9d36
--- /dev/null
+++ b/recipes-devtools/atenl/Makefile
@@ -0,0 +1,35 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=atenl
+PKG_RELEASE=1
+
+PKG_LICENSE:=GPLv2
+PKG_LICENSE_FILES:=
+
+PKG_MAINTAINER:=Shayne Chen <shayne.chen@mediatek.com>
+PKG_BUILD_PARALLEL:=1
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+CMAKE_SOURCE_DIR:=$(PKG_BUILD_DIR)
+CMAKE_BINARY_DIR:=$(PKG_BUILD_DIR)
+
+define Package/atenl
+  SECTION:=MTK Properties
+  CATEGORY:=MTK Properties
+  TITLE:=testmode daemon for nl80211
+  SUBMENU:=Applications
+  DEPENDS:=+libnl-tiny
+endef
+
+TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
+
+define Package/atenl/install
+	mkdir -p $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/atenl $(1)/usr/sbin
+	$(INSTALL_BIN) ./files/ated.sh $(1)/usr/sbin/ated
+	$(INSTALL_BIN) ./files/iwpriv.sh $(1)/usr/sbin/iwpriv
+endef
+
+$(eval $(call BuildPackage,atenl))
diff --git a/recipes-devtools/atenl/atenl.bb b/recipes-devtools/atenl/atenl.bb
new file mode 100644
index 0000000..ca5cb4e
--- /dev/null
+++ b/recipes-devtools/atenl/atenl.bb
@@ -0,0 +1,27 @@
+DESCRIPTION = "testmode daemon for nl80211"

+SECTION = "applications"

+LICENSE = "GPLv2"

+LIC_FILES_CHKSUM = "file://COPYING;md5=751419260aa954499f7abaabaa882bbe"

+

+DEPENDS += "libnl-tiny"

+RDEPENDS_${PN} += "busybox"

+inherit pkgconfig cmake

+

+SRC_URI = " \

+    file://COPYING;subdir=git/src \

+    file://src;subdir=git \

+    file://ated.sh;subdir=git \

+    file://iwpriv.sh;subdir=git \

+    "

+

+S = "${WORKDIR}/git/src"

+

+CFLAGS_append = " -I=${includedir}/libnl-tiny "

+

+

+do_install_append() {

+    install -d ${D}${sbindir}

+    install -m 0755 ${WORKDIR}/git/ated.sh ${D}${sbindir}/ated

+    install -m 0755 ${WORKDIR}/git/iwpriv.sh ${D}${sbindir}/iwpriv

+}

+

diff --git a/recipes-devtools/atenl/files/COPYING b/recipes-devtools/atenl/files/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/recipes-devtools/atenl/files/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed 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
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/recipes-devtools/atenl/files/ated.sh b/recipes-devtools/atenl/files/ated.sh
new file mode 100644
index 0000000..1c24083
--- /dev/null
+++ b/recipes-devtools/atenl/files/ated.sh
@@ -0,0 +1,115 @@
+#!/bin/ash
+# This script is used for wrapping atenl daemon to ated
+# 0 is normal mode, 1 is used for doing specific commands such as "sync eeprom all"
+
+work_mode="RUN" # RUN/PRINT/DEBUG
+mode="0"
+add_quote="0"
+cmd="atenl"
+interface=""
+phy_idx=0
+ated_file="/tmp/interface"
+
+function do_cmd() {
+    case ${work_mode} in
+        "RUN")
+            eval "$1"
+            ;;
+        "PRINT")
+            echo "$1"
+            ;;
+        "DEBUG")
+            eval "$1"
+            echo "$1"
+            ;;
+    esac
+}
+
+function record_config() {
+    local tmp_file=$3
+    if [ -f ${tmp_file} ]; then
+        if grep -q $1 ${tmp_file}; then
+            sed -i "/$1/c\\$1=$2" ${tmp_file}
+        else
+            echo "$1=$2" >> ${tmp_file}
+        fi
+    else
+        echo "$1=$2" >> ${tmp_file}
+    fi
+}
+
+function get_config() {
+    local tmp_file=$2
+    if [ ! -f ${tmp_file} ]; then
+        echo ""
+        return
+    fi
+
+    if grep -q $1 ${tmp_file}; then
+        echo "$(cat ${tmp_file} | grep $1 | sed s/=/' '/g | cut -d " " -f 2)"
+    else
+        echo ""
+    fi
+}
+
+function convert_interface {
+    local start_idx_7986=$(get_config "STARTIDX" ${ated_file})
+    local eeprom_file=/sys/kernel/debug/ieee80211/phy0/mt76/eeprom
+    if [ -z "${start_idx_7986}" ]; then
+        if [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7916")" ]; then
+            start_idx_7986="2"
+        elif [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7915")" ]; then
+            start_idx_7986="1"
+        elif [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7986")" ]; then
+            start_idx_7986="0"
+        else
+            echo "Interface conversion failed!"
+            echo "Please use ated -i <phy0/phy1/..> ... or configure the sku of your board manually by the following commands"
+            echo "For AX6000: echo STARTIDX=0 >> ${ated_file}"
+            echo "For AX7800: echo STARTIDX=2 >> ${ated_file}"
+            echo "For AX8400: echo STARTIDX=1 >> ${ated_file}"
+            return 0
+        fi
+        record_config "STARTIDX" ${start_idx_7986} ${ated_file}
+    fi
+
+    if [[ $1 == "raix"* ]]; then
+        interface="phy1"
+        phy_idx=1
+    elif [[ $1 == "rai"* ]]; then
+        interface="phy0"
+        phy_idx=0
+    elif [[ $1 == "rax"* ]]; then
+        phy_idx=$((start_idx_7986+1))
+        interface="phy${phy_idx}"
+    else
+        phy_idx=$start_idx_7986
+        interface="phy${phy_idx}"
+    fi
+}
+
+
+for i in "$@"
+do
+    if [ "$i" = "-c" ]; then
+        cmd="${cmd} -c"
+        mode="1"
+        add_quote="1"
+    elif [ "${add_quote}" = "1" ]; then
+        cmd="${cmd} \"${i}\""
+        add_quote="0"
+    else
+        if [[ ${i} == "ra"* ]]; then
+            convert_interface $i
+            cmd="${cmd} ${interface}"
+        else
+            cmd="${cmd} ${i}"
+        fi
+    fi
+done
+
+if [ "$mode" = "0" ]; then
+    killall atenl > /dev/null 2>&1
+fi
+
+do_cmd "${cmd}"
diff --git a/recipes-devtools/atenl/files/iwpriv.sh b/recipes-devtools/atenl/files/iwpriv.sh
new file mode 100644
index 0000000..910e314
--- /dev/null
+++ b/recipes-devtools/atenl/files/iwpriv.sh
@@ -0,0 +1,931 @@
+#!/bin/ash
+
+interface=$1    # phy0/phy1/ra0
+cmd_type=$2     # set/show/e2p/mac
+full_cmd=$3
+interface_ori=${interface}
+start_idx_7986="0"
+
+work_mode="RUN" # RUN/PRINT/DEBUG
+iwpriv_file="/tmp/iwpriv_wrapper"
+interface_file="/tmp/interface"
+phy_idx=$(echo ${interface} | tr -dc '0-9')
+
+function do_cmd() {
+    case ${work_mode} in
+        "RUN")
+            eval "$1"
+            ;;
+        "PRINT")
+            echo "$1"
+            ;;
+        "DEBUG")
+            eval "$1"
+            echo "$1"
+            ;;
+    esac
+}
+
+function print_debug() {
+    if [ "${work_mode}" = "DEBUG" ]; then
+        echo "$1"
+    fi
+}
+
+function write_dmesg() {
+    echo "$1" > /dev/kmsg
+}
+
+function record_config() {
+    local config=$1
+    local tmp_file=$3
+
+    # check is mt7986 or mt7915/7916, and write its config
+    if [ ${config} != "STARTIDX" ]; then
+        if [ $phy_idx -lt $start_idx_7986 ]; then
+            config="${config}_PCIE"
+        elif [ $phy_idx -ge $start_idx_7986 ]; then
+            config="${config}_7986"
+        fi
+    fi
+
+    if [ -f ${tmp_file} ]; then
+        if grep -q ${config} ${tmp_file}; then
+            sed -i "/${config}/c\\${config}=$2" ${tmp_file}
+        else
+            echo "${config}=$2" >> ${tmp_file}
+        fi
+    else
+        echo "${config}=$2" >> ${tmp_file}
+    fi
+}
+
+function get_config() {
+    local config=$1
+    local tmp_file=$2
+
+    if [ ! -f ${tmp_file} ]; then
+        echo ""
+        return
+    fi
+
+    # check is mt7986 or mt7915/7916, and load its config
+    if [ ${config} != "STARTIDX" ]; then
+        if [ $phy_idx -lt $start_idx_7986 ]; then
+            config="${config}_PCIE"
+        elif [ $phy_idx -ge $start_idx_7986 ]; then
+            config="${config}_7986"
+        fi
+    fi
+
+    if grep -q ${config} ${tmp_file}; then
+        echo "$(cat ${tmp_file} | grep ${config} | sed s/=/' '/g | cut -d " " -f 2)"
+    else
+        echo ""
+    fi
+}
+
+function convert_interface {
+    start_idx_7986=$(get_config "STARTIDX" ${interface_file})
+    local eeprom_file=/sys/kernel/debug/ieee80211/phy0/mt76/eeprom
+    if [ -z "${start_idx_7986}" ]; then
+        if [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7916")" ]; then
+            start_idx_7986="2"
+        elif [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7915")" ]; then
+            start_idx_7986="1"
+        elif [ ! -z "$(head -c 2 ${eeprom_file} | hexdump | grep "7986")" ]; then
+            start_idx_7986="0"
+        else
+            echo "Interface Conversion Failed!"
+            echo "Please use iwpriv <phy0/phy1/..> set <...> or configure the sku of your board manually by the following commands"
+            echo "For AX6000: echo STARTIDX=0 >> ${interface_file}"
+            echo "For AX7800: echo STARTIDX=2 >> ${interface_file}"
+            echo "For AX8400: echo STARTIDX=1 >> ${interface_file}"
+            exit 0
+        fi
+        record_config "STARTIDX" ${start_idx_7986} ${interface_file}
+    fi
+
+    if [[ $1 == "raix"* ]]; then
+        phy_idx=1
+    elif [[ $1 == "rai"* ]]; then
+        phy_idx=0
+    elif [[ $1 == "rax"* ]]; then
+        phy_idx=$((start_idx_7986+1))
+    else
+        phy_idx=$start_idx_7986
+    fi
+
+    # convert phy index according to band idx
+    local band_idx=$(get_config "ATECTRLBANDIDX" ${iwpriv_file})
+    if [ "${band_idx}" = "0" ]; then
+        if [[ $1 == "raix"* ]]; then
+            phy_idx=0
+        elif [[ $1 == "rax"* ]]; then
+            phy_idx=$start_idx_7986
+        fi
+    elif [ "${band_idx}" = "1" ]; then
+        if [[ $1 == "rai"* ]]; then
+            phy_idx=1
+        elif [[ $1 == "ra"* ]]; then
+            phy_idx=$((start_idx_7986+1))
+        fi
+    fi
+
+    interface="phy${phy_idx}"
+}
+
+function change_band_idx {
+    local new_idx=$1
+    local new_phy_idx=$phy_idx
+
+    local old_idx=$(get_config "ATECTRLBANDIDX" ${iwpriv_file})
+
+
+    if [[ ${interface_ori} == "ra"* ]]; then
+        if [ -z "${old_idx}" ] || [ "${old_idx}" != "${new_idx}" ]; then
+            if [ "${new_idx}" = "0" ]; then
+                # raix0 & rai0 becomes rai0
+                if [[ $interface_ori == "rai"* ]]; then
+                    new_phy_idx=0
+                # rax0 & ra0 becomes ra0
+                elif [[ $interface_ori == "ra"* ]]; then
+                    new_phy_idx=$start_idx_7986
+                fi
+            elif [ "${new_idx}" = "1" ]; then
+                # raix0 & rai0 becomes raix0
+                if [[ $interface_ori == "rai"* ]]; then
+                    new_phy_idx=1
+                # rax0 & ra0 becomes rax0
+                elif [[ $interface_ori == "ra"* ]]; then
+                    new_phy_idx=$((start_idx_7986+1))
+                fi
+            fi
+        fi
+
+        if [ ${new_phy_idx} != ${phy_idx} ]; then
+            do_ate_work "ATESTOP"
+            phy_idx=$new_phy_idx
+            interface="phy${phy_idx}"
+            do_ate_work "ATESTART"
+        fi
+    fi
+    record_config "ATECTRLBANDIDX" ${new_idx} ${iwpriv_file}
+}
+
+function simple_convert() {
+    if [ "$1" = "ATETXCNT" ]; then
+        echo "tx_count"
+    elif [ "$1" = "ATETXLEN" ]; then
+        echo "tx_length"
+    elif [ "$1" = "ATETXMCS" ]; then
+        echo "tx_rate_idx"
+    elif [ "$1" = "ATEVHTNSS" ]; then
+        echo "tx_rate_nss"
+    elif [ "$1" = "ATETXLDPC" ]; then
+        echo "tx_rate_ldpc"
+    elif [ "$1" = "ATETXSTBC" ]; then
+        echo "tx_rate_stbc"
+    elif [ "$1" = "ATEPKTTXTIME" ]; then
+        echo "tx_time"
+    elif [ "$1" = "ATEIPG" ]; then
+        echo "tx_ipg"
+    elif [ "$1" = "ATEDUTYCYCLE" ]; then
+        echo "tx_duty_cycle"
+    elif [ "$1" = "ATETXFREQOFFSET" ]; then
+        echo "freq_offset"
+    else
+        echo "undefined"
+    fi
+}
+
+function convert_tx_mode() {
+    if [ "$1" = "0" ]; then
+        echo "cck"
+    elif [ "$1" = "1" ]; then
+        echo "ofdm"
+    elif [ "$1" = "2" ]; then
+        echo "ht"
+    elif [ "$1" = "4" ]; then
+        echo "vht"
+    elif [ "$1" = "8" ]; then
+        echo "he_su"
+    elif [ "$1" = "9" ]; then
+        echo "he_er"
+    elif [ "$1" = "10" ]; then
+        echo "he_tb"
+    elif [ "$1" = "11" ]; then
+        echo "he_mu"
+    else
+        echo "undefined"
+    fi
+}
+
+function convert_gi {
+    local tx_mode=$1
+    local val=$2
+    local sgi="0"
+    local he_ltf="0"
+
+    case ${tx_mode} in
+        "ht"|"vht")
+            sgi=${val}
+            ;;
+        "he_su"|"he_er")
+            case ${val} in
+                "0")
+                    ;;
+                "1")
+                    he_ltf="1"
+                    ;;
+                "2")
+                    sgi="1"
+                    he_ltf="1"
+                    ;;
+                "3")
+                    sgi="2"
+                    he_ltf="2"
+                    ;;
+                "4")
+                    he_ltf="2"
+                    ;;
+                *)
+                    echo "unknown gi"
+            esac
+            ;;
+        "he_mu")
+            case ${val} in
+                "0")
+                    he_ltf="2"
+                    ;;
+                "1")
+                    he_ltf="1"
+                    ;;
+                "2")
+                    sgi="1"
+                    he_ltf="1"
+                    ;;
+                "3")
+                    sgi="2"
+                    he_ltf="2"
+                    ;;
+                *)
+                    echo "unknown gi"
+            esac
+            ;;
+        "he_tb")
+            case ${val} in
+                "0")
+                    sgi="1"
+                    ;;
+                "1")
+                    sgi="1"
+                    he_ltf="1"
+                    ;;
+                "2")
+                    sgi="2"
+                    he_ltf="2"
+                    ;;
+                *)
+                    echo "unknown gi"
+            esac
+            ;;
+        *)
+            print_debug "legacy mode no need gi"
+    esac
+
+    do_cmd "mt76-test ${interface} set tx_rate_sgi=${sgi} tx_ltf=${he_ltf}"
+}
+
+function convert_channel {
+    local ctrl_band_idx=$(get_config "ATECTRLBANDIDX" ${iwpriv_file})
+    local ch=$(echo $1 | sed s/:/' '/g | cut -d " " -f 1)
+    local bw=$(get_config "ATETXBW" ${iwpriv_file} | cut -d ":" -f 1)
+    local bw_str="HT20"
+    local base_chan=1
+    local control_freq=0
+    local base_freq=0
+
+    if [ -z ${ctrl_band_idx} ]; then
+        local band=$(echo $1 | sed s/:/' '/g | cut -d " " -f 2)
+    else
+        local band=$ctrl_band_idx
+    fi
+
+    if [[ $1 != *":"* ]] || [ "${band}" = "0" ]; then
+        case ${bw} in
+            "1")
+                if [ "${ch}" -lt "3" ] || [ "${ch}" -gt "12" ]; then
+                    local bw_str="HT20"
+                else
+                    local bw_str="HT40+"
+                    ch=$(expr ${ch} - "2")
+                fi
+                ;;
+        esac
+        local base_freq=2412
+    elif [ "${band}" = "1" ]; then
+        case ${bw} in
+            "5")
+                bw_str="160MHz"
+                if [ ${ch} -lt "68" ]; then
+                    ch="36"
+                elif [ ${ch} -lt "100" ]; then
+                    ch="68"
+                elif [ ${ch} -lt "132" ]; then
+                    ch="100"
+                elif [ ${ch} -lt "181" ]; then
+                    ch="149"
+                fi
+                ;;
+            "2")
+                bw_str="80MHz"
+                if [ ${ch} -lt "52" ]; then
+                    ch="36"
+                elif [ ${ch} -lt "68" ]; then
+                    ch="52"
+                elif [ ${ch} -lt "84" ]; then
+                    ch="68"
+                elif [ ${ch} -lt "100" ]; then
+                    ch="84"
+                elif [ ${ch} -lt "116" ]; then
+                    ch="100"
+                elif [ ${ch} -lt "132" ]; then
+                    ch="116"
+                elif [ ${ch} -lt "149" ]; then
+                    ch="132"
+                elif [ ${ch} -lt "165" ]; then
+                    ch="149"
+                elif [ ${ch} -lt "181" ]; then
+                    ch="165"
+                fi
+                ;;
+            "1")
+                if [ ${ch} -lt "44" ]; then
+                    ch=$([ "${ch}" -lt "40" ] && echo "36" || echo "40")
+                    bw_str=$([ "${ch}" -le "38" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "52" ]; then
+                    ch=$([ "${ch}" -lt "48" ] && echo "44" || echo "48")
+                    bw_str=$([ "${ch}" -le "46" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "60" ]; then
+                    ch=$([ "${ch}" -lt "56" ] && echo "52" || echo "56")
+                    bw_str=$([ "${ch}" -le "54" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "68" ]; then
+                    ch=$([ "${ch}" -lt "64" ] && echo "60" || echo "64")
+                    bw_str=$([ "${ch}" -le "62" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "76" ]; then
+                    ch=$([ "${ch}" -lt "72" ] && echo "68" || echo "72")
+                    bw_str=$([ "${ch}" -le "70" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "84" ]; then
+                    ch=$([ "${ch}" -lt "80" ] && echo "76" || echo "80")
+                    bw_str=$([ "${ch}" -le "78" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "92" ]; then
+                    ch=$([ "${ch}" -lt "88" ] && echo "84" || echo "88")
+                    bw_str=$([ "${ch}" -le "86" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "100" ]; then
+                    ch=$([ "${ch}" -lt "96" ] && echo "92" || echo "96")
+                    bw_str=$([ "${ch}" -le "94" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "108" ]; then
+                    ch=$([ "${ch}" -lt "104" ] && echo "100" || echo "104")
+                    bw_str=$([ "${ch}" -le "102" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "116" ]; then
+                    ch=$([ "${ch}" -lt "112" ] && echo "108" || echo "112")
+                    bw_str=$([ "${ch}" -le "110" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "124" ]; then
+                    ch=$([ "${ch}" -lt "120" ] && echo "116" || echo "120")
+                    bw_str=$([ "${ch}" -le "118" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "132" ]; then
+                    ch=$([ "${ch}" -lt "128" ] && echo "124" || echo "128")
+                    bw_str=$([ "${ch}" -le "126" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "140" ]; then
+                    ch=$([ "${ch}" -lt "136" ] && echo "132" || echo "136")
+                    bw_str=$([ "${ch}" -le "134" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "149" ]; then
+                    ch=$([ "${ch}" -lt "144" ] && echo "140" || echo "144")
+                    bw_str=$([ "${ch}" -le "142" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "157" ]; then
+                    ch=$([ "${ch}" -lt "153" ] && echo "149" || echo "153")
+                    bw_str=$([ "${ch}" -le "151" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "165" ]; then
+                    ch=$([ "${ch}" -lt "161" ] && echo "157" || echo "161")
+                    bw_str=$([ "${ch}" -le "159" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "173" ]; then
+                    ch=$([ "${ch}" -lt "169" ] && echo "165" || echo "169")
+                    bw_str=$([ "${ch}" -le "167" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "181" ]; then
+                    ch=$([ "${ch}" -lt "177" ] && echo "173" || echo "177")
+                    bw_str=$([ "${ch}" -le "175" ] && echo "HT40+" || echo "HT40-")
+                fi
+                ;;
+            "0")
+                local bw_str="HT20"
+                ;;
+        esac
+        local base_freq=5180
+        local base_chan=36
+    else
+        case ${bw} in
+            "5")
+                bw_str="160MHz"
+                if [ ${ch} -lt "33" ]; then
+                    ch="1"
+                elif [ ${ch} -lt "65" ]; then
+                    ch="33"
+                elif [ ${ch} -lt "97" ]; then
+                    ch="65"
+                elif [ ${ch} -lt "129" ]; then
+                    ch="97"
+                elif [ ${ch} -lt "161" ]; then
+                    ch="129"
+                elif [ ${ch} -lt "193" ]; then
+                    ch="161"
+                elif [ ${ch} -lt "225" ]; then
+                    ch="193"
+                fi
+                ;;
+            "2")
+                bw_str="80MHz"
+                if [ ${ch} -lt "17" ]; then
+                    ch="1"
+                elif [ ${ch} -lt "33" ]; then
+                    ch="17"
+                elif [ ${ch} -lt "49" ]; then
+                    ch="33"
+                elif [ ${ch} -lt "65" ]; then
+                    ch="49"
+                elif [ ${ch} -lt "81" ]; then
+                    ch="65"
+                elif [ ${ch} -lt "97" ]; then
+                    ch="81"
+                elif [ ${ch} -lt "113" ]; then
+                    ch="97"
+                elif [ ${ch} -lt "129" ]; then
+                    ch="113"
+                elif [ ${ch} -lt "145" ]; then
+                    ch="129"
+                elif [ ${ch} -lt "161" ]; then
+                    ch="145"
+                elif [ ${ch} -lt "177" ]; then
+                    ch="161"
+                elif [ ${ch} -lt "193" ]; then
+                    ch="177"
+                elif [ ${ch} -lt "209" ]; then
+                    ch="193"
+                elif [ ${ch} -lt "225" ]; then
+                    ch="209"
+                fi
+                ;;
+            "1")
+                if [ ${ch} -lt "9" ]; then
+                    ch=$([ "${ch}" -lt "5" ] && echo "1" || echo "5")
+                    bw_str=$([ "${ch}" -le "3" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "17" ]; then
+                    ch=$([ "${ch}" -lt "13" ] && echo "9" || echo "13")
+                    bw_str=$([ "${ch}" -le "11" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "25" ]; then
+                    ch=$([ "${ch}" -lt "21" ] && echo "17" || echo "21")
+                    bw_str=$([ "${ch}" -le "19" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "33" ]; then
+                    ch=$([ "${ch}" -lt "29" ] && echo "25" || echo "29")
+                    bw_str=$([ "${ch}" -le "27" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "33" ]; then
+                    ch=$([ "${ch}" -lt "29" ] && echo "25" || echo "29")
+                    bw_str=$([ "${ch}" -le "27" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "41" ]; then
+                    ch=$([ "${ch}" -lt "37" ] && echo "33" || echo "37")
+                    bw_str=$([ "${ch}" -le "35" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "49" ]; then
+                    ch=$([ "${ch}" -lt "45" ] && echo "41" || echo "45")
+                    bw_str=$([ "${ch}" -le "43" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "57" ]; then
+                    ch=$([ "${ch}" -lt "53" ] && echo "49" || echo "53")
+                    bw_str=$([ "${ch}" -le "51" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "65" ]; then
+                    ch=$([ "${ch}" -lt "61" ] && echo "57" || echo "61")
+                    bw_str=$([ "${ch}" -le "59" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "73" ]; then
+                    ch=$([ "${ch}" -lt "69" ] && echo "65" || echo "69")
+                    bw_str=$([ "${ch}" -le "67" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "81" ]; then
+                    ch=$([ "${ch}" -lt "77" ] && echo "73" || echo "77")
+                    bw_str=$([ "${ch}" -le "75" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "89" ]; then
+                    ch=$([ "${ch}" -lt "85" ] && echo "81" || echo "85")
+                    bw_str=$([ "${ch}" -le "83" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "97" ]; then
+                    ch=$([ "${ch}" -lt "93" ] && echo "89" || echo "93")
+                    bw_str=$([ "${ch}" -le "91" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "105" ]; then
+                    ch=$([ "${ch}" -lt "101" ] && echo "97" || echo "101")
+                    bw_str=$([ "${ch}" -le "99" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "113" ]; then
+                    ch=$([ "${ch}" -lt "109" ] && echo "105" || echo "109")
+                    bw_str=$([ "${ch}" -le "107" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "121" ]; then
+                    ch=$([ "${ch}" -lt "117" ] && echo "113" || echo "117")
+                    bw_str=$([ "${ch}" -le "115" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "129" ]; then
+                    ch=$([ "${ch}" -lt "125" ] && echo "121" || echo "125")
+                    bw_str=$([ "${ch}" -le "123" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "137" ]; then
+                    ch=$([ "${ch}" -lt "133" ] && echo "129" || echo "133")
+                    bw_str=$([ "${ch}" -le "131" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "145" ]; then
+                    ch=$([ "${ch}" -lt "141" ] && echo "137" || echo "141")
+                    bw_str=$([ "${ch}" -le "139" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "153" ]; then
+                    ch=$([ "${ch}" -lt "149" ] && echo "145" || echo "149")
+                    bw_str=$([ "${ch}" -le "147" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "161" ]; then
+                    ch=$([ "${ch}" -lt "157" ] && echo "153" || echo "157")
+                    bw_str=$([ "${ch}" -le "155" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "169" ]; then
+                    ch=$([ "${ch}" -lt "165" ] && echo "161" || echo "165")
+                    bw_str=$([ "${ch}" -le "163" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "177" ]; then
+                    ch=$([ "${ch}" -lt "173" ] && echo "169" || echo "173")
+                    bw_str=$([ "${ch}" -le "171" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "185" ]; then
+                    ch=$([ "${ch}" -lt "181" ] && echo "177" || echo "181")
+                    bw_str=$([ "${ch}" -le "179" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "193" ]; then
+                    ch=$([ "${ch}" -lt "189" ] && echo "185" || echo "189")
+                    bw_str=$([ "${ch}" -le "187" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "201" ]; then
+                    ch=$([ "${ch}" -lt "197" ] && echo "193" || echo "197")
+                    bw_str=$([ "${ch}" -le "195" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "209" ]; then
+                    ch=$([ "${ch}" -lt "205" ] && echo "201" || echo "205")
+                    bw_str=$([ "${ch}" -le "203" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "217" ]; then
+                    ch=$([ "${ch}" -lt "213" ] && echo "209" || echo "213")
+                    bw_str=$([ "${ch}" -le "211" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "225" ]; then
+                    ch=$([ "${ch}" -lt "221" ] && echo "217" || echo "221")
+                    bw_str=$([ "${ch}" -le "219" ] && echo "HT40+" || echo "HT40-")
+                elif [ ${ch} -lt "233" ]; then
+                    ch=$([ "${ch}" -lt "229" ] && echo "225" || echo "229")
+                    bw_str=$([ "${ch}" -le "227" ] && echo "HT40+" || echo "HT40-")
+                fi
+                ;;
+            "0")
+                local bw_str="HT20"
+                ;;
+        esac
+        local base_freq=5955
+    fi
+
+    local control_freq=$(((ch - base_chan) * 5 + base_freq))
+    do_cmd "iw dev mon${phy_idx} set freq ${control_freq} ${bw_str}"
+}
+
+function convert_rxstat {
+    local res=$(do_cmd "mt76-test ${interface} dump stats")
+    local mdrdy=$(echo "${res}" | grep "rx_packets" | cut -d "=" -f 2)
+    local fcs_error=$(echo "${res}" | grep "rx_fcs_error" | cut -d "=" -f 2)
+    local rcpi=$(echo "${res}" | grep "last_rcpi" | cut -d "=" -f 2 | sed 's/,/ /g')
+    local ib_rssi=$(echo "${res}" | grep "last_ib_rssi" | cut -d "=" -f 2 | sed 's/,/ /g')
+    local wb_rssi=$(echo "${res}" | grep "last_wb_rssi" | cut -d "=" -f 2 | sed 's/,/ /g')
+    local rx_ok=$(expr ${mdrdy} - ${fcs_error})
+
+    write_dmesg "rcpi: ${rcpi}"
+    write_dmesg "fagc rssi ib: ${ib_rssi}"
+    write_dmesg "fagc rssi wb: ${wb_rssi}"
+    write_dmesg "all_mac_rx_mdrdy_cnt: ${mdrdy}"
+    write_dmesg "all_mac_rx_fcs_err_cnt: ${fcs_error}"
+    write_dmesg "all_mac_rx_ok_cnt : ${rx_ok}"
+}
+
+function set_mac_addr {
+    record_config ${cmd} ${param} ${iwpriv_file}
+
+    local addr1=$(get_config "ATEDA" ${iwpriv_file})
+    local addr2=$(get_config "ATESA" ${iwpriv_file})
+    local addr3=$(get_config "ATEBSSID" ${iwpriv_file})
+
+    if [ -z "${addr1}" ]; then
+        addr1="00:11:22:33:44:55"
+    fi
+    if [ -z "${addr2}" ]; then
+        addr2="00:11:22:33:44:55"
+    fi
+    if [ -z "${addr3}" ]; then
+        addr3="00:11:22:33:44:55"
+    fi
+
+    do_cmd "mt76-test phy${phy_idx} set mac_addrs=${addr1},${addr2},${addr3}"
+}
+
+function convert_ibf {
+    local cmd=$1
+    local param=$2
+    local new_cmd=""
+    local new_param=$(echo ${param} | sed s/":"/","/g)
+
+    case ${cmd} in
+        "ATETxBfInit")
+            new_cmd="init"
+            new_param=1
+            do_cmd "mt76-test phy${phy_idx} set state=idle"
+            ;;
+        "ATEIBFPhaseComp")
+            new_cmd="phase_comp"
+            new_param="${new_param} aid=1"
+            ;;
+        "ATEEBfProfileConfig")
+            new_cmd="ebf_prof_update"
+            ;;
+        "ATEIBfProfileConfig")
+            new_cmd="ibf_prof_update"
+            ;;
+        "ATEIBfInstCal")
+            new_cmd="phase_cal"
+            ;;
+        "ATEIBfGdCal")
+            new_cmd="phase_cal"
+            new_param="${new_param},00"
+            ;;
+        "TxBfTxApply")
+            new_cmd="apply_tx"
+            ;;
+        "ATETxPacketWithBf")
+            local bf_on=${new_param:0:2}
+            local aid="01"
+            local wlan_idx=${new_param:3:2}
+            local update="00"
+            local tx_len=${new_param:6}
+
+            new_cmd="tx_prep"
+            new_param="${bf_on},${aid},${wlan_idx},${update}"
+            if [ "${tx_len}" = "00" ]; then
+                new_param="${new_param} aid=1 tx_count=10000000 tx_length=1024"
+            else
+                new_param="${new_param} aid=1 tx_count=${tx_len} tx_length=1024"
+            fi
+            do_cmd "mt76-test phy${phy_idx} set state=idle"
+            ;;
+        "TxBfProfileData20MAllWrite")
+            new_cmd="prof_update_all"
+            ;;
+        "ATEIBFPhaseE2pUpdate")
+            new_cmd="e2p_update"
+            ;;
+        *)
+    esac
+
+    do_cmd "mt76-test phy${phy_idx} set txbf_act=${new_cmd} txbf_param=${new_param}"
+
+    if [ "${cmd}" = "ATETxPacketWithBf" ]; then
+        do_cmd "mt76-test phy${phy_idx} set state=tx_frames"
+    fi
+}
+
+function do_ate_work() {
+    local ate_cmd=$1
+
+    case ${ate_cmd} in
+        "ATESTART")
+            local if_str=$(ifconfig | grep mon${phy_idx})
+
+            if [ ! -z "${if_str}" -a "${if_str}" != " " ]; then
+                echo "ATE already starts."
+            else
+                do_cmd "iw phy ${interface} interface add mon${phy_idx} type monitor"
+                do_cmd "iw dev wlan${phy_idx} del"
+                do_cmd "ifconfig mon${phy_idx} up"
+                do_cmd "iw reg set VV"
+            fi
+            ;;
+        "ATESTOP")
+            local if_str=$(ifconfig | grep mon${phy_idx})
+
+            if [ -z "${if_str}" -a "${if_str}" != " " ]; then
+                echo "ATE does not start."
+            else
+                do_cmd "mt76-test ${interface} set state=off"
+                do_cmd "iw dev mon${phy_idx} del"
+                do_cmd "iw phy ${interface} interface add wlan${phy_idx} type managed"
+                do_cmd "mt76-test ${interface} set aid=0"
+            fi
+
+            if [ ${phy_idx} -lt ${start_idx_7986} ]; then
+                sed -i '/_PCIE=/d' ${iwpriv_file}
+            elif [ ${phy_idx} -ge ${start_idx_7986} ]; then
+                sed -i '/_7986=/d' ${iwpriv_file}
+            fi
+            ;;
+        "TXCOMMIT")
+            do_cmd "mt76-test ${interface} set aid=1"
+            ;;
+        "TXFRAME")
+            do_cmd "mt76-test ${interface} set state=tx_frames"
+            ;;
+        "TXSTOP"|"RXSTOP")
+            do_cmd "mt76-test ${interface} set state=idle"
+            ;;
+        "TXREVERT")
+            do_cmd "mt76-test ${interface} set aid=0"
+            ;;
+        "RXFRAME")
+            do_cmd "mt76-test ${interface} set state=rx_frames"
+            ;;
+        "TXCONT")
+            do_cmd "mt76-test ${interface} set state=tx_cont"
+            ;;
+        "GROUPREK")
+            do_cmd "mt76-test ${interface} set state=group_prek"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal sync group\""
+            ;;
+        "GROUPREKDump")
+            do_cmd "mt76-test ${interface} set state=group_prek_dump"
+            ;;
+        "GROUPREKClean")
+            do_cmd "mt76-test ${interface} set state=group_prek_clean"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal group clean\""
+            ;;
+        "DPD2G")
+            do_cmd "mt76-test ${interface} set state=dpd_2g"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal sync dpd 2g\""
+            ;;
+        "DPD5G")
+            do_cmd "mt76-test ${interface} set state=dpd_5g"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal sync dpd 5g\""
+            ;;
+        "DPD6G")
+            do_cmd "mt76-test ${interface} set state=dpd_6g"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal sync dpd 6g\""
+            ;;
+        "DPDDump")
+            do_cmd "mt76-test ${interface} set state=dpd_dump"
+            ;;
+        "DPDClean")
+            do_cmd "mt76-test ${interface} set state=dpd_clean"
+            do_cmd "atenl -i ${interface} -c \"eeprom precal dpd clean\""
+            ;;
+        *)
+            print_debug "skip ${ate_cmd}"
+            ;;
+    esac
+}
+
+# main start here
+
+if [[ ${interface} == "ra"* ]]; then
+    convert_interface $interface
+fi
+
+tmp_work_mode=$(get_config "WORKMODE" ${iwpriv_file})
+
+if [ ! -z ${tmp_work_mode} ]; then
+    work_mode=${tmp_work_mode}
+fi
+
+cmd=$(echo ${full_cmd} | sed s/=/' '/g | cut -d " " -f 1)
+param=$(echo ${full_cmd} | sed s/=/' '/g | cut -d " " -f 2)
+
+if [ "${cmd_type}" = "set" ]; then
+    skip=0
+    case ${cmd} in
+        "ATE")
+            do_ate_work ${param}
+
+            skip=1
+            ;;
+        "ATETXCNT"|"ATETXLEN"|"ATETXMCS"|"ATEVHTNSS"|"ATETXLDPC"|"ATETXSTBC"| \
+        "ATEPKTTXTIME"|"ATEIPG"|"ATEDUTYCYCLE"|"ATETXFREQOFFSET")
+            cmd_new=$(simple_convert ${cmd})
+            if [ "${param_new}" = "undefined" ]; then
+                echo "unknown cmd: ${cmd}"
+                exit
+            fi
+            param_new=${param}
+            ;;
+        "ATETXANT"|"ATERXANT")
+            cmd_new="tx_antenna"
+            param_new=${param}
+            ;;
+        "ATETXGI")
+            tx_mode=$(convert_tx_mode $(get_config "ATETXMODE" ${iwpriv_file}))
+            convert_gi ${tx_mode} ${param}
+            skip=1
+            ;;
+        "ATETXMODE")
+            cmd_new="tx_rate_mode"
+            param_new=$(convert_tx_mode ${param})
+            if [ "${param_new}" = "undefined" ]; then
+                echo "unknown tx mode"
+                echo "0:cck, 1:ofdm, 2:ht, 4:vht, 8:he_su, 9:he_er, 10:he_tb, 11:he_mu"
+                exit
+            else
+                record_config ${cmd} ${param} ${iwpriv_file}
+            fi
+            ;;
+        "ATETXPOW0"|"ATETXPOW1"|"ATETXPOW2"|"ATETXPOW3")
+            cmd_new="tx_power"
+            param_new="${param},0,0,0"
+            ;;
+        "ATETXBW")
+            record_config ${cmd} ${param} ${iwpriv_file}
+            skip=1
+            ;;
+        "ATECHANNEL")
+            convert_channel ${param}
+            skip=1
+            ;;
+        "ATERXSTAT")
+            convert_rxstat
+            skip=1
+            ;;
+        "ATECTRLBANDIDX")
+            change_band_idx ${param}
+            skip=1
+            ;;
+        "ATEDA"|"ATESA"|"ATEBSSID")
+            set_mac_addr ${cmd} ${param}
+            skip=1
+            ;;
+        "ATETxBfInit"|"ATEIBFPhaseComp"|"ATEEBfProfileConfig"|"ATEIBfProfileConfig"| \
+        "TxBfTxApply"|"ATETxPacketWithBf"|"TxBfProfileData20MAllWrite"|"ATEIBfInstCal"|\
+        "ATEIBfGdCal"|"ATEIBFPhaseE2pUpdate")
+            convert_ibf ${cmd} ${param}
+            skip=1
+            ;;
+        "bufferMode")
+            if [ "${param}" = "2" ]; then
+                do_cmd "atenl -i ${interface} -c \"eeprom update buffermode\""
+            fi
+            skip=1
+            ;;
+        "ResetCounter"|"ATERXSTATRESET")
+            skip=1
+            ;;
+        "WORKMODE")
+            record_config "WORKMODE" ${param} ${iwpriv_file}
+            echo "Entering ${param} mode in iwpriv"
+            skip=1
+            ;;
+        *)
+            print_debug "Unknown command to set: ${cmd}"
+            skip=1
+    esac
+
+    if [ "${skip}" != "1" ]; then
+        do_cmd "mt76-test ${interface} set ${cmd_new}=${param_new}"
+    fi
+
+elif [ "${cmd_type}" = "show" ]; then
+    do_cmd "mt76-test ${interface} dump"
+    do_cmd "mt76-test ${interface} dump stats"
+
+elif [ "${cmd_type}" = "e2p" ]; then
+    offset=$(printf "0x%s" ${cmd})
+    val=$(printf "0x%s" ${param})
+
+    # eeprom offset write
+    if [[ ${full_cmd} == *"="* ]]; then
+        tmp=$((${val} & 0xff))
+        tmp=$(printf "0x%x" ${tmp})
+        do_cmd "atenl -i ${interface} -c \"eeprom set ${offset}=${tmp}\""
+
+        offset=$((${offset}))
+        offset=$(expr ${offset} + "1")
+        offset=$(printf "0x%x" ${offset})
+        tmp=$(((${val} >> 8) & 0xff))
+        tmp=$(printf "0x%x" ${tmp})
+        do_cmd "atenl -i ${interface} -c \"eeprom set ${offset}=${tmp}\""
+    else
+        v1=$(do_cmd "atenl -i ${interface} -c \"eeprom read ${param}\"")
+        v1=$(echo "${v1}" | grep "val =" | cut -d '(' -f 2 | grep -o -E '[0-9]+')
+
+        tmp=$(printf "0x%s" ${param})
+        tmp=$((${tmp}))
+        param2=$(expr ${tmp} + "1")
+        param2=$(printf "%x" ${param2})
+        v2=$(do_cmd "atenl -i ${interface} -c \"eeprom read ${param2}\"")
+        v2=$(echo "${v2}" | grep "val =" | cut -d '(' -f 2 | grep -o -E '[0-9]+')
+
+        param=$(printf "0x%s" ${param})
+        param=$(printf "%04x" ${param})
+        param=$(echo $param | tr 'a-z' 'A-Z')
+        printf "%s       e2p:\n" ${interface_ori}
+        printf "[0x%s]:0x%02x%02x\n" ${param} ${v2} ${v1}
+    fi
+
+elif [ "${cmd_type}" = "mac" ]; then
+    regidx=/sys/kernel/debug/ieee80211/phy${phy_idx}/mt76/regidx
+    regval=/sys/kernel/debug/ieee80211/phy${phy_idx}/mt76/regval
+    offset=$(printf "0x%s" ${cmd})
+    val=$(printf "0x%s" ${param})
+
+    echo ${offset} > ${regidx}
+    # reg write
+    if [[ ${full_cmd} == *"="* ]]; then
+        echo ${val} > ${regval}
+    fi
+
+    res=$(cat ${regval} | cut -d 'x' -f 2)
+    printf "%s       mac:[%s]:%s\n" ${interface_ori} ${offset} ${res}
+
+else
+    echo "Unknown command"
+fi
diff --git a/recipes-devtools/atenl/files/src/CMakeLists.txt b/recipes-devtools/atenl/files/src/CMakeLists.txt
new file mode 100644
index 0000000..cc91b05
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 2.8)
+
+PROJECT(atenl C)
+ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3)
+
+ADD_EXECUTABLE(atenl main.c eth.c hqa.c nl.c eeprom.c util.c)
+TARGET_LINK_LIBRARIES(atenl nl-tiny)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS atenl
+	RUNTIME DESTINATION sbin
+)
diff --git a/recipes-devtools/atenl/files/src/atenl.h b/recipes-devtools/atenl/files/src/atenl.h
new file mode 100644
index 0000000..75ee474
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/atenl.h
@@ -0,0 +1,418 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_H
+#define __ATENL_H
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/nl80211.h>
+#include <net/if.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "nl.h"
+#include "util.h"
+#include "debug.h"
+
+#define BRIDGE_NAME_OPENWRT	"br-lan"
+#define BRIDGE_NAME_RDKB	"brlan0"
+#define ETH_P_RACFG	0x2880
+#define RACFG_PKT_MAX_SIZE	1600
+#define RACFG_HLEN	12
+#define RACFG_MAGIC_NO	0x18142880
+#define PRE_CAL_INFO 16
+#define DPD_INFO_CH_SHIFT	24
+#define DPD_INFO_2G_SHIFT 	16
+#define DPD_INFO_5G_SHIFT	8
+#define DPD_INFO_6G_SHIFT	0
+#define DPD_INFO_MASK 		GENMASK(7, 0)
+#define MT_EE_CAL_UNIT		1024
+
+#define RACFG_CMD_TYPE_MASK	GENMASK(14, 0)
+#define RACFG_CMD_TYPE_ETHREQ	BIT(3)
+#define RACFG_CMD_TYPE_PLATFORM_MODULE	GENMASK(4, 3)
+
+#define set_band_val(_an, _band, _field, _val)	\
+	_an->anb[_band]._field = (_val)
+#define get_band_val(_an, _band, _field)	\
+	(_an->anb[_band]._field)
+
+enum atenl_rf_mode {
+	ATENL_RF_MODE_NORMAL,
+	ATENL_RF_MODE_TEST,
+	ATENL_RF_MODE_ICAP,
+	ATENL_RF_MODE_ICAP_OVERLAP,
+
+	__ATENL_RF_MODE_MAX,
+};
+
+struct atenl_rx_stat {
+	u64 total;
+	u64 ok_cnt;
+	u64 err_cnt;
+	u64 len_mismatch;
+};
+
+struct atenl_band {
+	bool valid;
+	u8 phy_idx;
+	u8 cap;
+	u8 chainmask;
+
+	enum mt76_testmode_state cur_state;
+	s8 tx_power;
+	enum atenl_rf_mode rf_mode;
+
+	bool use_tx_time;
+	u32 tx_time;
+	u32 tx_mpdu_len;
+
+	bool reset_tx_cnt;
+	bool reset_rx_cnt;
+
+	/* history */
+	struct atenl_rx_stat rx_stat;
+};
+
+#define MAX_BAND_NUM	3
+
+struct atenl {
+	struct atenl_band anb[MAX_BAND_NUM];
+	u16 chip_id;
+	u16 adie_id;
+	u8 sub_chip_id;
+	u8 cur_band;
+
+	u8 mac_addr[ETH_ALEN];
+	char *bridge_name;
+	bool unicast;
+	int sock_eth;
+
+	const char *mtd_part;
+	u32 mtd_offset;
+	u8 is_main_phy;
+	u8 *eeprom_data;
+	int eeprom_fd;
+	u16 eeprom_size;
+	u32 eeprom_prek_offs;
+
+	u8 *cal;
+	u32 cal_info[5];
+
+	bool cmd_mode;
+
+	/* intermediate data */
+	u8 ibf_mcs;
+	u8 ibf_ant;
+};
+
+struct atenl_cmd_hdr {
+	__be32 magic_no;
+	__be16 cmd_type;
+	__be16 cmd_id;
+	__be16 len;
+	__be16 seq;
+	u8 data[2048];
+} __attribute__((packed));
+
+enum atenl_cmd {
+	HQA_CMD_UNKNOWN,
+	HQA_CMD_LEGACY,	/* legacy or deprecated */
+
+	HQA_CMD_OPEN_ADAPTER,
+	HQA_CMD_CLOSE_ADAPTER,
+	HQA_CMD_GET_CHIP_ID,
+	HQA_CMD_GET_SUB_CHIP_ID,
+	HQA_CMD_SET_TX_BW,
+	HQA_CMD_SET_TX_PKT_BW,
+	HQA_CMD_SET_TX_PRI_BW,
+	HQA_CMD_GET_TX_INFO,
+	HQA_CMD_SET_TX_PATH,
+	HQA_CMD_SET_TX_POWER,
+	HQA_CMD_SET_TX_POWER_MANUAL,
+	HQA_CMD_SET_RF_MODE,
+	HQA_CMD_SET_RX_PATH,
+	HQA_CMD_SET_RX_PKT_LEN,
+	HQA_CMD_SET_FREQ_OFFSET,
+	HQA_CMD_SET_TSSI,
+	HQA_CMD_SET_CFG,
+	HQA_CMD_SET_RU,
+	HQA_CMD_SET_BAND,
+	HQA_CMD_SET_EEPROM_TO_FW,
+	HQA_CMD_READ_MAC_BBP_REG,
+	HQA_CMD_READ_MAC_BBP_REG_QA,
+	HQA_CMD_READ_RF_REG,
+	HQA_CMD_READ_EEPROM_BULK,
+	HQA_CMD_READ_TEMPERATURE,
+	HQA_CMD_WRITE_MAC_BBP_REG,
+	HQA_CMD_WRITE_RF_REG,
+	HQA_CMD_WRITE_EEPROM_BULK,
+	HQA_CMD_WRITE_BUFFER_DONE,
+	HQA_CMD_GET_BAND,
+	HQA_CMD_GET_CFG,
+	HQA_CMD_GET_TX_POWER,
+	HQA_CMD_GET_TX_TONE_POWER,
+	HQA_CMD_GET_EFUSE_FREE_BLOCK,
+	HQA_CMD_GET_FREQ_OFFSET,
+	HQA_CMD_GET_FW_INFO,
+	HQA_CMD_GET_RX_INFO,
+	HQA_CMD_GET_RF_CAP,
+	HQA_CMD_CHECK_EFUSE_MODE,
+	HQA_CMD_CHECK_EFUSE_MODE_TYPE,
+	HQA_CMD_CHECK_EFUSE_MODE_NATIVE,
+	HQA_CMD_ANT_SWAP_CAP,
+	HQA_CMD_RESET_TX_RX_COUNTER,
+	HQA_CMD_CONTINUOUS_TX,
+
+	HQA_CMD_EXT,
+	HQA_CMD_ERR,
+
+	__HQA_CMD_MAX_NUM,
+};
+
+enum atenl_ext_cmd {
+	HQA_EXT_CMD_UNSPEC,
+
+	HQA_EXT_CMD_SET_CHANNEL,
+	HQA_EXT_CMD_SET_TX,
+	HQA_EXT_CMD_START_TX,
+	HQA_EXT_CMD_START_RX,
+	HQA_EXT_CMD_STOP_TX,
+	HQA_EXT_CMD_STOP_RX,
+	HQA_EXT_CMD_SET_TX_TIME_OPT,
+
+	HQA_EXT_CMD_OFF_CH_SCAN,
+
+	HQA_EXT_CMD_IBF_SET_VAL,
+	HQA_EXT_CMD_IBF_GET_STATUS,
+	HQA_EXT_CMD_IBF_PROF_UPDATE_ALL,
+
+	HQA_EXT_CMD_ERR,
+
+	__HQA_EXT_CMD_MAX_NUM,
+};
+
+struct atenl_data {
+	u8 buf[RACFG_PKT_MAX_SIZE];
+	int len;
+	u16 cmd_id;
+	u8 ext_id;
+	enum atenl_cmd cmd;
+	enum atenl_ext_cmd ext_cmd;
+};
+
+struct atenl_ops {
+	int (*ops)(struct atenl *an, struct atenl_data *data);
+	u8 cmd;
+	u8 flags;
+	u16 cmd_id;
+	u16 resp_len;
+};
+
+#define ATENL_OPS_FLAG_EXT_CMD	BIT(0)
+#define ATENL_OPS_FLAG_LEGACY	BIT(1)
+#define ATENL_OPS_FLAG_SKIP	BIT(2)
+
+static inline struct atenl_cmd_hdr * atenl_hdr(struct atenl_data *data)
+{
+	u8 *hqa_data = (u8 *)data->buf + ETH_HLEN;
+
+	return (struct atenl_cmd_hdr *)hqa_data;
+}
+
+enum atenl_phy_type {
+	ATENL_PHY_TYPE_CCK,
+	ATENL_PHY_TYPE_OFDM,
+	ATENL_PHY_TYPE_HT,
+	ATENL_PHY_TYPE_HT_GF,
+	ATENL_PHY_TYPE_VHT,
+	ATENL_PHY_TYPE_HE_SU = 8,
+	ATENL_PHY_TYPE_HE_EXT_SU,
+	ATENL_PHY_TYPE_HE_TB,
+	ATENL_PHY_TYPE_HE_MU,
+};
+
+enum atenl_e2p_mode {
+	E2P_EFUSE_MODE = 1,
+	E2P_FLASH_MODE,
+	E2P_EEPROM_MODE,
+	E2P_BIN_MODE,
+};
+
+enum atenl_band_type {
+	BAND_TYPE_UNUSE,
+	BAND_TYPE_2G,
+	BAND_TYPE_5G,
+	BAND_TYPE_2G_5G,
+	BAND_TYPE_6G,
+	BAND_TYPE_2G_6G,
+	BAND_TYPE_5G_6G,
+	BAND_TYPE_2G_5G_6G,
+};
+
+enum atenl_ch_band {
+	CH_BAND_2GHZ,
+	CH_BAND_5GHZ,
+	CH_BAND_6GHZ,
+};
+
+/* for mt7915 */
+enum {
+	MT_EE_BAND_SEL_DEFAULT,
+	MT_EE_BAND_SEL_5GHZ,
+	MT_EE_BAND_SEL_2GHZ,
+	MT_EE_BAND_SEL_DUAL,
+};
+
+/* for mt7916/mt7986 */
+enum {
+	MT_EE_BAND_SEL_2G,
+	MT_EE_BAND_SEL_5G,
+	MT_EE_BAND_SEL_6G,
+	MT_EE_BAND_SEL_5G_6G,
+};
+
+#define MT_EE_WIFI_CONF				0x190
+#define MT_EE_WIFI_CONF0_BAND_SEL		GENMASK(7, 6)
+
+enum {
+	MT7976_ONE_ADIE_DBDC		= 0x7,
+	MT7975_ONE_ADIE_SINGLE_BAND	= 0x8, /* AX7800 */
+	MT7976_ONE_ADIE_SINGLE_BAND	= 0xa, /* AX7800 */
+	MT7975_DUAL_ADIE_DBDC		= 0xd, /* AX6000 */
+	MT7976_DUAL_ADIE_DBDC		= 0xf, /* AX6000 */
+};
+
+enum {
+	TEST_CBW_20MHZ,
+	TEST_CBW_40MHZ,
+	TEST_CBW_80MHZ,
+	TEST_CBW_10MHZ,
+	TEST_CBW_5MHZ,
+	TEST_CBW_160MHZ,
+	TEST_CBW_8080MHZ,
+
+	TEST_CBW_MAX = TEST_CBW_8080MHZ - 1,
+};
+
+struct atenl_rx_info_hdr {
+	__be32 type;
+	__be32 ver;
+	__be32 val;
+	__be32 len;
+} __attribute__((packed));
+
+struct atenl_rx_info_band {
+	__be32 mac_rx_fcs_err_cnt;
+	__be32 mac_rx_mdrdy_cnt;
+	__be32 mac_rx_len_mismatch;
+	__be32 mac_rx_fcs_ok_cnt;
+	__be32 phy_rx_fcs_err_cnt_cck;
+	__be32 phy_rx_fcs_err_cnt_ofdm;
+	__be32 phy_rx_pd_cck;
+	__be32 phy_rx_pd_ofdm;
+	__be32 phy_rx_sig_err_cck;
+	__be32 phy_rx_sfd_err_cck;
+	__be32 phy_rx_sig_err_ofdm;
+	__be32 phy_rx_tag_err_ofdm;
+	__be32 phy_rx_mdrdy_cnt_cck;
+	__be32 phy_rx_mdrdy_cnt_ofdm;
+} __attribute__((packed));
+
+struct atenl_rx_info_path {
+	__be32 rcpi;
+	__be32 rssi;
+	__be32 fagc_ib_rssi;
+	__be32 fagc_wb_rssi;
+	__be32 inst_ib_rssi;
+	__be32 inst_wb_rssi;
+} __attribute__((packed));
+
+struct atenl_rx_info_user {
+	__be32 freq_offset;
+	__be32 snr;
+	__be32 fcs_error_cnt;
+} __attribute__((packed));
+
+struct atenl_rx_info_comm {
+	__be32 rx_fifo_full;
+	__be32 aci_hit_low;
+	__be32 aci_hit_high;
+	__be32 mu_pkt_count;
+	__be32 sig_mcs;
+	__be32 sinr;
+	__be32 driver_rx_count;
+} __attribute__((packed));
+
+enum atenl_ibf_action {
+	TXBF_ACT_INIT = 1,
+	TXBF_ACT_CHANNEL,
+	TXBF_ACT_MCS,
+	TXBF_ACT_POWER,
+	TXBF_ACT_TX_ANT,
+	TXBF_ACT_RX_START,
+	TXBF_ACT_RX_ANT,
+	TXBF_ACT_LNA_GAIN,
+	TXBF_ACT_IBF_PHASE_COMP,
+	TXBF_ACT_TX_PKT,
+	TXBF_ACT_IBF_PROF_UPDATE,
+	TXBF_ACT_EBF_PROF_UPDATE,
+	TXBF_ACT_IBF_PHASE_CAL,
+	TXBF_ACT_IBF_PHASE_E2P_UPDATE = 16,
+};
+
+enum prek_ops {
+	PREK_SYNC_ALL = 1,
+	PREK_SYNC_GROUP,
+	PREK_SYNC_DPD_2G,
+	PREK_SYNC_DPD_5G,
+	PREK_SYNC_DPD_6G,
+	PREK_CLEAN_GROUP,
+	PREK_CLEAN_DPD,
+};
+
+static inline bool is_mt7915(struct atenl *an)
+{
+	return an->chip_id == 0x7915;
+}
+
+static inline bool is_mt7916(struct atenl *an)
+{
+	return (an->chip_id == 0x7916) || (an->chip_id == 0x7906);
+}
+
+static inline bool is_mt7986(struct atenl *an)
+{
+	return an->chip_id == 0x7986;
+}
+
+int atenl_eth_init(struct atenl *an);
+int atenl_eth_recv(struct atenl *an, struct atenl_data *data);
+int atenl_eth_send(struct atenl *an, struct atenl_data *data);
+int atenl_hqa_proc_cmd(struct atenl *an);
+int atenl_nl_process(struct atenl *an, struct atenl_data *data);
+int atenl_nl_process_many(struct atenl *an, struct atenl_data *data);
+int atenl_nl_check_mtd(struct atenl *an);
+int atenl_nl_write_eeprom(struct atenl *an, u32 offset, u8 *val, int len);
+int atenl_nl_write_efuse_all(struct atenl *an);
+int atenl_nl_update_buffer_mode(struct atenl *an);
+int atenl_nl_set_state(struct atenl *an, u8 band,
+		       enum mt76_testmode_state state);
+int atenl_nl_set_aid(struct atenl *an, u8 band, u8 aid);
+int atenl_nl_precal_sync_from_driver(struct atenl *an, enum prek_ops ops);
+int atenl_eeprom_init(struct atenl *an, u8 phy_idx);
+void atenl_eeprom_close(struct atenl *an);
+int atenl_eeprom_write_mtd(struct atenl *an);
+int atenl_eeprom_update_precal(struct atenl *an, int write_offs, int size);
+int atenl_eeprom_read_from_driver(struct atenl *an, u32 offset, int len);
+void atenl_eeprom_cmd_handler(struct atenl *an, u8 phy_idx, char *cmd);
+u16 atenl_get_center_channel(u8 bw, u8 ch_band, u16 ctrl_ch);
+int atenl_reg_read(struct atenl *an, u32 offset, u32 *res);
+int atenl_reg_write(struct atenl *an, u32 offset, u32 val);
+int atenl_rf_read(struct atenl *an, u32 wf_sel, u32 offset, u32 *res);
+int atenl_rf_write(struct atenl *an, u32 wf_sel, u32 offset, u32 val);
+
+#endif
diff --git a/recipes-devtools/atenl/files/src/debug.h b/recipes-devtools/atenl/files/src/debug.h
new file mode 100644
index 0000000..7621887
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/debug.h
@@ -0,0 +1,31 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_DEBUG_H
+#define __ATENL_DEBUG_H
+
+/* #define CONFIG_ATENL_DEBUG     1 */
+/* #define CONFIG_ATENL_DEBUG_VERBOSE     1 */
+
+#define atenl_info(fmt, ...)	(void)fprintf(stdout, fmt, ##__VA_ARGS__)
+#define atenl_err(fmt, ...)	(void)fprintf(stderr, fmt, ##__VA_ARGS__)
+#ifdef CONFIG_ATENL_DEBUG
+#define atenl_dbg(fmt, ...)	atenl_info(fmt, ##__VA_ARGS__)
+#else
+#define atenl_dbg(fmt, ...)
+#endif
+
+static inline void
+atenl_dbg_print_data(const void *data, const char *func_name, u32 len)
+{
+#ifdef CONFIG_ATENL_DEBUG_VERBOSE
+	u32 *tmp = (u32 *)data;
+	int i;
+
+	for (i = 0; i < DIV_ROUND_UP(len, 4); i++)
+		atenl_dbg("%s: [%d] = 0x%08x\n", func_name, i, tmp[i]);
+#endif
+}
+
+/* #define debug_print(fmt, ...) \ */
+/* 	do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0) */
+
+#endif
diff --git a/recipes-devtools/atenl/files/src/eeprom.c b/recipes-devtools/atenl/files/src/eeprom.c
new file mode 100644
index 0000000..1c1cb6c
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/eeprom.c
@@ -0,0 +1,563 @@
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "atenl.h"
+
+#define EEPROM_PART_SIZE 0x64000
+char *eeprom_file;
+
+static FILE *mtd_open(const char *mtd)
+{
+	char line[128], name[64];
+	FILE *fp;
+	int i;
+
+	fp = fopen("/proc/mtd", "r");
+	if (!fp)
+		return NULL;
+
+	snprintf(name, sizeof(name), "\"%s\"", mtd);
+	while (fgets(line, sizeof(line), fp)) {
+		if (!sscanf(line, "mtd%d:", &i) || !strstr(line, name))
+			continue;
+
+		snprintf(line, sizeof(line), "/dev/mtd%d", i);
+		fclose(fp);
+		return fopen(line, "r");
+	}
+	fclose(fp);
+
+	return NULL;
+}
+
+static int
+atenl_flash_create_file(struct atenl *an)
+{
+#define READ_LEN_LIMIT	0x64000
+	char buf[1024];
+	ssize_t len, limit = 0;
+	FILE *f;
+	int fd, ret;
+
+	f = mtd_open(an->mtd_part);
+	if (!f) {
+		atenl_err("Failed to open MTD device\n");
+		return -1;
+	}
+	fseek(f, an->mtd_offset, SEEK_SET);
+
+	fd = open(eeprom_file, O_RDWR | O_CREAT | O_EXCL, 00644);
+	if (fd < 0)
+		goto out;
+
+	while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+		ssize_t w;
+
+retry:
+		w = write(fd, buf, len);
+		if (w > 0) {
+			limit += len;
+
+			if (limit >= READ_LEN_LIMIT)
+				break;
+			continue;
+		}
+
+		if (errno == EINTR)
+			goto retry;
+
+		perror("write");
+		unlink(eeprom_file);
+		close(fd);
+		fd = -1;
+		goto out;
+	}
+
+	ret = lseek(fd, 0, SEEK_SET);
+	if (ret) {
+		fclose(f);
+		close(fd);
+		return ret;
+	}
+
+out:
+	fclose(f);
+	return fd;
+}
+
+static int
+atenl_efuse_create_file(struct atenl *an)
+{
+	char fname[64], buf[1024];
+	ssize_t len;
+	int fd_ori, fd, ret;
+
+	snprintf(fname, sizeof(fname),
+		"/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom", get_band_val(an, 0, phy_idx));
+	fd_ori = open(fname, O_RDONLY);
+	if (fd_ori < 0)
+		return -1;
+
+	fd = open(eeprom_file, O_RDWR | O_CREAT | O_EXCL, 00644);
+	if (fd < 0)
+		goto out;
+
+	while ((len = read(fd_ori, buf, sizeof(buf))) > 0) {
+		ssize_t w;
+
+retry:
+		w = write(fd, buf, len);
+		if (w > 0)
+			continue;
+
+		if (errno == EINTR)
+			goto retry;
+
+		perror("write");
+		unlink(eeprom_file);
+		close(fd);
+		fd = -1;
+		goto out;
+	}
+
+	ret = lseek(fd, 0, SEEK_SET);
+	if (ret) {
+		close(fd_ori);
+		close(fd);
+		return ret;
+	}
+
+out:
+	close(fd_ori);
+	return fd;
+}
+
+static bool
+atenl_eeprom_file_exists(void)
+{
+	struct stat st;
+
+	return stat(eeprom_file, &st) == 0;
+}
+
+static int
+atenl_eeprom_init_file(struct atenl *an, bool flash_mode)
+{
+	int fd;
+
+	if (!atenl_eeprom_file_exists()) {
+		if (flash_mode)
+			atenl_dbg("%s: init eeprom with flash mode\n", __func__);
+		else
+			atenl_dbg("%s: init eeprom with efuse mode\n", __func__);
+
+		if (flash_mode)
+			return atenl_flash_create_file(an);
+
+		return atenl_efuse_create_file(an);
+	}
+
+	fd = open(eeprom_file, O_RDWR);
+	if (fd < 0)
+		perror("open");
+
+	return fd;
+}
+
+static void
+atenl_eeprom_init_chip_id(struct atenl *an)
+{
+	an->chip_id = *(u16 *)an->eeprom_data;
+
+	if (is_mt7915(an)) {
+		an->adie_id = 0x7975;
+	} else if (is_mt7916(an)) {
+		an->adie_id = 0x7976;
+	} else if (is_mt7986(an)) {
+		bool is_7975 = false;
+		u32 val;
+		u8 sub_id;
+
+		atenl_reg_read(an, 0x18050000, &val);
+
+		switch (val & 0xf) {
+		case MT7975_ONE_ADIE_SINGLE_BAND:
+			is_7975 = true;
+			/* fallthrough */
+		case MT7976_ONE_ADIE_SINGLE_BAND:
+			sub_id = 0xa;
+			break;
+		case MT7976_ONE_ADIE_DBDC:
+			sub_id = 0x7;
+			break;
+		case MT7975_DUAL_ADIE_DBDC:
+			is_7975 = true;
+			/* fallthrough */
+		case MT7976_DUAL_ADIE_DBDC:
+		default:
+			sub_id = 0xf;
+			break;
+		}
+
+		an->sub_chip_id = sub_id;
+		an->adie_id = is_7975 ? 0x7975 : 0x7976;
+	}
+}
+
+static void
+atenl_eeprom_init_max_size(struct atenl *an)
+{
+	switch (an->chip_id) {
+	case 0x7915:
+		an->eeprom_size = 3584;
+		an->eeprom_prek_offs = 0x62;
+		break;
+	case 0x7906:
+	case 0x7916:
+	case 0x7986:
+		an->eeprom_size = 4096;
+		an->eeprom_prek_offs = 0x19a;
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+atenl_eeprom_init_band_cap(struct atenl *an)
+{
+	u8 *eeprom = an->eeprom_data;
+
+	if (is_mt7915(an)) {
+		u8 val = eeprom[MT_EE_WIFI_CONF];
+		u8 band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+		struct atenl_band *anb = &an->anb[0];
+
+		/* MT7915A */
+		if (band_sel == MT_EE_BAND_SEL_DEFAULT) {
+			anb->valid = true;
+			anb->cap = BAND_TYPE_2G_5G;
+			return;
+		}
+
+		/* MT7915D */
+		if (band_sel == MT_EE_BAND_SEL_2GHZ) {
+			anb->valid = true;
+			anb->cap = BAND_TYPE_2G;
+		}
+
+		val = eeprom[MT_EE_WIFI_CONF + 1];
+		band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+		anb++;
+
+		if (band_sel == MT_EE_BAND_SEL_5GHZ) {
+			anb->valid = true;
+			anb->cap = BAND_TYPE_5G;
+		}
+	} else if (is_mt7916(an) || is_mt7986(an)) {
+		struct atenl_band *anb;
+		u8 val, band_sel;
+		int i;
+
+		for (i = 0; i < 2; i++) {
+			val = eeprom[MT_EE_WIFI_CONF + i];
+			band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+			anb = &an->anb[i];
+
+			anb->valid = true;
+			switch (band_sel) {
+			case MT_EE_BAND_SEL_2G:
+				anb->cap = BAND_TYPE_2G;
+				break;
+			case MT_EE_BAND_SEL_5G:
+				anb->cap = BAND_TYPE_5G;
+				break;
+			case MT_EE_BAND_SEL_6G:
+				anb->cap = BAND_TYPE_6G;
+				break;
+			case MT_EE_BAND_SEL_5G_6G:
+				anb->cap = BAND_TYPE_5G_6G;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+}
+
+static void
+atenl_eeprom_init_antenna_cap(struct atenl *an)
+{
+	if (is_mt7915(an)) {
+		if (an->anb[0].cap == BAND_TYPE_2G_5G)
+			an->anb[0].chainmask = 0xf;
+		else {
+			an->anb[0].chainmask = 0x3;
+			an->anb[1].chainmask = 0xc;
+		}
+	} else if (is_mt7916(an)) {
+		an->anb[0].chainmask = 0x3;
+		an->anb[1].chainmask = 0x3;
+	} else if (is_mt7986(an)) {
+		an->anb[0].chainmask = 0xf;
+		an->anb[1].chainmask = 0xf;
+	}
+}
+
+int atenl_eeprom_init(struct atenl *an, u8 phy_idx)
+{
+	bool flash_mode;
+	int eeprom_fd;
+	char buf[30];
+	u8 main_phy_idx = phy_idx;
+
+	set_band_val(an, 0, phy_idx, phy_idx);
+	atenl_nl_check_mtd(an);
+	flash_mode = an->mtd_part != NULL;
+
+	if (flash_mode)
+		main_phy_idx = an->is_main_phy ? main_phy_idx : (main_phy_idx - 1);
+
+	snprintf(buf, sizeof(buf), "/tmp/atenl-eeprom-phy%u", main_phy_idx);
+	eeprom_file = strdup(buf);
+
+	eeprom_fd = atenl_eeprom_init_file(an, flash_mode);
+	if (eeprom_fd < 0)
+		return -1;
+
+	an->eeprom_data = mmap(NULL, EEPROM_PART_SIZE, PROT_READ | PROT_WRITE,
+			       MAP_SHARED, eeprom_fd, 0);
+	if (!an->eeprom_data) {
+		perror("mmap");
+		close(eeprom_fd);
+		return -1;
+	}
+
+	an->eeprom_fd = eeprom_fd;
+	atenl_eeprom_init_chip_id(an);
+	atenl_eeprom_init_max_size(an);
+	atenl_eeprom_init_band_cap(an);
+	atenl_eeprom_init_antenna_cap(an);
+
+	if (get_band_val(an, 1, valid))
+		set_band_val(an, 1, phy_idx, phy_idx + 1);
+
+	return 0;
+}
+
+void atenl_eeprom_close(struct atenl *an)
+{
+	msync(an->eeprom_data, EEPROM_PART_SIZE, MS_SYNC);
+	munmap(an->eeprom_data, EEPROM_PART_SIZE);
+	close(an->eeprom_fd);
+
+	if (!an->cmd_mode) {
+		if (remove(eeprom_file))
+			perror("remove");
+	}
+
+	free(eeprom_file);
+}
+
+int atenl_eeprom_update_precal(struct atenl *an, int write_offs, int size)
+{
+	u32 offs = an->eeprom_prek_offs;
+	u8 cal_indicator, *eeprom, *pre_cal;
+
+	if (!an->cal && !an->cal_info)
+		return 0;
+
+	eeprom = an->eeprom_data;
+	pre_cal = eeprom + an->eeprom_size;
+	cal_indicator = an->cal_info[4];
+
+	memcpy(eeprom + offs, &cal_indicator, sizeof(u8));
+	memcpy(pre_cal, an->cal_info, PRE_CAL_INFO);
+	pre_cal += (PRE_CAL_INFO + write_offs);
+
+	if (an->cal)
+		memcpy(pre_cal, an->cal, size);
+	else
+		memset(pre_cal, 0, size);
+
+	return 0;
+}
+
+int atenl_eeprom_write_mtd(struct atenl *an)
+{
+	bool flash_mode = an->mtd_part != NULL;
+	pid_t pid;
+	char offset[10];
+
+	if (!flash_mode)
+		return 0;
+
+	pid = fork();
+	if (pid < 0) {
+		perror("Fork");
+		return EXIT_FAILURE;
+	} else if (pid == 0) {
+		int ret;
+		char *part = strdup(an->mtd_part);
+		snprintf(offset, sizeof(offset), "%d", an->mtd_offset);
+		char *cmd[] = {"mtd", "-p", offset, "write", eeprom_file, part, NULL};
+
+		ret = execvp("mtd", cmd);
+		if (ret < 0) {
+			atenl_err("%s: exec error\n", __func__);
+			exit(0);
+		}
+	} else {
+		wait(&pid);
+	}
+
+	return 0;
+}
+
+/* Directly read values from driver's eeprom.
+ * It's usally used to get calibrated data from driver.
+ */
+int atenl_eeprom_read_from_driver(struct atenl *an, u32 offset, int len)
+{
+	u8 *eeprom_data = an->eeprom_data + offset;
+	char fname[64], buf[1024];
+	int fd_ori, ret;
+	ssize_t rd;
+
+	snprintf(fname, sizeof(fname),
+		"/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom",
+		get_band_val(an, 0, phy_idx));
+	fd_ori = open(fname, O_RDONLY);
+	if (fd_ori < 0)
+		return -1;
+
+	ret = lseek(fd_ori, offset, SEEK_SET);
+	if (ret < 0)
+		goto out;
+
+	while ((rd = read(fd_ori, buf, sizeof(buf))) > 0 && len) {
+		if (len < rd) {
+			memcpy(eeprom_data, buf, len);
+			break;
+		} else {
+			memcpy(eeprom_data, buf, rd);
+			eeprom_data += rd;
+			len -= rd;
+		}
+	}
+
+	ret = 0;
+out:
+	close(fd_ori);
+	return ret;
+}
+
+/* Update all eeprom values to driver before writing efuse */
+static void
+atenl_eeprom_sync_to_driver(struct atenl *an)
+{
+	int i;
+
+	for (i = 0; i < an->eeprom_size; i += 16)
+		atenl_nl_write_eeprom(an, i, &an->eeprom_data[i], 16);
+}
+
+void atenl_eeprom_cmd_handler(struct atenl *an, u8 phy_idx, char *cmd)
+{
+	bool flash_mode;
+
+	an->cmd_mode = true;
+
+	atenl_eeprom_init(an, phy_idx);
+	flash_mode = an->mtd_part != NULL;
+
+	if (!strncmp(cmd, "sync eeprom all", 15)) {
+		atenl_eeprom_write_mtd(an);
+	} else if (!strncmp(cmd, "eeprom", 6)) {
+		char *s = strchr(cmd, ' ');
+
+		if (!s) {
+			atenl_err("eeprom: please type a correct command\n");
+			return;
+		}
+
+		s++;
+		if (!strncmp(s, "reset", 5)) {
+			unlink(eeprom_file);
+		} else if (!strncmp(s, "file", 4)) {
+			atenl_info("%s\n", eeprom_file);
+			atenl_info("Flash mode: %d\n", flash_mode);
+		} else if (!strncmp(s, "set", 3)) {
+			u32 offset, val;
+
+			s = strchr(s, ' ');
+			if (!s)
+				return;
+			s++;
+
+			if (!sscanf(s, "%x=%x", &offset, &val) ||
+			    offset > EEPROM_PART_SIZE)
+				return;
+
+			an->eeprom_data[offset] = val;
+			atenl_info("set offset 0x%x to 0x%x\n", offset, val);
+		} else if (!strncmp(s, "update buffermode", 17)) {
+			atenl_eeprom_sync_to_driver(an);
+			atenl_nl_update_buffer_mode(an);
+		} else if (!strncmp(s, "write", 5)) {
+			s = strchr(s, ' ');
+			if (!s)
+				return;
+			s++;
+
+			if (!strncmp(s, "flash", 5)) {
+				atenl_eeprom_write_mtd(an);
+            } else if (!strncmp(s, "to efuse", 8)) {
+                atenl_eeprom_sync_to_driver(an);
+                atenl_nl_write_efuse_all(an);
+            }
+		} else if (!strncmp(s, "read", 4)) {
+			u32 offset;
+
+			s = strchr(s, ' ');
+			if (!s)
+				return;
+			s++;
+
+			if (!sscanf(s, "%x", &offset) ||
+			    offset > EEPROM_PART_SIZE)
+				return;
+
+			atenl_info("val = 0x%x (%u)\n", an->eeprom_data[offset],
+							an->eeprom_data[offset]);
+		} else if (!strncmp(s, "precal", 6)) {
+			s = strchr(s, ' ');
+			if (!s)
+				return;
+			s++;
+
+			if (!strncmp(s, "sync group", 10)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_SYNC_GROUP);
+			} else if (!strncmp(s, "sync dpd 2g", 11)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_2G);
+			} else if (!strncmp(s, "sync dpd 5g", 11)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_5G);
+			} else if (!strncmp(s, "sync dpd 6g", 11)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_6G);
+			} else if (!strncmp(s, "group clean", 11)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_CLEAN_GROUP);
+			} else if (!strncmp(s, "dpd clean", 9)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_CLEAN_DPD);
+			} else if (!strncmp(s, "sync", 4)) {
+				atenl_nl_precal_sync_from_driver(an, PREK_SYNC_ALL);
+			}
+		} else {
+            atenl_err("Unknown eeprom command: %s\n", cmd);
+        }
+	} else {
+		atenl_err("Unknown command: %s\n", cmd);
+	}
+}
diff --git a/recipes-devtools/atenl/files/src/eth.c b/recipes-devtools/atenl/files/src/eth.c
new file mode 100644
index 0000000..cd32f71
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/eth.c
@@ -0,0 +1,118 @@
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include "atenl.h"
+
+int atenl_eth_init(struct atenl *an)
+{
+	struct sockaddr_ll addr = {};
+	struct ifreq ifr = {};
+	int ret;
+ 
+	if (!an->bridge_name) {
+		perror("Bridge name not specified");
+		goto out;
+	}
+
+	memcpy(ifr.ifr_name, an->bridge_name, strlen(an->bridge_name));
+	ret = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_RACFG));
+	if (ret < 0) {
+		perror("socket");
+		goto out;
+	}
+	an->sock_eth = ret;
+
+	addr.sll_family = AF_PACKET;
+	addr.sll_ifindex = if_nametoindex(an->bridge_name);
+
+	ret = bind(an->sock_eth, (struct sockaddr *)&addr, sizeof(addr));
+	if (ret < 0) {
+		perror("bind");
+		goto out;
+	}
+
+	ret = ioctl(an->sock_eth, SIOCGIFHWADDR, &ifr);
+	if (ret < 0) {
+		perror("ioctl(SIOCGIFHWADDR)");
+		goto out;
+	}
+
+	memcpy(an->mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+	atenl_info("Open Ethernet socket success on %s, mac addr = " MACSTR "\n",
+		   an->bridge_name, MAC2STR(an->mac_addr));
+
+	ret = 0;
+out:
+	return ret;
+}
+
+int atenl_eth_recv(struct atenl *an, struct atenl_data *data)
+{
+	struct ethhdr *hdr;
+	int len;
+
+	len = recvfrom(an->sock_eth, data->buf, sizeof(data->buf), 0, NULL, NULL);
+
+	if (len < ETH_HLEN + RACFG_HLEN) {
+		atenl_err("packet len is too short: %d\n", len);
+		return -EINVAL;
+	}
+
+	hdr = (struct ethhdr *)data->buf;
+	if (hdr->h_proto != htons(ETH_P_RACFG)) {
+		atenl_err("invalid protocol type\n");
+		return -EINVAL;
+	}
+
+	if (!ether_addr_equal(an->mac_addr, hdr->h_dest) &&
+	    !is_broadcast_ether_addr(hdr->h_dest)) {
+		atenl_err("invalid dest MAC\n");
+		return -EINVAL;
+	}
+
+	data->len = len;
+
+	return 0;
+}
+
+int atenl_eth_send(struct atenl *an, struct atenl_data *data)
+{
+	struct ethhdr *ehdr = (struct ethhdr *)data->buf;
+	struct sockaddr_ll addr = {};
+	int ret, len = data->len;
+
+	if (an->unicast)
+		ether_addr_copy(ehdr->h_dest, ehdr->h_source);
+	else
+		eth_broadcast_addr(ehdr->h_dest);
+
+	ether_addr_copy(ehdr->h_source, an->mac_addr);
+	ehdr->h_proto = htons(ETH_P_RACFG);
+
+	if (len < 60)
+		len = 60;
+	else if (len > 1514) {
+		atenl_err("response ethernet length is too long\n");
+		return -1;
+	}
+
+	atenl_dbg_print_data(data->buf, __func__, len);
+
+	addr.sll_family = PF_PACKET;
+	addr.sll_protocol = htons(ETH_P_RACFG);
+	addr.sll_ifindex = if_nametoindex(an->bridge_name);
+	addr.sll_pkttype = PACKET_BROADCAST;
+	addr.sll_hatype = ARPHRD_ETHER;
+	addr.sll_halen = ETH_ALEN;
+	memset(addr.sll_addr, 0, 8);
+	eth_broadcast_addr(addr.sll_addr);
+
+	ret = sendto(an->sock_eth, data->buf, len, 0,
+		     (struct sockaddr *)&addr, sizeof(addr));
+	if (ret < 0) {
+		perror("sendto");
+		return ret;
+	}
+	
+	return 0;
+}
diff --git a/recipes-devtools/atenl/files/src/hqa.c b/recipes-devtools/atenl/files/src/hqa.c
new file mode 100644
index 0000000..eabfce5
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/hqa.c
@@ -0,0 +1,1236 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#include "atenl.h"
+
+#define CHAN(_ch, _freq, _ch_80, _ch_160, ...) {	\
+	.ch = _ch,				\
+	.freq = _freq,				\
+	.ch_80 = _ch_80,			\
+	.ch_160 = _ch_160,			\
+	__VA_ARGS__				\
+}
+
+struct atenl_channel {
+	/* ctrl ch */
+	u16 ch;
+	u16 freq;
+	/* center ch */
+	u16 ch_80;
+	u16 ch_160;
+	/* only use for channels that don't have 80 but has 40 */
+	u16 ch_40;
+};
+
+static const struct atenl_channel atenl_channels_5ghz[] = {
+	CHAN(8, 5040, 0, 0),
+	CHAN(12, 5060, 0, 0),
+	CHAN(16, 5080, 0, 0),
+
+	CHAN(36, 5180, 42, 50),
+	CHAN(40, 5200, 42, 50),
+	CHAN(44, 5220, 42, 50),
+	CHAN(48, 5240, 42, 50),
+
+	CHAN(52, 5260, 58, 50),
+	CHAN(56, 5280, 58, 50),
+	CHAN(60, 5300, 58, 50),
+	CHAN(64, 5320, 58, 50),
+
+	CHAN(68, 5340, 0, 0),
+	CHAN(80, 5400, 0, 0),
+	CHAN(84, 5420, 0, 0),
+	CHAN(88, 5440, 0, 0),
+	CHAN(92, 5460, 0, 0),
+	CHAN(96, 5480, 0, 0),
+
+	CHAN(100, 5500, 106, 114),
+	CHAN(104, 5520, 106, 114),
+	CHAN(108, 5540, 106, 114),
+	CHAN(112, 5560, 106, 114),
+	CHAN(116, 5580, 122, 114),
+	CHAN(120, 5600, 122, 114),
+	CHAN(124, 5620, 122, 114),
+	CHAN(128, 5640, 122, 114),
+
+	CHAN(132, 5660, 138, 0),
+	CHAN(136, 5680, 138, 0),
+	CHAN(140, 5700, 138, 0),
+	CHAN(144, 5720, 138, 0),
+
+	CHAN(149, 5745, 155, 0),
+	CHAN(153, 5765, 155, 0),
+	CHAN(157, 5785, 155, 0),
+	CHAN(161, 5805, 155, 0),
+	CHAN(165, 5825, 0, 0, .ch_40 = 167),
+	CHAN(169, 5845, 0, 0, .ch_40 = 167),
+	CHAN(173, 5865, 0, 0),
+
+	CHAN(184, 4920, 0, 0),
+	CHAN(188, 4940, 0, 0),
+	CHAN(192, 4960, 0, 0),
+	CHAN(196, 4980, 0, 0),
+};
+
+static const struct atenl_channel atenl_channels_6ghz[] = {
+	/* UNII-5 */
+	CHAN(1, 5955, 7, 15),
+	CHAN(5, 5975, 7, 15),
+	CHAN(9, 5995, 7, 15),
+	CHAN(13, 6015, 7, 15),
+	CHAN(17, 6035, 23, 15),
+	CHAN(21, 6055, 23, 15),
+	CHAN(25, 6075, 23, 15),
+	CHAN(29, 6095, 23, 15),
+	CHAN(33, 6115, 39, 47),
+	CHAN(37, 6135, 39, 47),
+	CHAN(41, 6155, 39, 47),
+	CHAN(45, 6175, 39, 47),
+	CHAN(49, 6195, 55, 47),
+	CHAN(53, 6215, 55, 47),
+	CHAN(57, 6235, 55, 47),
+	CHAN(61, 6255, 55, 47),
+	CHAN(65, 6275, 71, 79),
+	CHAN(69, 6295, 71, 79),
+	CHAN(73, 6315, 71, 79),
+	CHAN(77, 6335, 71, 79),
+	CHAN(81, 6355, 87, 79),
+	CHAN(85, 6375, 87, 79),
+	CHAN(89, 6395, 87, 79),
+	CHAN(93, 6415, 87, 79),
+	/* UNII-6 */
+	CHAN(97, 6435, 103, 111),
+	CHAN(101, 6455, 103, 111),
+	CHAN(105, 6475, 103, 111),
+	CHAN(109, 6495, 103, 111),
+	CHAN(113, 6515, 119, 111),
+	CHAN(117, 6535, 119, 111),
+	/* UNII-7 */
+	CHAN(121, 6555, 119, 111),
+	CHAN(125, 6575, 119, 111),
+	CHAN(129, 6595, 135, 143),
+	CHAN(133, 6615, 135, 143),
+	CHAN(137, 6635, 135, 143),
+	CHAN(141, 6655, 135, 143),
+	CHAN(145, 6675, 151, 143),
+	CHAN(149, 6695, 151, 143),
+	CHAN(153, 6715, 151, 143),
+	CHAN(157, 6735, 151, 143),
+	CHAN(161, 6755, 167, 175),
+	CHAN(165, 6775, 167, 175),
+	CHAN(169, 6795, 167, 175),
+	CHAN(173, 6815, 167, 175),
+	CHAN(177, 6835, 183, 175),
+	CHAN(181, 6855, 183, 175),
+	CHAN(185, 6875, 183, 175),
+	/* UNII-8 */
+	CHAN(189, 6895, 183, 175),
+	CHAN(193, 6915, 199, 207),
+	CHAN(197, 6935, 199, 207),
+	CHAN(201, 6955, 199, 207),
+	CHAN(205, 6975, 199, 207),
+	CHAN(209, 6995, 215, 207),
+	CHAN(213, 7015, 215, 207),
+	CHAN(217, 7035, 215, 207),
+	CHAN(221, 7055, 215, 207),
+	CHAN(225, 7075, 0, 0, .ch_40 = 227),
+	CHAN(229, 7095, 0, 0, .ch_40 = 227),
+	CHAN(233, 7115, 0, 0),
+};
+
+static int
+atenl_hqa_adapter(struct atenl *an, struct atenl_data *data)
+{
+	char cmd[64];
+	u8 i;
+
+	for (i = 0; i < MAX_BAND_NUM; i++) {
+		u8 phy = get_band_val(an, i, phy_idx);
+
+		if (!get_band_val(an, i, valid))
+			continue;
+
+		if (data->cmd == HQA_CMD_OPEN_ADAPTER) {
+			sprintf(cmd, "iw phy phy%u interface add mon%u type monitor", phy, phy);
+			system(cmd);
+			sprintf(cmd, "iw dev wlan%u del", phy);
+			system(cmd);
+			sprintf(cmd, "ifconfig mon%u up", phy);
+			system(cmd);
+			/* set a special-defined country */
+			sprintf(cmd, "iw reg set VV");
+			system(cmd);
+			atenl_nl_set_state(an, i, MT76_TM_STATE_IDLE);
+		} else {
+			atenl_nl_set_state(an, i, MT76_TM_STATE_OFF);
+			sprintf(cmd, "iw reg set 00");
+			system(cmd);
+			sprintf(cmd, "iw dev mon%u del", phy);
+			system(cmd);
+			sprintf(cmd, "iw phy phy%u interface add wlan%u type managed", phy, phy);
+			system(cmd);
+		}
+	}
+
+	return 0;
+}
+
+static int
+atenl_hqa_set_rf_mode(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+	/* The testmode rf mode change applies to all bands */
+	set_band_val(an, 0, rf_mode, ntohl(*(u32 *)hdr->data));
+	set_band_val(an, 1, rf_mode, ntohl(*(u32 *)hdr->data));
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_chip_id(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+	*(u32 *)(hdr->data + 2) = htonl(an->chip_id);
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_sub_chip_id(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+	*(u32 *)(hdr->data + 2) = htonl(an->sub_chip_id);
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_rf_cap(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 band = ntohl(*(u32 *)hdr->data);
+	struct atenl_band *anb;
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	anb = &an->anb[band];
+	/* fill tx and rx ant */
+	*(u32 *)(hdr->data + 2) = htonl(__builtin_popcount(anb->chainmask));
+	*(u32 *)(hdr->data + 2 + 4) = *(u32 *)(hdr->data + 2);
+
+	return 0;
+}
+
+static int
+atenl_hqa_reset_counter(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_band *anb = &an->anb[an->cur_band];
+
+	anb->reset_tx_cnt = true;
+	anb->reset_rx_cnt = true;
+
+	memset(&anb->rx_stat, 0, sizeof(anb->rx_stat));
+
+	return 0;
+}
+
+static int
+atenl_hqa_mac_bbp_reg(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	enum atenl_cmd cmd = data->cmd;
+	u32 *v = (u32 *)hdr->data;
+	u32 offset = ntohl(v[0]), res;
+	int ret;
+
+	if (cmd == HQA_CMD_READ_MAC_BBP_REG) {
+		u16 num = ntohs(*(u16 *)(hdr->data + 4));
+		u32 *ptr = (u32 *)(hdr->data + 2);
+		int i;
+
+		if (num > SHRT_MAX) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		hdr->len = htons(2 + num * 4);
+		for (i = 0; i < num && i < sizeof(hdr->data) / 4; i++) {
+			ret = atenl_reg_read(an, offset + i * 4, &res);
+			if (ret)
+				goto out;
+
+			res = htonl(res);
+			memcpy(ptr + i, &res, 4);
+		}
+	} else if (cmd == HQA_CMD_READ_MAC_BBP_REG_QA) {
+		ret = atenl_reg_read(an, offset, &res);
+		if (ret)
+			goto out;
+
+		res = htonl(res);
+		memcpy(hdr->data + 2, &res, 4);
+	} else {
+		u32 val = ntohl(v[1]);
+
+		ret = atenl_reg_write(an, offset, val);
+		if (ret)
+			goto out;
+	}
+
+	ret = 0;
+out:
+	memset(hdr->data, 0, 2);
+
+	return ret;
+}
+
+static int
+atenl_hqa_rf_reg(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	enum atenl_cmd cmd = data->cmd;
+	u32 *v = (u32 *)hdr->data;
+	u32 wf_sel = ntohl(v[0]);
+	u32 offset = ntohl(v[1]);
+	u32 num = ntohl(v[2]);
+	int ret, i;
+
+	if (cmd == HQA_CMD_READ_RF_REG) {
+		u32 *ptr = (u32 *)(hdr->data + 2);
+		u32 res;
+
+		hdr->len = htons(2 + num * 4);
+		for (i = 0; i < num && i < sizeof(hdr->data) / 4; i++) {
+			ret = atenl_rf_read(an, wf_sel, offset + i * 4, &res);
+			if (ret)
+				goto out;
+
+			res = htonl(res);
+			memcpy(ptr + i, &res, 4);
+		}
+	} else {
+		u32 *ptr = (u32 *)(hdr->data + 12);
+
+		for (i = 0; i < num && i < sizeof(hdr->data) / 4; i++) {
+			u32 val = ntohl(ptr[i]);
+
+			ret = atenl_rf_write(an, wf_sel, offset + i * 4, val);
+			if (ret)
+				goto out;
+		}
+	}
+
+	ret = 0;
+out:
+	memset(hdr->data, 0, 2);
+
+	return ret;
+}
+
+static int
+atenl_hqa_eeprom_bulk(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	enum atenl_cmd cmd = data->cmd;
+
+	if (cmd == HQA_CMD_WRITE_BUFFER_DONE) {
+		u32 buf_mode = ntohl(*(u32 *)hdr->data);
+
+		switch (buf_mode) {
+		case E2P_EFUSE_MODE:
+			atenl_nl_write_efuse_all(an);
+			break;
+		default:
+			break;
+		}
+	} else {
+		u16 *v = (u16 *)hdr->data;
+		u16 offset = ntohs(v[0]), len = ntohs(v[1]);
+		u16 val;
+		size_t i;
+
+		if (offset >= an->eeprom_size || (len > sizeof(hdr->data) - 2))
+			return -EINVAL;
+
+		if (cmd == HQA_CMD_READ_EEPROM_BULK) {
+			hdr->len = htons(len + 2);
+			for (i = 0; i < len; i += 2) {
+				if (offset + i >= an->eeprom_size)
+					val = 0;
+				else
+					val = ntohs(*(u16 *)(an->eeprom_data + offset + i));
+				*(u16 *)(hdr->data + 2 + i) = val;
+			}
+		} else { /* write eeprom */
+			for (i = 0; i < DIV_ROUND_UP(len, 2); i++) {
+				val = ntohs(v[i + 2]);
+				memcpy(&an->eeprom_data[offset + i * 2], &val, 2);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_efuse_free_block(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 free_block = htonl(0x14);
+
+	/* TODO */
+	*(u32 *)(hdr->data + 2) = free_block;
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_band(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 band = ntohl(*(u32 *)hdr->data);
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	*(u32 *)(hdr->data + 2) = htonl(an->anb[band].cap);
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_tx_power(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 tx_power = htonl(28);
+
+	memcpy(hdr->data + 6, &tx_power, 4);
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_freq_offset(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 freq_offset = htonl(10);
+
+	/* TODO */
+	memcpy(hdr->data + 2, &freq_offset, 4);
+
+	return 0;
+}
+
+static int
+atenl_hqa_get_cfg(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 val = htonl(1);
+
+	/* TODO */
+	memcpy(hdr->data + 2, &val, 4);
+
+	return 0;
+}
+
+static int
+atenl_hqa_read_temperature(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	char buf[64], *str;
+	int fd, ret;
+	u32 temp;
+	u8 phy_idx = get_band_val(an, an->cur_band, phy_idx);
+
+	ret = snprintf(buf, sizeof(buf),
+		       "/sys/class/ieee80211/phy%u/hwmon%u/temp1_input",
+		       phy_idx, phy_idx);
+	if (snprintf_error(sizeof(buf), ret))
+		return -1;
+
+	fd = open(buf, O_RDONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = read(fd, buf, sizeof(buf) - 1);
+	if (ret < 0)
+		goto out;
+	buf[ret] = 0;
+
+	str = strchr(buf, ':');
+	str += 2;
+	temp = strtol(str, NULL, 10);
+	/* unit conversion */
+	*(u32 *)(hdr->data + 2) = htonl(temp / 1000);
+
+	ret = 0;
+out:
+	close(fd);
+
+	return ret;
+}
+
+static int
+atenl_hqa_check_efuse_mode(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	bool flash_mode = an->mtd_part != NULL;
+	enum atenl_cmd cmd = data->cmd;
+	u32 mode;
+
+	switch (cmd) {
+	case HQA_CMD_CHECK_EFUSE_MODE:
+		mode = flash_mode ? 0 : 1;
+		break;
+	case HQA_CMD_CHECK_EFUSE_MODE_TYPE:
+		mode = flash_mode ? E2P_FLASH_MODE : E2P_BIN_MODE;
+		break;
+	case HQA_CMD_CHECK_EFUSE_MODE_NATIVE:
+		mode = flash_mode ? E2P_FLASH_MODE : E2P_EFUSE_MODE;
+		break;
+	default:
+		mode = E2P_BIN_MODE;
+		break;
+	}
+
+	*(u32 *)(hdr->data + 2) = htonl(mode);
+
+	return 0;
+}
+
+static inline u16
+atenl_get_freq_by_channel(u8 ch_band, u16 ch)
+{
+	u16 base;
+
+	if (ch_band == CH_BAND_6GHZ) {
+		base = 5950;
+	} else if (ch_band == CH_BAND_5GHZ) {
+		if (ch >= 184)
+			return 4920 + (ch - 184) * 5;
+
+		base = 5000;
+	} else {
+		base = 2407;
+	}
+
+	return base + ch * 5;
+}
+
+u16 atenl_get_center_channel(u8 bw, u8 ch_band, u16 ctrl_ch)
+{
+	const struct atenl_channel *chan = NULL;
+	const struct atenl_channel *ch_list;
+	u16 center_ch;
+	u8 ch_num;
+	int i;
+
+	if (ch_band == CH_BAND_2GHZ || bw <= TEST_CBW_40MHZ)
+		return 0;
+
+	if (ch_band == CH_BAND_6GHZ) {
+		ch_list = atenl_channels_6ghz;
+		ch_num = ARRAY_SIZE(atenl_channels_6ghz);
+	} else {
+		ch_list = atenl_channels_5ghz;
+		ch_num = ARRAY_SIZE(atenl_channels_5ghz);
+	}
+
+	for (i = 0; i < ch_num; i++) {
+		if (ctrl_ch == ch_list[i].ch) {
+			chan = &ch_list[i];
+			break;
+		}
+	}
+
+	if (!chan)
+		return 0;
+
+	switch (bw) {
+	case TEST_CBW_160MHZ:
+		center_ch = chan->ch_160;
+		break;
+	case TEST_CBW_80MHZ:
+		center_ch = chan->ch_80;
+		break;
+	default:
+		center_ch = 0;
+		break;
+	}
+
+	return center_ch;
+}
+
+static void atenl_get_bw_string(u8 bw, char *buf)
+{
+	switch (bw) {
+	case TEST_CBW_160MHZ:
+		sprintf(buf, "160");
+		break;
+	case TEST_CBW_80MHZ:
+		sprintf(buf, "80");
+		break;
+	case TEST_CBW_40MHZ:
+		sprintf(buf, "40");
+		break;
+	default:
+		sprintf(buf, "20");
+		break;
+	}
+}
+
+void atenl_set_channel(struct atenl *an, u8 bw, u8 ch_band,
+		       u16 ch, u16 center_ch1, u16 center_ch2)
+{
+	char bw_str[8] = {};
+	char cmd[128];
+	u16 freq = atenl_get_freq_by_channel(ch_band, ch);
+	u16 freq_center1 = atenl_get_freq_by_channel(ch_band, center_ch1);
+	int ret;
+
+	if (bw > TEST_CBW_MAX)
+		return;
+
+	atenl_get_bw_string(bw, bw_str);
+
+	if (bw == TEST_CBW_20MHZ)
+		ret = snprintf(cmd, sizeof(cmd), "iw dev mon%d set freq %u %s",
+						 get_band_val(an, an->cur_band, phy_idx),
+						 freq, bw_str);
+	else
+		ret = snprintf(cmd, sizeof(cmd), "iw dev mon%d set freq %u %s %u",
+						 get_band_val(an, an->cur_band, phy_idx),
+						 freq, bw_str, freq_center1);
+	if (snprintf_error(sizeof(cmd), ret))
+		return;
+
+	atenl_dbg("%s: cmd: %s\n", __func__, cmd);
+
+	system(cmd);
+}
+
+static int
+atenl_hqa_set_channel(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 *v = (u32 *)hdr->data;
+	u8 band = ntohl(v[2]);
+	u16 ch1 = ntohl(v[3]);	/* center */
+	u16 ch2 = ntohl(v[4]);
+	u8 bw = ntohl(v[5]);
+	u8 pri_sel = ntohl(v[7]);
+	u8 ch_band = ntohl(v[9]);
+	u16 ctrl_ch = 0;
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	if ((bw == TEST_CBW_160MHZ && pri_sel > 7) ||
+	    (bw == TEST_CBW_80MHZ && pri_sel > 3) ||
+	    (bw == TEST_CBW_40MHZ && pri_sel > 1)) {
+		atenl_err("%s: ctrl channel select error\n", __func__);
+		return -EINVAL;
+	}
+
+	an->cur_band = band;
+
+	if (ch_band == CH_BAND_2GHZ) {
+		ctrl_ch = ch1;
+		switch (bw) {
+		case TEST_CBW_40MHZ:
+			if (pri_sel == 1)
+				ctrl_ch += 2;
+			else
+				ctrl_ch -= 2;
+			break;
+		default:
+			break;
+		}
+
+		atenl_set_channel(an, bw, CH_BAND_2GHZ, ctrl_ch, ch1, 0);
+	} else {
+		const struct atenl_channel *chan = NULL;
+		const struct atenl_channel *ch_list;
+		u8 ch_num;
+		int i;
+
+		if (ch_band == CH_BAND_6GHZ) {
+			ch_list = atenl_channels_6ghz;
+			ch_num = ARRAY_SIZE(atenl_channels_6ghz);
+		} else {
+			ch_list = atenl_channels_5ghz;
+			ch_num = ARRAY_SIZE(atenl_channels_5ghz);
+		}
+
+		if (bw == TEST_CBW_160MHZ) {
+			for (i = 0; i < ch_num; i++) {
+				if (ch1 == ch_list[i].ch_160) {
+					chan = &ch_list[i];
+					break;
+				} else if (ch1 < ch_list[i].ch_160) {
+					chan = &ch_list[i - 1];
+					break;
+				}
+			}
+		} else if (bw == TEST_CBW_80MHZ) {
+			for (i = 0; i < ch_num; i++) {
+				if (ch1 == ch_list[i].ch_80) {
+					chan = &ch_list[i];
+					break;
+				} else if (ch1 < ch_list[i].ch_80) {
+					chan = &ch_list[i - 1];
+					break;
+				}
+			}
+		} else {
+			for (i = 0; i < ch_num; i++) {
+				if (ch1 <= ch_list[i].ch) {
+					if (ch1 == ch_list[i].ch)
+						chan = &ch_list[i];
+					else
+						chan = &ch_list[i - 1];
+					break;
+				}
+			}
+		}
+
+		if (!chan)
+			return -EINVAL;
+
+		if (bw != TEST_CBW_20MHZ) {
+			chan += pri_sel;
+			if (chan > &ch_list[ch_num - 1])
+				return -EINVAL;
+		}
+		ctrl_ch = chan->ch;
+
+		atenl_set_channel(an, bw, ch_band, ctrl_ch, ch1, ch2);
+	}
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	atenl_nl_set_aid(an, band, 0);
+
+	return 0;
+}
+
+static int
+atenl_hqa_tx_time_option(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 *v = (u32 *)hdr->data;
+	u8 band = ntohl(v[1]);
+	u32 option = ntohl(v[2]);
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	set_band_val(an, band, use_tx_time, !!option);
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return 0;
+}
+
+/* should be placed in order for binary search */
+static const struct atenl_ops hqa_ops[] = {
+	{
+		.cmd = HQA_CMD_OPEN_ADAPTER,
+		.cmd_id = 0x1000,
+		.resp_len = 2,
+		.ops = atenl_hqa_adapter,
+	},
+	{
+		.cmd = HQA_CMD_CLOSE_ADAPTER,
+		.cmd_id = 0x1001,
+		.resp_len = 2,
+		.ops = atenl_hqa_adapter,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_PATH,
+		.cmd_id = 0x100b,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_RX_PATH,
+		.cmd_id = 0x100c,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_LEGACY,
+		.cmd_id = 0x100d,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_POWER,
+		.cmd_id = 0x1011,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_POWER_MANUAL,
+		.cmd_id = 0x1018,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_LEGACY,
+		.cmd_id = 0x1101,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_LEGACY,
+		.cmd_id = 0x1102,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_BW,
+		.cmd_id = 0x1104,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_PKT_BW,
+		.cmd_id = 0x1105,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_SET_TX_PRI_BW,
+		.cmd_id = 0x1106,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_SET_FREQ_OFFSET,
+		.cmd_id = 0x1107,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_TSSI,
+		.cmd_id = 0x1109,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_EEPROM_TO_FW,
+		.cmd_id = 0x110c,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_ANT_SWAP_CAP,
+		.cmd_id = 0x110d,
+		.resp_len = 6,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_RESET_TX_RX_COUNTER,
+		.cmd_id = 0x1200,
+		.resp_len = 2,
+		.ops = atenl_hqa_reset_counter,
+	},
+	{
+		.cmd = HQA_CMD_READ_MAC_BBP_REG_QA,
+		.cmd_id = 0x1300,
+		.resp_len = 6,
+		.ops = atenl_hqa_mac_bbp_reg,
+	},
+	{
+		.cmd = HQA_CMD_WRITE_MAC_BBP_REG,
+		.cmd_id = 0x1301,
+		.resp_len = 2,
+		.ops = atenl_hqa_mac_bbp_reg,
+	},
+	{
+		.cmd = HQA_CMD_READ_MAC_BBP_REG,
+		.cmd_id = 0x1302,
+		.ops = atenl_hqa_mac_bbp_reg,
+	},
+	{
+		.cmd = HQA_CMD_READ_RF_REG,
+		.cmd_id = 0x1303,
+		.ops = atenl_hqa_rf_reg,
+	},
+	{
+		.cmd = HQA_CMD_WRITE_RF_REG,
+		.cmd_id = 0x1304,
+		.resp_len = 2,
+		.ops = atenl_hqa_rf_reg,
+	},
+	{
+		.cmd = HQA_CMD_WRITE_EEPROM_BULK,
+		.cmd_id = 0x1306,
+		.resp_len = 2,
+		.ops = atenl_hqa_eeprom_bulk,
+	},
+	{
+		.cmd = HQA_CMD_READ_EEPROM_BULK,
+		.cmd_id = 0x1307,
+		.ops = atenl_hqa_eeprom_bulk,
+	},
+	{
+		.cmd = HQA_CMD_WRITE_EEPROM_BULK,
+		.cmd_id = 0x1308,
+		.resp_len = 2,
+		.ops = atenl_hqa_eeprom_bulk,
+	},
+	{
+		.cmd = HQA_CMD_CHECK_EFUSE_MODE,
+		.cmd_id = 0x1309,
+		.resp_len = 6,
+		.ops = atenl_hqa_check_efuse_mode,
+	},
+	{
+		.cmd = HQA_CMD_GET_EFUSE_FREE_BLOCK,
+		.cmd_id = 0x130a,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_efuse_free_block,
+	},
+	{
+		.cmd = HQA_CMD_GET_TX_POWER,
+		.cmd_id = 0x130d,
+		.resp_len = 10,
+		.ops = atenl_hqa_get_tx_power,
+	},
+	{
+		.cmd = HQA_CMD_SET_CFG,
+		.cmd_id = 0x130e,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_GET_FREQ_OFFSET,
+		.cmd_id = 0x130f,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_freq_offset,
+	},
+	{
+		.cmd = HQA_CMD_CONTINUOUS_TX,
+		.cmd_id = 0x1311,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_RX_PKT_LEN,
+		.cmd_id = 0x1312,
+		.resp_len = 2,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_GET_TX_INFO,
+		.cmd_id = 0x1313,
+		.resp_len = 10,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_GET_CFG,
+		.cmd_id = 0x1314,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_cfg,
+	},
+	{
+		.cmd = HQA_CMD_GET_TX_TONE_POWER,
+		.cmd_id = 0x131a,
+		.resp_len = 6,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_UNKNOWN,
+		.cmd_id = 0x131f,
+		.resp_len = 1024,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd = HQA_CMD_READ_TEMPERATURE,
+		.cmd_id = 0x1401,
+		.resp_len = 6,
+		.ops = atenl_hqa_read_temperature,
+	},
+	{
+		.cmd = HQA_CMD_GET_FW_INFO,
+		.cmd_id = 0x1500,
+		.resp_len = 32,
+		.flags = ATENL_OPS_FLAG_SKIP,
+	},
+	{
+		.cmd_id = 0x1502,
+		.flags = ATENL_OPS_FLAG_LEGACY,
+	},
+	{
+		.cmd = HQA_CMD_SET_TSSI,
+		.cmd_id = 0x1505,
+		.resp_len = 2,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_SET_RF_MODE,
+		.cmd_id = 0x1509,
+		.resp_len = 2,
+		.ops = atenl_hqa_set_rf_mode,
+	},
+	{
+		.cmd_id = 0x150b,
+		.flags = ATENL_OPS_FLAG_LEGACY,
+	},
+	{
+		.cmd = HQA_CMD_WRITE_BUFFER_DONE,
+		.cmd_id = 0x1511,
+		.resp_len = 2,
+		.ops = atenl_hqa_eeprom_bulk,
+	},
+	{
+		.cmd = HQA_CMD_GET_CHIP_ID,
+		.cmd_id = 0x1514,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_chip_id,
+	},
+	{
+		.cmd = HQA_CMD_GET_SUB_CHIP_ID,
+		.cmd_id = 0x151b,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_sub_chip_id,
+	},
+	{
+		.cmd = HQA_CMD_GET_RX_INFO,
+		.cmd_id = 0x151c,
+		.ops = atenl_nl_process,
+	},
+	{
+		.cmd = HQA_CMD_GET_RF_CAP,
+		.cmd_id = 0x151e,
+		.resp_len = 10,
+		.ops = atenl_hqa_get_rf_cap,
+	},
+	{
+		.cmd = HQA_CMD_CHECK_EFUSE_MODE_TYPE,
+		.cmd_id = 0x1522,
+		.resp_len = 6,
+		.ops = atenl_hqa_check_efuse_mode,
+	},
+	{
+		.cmd = HQA_CMD_CHECK_EFUSE_MODE_NATIVE,
+		.cmd_id = 0x1523,
+		.resp_len = 6,
+		.ops = atenl_hqa_check_efuse_mode,
+	},
+	{
+		.cmd = HQA_CMD_GET_BAND,
+		.cmd_id = 0x152d,
+		.resp_len = 6,
+		.ops = atenl_hqa_get_band,
+	},
+	{
+		.cmd = HQA_CMD_SET_RU,
+		.cmd_id = 0x1594,
+		.resp_len = 2,
+		.ops = atenl_nl_process_many,
+	},
+};
+
+static const struct atenl_ops hqa_ops_ext[] = {
+	{
+		.cmd = HQA_EXT_CMD_SET_CHANNEL,
+		.cmd_id = 0x01,
+		.resp_len = 6,
+		.ops = atenl_hqa_set_channel,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_SET_TX,
+		.cmd_id = 0x02,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_START_TX,
+		.cmd_id = 0x03,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_START_RX,
+		.cmd_id = 0x04,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_STOP_TX,
+		.cmd_id = 0x05,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_STOP_RX,
+		.cmd_id = 0x06,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_IBF_SET_VAL,
+		.cmd_id = 0x08,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_IBF_GET_STATUS,
+		.cmd_id = 0x09,
+		.resp_len = 10,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_IBF_PROF_UPDATE_ALL,
+		.cmd_id = 0x0c,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_SET_TX_TIME_OPT,
+		.cmd_id = 0x26,
+		.resp_len = 6,
+		.ops = atenl_hqa_tx_time_option,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+	{
+		.cmd = HQA_EXT_CMD_OFF_CH_SCAN,
+		.cmd_id = 0x27,
+		.resp_len = 6,
+		.ops = atenl_nl_process,
+		.flags = ATENL_OPS_FLAG_EXT_CMD,
+	},
+};
+
+static const struct atenl_ops *
+atenl_get_ops(struct atenl_data *data)
+{
+	const struct atenl_ops *group;
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u16 cmd_id = ntohs(hdr->cmd_id), id = cmd_id;
+	int size, low = 0, high;
+
+	switch (cmd_id) {
+	case 0x1600:
+		group = hqa_ops_ext;
+		size = ARRAY_SIZE(hqa_ops_ext);
+		break;
+	default:
+		group = hqa_ops;
+		size = ARRAY_SIZE(hqa_ops);
+		break;
+	}
+
+	if (group[0].flags & ATENL_OPS_FLAG_EXT_CMD)
+		id = ntohl(*(u32 *)hdr->data);
+
+	/* binary search */
+	high = size - 1;
+	while (low <= high) {
+		int mid = low + (high - low) / 2;
+
+		if (group[mid].cmd_id == id)
+			return &group[mid];
+		else if (group[mid].cmd_id > id)
+			high = mid - 1;
+		else
+			low = mid + 1;
+	}
+
+	return NULL;
+}
+
+static int
+atenl_hqa_handler(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	const struct atenl_ops *ops = NULL;
+	u16 cmd_id = ntohs(hdr->cmd_id);
+	u16 status = 0;
+
+	atenl_dbg("handle command: 0x%x\n", cmd_id);
+
+	ops = atenl_get_ops(data);
+	if (!ops || (!ops->ops && !ops->flags)) {
+		atenl_err("Unknown command id: 0x%x\n", cmd_id);
+		goto done;
+	}
+
+	data->cmd = ops->cmd;
+	data->cmd_id = ops->cmd_id;
+	if (ops->flags & ATENL_OPS_FLAG_EXT_CMD) {
+		data->ext_cmd = ops->cmd;
+		data->ext_id = ops->cmd_id;
+	}
+
+	if (ops->flags & ATENL_OPS_FLAG_SKIP)
+		goto done;
+
+	atenl_dbg_print_data(data->buf, __func__,
+			     ntohs(hdr->len) + ETH_HLEN + RACFG_HLEN);
+	if (ops->ops)
+		status = htons(ops->ops(an, data));
+	if (ops->resp_len)
+		hdr->len = htons(ops->resp_len);
+
+done:
+	*(u16 *)hdr->data = status;
+	data->len = ntohs(hdr->len) + ETH_HLEN + RACFG_HLEN;
+	hdr->cmd_type |= ~htons(RACFG_CMD_TYPE_MASK);
+
+	return 0;
+}
+
+int atenl_hqa_proc_cmd(struct atenl *an)
+{
+	struct atenl_data *data;
+	struct atenl_cmd_hdr *hdr;
+	u16 cmd_type;
+	int ret = -EINVAL;
+
+	data = calloc(1, sizeof(struct atenl_data));
+	if (!data)
+		return -ENOMEM;
+
+	ret = atenl_eth_recv(an, data);
+	if (ret)
+		goto out;
+
+	hdr = atenl_hdr(data);
+	if (ntohl(hdr->magic_no) != RACFG_MAGIC_NO)
+		goto out;
+
+	cmd_type = ntohs(hdr->cmd_type);
+	if (FIELD_GET(RACFG_CMD_TYPE_MASK, cmd_type) != RACFG_CMD_TYPE_ETHREQ &&
+	    FIELD_GET(RACFG_CMD_TYPE_MASK, cmd_type) != RACFG_CMD_TYPE_PLATFORM_MODULE) {
+		atenl_err("cmd type error = 0x%x\n", cmd_type);
+		goto out;
+	}
+
+	ret = atenl_hqa_handler(an, data);
+	if (ret)
+		goto out;
+
+	ret = atenl_eth_send(an, data);
+	if (ret)
+		goto out;
+
+	ret = 0;
+out:
+	free(data);
+
+	return ret;
+}
diff --git a/recipes-devtools/atenl/files/src/main.c b/recipes-devtools/atenl/files/src/main.c
new file mode 100644
index 0000000..36ac8f3
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/main.c
@@ -0,0 +1,214 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include "atenl.h"
+
+static const char *progname;
+bool atenl_enable;
+
+void sig_handler(int signum)
+{
+	atenl_enable = false;
+}
+
+void atenl_init_signals()
+{
+	if (signal(SIGINT, sig_handler) == SIG_ERR)
+		goto out;
+	if (signal(SIGTERM, sig_handler) == SIG_ERR)
+		goto out;
+	if (signal(SIGABRT, sig_handler) == SIG_ERR)
+		goto out;
+	if (signal(SIGUSR1, sig_handler) == SIG_ERR)
+		goto out;
+	if (signal(SIGUSR2, sig_handler) == SIG_ERR)
+		goto out;
+
+	return;
+out:
+	perror("signal");
+}
+
+static int phy_lookup_idx(const char *name)
+{
+	char buf[128];
+	FILE *f;
+	size_t len;
+	int ret;
+
+	ret = snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name);
+	if (snprintf_error(sizeof(buf), ret))
+		return -1;
+
+	f = fopen(buf, "r");
+	if (!f)
+		return -1;
+
+	len = fread(buf, 1, sizeof(buf) - 1, f);
+	fclose(f);
+
+	if (!len)
+		return -1;
+
+	buf[len] = 0;
+	return atoi(buf);
+}
+
+static int get_default_bridge_name(struct atenl *an)
+{
+	char buf[128];
+	FILE *f;
+	size_t len;
+	int ret;
+
+	ret = snprintf(buf, sizeof(buf), "/sbin/procd");
+	if (snprintf_error(sizeof(buf), ret))
+		return -1;
+
+	f = fopen(buf, "r");
+
+	/* This procd is openwrt only */
+	if (f) {
+		an->bridge_name = BRIDGE_NAME_OPENWRT;
+		fclose(f);
+	} else {
+		an->bridge_name = BRIDGE_NAME_RDKB;
+	}
+
+	return 0;
+}
+
+static void usage(void)
+{
+	printf("Usage:\n");
+	printf("  %s [-u] [-i phyX]\n", progname);
+	printf("options:\n"
+	       "  -h = show help text\n"
+	       "  -i = phy name of driver interface, please use first phy for dbdc\n"
+	       "  -u = use unicast to respond to HQADLL\n"
+	       "  -b = specify your bridge name\n");
+	printf("examples:\n"
+	       "  %s -u -i phy0 -b br-lan\n", progname);
+
+	exit(EXIT_FAILURE);
+}
+
+static void atenl_handler_run(struct atenl *an)
+{
+	int count, sock_eth = an->sock_eth;
+	fd_set readfds;
+
+	atenl_info("Start atenl HQA command handler\n");
+
+	while (atenl_enable) {
+		FD_ZERO(&readfds);
+		FD_SET(sock_eth, &readfds);
+		count = select(sock_eth + 1, &readfds, NULL, NULL, NULL);
+
+		if (count < 0) {
+			atenl_err("%s: select failed, %s\n", __func__, strerror(errno));
+		} else if (count == 0) {
+			usleep(1000);
+		} else {
+			if (!FD_ISSET(sock_eth, &readfds))
+				continue;
+			atenl_hqa_proc_cmd(an);
+		}
+	}
+
+	atenl_dbg("HQA command handler end\n");
+}
+
+int main(int argc, char **argv)
+{
+	int opt, phy_idx, ret = 0;
+	char *phy = "phy0", *cmd = NULL;
+	struct atenl *an;
+
+	progname = argv[0];
+
+	an = calloc(1, sizeof(struct atenl));
+	if (!an)
+		return -ENOMEM;
+
+	while(1) {
+		opt = getopt(argc, argv, "hi:uc:b:");
+		if (opt == -1)
+			break;
+
+		switch (opt) {
+			case 'h':
+				usage();
+				goto out;
+			case 'i':
+				phy = optarg;
+				break;
+			case 'b':
+				an->bridge_name = optarg;
+				break;
+			case 'u':
+				an->unicast = true;
+				printf("Opt: use unicast to send response\n");
+				break;
+			case 'c':
+				cmd = optarg;
+				break;
+			default:
+				atenl_err("Not supported option: %c\n", opt);
+				goto out;
+		}
+	}
+
+	phy_idx = phy_lookup_idx(phy);
+	if (phy_idx < 0 || phy_idx > UCHAR_MAX) {
+		atenl_err("Could not find phy '%s'\n", phy);
+		goto out;
+	}
+
+	if (cmd) {
+		atenl_eeprom_cmd_handler(an, phy_idx, cmd);
+		goto out;
+	}
+
+	atenl_enable = true;
+	atenl_init_signals();
+
+	if (!an->bridge_name) {
+		ret = get_default_bridge_name(an);
+		if (ret) {
+			atenl_err("Get default bridge name failed\n");
+			goto out;
+		}
+
+		atenl_info("Bridge name is not specified, use default bridge name: %s\n", an->bridge_name);
+	} else {
+		atenl_info("Currently using bridge name: %s\n", an->bridge_name);
+	}
+	
+	/* background ourself */
+	if (!fork()) {
+		ret = atenl_eeprom_init(an, phy_idx);
+		if (ret)
+			goto out;
+
+		ret = atenl_eth_init(an);
+		if (ret)
+			goto out;
+
+		atenl_handler_run(an);
+	} else {
+		usleep(800000);
+	}
+
+	ret = 0;
+out:
+	if (an->sock_eth)
+		close(an->sock_eth);
+	if (an->eeprom_fd || an->eeprom_data)
+		atenl_eeprom_close(an);
+	free(an);
+
+	return ret;
+}
diff --git a/recipes-devtools/atenl/files/src/nl.c b/recipes-devtools/atenl/files/src/nl.c
new file mode 100644
index 0000000..b919356
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/nl.c
@@ -0,0 +1,1499 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include <unl.h>
+
+#include "atenl.h"
+
+#define to_rssi(_rcpi)	((_rcpi - 220) / 2)
+
+struct atenl_nl_priv {
+	struct atenl *an;
+	struct unl unl;
+	struct nl_msg *msg;
+	int attr;
+	void *res;
+};
+
+struct atenl_nl_ops {
+	int set;
+	int dump;
+	int (*ops)(struct atenl *an, struct atenl_data *data,
+		   struct atenl_nl_priv *nl_priv);
+};
+
+static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+	[MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_MTD_PART] = { .type = NLA_STRING },
+	[MT76_TM_ATTR_MTD_OFFSET] = { .type = NLA_U32 },
+	[MT76_TM_ATTR_IS_MAIN_PHY] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
+	[MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
+	[MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_RATE_NSS] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_RATE_IDX] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_RATE_SGI] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 },
+	[MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+	[MT76_TM_ATTR_STATS] = { .type = NLA_NESTED },
+	[MT76_TM_ATTR_PRECAL] = { .type = NLA_NESTED },
+	[MT76_TM_ATTR_PRECAL_INFO] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = {
+	[MT76_TM_STATS_ATTR_TX_PENDING] = { .type = NLA_U32 },
+	[MT76_TM_STATS_ATTR_TX_QUEUED] = { .type = NLA_U32 },
+	[MT76_TM_STATS_ATTR_TX_DONE] = { .type = NLA_U32 },
+	[MT76_TM_STATS_ATTR_RX_PACKETS] = { .type = NLA_U64 },
+	[MT76_TM_STATS_ATTR_RX_FCS_ERROR] = { .type = NLA_U64 },
+};
+
+static struct nla_policy rx_policy[NUM_MT76_TM_RX_ATTRS] = {
+	[MT76_TM_RX_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+	[MT76_TM_RX_ATTR_RCPI] = { .type = NLA_NESTED },
+	[MT76_TM_RX_ATTR_IB_RSSI] = { .type = NLA_NESTED },
+	[MT76_TM_RX_ATTR_WB_RSSI] = { .type = NLA_NESTED },
+	[MT76_TM_RX_ATTR_SNR] = { .type = NLA_U8 },
+};
+
+struct he_sgi {
+	enum mt76_testmode_tx_mode tx_mode;
+	u8 sgi;
+	u8 tx_ltf;
+};
+
+#define HE_SGI_GROUP(_tx_mode, _sgi, _tx_ltf)	\
+	{ .tx_mode = MT76_TM_TX_MODE_##_tx_mode, .sgi = _sgi, .tx_ltf = _tx_ltf }
+static const struct he_sgi he_sgi_groups[] = {
+	HE_SGI_GROUP(HE_SU, 0, 0),
+	HE_SGI_GROUP(HE_SU, 0, 1),
+	HE_SGI_GROUP(HE_SU, 1, 1),
+	HE_SGI_GROUP(HE_SU, 2, 2),
+	HE_SGI_GROUP(HE_SU, 0, 2),
+	HE_SGI_GROUP(HE_EXT_SU, 0, 0),
+	HE_SGI_GROUP(HE_EXT_SU, 0, 1),
+	HE_SGI_GROUP(HE_EXT_SU, 1, 1),
+	HE_SGI_GROUP(HE_EXT_SU, 2, 2),
+	HE_SGI_GROUP(HE_EXT_SU, 0, 2),
+	HE_SGI_GROUP(HE_TB, 1, 0),
+	HE_SGI_GROUP(HE_TB, 1, 1),
+	HE_SGI_GROUP(HE_TB, 2, 2),
+	HE_SGI_GROUP(HE_MU, 0, 2),
+	HE_SGI_GROUP(HE_MU, 0, 1),
+	HE_SGI_GROUP(HE_MU, 1, 1),
+	HE_SGI_GROUP(HE_MU, 2, 2),
+};
+#undef HE_SGI_LTF_GROUP
+
+static u8 phy_type_to_attr(u8 phy_type)
+{
+	static const u8 phy_type_to_attr[] = {
+		[ATENL_PHY_TYPE_CCK] = MT76_TM_TX_MODE_CCK,
+		[ATENL_PHY_TYPE_OFDM] = MT76_TM_TX_MODE_OFDM,
+		[ATENL_PHY_TYPE_HT] = MT76_TM_TX_MODE_HT,
+		[ATENL_PHY_TYPE_HT_GF] = MT76_TM_TX_MODE_HT,
+		[ATENL_PHY_TYPE_VHT] = MT76_TM_TX_MODE_VHT,
+		[ATENL_PHY_TYPE_HE_SU] = MT76_TM_TX_MODE_HE_SU,
+		[ATENL_PHY_TYPE_HE_EXT_SU] = MT76_TM_TX_MODE_HE_EXT_SU,
+		[ATENL_PHY_TYPE_HE_TB] = MT76_TM_TX_MODE_HE_TB,
+		[ATENL_PHY_TYPE_HE_MU] = MT76_TM_TX_MODE_HE_MU,
+	};
+
+	if (phy_type >= ARRAY_SIZE(phy_type_to_attr))
+		return 0;
+
+	return phy_type_to_attr[phy_type];
+}
+
+static void
+atenl_set_attr_state(struct atenl *an, struct nl_msg *msg,
+		     u8 band, enum mt76_testmode_state state)
+{
+	if (get_band_val(an, band, cur_state) == state)
+		return;
+
+	nla_put_u8(msg, MT76_TM_ATTR_STATE, state);
+	set_band_val(an, band, cur_state, state);
+}
+
+static void
+atenl_set_attr_antenna(struct atenl *an, struct nl_msg *msg, u8 tx_antenna)
+{
+	if (!tx_antenna)
+		return;
+	if (is_mt7915(an))
+		nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA,
+			   tx_antenna << (2 * an->cur_band));
+	else if (is_mt7916(an) || is_mt7986(an))
+		nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, tx_antenna);
+}
+
+static int
+atenl_nl_set_attr(struct atenl *an, struct atenl_data *data,
+		  struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 val = ntohl(*(u32 *)hdr->data);
+	int attr = nl_priv->attr;
+	void *ptr, *a;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	switch (attr) {
+	case MT76_TM_ATTR_TX_ANTENNA:
+		atenl_set_attr_antenna(an, msg, val);
+		break;
+	case MT76_TM_ATTR_FREQ_OFFSET:
+		nla_put_u32(msg, attr, val);
+		break;
+	case MT76_TM_ATTR_TX_POWER:
+		a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
+		if (!a)
+			return -ENOMEM;
+		nla_put_u8(msg, 0, val);
+		nla_nest_end(msg, a);
+		break;
+	default:
+		nla_put_u8(msg, attr, val);
+		break;
+	}
+
+	nla_nest_end(msg, ptr);
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_set_cfg(struct atenl *an, struct atenl_data *data,
+		 struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	enum atenl_cmd cmd = data->cmd;
+	u32 *v = (u32 *)hdr->data;
+	u8 type = ntohl(v[0]);
+	u8 enable = ntohl(v[1]);
+	void *ptr, *cfg;
+
+	if (cmd == HQA_CMD_SET_TSSI) {
+		type = 0;
+		enable = 1;
+	}
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	cfg = nla_nest_start(msg, MT76_TM_ATTR_CFG);
+	if (!cfg)
+		return -ENOMEM;
+
+	if (nla_put_u8(msg, 0, type) ||
+	    nla_put_u8(msg, 1, enable))
+		return -EINVAL;
+
+	nla_nest_end(msg, cfg);
+
+	nla_nest_end(msg, ptr);
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_set_tx(struct atenl *an, struct atenl_data *data,
+		struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)hdr->data;
+	u8 *addr1 = hdr->data + 36;
+	u8 *addr2 = addr1 + ETH_ALEN;
+	u8 *addr3 = addr2 + ETH_ALEN;
+	u8 def_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+	void *ptr, *a;
+
+	if (get_band_val(an, an->cur_band, use_tx_time))
+		set_band_val(an, an->cur_band, tx_time, ntohl(v[7]));
+	else
+		set_band_val(an, an->cur_band, tx_mpdu_len, ntohl(v[7]));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	a = nla_nest_start(msg, MT76_TM_ATTR_MAC_ADDRS);
+	if (!a)
+		return -ENOMEM;
+
+	nla_put(msg, 0, ETH_ALEN, use_default_addr(addr1) ? def_mac : addr1);
+	nla_put(msg, 1, ETH_ALEN, use_default_addr(addr2) ? def_mac : addr2);
+	nla_put(msg, 2, ETH_ALEN, use_default_addr(addr3) ? def_mac : addr3);
+
+	nla_nest_end(msg, a);
+
+	nla_nest_end(msg, ptr);
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_tx(struct atenl *an, struct atenl_data *data, struct atenl_nl_priv *nl_priv)
+{
+#define USE_SPE_IDX	BIT(31)
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)hdr->data;
+	u8 band = ntohl(v[2]);
+	void *ptr;
+	int ret;
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	if (data->ext_cmd == HQA_EXT_CMD_STOP_TX) {
+		atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+	} else {
+		u32 tx_count = ntohl(v[3]);
+		u8 tx_rate_mode = phy_type_to_attr(ntohl(v[4]));
+		u8 aid = ntohl(v[11]);
+		u8 sgi = ntohl(v[13]);
+		u32 tx_antenna = ntohl(v[14]);
+		void *a;
+
+		if (sgi > 5)
+			return -EINVAL;
+
+		if (!tx_count)
+			tx_count = 10000000;
+
+		nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, tx_count);
+		nla_put_u32(msg, MT76_TM_ATTR_TX_IPG, ntohl(v[12]));
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, tx_rate_mode);
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, ntohl(v[5]));
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, ntohl(v[7]));
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, ntohl(v[8]));
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, ntohl(v[15]));
+
+		if (get_band_val(an, band, use_tx_time))
+			nla_put_u32(msg, MT76_TM_ATTR_TX_TIME,
+				    get_band_val(an, band, tx_time));
+		else
+			nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH,
+				    get_band_val(an, band, tx_mpdu_len));
+
+		/* for chips after 7915, tx need to use at least wcid = 1 */
+		if (!is_mt7915(an) && !aid)
+			aid = 1;
+		nla_put_u8(msg, MT76_TM_ATTR_AID, aid);
+
+		if (tx_antenna & USE_SPE_IDX) {
+			nla_put_u8(msg, MT76_TM_ATTR_TX_SPE_IDX,
+				   tx_antenna & ~USE_SPE_IDX);
+		} else {
+			nla_put_u8(msg, MT76_TM_ATTR_TX_SPE_IDX, 0);
+			atenl_set_attr_antenna(an, msg, tx_antenna);
+		}
+
+		if (tx_rate_mode >= MT76_TM_TX_MODE_HE_SU) {
+			u8 ofs = sgi;
+			size_t i;
+
+			for (i = 0; i < ARRAY_SIZE(he_sgi_groups); i++)
+				if (he_sgi_groups[i].tx_mode == tx_rate_mode)
+					break;
+
+			if ((i + ofs) >= ARRAY_SIZE(he_sgi_groups))
+				return -EINVAL;
+
+			sgi = he_sgi_groups[i + ofs].sgi;
+			nla_put_u8(msg, MT76_TM_ATTR_TX_LTF,
+				   he_sgi_groups[i + ofs].tx_ltf);
+		}
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_SGI, sgi);
+
+		a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
+		if (!a)
+			return -ENOMEM;
+		nla_put_u8(msg, 0, ntohl(v[6]));
+		nla_nest_end(msg, a);
+
+		atenl_set_attr_state(an, msg, band, MT76_TM_STATE_TX_FRAMES);
+	}
+
+	nla_nest_end(msg, ptr);
+
+	ret = unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+	if (ret)
+		return ret;
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return 0;
+}
+
+static int
+atenl_nl_rx(struct atenl *an, struct atenl_data *data, struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct atenl_band *anb = &an->anb[an->cur_band];
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)hdr->data;
+	u8 band = ntohl(v[2]);
+	void *ptr;
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	if (data->ext_cmd == HQA_EXT_CMD_STOP_RX) {
+		atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+	} else {
+		v = (u32 *)(hdr->data + 18);
+
+		atenl_set_attr_antenna(an, msg, ntohl(v[0]));
+		nla_put_u8(msg, MT76_TM_ATTR_AID, ntohl(v[1]));
+		atenl_set_attr_state(an, msg, band, MT76_TM_STATE_RX_FRAMES);
+
+		anb->reset_rx_cnt = false;
+
+		/* clear history buffer */
+		memset(&anb->rx_stat, 0, sizeof(anb->rx_stat));
+	}
+
+	nla_nest_end(msg, ptr);
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_off_ch_scan(struct atenl *an, struct atenl_data *data,
+		  struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)hdr->data;
+	u8 ch = ntohl(v[2]);
+	u8 bw = ntohl(v[4]);
+	u8 tx_path = ntohl(v[5]);
+	u8 status = ntohl(v[6]);
+	void *ptr;
+
+	if (!status)
+		ch = 0; /* stop */
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CH, ch);
+	nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
+		   atenl_get_center_channel(bw, CH_BAND_5GHZ, ch));
+	nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_BW, bw);
+	nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_PATH, tx_path);
+
+	nla_nest_end(msg, ptr);
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return 0;
+}
+
+static int atenl_nl_dump_cb(struct nl_msg *msg, void *arg)
+{
+	struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+	struct nlattr *tb1[NUM_MT76_TM_ATTRS];
+	struct nlattr *tb2[NUM_MT76_TM_STATS_ATTRS];
+	struct nlattr *nl_attr;
+	int attr = nl_priv->attr;
+	u64 *res = nl_priv->res;
+
+	nl_attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+	if (!nl_attr) {
+		atenl_err("Testdata attribute not found\n");
+		return NL_SKIP;
+	}
+
+	nla_parse_nested(tb1, MT76_TM_ATTR_MAX, nl_attr, testdata_policy);
+	nla_parse_nested(tb2, MT76_TM_STATS_ATTR_MAX,
+			 tb1[MT76_TM_ATTR_STATS], stats_policy);
+
+	if (attr == MT76_TM_STATS_ATTR_TX_DONE)
+		*res = nla_get_u32(tb2[MT76_TM_STATS_ATTR_TX_DONE]);
+
+	return NL_SKIP;
+}
+
+static int
+atenl_nl_dump_attr(struct atenl *an, struct atenl_data *data,
+		   struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	void *ptr;
+	u64 res = 0;
+
+	nl_priv->res = (void *)&res;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+	nla_put_flag(msg, MT76_TM_ATTR_STATS);
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv->unl, msg, atenl_nl_dump_cb, (void *)nl_priv);
+
+	if (nl_priv->attr == MT76_TM_STATS_ATTR_TX_DONE)
+		*(u32 *)(hdr->data + 2 + 4 * an->cur_band) = htonl(res);
+
+	return 0;
+}
+
+static int atenl_nl_continuous_tx(struct atenl *an,
+				  struct atenl_data *data,
+				  struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)hdr->data;
+	u8 band = ntohl(v[0]);
+	bool enable = ntohl(v[1]);
+	void *ptr;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	if (band >= MAX_BAND_NUM)
+		return -EINVAL;
+
+	if (!enable) {
+		int phy = get_band_val(an, band, phy_idx);
+		char cmd[64];
+
+		atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+		nla_nest_end(msg, ptr);
+		unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+		sprintf(cmd, "iw dev mon%d del", phy);
+		system(cmd);
+		sprintf(cmd, "iw phy phy%d interface add mon%d type monitor", phy, phy);
+		system(cmd);
+		sprintf(cmd, "ifconfig mon%d up", phy);
+		system(cmd);
+
+		return 0;
+	}
+
+	if (get_band_val(an, band, rf_mode) != ATENL_RF_MODE_TEST)
+		return 0;
+
+	nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, ntohl(v[2]));
+	nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, phy_type_to_attr(ntohl(v[3])));
+	nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, ntohl(v[6]));
+
+	atenl_dbg("%s: enable = %d, ant=%u, tx_rate_mode=%u, rate_idx=%u\n",
+		  __func__, enable, ntohl(v[2]), ntohl(v[3]), ntohl(v[6]));
+
+	atenl_set_attr_state(an, msg, band, MT76_TM_STATE_TX_CONT);
+
+	nla_nest_end(msg, ptr);
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int atenl_nl_get_rx_info_cb(struct nl_msg *msg, void *arg)
+{
+	struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+	struct atenl *an = nl_priv->an;
+	struct atenl_band *anb = &an->anb[an->cur_band];
+	struct atenl_data *data = nl_priv->res;
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct atenl_rx_info_hdr *rx_hdr;
+	struct atenl_rx_info_band *rx_band;
+	struct atenl_rx_info_user *rx_user;
+	struct atenl_rx_info_path *rx_path;
+	struct atenl_rx_info_comm *rx_comm;
+	struct nlattr *tb1[NUM_MT76_TM_ATTRS];
+	struct nlattr *tb2[NUM_MT76_TM_STATS_ATTRS];
+	struct nlattr *tb3[NUM_MT76_TM_RX_ATTRS];
+	struct nlattr *nl_attr, *cur;
+	struct atenl_rx_stat rx_cur, rx_diff = {};
+	u32 rcpi[4] = {};
+	u32 type_num = htonl(4);
+	s32 ib_rssi[4] = {}, wb_rssi[4] = {};
+	u8 path = an->anb[an->cur_band].chainmask;
+	u8 path_num = __builtin_popcount(path);
+	u8 *buf = hdr->data + 2;
+	int i, rem;
+
+	*(u32 *)buf = type_num;
+	buf += sizeof(type_num);
+
+#define RX_PUT_HDR(_hdr, _type, _val, _size) do {	\
+		_hdr->type = htonl(_type);		\
+		_hdr->val = htonl(_val);		\
+		_hdr->len = htonl(_size);		\
+		buf += sizeof(*_hdr);			\
+	} while (0)
+
+	rx_hdr = (struct atenl_rx_info_hdr *)buf;
+	RX_PUT_HDR(rx_hdr, 0, BIT(an->cur_band), sizeof(*rx_band));
+	rx_band = (struct atenl_rx_info_band *)buf;
+	buf += sizeof(*rx_band);
+
+	rx_hdr = (struct atenl_rx_info_hdr *)buf;
+	RX_PUT_HDR(rx_hdr, 1, path, path_num * sizeof(*rx_path));
+	rx_path = (struct atenl_rx_info_path *)buf;
+	buf += path_num * sizeof(*rx_path);
+
+	rx_hdr = (struct atenl_rx_info_hdr *)buf;
+	RX_PUT_HDR(rx_hdr, 2, GENMASK(15, 0), 16 * sizeof(*rx_user));
+	rx_user = (struct atenl_rx_info_user *)buf;
+	buf += 16 * sizeof(*rx_user);
+
+	rx_hdr = (struct atenl_rx_info_hdr *)buf;
+	RX_PUT_HDR(rx_hdr, 3, BIT(0), sizeof(*rx_comm));
+	rx_comm = (struct atenl_rx_info_comm *)buf;
+	buf += sizeof(*rx_comm);
+
+	hdr->len = htons(buf - hdr->data);
+
+	nl_attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+	if (!nl_attr) {
+		atenl_err("Testdata attribute not found\n");
+		return NL_SKIP;
+	}
+
+	nla_parse_nested(tb1, MT76_TM_ATTR_MAX, nl_attr, testdata_policy);
+	nla_parse_nested(tb2, MT76_TM_STATS_ATTR_MAX,
+			 tb1[MT76_TM_ATTR_STATS], stats_policy);
+
+	rx_cur.total = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_PACKETS]);
+	rx_cur.err_cnt = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_FCS_ERROR]);
+	rx_cur.len_mismatch = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_LEN_MISMATCH]);
+	rx_cur.ok_cnt = rx_cur.total - rx_cur.err_cnt - rx_cur.len_mismatch;
+
+	if (!anb->reset_rx_cnt ||
+	    get_band_val(an, an->cur_band, cur_state) == MT76_TM_STATE_RX_FRAMES) {
+#define RX_COUNT_DIFF(_field)	\
+	rx_diff._field = (rx_cur._field) - (anb->rx_stat._field);
+		RX_COUNT_DIFF(total);
+		RX_COUNT_DIFF(err_cnt);
+		RX_COUNT_DIFF(len_mismatch);
+		RX_COUNT_DIFF(ok_cnt);
+#undef RX_COUNT_DIFF
+
+		memcpy(&anb->rx_stat, &rx_cur, sizeof(anb->rx_stat));
+	}
+
+	rx_band->mac_rx_mdrdy_cnt = htonl((u32)rx_diff.total);
+	rx_band->mac_rx_fcs_err_cnt = htonl((u32)rx_diff.err_cnt);
+	rx_band->mac_rx_fcs_ok_cnt = htonl((u32)rx_diff.ok_cnt);
+	rx_band->mac_rx_len_mismatch = htonl((u32)rx_diff.len_mismatch);
+	rx_user->fcs_error_cnt = htonl((u32)rx_diff.err_cnt);
+
+	nla_parse_nested(tb3, MT76_TM_RX_ATTR_MAX,
+			 tb2[MT76_TM_STATS_ATTR_LAST_RX], rx_policy);
+
+	rx_user->freq_offset = htonl(nla_get_u32(tb3[MT76_TM_RX_ATTR_FREQ_OFFSET]));
+	rx_user->snr = htonl(nla_get_u8(tb3[MT76_TM_RX_ATTR_SNR]));
+
+	i = 0;
+	nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_RCPI], rem) {
+		if (nla_len(cur) != 1 || i >= 4)
+			break;
+
+		rcpi[i++] = nla_get_u8(cur);
+	}
+
+	i = 0;
+	nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_IB_RSSI], rem) {
+		if (nla_len(cur) != 1 || i >= 4)
+			break;
+
+		ib_rssi[i++] = (s8)nla_get_u8(cur);
+	}
+
+	i = 0;
+	nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_WB_RSSI], rem) {
+		if (nla_len(cur) != 1 || i >= 4)
+			break;
+
+		wb_rssi[i++] = (s8)nla_get_u8(cur);
+	}
+
+	for (i = 0; i < 4; i++) {
+		struct atenl_rx_info_path *path = &rx_path[i];
+
+		path->rcpi = htonl(rcpi[i]);
+		path->rssi = htonl(to_rssi((u8)rcpi[i]));
+		path->fagc_ib_rssi = htonl(ib_rssi[i]);
+		path->fagc_wb_rssi = htonl(wb_rssi[i]);
+	}
+
+	return NL_SKIP;
+}
+
+static int atenl_nl_get_rx_info(struct atenl *an, struct atenl_data *data,
+				struct atenl_nl_priv *nl_priv)
+{
+	struct nl_msg *msg = nl_priv->msg;
+	void *ptr;
+
+	nl_priv->an = an;
+	nl_priv->res = (void *)data;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_flag(msg, MT76_TM_ATTR_STATS);
+
+	nla_nest_end(msg, ptr);
+
+	return unl_genl_request(&nl_priv->unl, msg, atenl_nl_get_rx_info_cb,
+				(void *)nl_priv);
+}
+
+static int
+atenl_nl_set_ru(struct atenl *an, struct atenl_data *data,
+		struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg;
+	u32 *v = (u32 *)(hdr->data + 4);
+	u32 seg0_num = ntohl(v[0]);	/* v[1] seg1_num unused */
+	void *ptr;
+	int i, ret;
+
+	if (seg0_num > 8)
+		return -EINVAL;
+
+	for (i = 0, v = &v[2]; i < seg0_num; i++, v += 11) {
+		u32 ru_alloc = ntohl(v[1]);
+		u32 aid = ntohl(v[2]);
+		u32 ru_idx = ntohl(v[3]);
+		u32 mcs = ntohl(v[4]);
+		u32 ldpc = ntohl(v[5]);
+		u32 nss = ntohl(v[6]);
+		u32 tx_length = ntohl(v[8]);
+		char buf[10];
+
+		if (unl_genl_init(&nl_priv->unl, "nl80211") < 0) {
+			atenl_err("Failed to connect to nl80211\n");
+			return 2;
+		}
+
+		msg = unl_genl_msg(&nl_priv->unl, NL80211_CMD_TESTMODE, false);
+		nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+
+		ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+		if (!ptr)
+			return -ENOMEM;
+
+		if (i == 0)
+			atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_IDLE);
+
+		nla_put_u8(msg, MT76_TM_ATTR_AID, aid);
+		nla_put_u8(msg, MT76_TM_ATTR_RU_IDX, ru_idx);
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, mcs);
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, ldpc);
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, nss);
+		nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, tx_length);
+
+		ret = snprintf(buf, sizeof(buf), "%x", ru_alloc);
+		if (snprintf_error(sizeof(buf), ret))
+			return -EINVAL;
+
+		nla_put_u8(msg, MT76_TM_ATTR_RU_ALLOC, strtol(buf, NULL, 2));
+
+		nla_nest_end(msg, ptr);
+
+		unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+		unl_free(&nl_priv->unl);
+	}
+
+	return 0;
+}
+
+static int
+atenl_nl_ibf_init(struct atenl *an, u8 band)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr, *a;
+	int ret;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, band, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, MT76_TM_TX_MODE_HT);
+	nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, an->ibf_mcs);
+	nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, an->ibf_ant);
+	nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_INIT);
+
+	a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+	if (!a) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	nla_put_u16(msg, 0, 1);
+	nla_nest_end(msg, a);
+
+	nla_nest_end(msg, ptr);
+
+	ret = unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+out:
+	unl_free(&nl_priv.unl);
+	return ret;
+}
+
+static int
+atenl_nl_ibf_e2p_update(struct atenl *an)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr, *a;
+	int ret;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_E2P_UPDATE);
+	a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+	if (!a) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	nla_put_u16(msg, 0, 0);
+	nla_nest_end(msg, a);
+
+	nla_nest_end(msg, ptr);
+
+	ret = unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+out:
+	unl_free(&nl_priv.unl);
+	return ret;
+}
+
+static void
+atenl_get_ibf_cal_result(struct atenl *an)
+{
+	u16 offset;
+
+	if (an->adie_id == 0x7975)
+		offset = 0x651;
+	else if (an->adie_id == 0x7976)
+		offset = 0x60a;
+
+	/* per group size = 40, for group 0-8 */
+	atenl_eeprom_read_from_driver(an, offset, 40 * 9);
+}
+
+static int
+atenl_nl_ibf_set_val(struct atenl *an, struct atenl_data *data,
+		     struct atenl_nl_priv *nl_priv)
+{
+#define MT_IBF(_act)	MT76_TM_TXBF_ACT_##_act
+	static const u8 bf_act_map[] = {
+		[TXBF_ACT_IBF_PHASE_COMP] = MT_IBF(PHASE_COMP),
+		[TXBF_ACT_IBF_PROF_UPDATE] = MT_IBF(IBF_PROF_UPDATE),
+		[TXBF_ACT_EBF_PROF_UPDATE] = MT_IBF(EBF_PROF_UPDATE),
+		[TXBF_ACT_IBF_PHASE_CAL] = MT_IBF(PHASE_CAL),
+	};
+#undef MT_IBF
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg = nl_priv->msg;
+	u32 *v = (u32 *)(hdr->data + 4);
+	u32 action = ntohl(v[0]);
+	u16 val[8], is_atenl = 1;
+	u8 tmp_ant;
+	void *ptr, *a;
+	char cmd[64];
+	int i;
+
+	for (i = 0; i < 8; i++)
+		val[i] = ntohl(v[i + 1]);
+
+	atenl_dbg("%s: action = %u, val = %u, %u, %u, %u, %u\n",
+		  __func__, action, val[0], val[1], val[2], val[3], val[4]);
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	switch (action) {
+	case TXBF_ACT_CHANNEL:
+		an->cur_band = val[1];
+		/* a sanity to prevent script band idx error */
+		if (val[0] > 14)
+			an->cur_band = 1;
+		atenl_nl_ibf_init(an, an->cur_band);
+		atenl_set_channel(an, 0, an->cur_band, val[0], 0, 0);
+
+		nla_put_u8(msg, MT76_TM_ATTR_AID, 0);
+		nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_UPDATE_CH);
+		a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+		if (!a)
+			return -ENOMEM;
+		nla_put_u16(msg, 0, 0);
+		nla_nest_end(msg, a);
+		break;
+	case TXBF_ACT_MCS:
+		tmp_ant = (1 << DIV_ROUND_UP(val[0], 8)) - 1 ?: 1;
+		/* sometimes the correct band idx will be set after this action,
+		 * so maintain a temp variable to allow mcs update in anthor action.
+		 */
+		an->ibf_mcs = val[0];
+		an->ibf_ant = tmp_ant;
+		nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, an->ibf_mcs);
+		nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, an->ibf_ant);
+		break;
+	case TXBF_ACT_TX_ANT:
+		nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, val[0]);
+		break;
+	case TXBF_ACT_RX_START:
+		atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_RX_FRAMES);
+		break;
+	case TXBF_ACT_RX_ANT:
+		nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, val[0]);
+		break;
+	case TXBF_ACT_TX_PKT:
+		nla_put_u8(msg, MT76_TM_ATTR_AID, val[1]);
+		nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_TX_PREP);
+		nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, 10000000);
+		nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, 1024);
+		a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+		if (!a)
+			return -ENOMEM;
+
+		for (i = 0; i < 5; i++)
+			nla_put_u16(msg, i, val[i]);
+		nla_nest_end(msg, a);
+
+		atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_TX_FRAMES);
+		break;
+	case TXBF_ACT_IBF_PHASE_COMP:
+		nla_put_u8(msg, MT76_TM_ATTR_AID, 1);
+	case TXBF_ACT_IBF_PROF_UPDATE:
+	case TXBF_ACT_EBF_PROF_UPDATE:
+	case TXBF_ACT_IBF_PHASE_CAL:
+		nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, bf_act_map[action]);
+		a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+		if (!a)
+			return -ENOMEM;
+		/* Note: litepoint may send random number for lna_gain_level, reset to 0 */
+		if (action == TXBF_ACT_IBF_PHASE_CAL)
+			val[4] = 0;
+		for (i = 0; i < 5; i++)
+			nla_put_u16(msg, i, val[i]);
+		/* Used to distinguish between command mode and HQADLL mode */
+		nla_put_u16(msg, 5, is_atenl);
+		nla_nest_end(msg, a);
+		break;
+	case TXBF_ACT_IBF_PHASE_E2P_UPDATE:
+		atenl_nl_ibf_e2p_update(an);
+		atenl_get_ibf_cal_result(an);
+
+		nla_put_u8(msg, MT76_TM_ATTR_AID, 0);
+		nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_INIT);
+
+		a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+		if (!a)
+			return -ENOMEM;
+		nla_put_u16(msg, 0, 0);
+		nla_nest_end(msg, a);
+		break;
+	case TXBF_ACT_INIT:
+	case TXBF_ACT_POWER:
+	default:
+		break;
+	}
+
+	nla_nest_end(msg, ptr);
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_ibf_get_status(struct atenl *an, struct atenl_data *data,
+			struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	u32 status = htonl(1);
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+	memcpy(hdr->data + 6, &status, 4);
+
+	return 0;
+}
+
+static int
+atenl_nl_ibf_profile_update_all(struct atenl *an, struct atenl_data *data,
+				struct atenl_nl_priv *nl_priv)
+{
+	struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+	struct nl_msg *msg;
+	void *ptr, *a;
+	u32 *v = (u32 *)(hdr->data + 4);
+	u16 pfmu_idx = ntohl(v[0]);
+	int i;
+
+	for (i = 0, v = &v[5]; i < 64; i++, v += 5) {
+		int j;
+
+		if (unl_genl_init(&nl_priv->unl, "nl80211") < 0) {
+			atenl_err("Failed to connect to nl80211\n");
+			return 2;
+		}
+
+		msg = unl_genl_msg(&nl_priv->unl, NL80211_CMD_TESTMODE, false);
+		nla_put_u32(msg, NL80211_ATTR_WIPHY,
+			    get_band_val(an, an->cur_band, phy_idx));
+
+		ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+		if (!ptr)
+			return -ENOMEM;
+
+		nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_PROF_UPDATE_ALL);
+		a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+		if (!a)
+			return -ENOMEM;
+		nla_put_u16(msg, 0, pfmu_idx);
+
+		for (j = 0; j < 5; j++)
+			nla_put_u16(msg, j + 1, ntohl(v[j]));
+		nla_nest_end(msg, a);
+
+		nla_nest_end(msg, ptr);
+
+		unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+		unl_free(&nl_priv->unl);
+	}
+
+	*(u32 *)(hdr->data + 2) = data->ext_id;
+
+	return 0;
+}
+
+#define NL_OPS_GROUP(cmd, ...)	[HQA_CMD_##cmd] = { __VA_ARGS__ }
+static const struct atenl_nl_ops nl_ops[] = {
+	NL_OPS_GROUP(SET_TX_PATH, .set=MT76_TM_ATTR_TX_ANTENNA),
+	NL_OPS_GROUP(SET_TX_POWER, .set=MT76_TM_ATTR_TX_POWER),
+	NL_OPS_GROUP(SET_RX_PATH, .set=MT76_TM_ATTR_TX_ANTENNA),
+	NL_OPS_GROUP(SET_FREQ_OFFSET, .set=MT76_TM_ATTR_FREQ_OFFSET),
+	NL_OPS_GROUP(SET_CFG, .ops=atenl_nl_set_cfg),
+	NL_OPS_GROUP(SET_TSSI, .ops=atenl_nl_set_cfg),
+	NL_OPS_GROUP(CONTINUOUS_TX, .ops=atenl_nl_continuous_tx),
+	NL_OPS_GROUP(GET_TX_INFO, .dump=MT76_TM_STATS_ATTR_TX_DONE),
+	NL_OPS_GROUP(GET_RX_INFO, .ops=atenl_nl_get_rx_info, .dump=true),
+	NL_OPS_GROUP(SET_RU, .ops=atenl_nl_set_ru),
+};
+#undef NL_OPS_GROUP
+
+#define NL_OPS_EXT(cmd, ...)	[HQA_EXT_CMD_##cmd] = { __VA_ARGS__ }
+static const struct atenl_nl_ops nl_ops_ext[] = {
+	NL_OPS_EXT(SET_TX, .ops=atenl_nl_set_tx),
+	NL_OPS_EXT(START_TX, .ops=atenl_nl_tx),
+	NL_OPS_EXT(STOP_TX, .ops=atenl_nl_tx),
+	NL_OPS_EXT(START_RX, .ops=atenl_nl_rx),
+	NL_OPS_EXT(STOP_RX, .ops=atenl_nl_rx),
+	NL_OPS_EXT(OFF_CH_SCAN, .ops=atenl_off_ch_scan),
+	NL_OPS_EXT(IBF_SET_VAL, .ops=atenl_nl_ibf_set_val),
+	NL_OPS_EXT(IBF_GET_STATUS, .ops=atenl_nl_ibf_get_status),
+	NL_OPS_EXT(IBF_PROF_UPDATE_ALL, .ops=atenl_nl_ibf_profile_update_all),
+};
+#undef NL_OPS_EXT
+
+int atenl_nl_process(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_nl_priv nl_priv = {};
+	const struct atenl_nl_ops *ops;
+	struct nl_msg *msg;
+	int ret = 0;
+
+	if (data->ext_cmd != 0)
+		ops = &nl_ops_ext[data->ext_cmd];
+	else
+		ops = &nl_ops[data->cmd];
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return -1;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, !!ops->dump);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+	nl_priv.msg = msg;
+
+	if (ops->ops) {
+		ret = ops->ops(an, data, &nl_priv);
+	} else if (ops->dump) {
+		nl_priv.attr = ops->dump;
+		ret = atenl_nl_dump_attr(an, data, &nl_priv);
+	} else {
+		nl_priv.attr = ops->set;
+		ret = atenl_nl_set_attr(an, data, &nl_priv);
+	}
+
+	if (ret)
+		atenl_err("command process error: 0x%x (0x%x)\n", data->cmd_id, data->ext_id);
+
+	unl_free(&nl_priv.unl);
+
+	return ret;
+}
+
+int atenl_nl_process_many(struct atenl *an, struct atenl_data *data)
+{
+	struct atenl_nl_priv nl_priv = {};
+	const struct atenl_nl_ops *ops;
+	int ret = 0;
+
+	if (data->ext_cmd != 0)
+		ops = &nl_ops_ext[data->ext_cmd];
+	else
+		ops = &nl_ops[data->cmd];
+
+	if (ops->ops)
+		ret = ops->ops(an, data, &nl_priv);
+
+	return ret;
+}
+
+int atenl_nl_set_state(struct atenl *an, u8 band,
+		       enum mt76_testmode_state state)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, band, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	atenl_set_attr_state(an, msg, band, state);
+
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+	unl_free(&nl_priv.unl);
+
+	return 0;
+}
+
+int atenl_nl_set_aid(struct atenl *an, u8 band, u8 aid)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, band, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MT76_TM_ATTR_AID, aid);
+
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+	unl_free(&nl_priv.unl);
+
+	return 0;
+}
+
+static int atenl_nl_check_mtd_cb(struct nl_msg *msg, void *arg)
+{
+	struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+	struct atenl *an = nl_priv->an;
+	struct nlattr *tb[NUM_MT76_TM_ATTRS];
+	struct nlattr *attr;
+
+	attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+	if (!attr)
+		return NL_SKIP;
+
+	nla_parse_nested(tb, MT76_TM_ATTR_MAX, attr, testdata_policy);
+	if (!tb[MT76_TM_ATTR_MTD_PART] || !tb[MT76_TM_ATTR_MTD_OFFSET])
+		return NL_SKIP;
+
+	an->mtd_part = strdup(nla_get_string(tb[MT76_TM_ATTR_MTD_PART]));
+	an->mtd_offset = nla_get_u32(tb[MT76_TM_ATTR_MTD_OFFSET]);
+	an->is_main_phy = nla_get_u32(tb[MT76_TM_ATTR_IS_MAIN_PHY]);
+
+	return NL_SKIP;
+}
+
+int atenl_nl_check_mtd(struct atenl *an)
+{
+	struct atenl_nl_priv nl_priv = { .an = an };
+	struct nl_msg *msg;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, true);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+	unl_genl_request(&nl_priv.unl, msg, atenl_nl_check_mtd_cb, (void *)&nl_priv);
+
+	unl_free(&nl_priv.unl);
+
+	return 0;
+}
+
+int atenl_nl_write_eeprom(struct atenl *an, u32 offset, u8 *val, int len)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr, *a;
+	int i;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	if (len > 16)
+		return -EINVAL;
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+		   MT76_TM_EEPROM_ACTION_UPDATE_DATA);
+	nla_put_u32(msg, MT76_TM_ATTR_EEPROM_OFFSET, offset);
+
+	a = nla_nest_start(msg, MT76_TM_ATTR_EEPROM_VAL);
+	if (!a)
+		return -ENOMEM;
+
+	for (i = 0; i < len; i++)
+		if (nla_put_u8(msg, i, val[i]))
+			goto out;
+
+	nla_nest_end(msg, a);
+
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+	unl_free(&nl_priv.unl);
+
+out:
+	return 0;
+}
+
+int atenl_nl_write_efuse_all(struct atenl *an)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+		   MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE);
+
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+	unl_free(&nl_priv.unl);
+
+	return 0;
+}
+
+int atenl_nl_update_buffer_mode(struct atenl *an)
+{
+	struct atenl_nl_priv nl_priv = {};
+	struct nl_msg *msg;
+	void *ptr;
+
+	if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+		atenl_err("Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+		   MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE);
+
+	nla_nest_end(msg, ptr);
+
+	unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+	unl_free(&nl_priv.unl);
+
+	return 0;
+}
+
+static int atenl_nl_precal_sync_from_driver_cb(struct nl_msg *msg, void *arg)
+{
+	struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+	struct atenl *an = nl_priv->an;
+	struct nlattr *tb[NUM_MT76_TM_ATTRS];
+	struct nlattr *attr, *cur;
+	int i, rem, prek_offset = nl_priv->attr;
+
+
+	attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+	if (!attr)
+		return NL_SKIP;
+
+	nla_parse_nested(tb, MT76_TM_ATTR_MAX, attr, testdata_policy);
+
+	if (!tb[MT76_TM_ATTR_PRECAL_INFO] && !tb[MT76_TM_ATTR_PRECAL]) {
+		atenl_info("No Pre cal data or info!\n");
+		return NL_SKIP;
+	}
+
+	if (tb[MT76_TM_ATTR_PRECAL_INFO]) {
+		i = 0;
+		nla_for_each_nested(cur, tb[MT76_TM_ATTR_PRECAL_INFO], rem) {
+			an->cal_info[i] = (u32) nla_get_u32(cur);
+			i++;
+		}
+		return NL_SKIP;
+	}
+
+	if (tb[MT76_TM_ATTR_PRECAL] && an->cal) {
+		i = prek_offset;
+		nla_for_each_nested(cur, tb[MT76_TM_ATTR_PRECAL], rem) {
+			an->cal[i] = (u8) nla_get_u8(cur);
+			i++;
+		}
+		return NL_SKIP;
+	}
+	atenl_info("No data found for pre-cal!\n");
+
+	return NL_SKIP;
+}
+
+static int
+atenl_nl_precal_sync_partition(struct atenl_nl_priv *nl_priv, enum mt76_testmode_attr attr,
+			       int prek_type, int prek_offset)
+{
+	int ret;
+	void *ptr;
+	struct nl_msg *msg;
+	struct atenl *an = nl_priv->an;
+
+	msg = unl_genl_msg(&(nl_priv->unl), NL80211_CMD_TESTMODE, true);
+	nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+	nl_priv->msg = msg;
+	nl_priv->attr = prek_offset;
+
+	ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+	if (!ptr)
+		return -ENOMEM;
+
+	nla_put_flag(msg, attr);
+	if (attr == MT76_TM_ATTR_PRECAL)
+		nla_put_u8(msg, MT76_TM_ATTR_PRECAL_INFO, prek_type);
+	nla_nest_end(msg, ptr);
+
+	ret = unl_genl_request(&(nl_priv->unl), msg, atenl_nl_precal_sync_from_driver_cb, (void *)nl_priv);
+
+	if (ret) {
+		atenl_err("command process error!\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int atenl_nl_precal_sync_from_driver(struct atenl *an, enum prek_ops ops)
+{
+#define GROUP_IND_MASK  	BIT(0)
+#define DPD_IND_MASK  		GENMASK(3, 1)
+	int ret;
+	u32 i, times, group_size, dpd_size, total_size, transmit_size, offs;
+	u32 dpd_per_chan_size, dpd_chan_num[3], total_chan_num;
+	u32 size, base, base_idx, *size_ptr;
+	u8 cal_indicator, *precal_info;
+	struct atenl_nl_priv nl_priv = { .an = an };
+
+	offs = an->eeprom_prek_offs;
+	cal_indicator = an->eeprom_data[offs];
+
+	if (cal_indicator) {
+		precal_info = an->eeprom_data + an->eeprom_size;
+		memcpy(an->cal_info, precal_info, PRE_CAL_INFO);
+		group_size = an->cal_info[0];
+		dpd_size = an->cal_info[1];
+		total_size = group_size + dpd_size;
+		dpd_chan_num[0] = (an->cal_info[2] >> DPD_INFO_6G_SHIFT) & DPD_INFO_MASK;
+		dpd_chan_num[1] = (an->cal_info[2] >> DPD_INFO_5G_SHIFT) & DPD_INFO_MASK;
+		dpd_chan_num[2] = (an->cal_info[2] >> DPD_INFO_2G_SHIFT) & DPD_INFO_MASK;
+		dpd_per_chan_size = (an->cal_info[2] >> DPD_INFO_CH_SHIFT) & DPD_INFO_MASK;
+		total_chan_num = dpd_chan_num[0] + dpd_chan_num[1] + dpd_chan_num[2];
+	}
+
+	switch (ops){
+	case PREK_SYNC_ALL:
+		size_ptr = &total_size;
+		base_idx = 0;
+		goto start;
+	case PREK_SYNC_GROUP:
+		size_ptr = &group_size;
+		base_idx = 0;
+		goto start;
+	case PREK_SYNC_DPD_6G:
+		size_ptr = &dpd_size;
+		base_idx = 0;
+		goto start;
+	case PREK_SYNC_DPD_5G:
+		size_ptr = &dpd_size;
+		base_idx = 1;
+		goto start;
+	case PREK_SYNC_DPD_2G:
+		size_ptr = &dpd_size;
+		base_idx = 2;
+
+start:
+		if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+			atenl_err("Failed to connect to nl80211\n");
+			return 2;
+		}
+
+		ret = atenl_nl_precal_sync_partition(&nl_priv, MT76_TM_ATTR_PRECAL_INFO, 0, 0);
+		if (ret || !an->cal_info)
+			goto out;
+
+		group_size = an->cal_info[0];
+		dpd_size = an->cal_info[1];
+		total_size = group_size + dpd_size;
+		dpd_chan_num[0] = (an->cal_info[2] >> DPD_INFO_6G_SHIFT) & DPD_INFO_MASK;
+		dpd_chan_num[1] = (an->cal_info[2] >> DPD_INFO_5G_SHIFT) & DPD_INFO_MASK;
+		dpd_chan_num[2] = (an->cal_info[2] >> DPD_INFO_2G_SHIFT) & DPD_INFO_MASK;
+		dpd_per_chan_size = (an->cal_info[2] >> DPD_INFO_CH_SHIFT) & DPD_INFO_MASK;
+		total_chan_num = dpd_chan_num[0] + dpd_chan_num[1] + dpd_chan_num[2];
+		transmit_size = an->cal_info[3];
+
+		size = *size_ptr;
+		size = (size_ptr == &dpd_size) ? (size / total_chan_num * dpd_chan_num[base_idx]) :
+						 size;
+		base = 0;
+		for (i = 0; i < base_idx; i++) {
+			base += dpd_chan_num[i] * dpd_per_chan_size * MT_EE_CAL_UNIT;
+		}
+		base += (size_ptr == &dpd_size) ? group_size : 0;
+
+		if (!an->cal)
+			an->cal = (u8 *) calloc(size, sizeof(u8));
+		times = size / transmit_size + 1;
+		for (i = 0; i < times; i++) {
+			ret = atenl_nl_precal_sync_partition(&nl_priv, MT76_TM_ATTR_PRECAL, ops,
+							     i * transmit_size);
+			if (ret)
+				goto out;
+		}
+
+		ret = atenl_eeprom_update_precal(an, base, size);
+		break;
+	case PREK_CLEAN_GROUP:
+		if (!(cal_indicator & GROUP_IND_MASK))
+			return 0;
+		an->cal_info[4] = cal_indicator & (u8) ~GROUP_IND_MASK;
+		ret = atenl_eeprom_update_precal(an, 0, group_size);
+		break;
+	case PREK_CLEAN_DPD:
+		if (!(cal_indicator & DPD_IND_MASK))
+			return 0;
+		an->cal_info[4] = cal_indicator & (u8) ~DPD_IND_MASK;
+		ret = atenl_eeprom_update_precal(an, group_size, dpd_size);
+		break;
+	default:
+		break;
+	}
+
+out:
+	unl_free(&nl_priv.unl);
+	return ret;
+}
diff --git a/recipes-devtools/atenl/files/src/nl.h b/recipes-devtools/atenl/files/src/nl.h
new file mode 100644
index 0000000..27336bd
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/nl.h
@@ -0,0 +1,254 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_NL_H
+#define __ATENL_NL_H
+
+/* This is copied from mt76/testmode.h */
+
+/**
+ * enum mt76_testmode_attr - testmode attributes inside NL80211_ATTR_TESTDATA
+ *
+ * @MT76_TM_ATTR_UNSPEC: (invalid attribute)
+ *
+ * @MT76_TM_ATTR_RESET: reset parameters to default (flag)
+ * @MT76_TM_ATTR_STATE: test state (u32), see &enum mt76_testmode_state
+ *
+ * @MT76_TM_ATTR_MTD_PART: mtd partition used for eeprom data (string)
+ * @MT76_TM_ATTR_MTD_OFFSET: offset of eeprom data within the partition (u32)
+ * @MT76_TM_ATTR_IS_MAIN_PHY: Is current phy index the main phy or the ext phy (u8)
+ *
+ * @MT76_TM_ATTR_TX_COUNT: configured number of frames to send when setting
+ *	state to MT76_TM_STATE_TX_FRAMES (u32)
+ * @MT76_TM_ATTR_TX_PENDING: pending frames during MT76_TM_STATE_TX_FRAMES (u32)
+ * @MT76_TM_ATTR_TX_LENGTH: packet tx msdu length (u32)
+ * @MT76_TM_ATTR_TX_RATE_MODE: packet tx mode (u8, see &enum mt76_testmode_tx_mode)
+ * @MT76_TM_ATTR_TX_RATE_NSS: packet tx number of spatial streams (u8)
+ * @MT76_TM_ATTR_TX_RATE_IDX: packet tx rate/MCS index (u8)
+ * @MT76_TM_ATTR_TX_RATE_SGI: packet tx use short guard interval (u8)
+ * @MT76_TM_ATTR_TX_RATE_LDPC: packet tx enable LDPC (u8)
+ * @MT76_TM_ATTR_TX_RATE_STBC: packet tx enable STBC (u8)
+ * @MT76_TM_ATTR_TX_LTF: packet tx LTF, set 0 to 2 for 1x, 2x, and 4x LTF (u8)
+ *
+ * @MT76_TM_ATTR_TX_ANTENNA: tx antenna mask (u8)
+ * @MT76_TM_ATTR_TX_POWER_CONTROL: enable tx power control (u8)
+ * @MT76_TM_ATTR_TX_POWER: per-antenna tx power array (nested, u8 attrs)
+ *
+ * @MT76_TM_ATTR_FREQ_OFFSET: RF frequency offset (u32)
+ *
+ * @MT76_TM_ATTR_STATS: statistics (nested, see &enum mt76_testmode_stats_attr)
+ *
+ * @MT76_TM_ATTR_PRECAL: Pre-cal data (u8)
+ * @MT76_TM_ATTR_PRECAL: Pre-cal data (u8)
+ * @MT76_TM_ATTR_PRECAL_INFO: group size, dpd size, dpd_info, transmit size,
+ *                            eeprom cal indicator (u32),
+ *                            dpd_info = [dpd_per_chan_size, chan_num_2g,
+ *                                        chan_num_5g, chan_num_6g]
+ * @MT76_TM_ATTR_TX_SPE_IDX: tx spatial extension index (u8)
+ *
+ * @MT76_TM_ATTR_TX_DUTY_CYCLE: packet tx duty cycle (u8)
+ * @MT76_TM_ATTR_TX_IPG: tx inter-packet gap, in unit of us (u32)
+ * @MT76_TM_ATTR_TX_TIME: packet transmission time, in unit of us (u32)
+ *
+ */
+enum mt76_testmode_attr {
+	MT76_TM_ATTR_UNSPEC,
+
+	MT76_TM_ATTR_RESET,
+	MT76_TM_ATTR_STATE,
+
+	MT76_TM_ATTR_MTD_PART,
+	MT76_TM_ATTR_MTD_OFFSET,
+	MT76_TM_ATTR_IS_MAIN_PHY,
+
+	MT76_TM_ATTR_TX_COUNT,
+	MT76_TM_ATTR_TX_LENGTH,
+	MT76_TM_ATTR_TX_RATE_MODE,
+	MT76_TM_ATTR_TX_RATE_NSS,
+	MT76_TM_ATTR_TX_RATE_IDX,
+	MT76_TM_ATTR_TX_RATE_SGI,
+	MT76_TM_ATTR_TX_RATE_LDPC,
+	MT76_TM_ATTR_TX_RATE_STBC,
+	MT76_TM_ATTR_TX_LTF,
+
+	MT76_TM_ATTR_TX_ANTENNA,
+	MT76_TM_ATTR_TX_POWER_CONTROL,
+	MT76_TM_ATTR_TX_POWER,
+
+	MT76_TM_ATTR_FREQ_OFFSET,
+
+	MT76_TM_ATTR_STATS,
+
+	MT76_TM_ATTR_PRECAL,
+	MT76_TM_ATTR_PRECAL_INFO,
+
+	MT76_TM_ATTR_TX_SPE_IDX,
+
+	MT76_TM_ATTR_TX_DUTY_CYCLE,
+	MT76_TM_ATTR_TX_IPG,
+	MT76_TM_ATTR_TX_TIME,
+
+	MT76_TM_ATTR_DRV_DATA,
+
+	MT76_TM_ATTR_MAC_ADDRS,
+	MT76_TM_ATTR_AID,
+	MT76_TM_ATTR_RU_ALLOC,
+	MT76_TM_ATTR_RU_IDX,
+
+	MT76_TM_ATTR_EEPROM_ACTION,
+	MT76_TM_ATTR_EEPROM_OFFSET,
+	MT76_TM_ATTR_EEPROM_VAL,
+
+	MT76_TM_ATTR_CFG,
+	MT76_TM_ATTR_TXBF_ACT,
+	MT76_TM_ATTR_TXBF_PARAM,
+
+	MT76_TM_ATTR_OFF_CH_SCAN_CH,
+	MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
+	MT76_TM_ATTR_OFF_CH_SCAN_BW,
+	MT76_TM_ATTR_OFF_CH_SCAN_PATH,
+
+	/* keep last */
+	NUM_MT76_TM_ATTRS,
+	MT76_TM_ATTR_MAX = NUM_MT76_TM_ATTRS - 1,
+};
+
+/**
+ * enum mt76_testmode_state - statistics attributes
+ *
+ * @MT76_TM_STATS_ATTR_TX_PENDING: pending tx frames (u32)
+ * @MT76_TM_STATS_ATTR_TX_QUEUED: queued tx frames (u32)
+ * @MT76_TM_STATS_ATTR_TX_QUEUED: completed tx frames (u32)
+ *
+ * @MT76_TM_STATS_ATTR_RX_PACKETS: number of rx packets (u64)
+ * @MT76_TM_STATS_ATTR_RX_FCS_ERROR: number of rx packets with FCS error (u64)
+ * @MT76_TM_STATS_ATTR_LAST_RX: information about the last received packet
+ *	see &enum mt76_testmode_rx_attr
+ */
+enum mt76_testmode_stats_attr {
+	MT76_TM_STATS_ATTR_UNSPEC,
+	MT76_TM_STATS_ATTR_PAD,
+
+	MT76_TM_STATS_ATTR_TX_PENDING,
+	MT76_TM_STATS_ATTR_TX_QUEUED,
+	MT76_TM_STATS_ATTR_TX_DONE,
+
+	MT76_TM_STATS_ATTR_RX_PACKETS,
+	MT76_TM_STATS_ATTR_RX_FCS_ERROR,
+	MT76_TM_STATS_ATTR_LAST_RX,
+	MT76_TM_STATS_ATTR_RX_LEN_MISMATCH,
+
+	/* keep last */
+	NUM_MT76_TM_STATS_ATTRS,
+	MT76_TM_STATS_ATTR_MAX = NUM_MT76_TM_STATS_ATTRS - 1,
+};
+
+
+/**
+ * enum mt76_testmode_rx_attr - packet rx information
+ *
+ * @MT76_TM_RX_ATTR_FREQ_OFFSET: frequency offset (s32)
+ * @MT76_TM_RX_ATTR_RCPI: received channel power indicator (array, u8)
+ * @MT76_TM_RX_ATTR_IB_RSSI: internal inband RSSI (array, s8)
+ * @MT76_TM_RX_ATTR_WB_RSSI: internal wideband RSSI (array, s8)
+ * @MT76_TM_RX_ATTR_SNR: signal-to-noise ratio (u8)
+ */
+enum mt76_testmode_rx_attr {
+	MT76_TM_RX_ATTR_UNSPEC,
+
+	MT76_TM_RX_ATTR_FREQ_OFFSET,
+	MT76_TM_RX_ATTR_RCPI,
+	MT76_TM_RX_ATTR_IB_RSSI,
+	MT76_TM_RX_ATTR_WB_RSSI,
+	MT76_TM_RX_ATTR_SNR,
+
+	/* keep last */
+	NUM_MT76_TM_RX_ATTRS,
+	MT76_TM_RX_ATTR_MAX = NUM_MT76_TM_RX_ATTRS - 1,
+};
+
+/**
+ * enum mt76_testmode_state - phy test state
+ *
+ * @MT76_TM_STATE_OFF: test mode disabled (normal operation)
+ * @MT76_TM_STATE_IDLE: test mode enabled, but idle
+ * @MT76_TM_STATE_TX_FRAMES: send a fixed number of test frames
+ * @MT76_TM_STATE_RX_FRAMES: receive packets and keep statistics
+ * @MT76_TM_STATE_TX_CONT: waveform tx without time gap
+ * @MT76_TM_STATE_ON: test mode enabled used in offload firmware
+ */
+enum mt76_testmode_state {
+	MT76_TM_STATE_OFF,
+	MT76_TM_STATE_IDLE,
+	MT76_TM_STATE_TX_FRAMES,
+	MT76_TM_STATE_RX_FRAMES,
+	MT76_TM_STATE_TX_CONT,
+	MT76_TM_STATE_ON,
+
+	/* keep last */
+	NUM_MT76_TM_STATES,
+	MT76_TM_STATE_MAX = NUM_MT76_TM_STATES - 1,
+};
+
+/**
+ * enum mt76_testmode_tx_mode - packet tx phy mode
+ *
+ * @MT76_TM_TX_MODE_CCK: legacy CCK mode
+ * @MT76_TM_TX_MODE_OFDM: legacy OFDM mode
+ * @MT76_TM_TX_MODE_HT: 802.11n MCS
+ * @MT76_TM_TX_MODE_VHT: 802.11ac MCS
+ * @MT76_TM_TX_MODE_HE_SU: 802.11ax single-user MIMO
+ * @MT76_TM_TX_MODE_HE_EXT_SU: 802.11ax extended-range SU
+ * @MT76_TM_TX_MODE_HE_TB: 802.11ax trigger-based
+ * @MT76_TM_TX_MODE_HE_MU: 802.11ax multi-user MIMO
+ */
+enum mt76_testmode_tx_mode {
+	MT76_TM_TX_MODE_CCK,
+	MT76_TM_TX_MODE_OFDM,
+	MT76_TM_TX_MODE_HT,
+	MT76_TM_TX_MODE_VHT,
+	MT76_TM_TX_MODE_HE_SU,
+	MT76_TM_TX_MODE_HE_EXT_SU,
+	MT76_TM_TX_MODE_HE_TB,
+	MT76_TM_TX_MODE_HE_MU,
+
+	/* keep last */
+	NUM_MT76_TM_TX_MODES,
+	MT76_TM_TX_MODE_MAX = NUM_MT76_TM_TX_MODES - 1,
+};
+
+/**
+ * enum mt76_testmode_eeprom_action - eeprom setting actions
+ *
+ * @MT76_TM_EEPROM_ACTION_UPDATE_DATA: update rf values to specific
+ * 	eeprom data block
+ * @MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE: send updated eeprom data to fw
+ * @MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE: write eeprom data back to efuse
+ */
+enum mt76_testmode_eeprom_action {
+	MT76_TM_EEPROM_ACTION_UPDATE_DATA,
+	MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE,
+	MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE,
+
+	/* keep last */
+	NUM_MT76_TM_EEPROM_ACTION,
+	MT76_TM_EEPROM_ACTION_MAX = NUM_MT76_TM_EEPROM_ACTION - 1,
+};
+
+enum mt76_testmode_txbf_act {
+	MT76_TM_TXBF_ACT_INIT,
+	MT76_TM_TXBF_ACT_UPDATE_CH,
+	MT76_TM_TXBF_ACT_PHASE_COMP,
+	MT76_TM_TXBF_ACT_TX_PREP,
+	MT76_TM_TXBF_ACT_IBF_PROF_UPDATE,
+	MT76_TM_TXBF_ACT_EBF_PROF_UPDATE,
+	MT76_TM_TXBF_ACT_APPLY_TX,
+	MT76_TM_TXBF_ACT_PHASE_CAL,
+	MT76_TM_TXBF_ACT_PROF_UPDATE_ALL,
+	MT76_TM_TXBF_ACT_PROF_UPDATE_ALL_CMD,
+	MT76_TM_TXBF_ACT_E2P_UPDATE,
+
+	/* keep last */
+	NUM_MT76_TM_TXBF_ACT,
+	MT76_TM_TXBF_ACT_MAX = NUM_MT76_TM_TXBF_ACT - 1,
+};
+
+#endif
diff --git a/recipes-devtools/atenl/files/src/util.c b/recipes-devtools/atenl/files/src/util.c
new file mode 100644
index 0000000..b224040
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/util.c
@@ -0,0 +1,217 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+
+#include "atenl.h"
+
+int atenl_reg_read(struct atenl *an, u32 offset, u32 *res)
+{
+	char dir[64], buf[16];
+	unsigned long val;
+	int fd, ret;
+
+	/* write offset into regidx */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", offset);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	close(fd);
+
+	/* read value from regval */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regval",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_RDONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = read(fd, buf, sizeof(buf) - 1);
+	if (ret < 0)
+		goto out;
+	buf[ret] = 0;
+
+	val = strtoul(buf, NULL, 16);
+	if (val > (u32) -1)
+		return -EINVAL;
+
+	*res = val;
+	ret = 0;
+out:
+	close(fd);
+
+	return ret;
+}
+
+int atenl_reg_write(struct atenl *an, u32 offset, u32 val)
+{
+	char dir[64], buf[16];
+	int fd, ret;
+
+	/* write offset into regidx */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", offset);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	close(fd);
+
+	/* write value into regval */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regval",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", val);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+	buf[ret] = 0;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	ret = 0;
+out:
+	close(fd);
+
+	return ret;
+}
+
+int atenl_rf_read(struct atenl *an, u32 wf_sel, u32 offset, u32 *res)
+{
+	char dir[64], buf[16];
+	unsigned long val;
+	int fd, ret;
+	u32 regidx;
+
+	/* merge wf_sel and offset into regidx */
+	regidx = FIELD_PREP(GENMASK(31, 28), wf_sel) |
+		 FIELD_PREP(GENMASK(27, 0), offset);
+
+	/* write regidx */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", regidx);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	close(fd);
+
+	/* read from rf_regval */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/rf_regval",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_RDONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = read(fd, buf, sizeof(buf) - 1);
+	if (ret < 0)
+		goto out;
+	buf[ret] = 0;
+
+	val = strtoul(buf, NULL, 16);
+	if (val > (u32) -1)
+		return -EINVAL;
+
+	*res = val;
+	ret = 0;
+out:
+	close(fd);
+
+	return ret;
+}
+
+int atenl_rf_write(struct atenl *an, u32 wf_sel, u32 offset, u32 val)
+{
+	char dir[64], buf[16];
+	int fd, ret;
+	u32 regidx;
+
+	/* merge wf_sel and offset into regidx */
+	regidx = FIELD_PREP(GENMASK(31, 28), wf_sel) |
+		 FIELD_PREP(GENMASK(27, 0), offset);
+
+	/* write regidx */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", regidx);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	close(fd);
+
+	/* write value into rf_val */
+	ret = snprintf(dir, sizeof(dir),
+		       "/sys/kernel/debug/ieee80211/phy%d/mt76/rf_regval",
+		       get_band_val(an, 0, phy_idx));
+	if (snprintf_error(sizeof(dir), ret))
+		return ret;
+
+	fd = open(dir, O_WRONLY);
+	if (fd < 0)
+		return fd;
+
+	ret = snprintf(buf, sizeof(buf), "0x%x", val);
+	if (snprintf_error(sizeof(buf), ret))
+		goto out;
+	buf[ret] = 0;
+
+	lseek(fd, 0, SEEK_SET);
+	write(fd, buf, sizeof(buf));
+	ret = 0;
+out:
+	close(fd);
+
+	return ret;
+}
diff --git a/recipes-devtools/atenl/files/src/util.h b/recipes-devtools/atenl/files/src/util.h
new file mode 100644
index 0000000..45c97b5
--- /dev/null
+++ b/recipes-devtools/atenl/files/src/util.h
@@ -0,0 +1,123 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_UTIL_H
+#define __ATENL_UTIL_H
+
+#include <linux/const.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <stdint.h>
+#include <string.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64, ktime_t;
+
+#ifndef __WORDSIZE
+#define __WORDSIZE (__SIZEOF_LONG__ * 8)
+#endif
+
+#ifndef BITS_PER_LONG
+#define BITS_PER_LONG __WORDSIZE
+#endif
+
+#define UL(x)		(_UL(x))
+#define ULL(x)		(_ULL(x))
+
+#define BIT(nr)		(1UL << (nr))
+
+#define GENMASK_INPUT_CHECK(h, l) 0
+#define __GENMASK(h, l) \
+	(((~UL(0)) - (UL(1) << (l)) + 1) & \
+	 (~UL(0) >> (BITS_PER_LONG - 1 - (h))))
+#define GENMASK(h, l) \
+	(GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
+
+#define __bf_shf(x) (__builtin_ffsll(x) - 1)
+#define FIELD_GET(_mask, _reg)						\
+	({								\
+		(typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask));	\
+	})
+#define FIELD_PREP(_mask, _val)						\
+	({								\
+		((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask);	\
+	})
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#ifndef DIV_ROUND_UP
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#endif
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
+#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
+
+static inline bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
+{
+	const u16 *a = (const u16 *)addr1;
+	const u16 *b = (const u16 *)addr2;
+
+	return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
+}
+
+static inline bool is_broadcast_ether_addr(const u8 *addr)
+{
+	return (*(const u16 *)(addr + 0) &
+		*(const u16 *)(addr + 2) &
+		*(const u16 *)(addr + 4)) == 0xffff;
+}
+
+static inline bool is_multicast_ether_addr(const u8 *addr)
+{
+	return 0x01 & addr[0];
+}
+
+static inline bool is_unicast_ether_addr(const u8 *addr)
+{
+	return !is_multicast_ether_addr(addr);
+}
+
+static inline bool is_zero_ether_addr(const u8 *addr)
+{
+	return (*(const u16 *)(addr + 0) |
+		*(const u16 *)(addr + 2) |
+		*(const u16 *)(addr + 4)) == 0;
+}
+
+static inline bool use_default_addr(const u8 *addr)
+{
+	return !is_unicast_ether_addr(addr) ||
+	       is_zero_ether_addr(addr);
+}
+
+static inline void eth_broadcast_addr(u8 *addr)
+{
+	memset(addr, 0xff, ETH_ALEN);
+}
+
+static inline void ether_addr_copy(u8 *dst, const u8 *src)
+{
+	u16 *a = (u16 *)dst;
+	const u16 *b = (const u16 *)src;
+
+	a[0] = b[0];
+	a[1] = b[1];
+	a[2] = b[2];
+}
+
+static inline int snprintf_error(size_t size, int res)
+{
+	return res < 0 || (unsigned int) res >= size;
+}
+
+#endif