This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 0f7aa511b22e2b3523f7ddabf6afc7f76862731c
Author: Kerogit <[email protected]>
AuthorDate: Wed Jun 24 00:16:17 2026 +0200

    arch/avr/src/avrdx/avrdx_ws2812: added lower-half driver for WS2812 LEDs
    
    This patch adds lower half for the WS2812 programmable LED driver
    to the AVR DA/DB family. USART peripheral is supported as the transmission
    hardware.
    
    Driver was verified by ws2812 example from nuttx-apps and by custom
    application that controls LEDs on two USART peripherals.
    
    [skip-cmake-parity]
    Signed-off-by: Kerogit <[email protected]>
---
 .codespellrc                                      |    2 +-
 Documentation/platforms/avr/avrdx/docs/ws2812.rst |  321 +++++
 arch/avr/src/avrdx/Kconfig                        |   18 +-
 arch/avr/src/avrdx/Kconfig-drivers                |  126 ++
 arch/avr/src/avrdx/Make.defs                      |    5 +
 arch/avr/src/avrdx/avrdx_swint.h                  |   12 +
 arch/avr/src/avrdx/avrdx_ws2812.c                 | 1342 +++++++++++++++++++++
 arch/avr/src/avrdx/avrdx_ws2812.h                 |  174 +++
 arch/avr/src/avrdx/avrdx_ws2812_uart_store.S      |  280 +++++
 arch/avr/src/avrdx/avrdx_ws2812_uart_xmit.S       |  479 ++++++++
 10 files changed, 2752 insertions(+), 7 deletions(-)

diff --git a/.codespellrc b/.codespellrc
index 69bd7c2718c..96cd556dedf 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -54,7 +54,7 @@ ignore-words-list =
   parm,
   parms,
   pres,
-  RCALL,
+  rcall,
   REGONS,
   SAIs,
   sie,
diff --git a/Documentation/platforms/avr/avrdx/docs/ws2812.rst 
b/Documentation/platforms/avr/avrdx/docs/ws2812.rst
new file mode 100644
index 00000000000..0949cf9e0e4
--- /dev/null
+++ b/Documentation/platforms/avr/avrdx/docs/ws2812.rst
@@ -0,0 +1,321 @@
+=========================================
+WS2812 RGB LED driver for AVR DA/DB chips
+=========================================
+
+This is a description of driver for WS2812 programmable RGB LED
+on AVR DA/DB chips.
+
+Developer notes - driver design philosophy
+==========================================
+
+The datasheets for various WS2812 devices differ slightly but
+in all of them, zero data bit starts with logical 1 pulse with 220-380ns
+length. The rest varies slightly across datasheets but overall, the LED
+operates at 800kHz frequency so the logical 0 state duration should be
+1250ns (period) minus length of log. 1 pulse.
+
+To put things into perspective - the maximum clock speed of the chip
+is 24MHz. Transmission of each data bit therefore takes 30 clock ticks
+of the CPU. Even with most instructions only taking one clock tick,
+that is not much time.
+
+**Using this driver with slower clock speed is therefore unsupported.**
+
+There are multiple options how to implement this. One of them is manual
+bit flipping with timing being achieved by instruction count. This would
+require the code to run with interrupts disabled (any interruption breaks
+the timing.)
+
+Considering that an RGB LED needs 24 bits of data (or 32bit for LED
+with white light) and each bit takes 1.25 microseconds, transmitting
+data to single LED ties up the CPU for 30/40 microseconds. This time
+increases with LED count which may cause latency problems. (Some of
+which can be alleviated by enabling interrupts between data bits
+where you can pause the transmission (for up to 280us, that is
+considered to be a reset interval by the LEDs.)
+
+Another option is to use some integrated peripheral to take care
+of the actual data transmission, the CPU would only be used to prepare
+the data. There are multiple options for hardware that can be chosen -
+the requirement is that the hardware must be able to generate
+a waveform interpreted as a valid bit by the LED.
+
+Naming note
+-----------
+
+This driver uses the word **lane** as a term for interconnected chain
+of LEDs.
+
+
+UART driver
+-----------
+
+For this, we split the 1.25us period to 4 time segments, each 312.5ns
+long. Logical 0 bit is then encoded as 1 segment high, 3 segments low,
+vice versa for logical 1.
+
+This waveform presents an issue for the UART because UART transmission
+starts with logical 0 pulse (the start bit) and the idle state
+on the line is logical 1. This is solved in the hardware using
+internal inverter.
+
+Since the LED does not recognize any start bit, the driver needs
+to include the start bit into the waveform. Not a problem, (inverted)
+start bit puts logical 1 on the line which is what the LED wants.
+
+Data bit waveform is then sent as a byte of data from the UART's
+perspective - the waveform needs to be mapped to a byte. The simplest
+way to do this would be to simply stretch the 4 time segments over
+8 UART bits. Since the start bit is a part of the transmission,
+this would only use 7 UART data bits - 8th bit would be left set,
+simply increasing the time the line stays in logical 0 (that time
+is also increased by stop bit) - since the LEDs only reset
+after a 280us delay, slight delay by trailing log. 1 bits is
+inconsequential.
+
+The problematic part of this solution is that UART bit length
+needs to be 312.5/2 nanoseconds, yielding 6.4Mbaud UART transfer
+rate - well beyond the capabilities of the chip.
+
+The solution is to only map each time segment to a single UART bit,
+using only the start bit and three data bits, injecting a delay
+when the remaining bits keep the line in logical 0 (inverting log. 1.)
+This can be done because first time segment, which maps to start bit,
+is always logical 1. This halves the required baud rate to 3.2Mbaud
+which is still too much for the hardware. The maximum achievable
+baud rate is 3Mbaud, making time segment 333.3 nanoseconds long
+and the overall waveform 1.33 microseconds long.
+
+This is within the device's (LED's) tolerances. Delay between bits
+is also acceptable.
+
+This solution only uses first 3 UART data bits (and the start bit),
+leaving 5 additional UART bits unused. Each LED bit consumes 4 UART
+data bits so it is even possible to pack second LED data bit
+into the same UART data byte. The only UART bits left unused
+after that are UART bit 7 and stop bit. Both need to be set
+to logical 1 (stop bit always is.) This will cause slight delay
+between LED bits but that should not be a problem - the LEDs only
+reset the transmission after 280 microseconds of logical 0.
+
+With 8 UART data bits,
+there is nothing more to be done. However, the chip has the ability
+to transmit (and receive) 9 data bits. With parity and stop bit added,
+that's 4 additional UART bits that can hold third LED data bit. We
+only need to be able to set the required value in parity bit and
+that can be done because each LED bit always differs in two UART bits
+and parity value therefore only depends on last LED bit. Stop bit
+is always set to one, even parity is in use.
+
+Complete UART frames look like this (LED bits shown in order in which
+they appear on the line.)
+
+.. code-block:: text
+
+ LED     UART St 0  1  2  3  4  5  6  7  8  P  Sp
+ 0 0 0        0  1  1  1  0  1  1  1  0  1  1  1
+ 0 0 1        0  1  1  1  0  1  1  1  0  0  0  1
+ 0 1 0        0  1  1  1  0  0  0  1  0  1  1  1
+ 0 1 1        0  1  1  1  0  0  0  1  0  0  0  1
+ 1 0 0        0  0  0  1  0  1  1  1  0  1  1  1
+ 1 0 1        0  0  0  1  0  1  1  1  0  0  0  1
+ 1 1 0        0  0  0  1  0  0  0  1  0  1  1  1
+ 1 1 1        0  0  0  1  0  0  0  1  0  0  0  1
+
+This is used for RGB LEDs (ie. LEDs without white component) because those
+have 24 total data bits, number divisible by 3 without remainder. For LEDs
+with white color component, the situation is different. Those have 32 data
+bits so there is nothing to be placed into the last 4 bits of the last frame.
+We need to make the line idle which is impossible:
+
+.. code-block:: text
+
+ LED     UART St 0  1  2  3  4  5  6  7  8  P  Sp
+ 0 0 X        0  1  1  1  0  1  1  1  1  1  0  1
+
+The line output will contain an unwanted logical 1 pulse in parity
+bit. This could possibly be solved by pushing bits from the next
+LED in the lane into the previous UART block. That complicates things
+significantly and depending on the LED count, it may not be a solution
+either.
+
+For this reason, only two bits per UART frame are sent. There is no need
+to use 9th bit nor stop bit.
+
+.. code-block:: text
+
+ LED     UART St 0  1  2  3  4  5  6  7  Sp
+ 0 0          0  1  1  1  0  1  1  1  1  1
+ 0 1          0  1  1  1  0  0  0  1  1  1
+ 1 0          0  0  0  1  0  1  1  1  1  1
+ 1 1          0  0  0  1  0  0  0  1  1  1
+
+This driver needs to enable ``DRE`` interrupt for the USART device in use.
+
+Limitations
+===========
+
+One transmission at a time
+--------------------------
+
+While this CPU is able to control these LEDs, it is a very demanding task
+for it. Even when using a peripheral to send the bits, simply pushing more
+data to that peripheral's I/O registers takes a lot of time. (For example,
+pushing bits via an UART peripheral takes 96 clock ticks of time and it takes
+over 50 clock ticks worth of instructions to make that happen.)
+
+For this reason, even when using multiple lanes connected to different
+peripheral, only one is being transmitted at a time. Lanes are serviced
+in round-robin fashion.
+
+Even with this, expect relatively high (50-80% for UART) CPU load during
+transmission.
+
+RGB vs BGR bit order
+--------------------
+
+The driver was tested with a WS2812 LED which requires data bits to be
+sent in RGB order (despite datasheet provided by the retailer claiming
+that the order is GBR.) It should be working correctly with the GBR-variant
+though - it is the RGB variant that is going to have red and green colors
+swapped.
+
+(RGB order on the LED was confirmed by a simple non-NuttX application
+that simply bitbanged the bits and used ``nop`` instruction for timing.)
+
+Configuration
+=============
+
+Driver is enabled using
+:menuselection:`System Type --> AVR DA/DB Drivers --> Enable driver for WS2812 
LEDs`.
+
+To be able to enable this option, it is first needed to enable:
+  - :menuselection:`System Type --> Enable software interrupt emulation`
+    (needs additional settings described in :doc:`software_interrupt`)
+  - :menuselection:`System Type --> Enable high priority interrupts`
+    (enable appropriate interrupt for the chosen peripheral)
+  - :menuselection:`Device Drivers --> LED Support --> WS2812 LED Driver`,
+    the driver needs to be configured as :menuselection:`New non-SPI driver`
+    in :menuselection:`WS2812 driver variant`
+  - :menuselection:`RTOS Features --> RTOS hooks --> Custom board late 
initialization`
+
+After the driver is enabled, it is also needed to choose the peripheral
+which is connected to the LED chain
+in :menuselection:`AVR DA/DB Drivers`. The driver currently supports UART
+peripherals. For example, select
+:menuselection:`Enable WS2812 lane on USART0` to use USART0 peripheral.
+
+Note that the peripheral must not be used by other drivers. Choosing
+an option also selects corresponding high priority interrupt handling.
+
+If you want to use alternative pinout for the selected peripheral,
+use configuration options in :menuselection:`AVR DA/DB Peripheral Selections`
+
+Upper-half options
+------------------
+
+:menuselection:`ws2812s have white LED` option in the upper-half driver
+enables support for WS2812-alike LEDs that have white component. The code
+supporting this option is present in the driver but disabled - author
+of the driver did not have any such device to use for testing.
+
+Also note that using this option increases code size of the driver
+to take care of necessary branching. Also increased is RAM
+usage - for example, the UART peripheral converts RGB data to 8 bytes
+of UART data without white component but to 12 bytes with white component
+(per LED.)
+
+Additional options
+------------------
+
+:menuselection:`Limit number of LEDs in lane for faster execution` reduces
+LED count limit to speed the ISR execution up. When pixel data is submitted
+to the driver, the pixels are converted to a format that allows the ISR
+to just load next byte from an array and send it. Loading next byte involves
+an index to the array - selecting this option
+reduces this index to a single byte, saving few instructions. This imposes
+LED count limit for a lane - 31 LEDs without the white component and 21 LEDs
+with the white component.
+
+Note that the driver also imposes hardcoded limit of 255 LEDs per lane.
+Keep in mind that each LED's RGB data expands into more than 24(32) bits
+of converted data and that the device is memory-constrained.
+
+:menuselection:`Disable reading the pixel buffer` reduces memory usage
+of the driver by not allocating memory to store RGB data. The upper-half
+driver provides both ``write`` and ``read`` syscalls, ``read`` allows
+the user to read RGB data written previously.
+
+If this option is selected, that ability is removed and ``read()``
+returns ``ENOSYS``.
+
+:menuselection:`Suppress warning for white LED`. As mentioned above, code
+supporting LEDs with white component is present in the driver but disabled.
+If the WS2812 upper-half driver enables support for such LEDs, the compiler
+will issue a warning.
+
+Use this option if you need to have the setting enabled in the upper-half.
+
+Usage
+=====
+
+Initialization
+--------------
+
+Lane is initialized by a call to ``avrdx_ws2812_register()``
+
+.. c:function:: int avrdx_ws2812_register (const char *path, uint8_t count, 
enum avrdx_ws2812_lane_e lane, bool has_white)
+
+  ``avrdx_ws2812_register()`` initializes a Lane, that is a set of WS2812
+  LEDs connected serially and driven from single hardware device
+  on the MCU side.
+
+  :param path: device path for this LED lane, for example ``"/dev/led0"``
+  :param count: LED count. Limited to 255 regardless of upper half setting
+  :param lane: enum that identifies peripheral to which are the LEDs connected
+     on the board
+  :param has_white: True if the lane has LEDs with white component.
+     (Ignored if CONFIG_WS2812_HAS_WHITE is unset.)
+
+  :return: ``OK`` or negated ``errno``. In particular, ``errno`` can be set
+     to:
+
+     -  ``ENOMEM`` - memory allocation failures
+     -  ``EINVAL`` - count parameter too high
+     -  ``ENOTSUP`` - parameter ``has_white`` was set and is not ignored
+
+Note that the driver will reset the LEDs connected to the peripheral
+automatically during boot and driver initialization. This accounts
+for a possibility that the system was already
+running and it was only the MCU that reset. In such case, it is not known
+how much time elapsed after last transmission and if enough time elapsed
+so the LEDs consider the transmission reset (see the datasheet for details.)
+
+For this reason, the driver will check for the uptime. If the uptime is lower
+than 330 microseconds, the ``avrdx_ws2812_register()`` will sleep (for as much
+time as needed for the uptime to exceed this value.)
+
+The possibility that the driver will sleep requires
+:menuselection:`Custom board late initialization` (``BOARD_LATE_INITIALIZE``)
+to be enabled.
+
+Example initialization:
+
+.. code-block:: c
+
+ ret = avrdx_ws2812_register("/dev/led0", 24, AVRDX_WS2812_LANE_USART0, false);
+
+Writing and reading pixel data
+------------------------------
+
+This does not differ from other platforms - open the character device file
+and write the data to it.
+
+.. code-block:: c
+
+ uint32_t pixels[4];
+
+ led0fd = open("/dev/led0", O_RDWR);
+ write(led0fd, pixels, sizeof(uint32_t) * 4);
+
+Ability to read the data may be restricted by configuration - see above.
diff --git a/arch/avr/src/avrdx/Kconfig b/arch/avr/src/avrdx/Kconfig
index 2e3388d25ea..82783b3526c 100644
--- a/arch/avr/src/avrdx/Kconfig
+++ b/arch/avr/src/avrdx/Kconfig
@@ -165,7 +165,7 @@ config AVR_USART0
 
 choice AVR_USART0_MUX
        prompt "USART0 pin selection"
-       depends on AVR_USART0
+       depends on AVR_USART0 || AVR_AVRDX_WS2812_USART0
        default AVR_USART0_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART0 interface.
@@ -199,7 +199,7 @@ config AVR_USART1
 
 choice AVR_USART1_MUX
        prompt "USART1 pin selection"
-       depends on AVR_USART1
+       depends on AVR_USART1 || AVR_AVRDX_WS2812_USART1
        default AVR_USART1_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART1 interface.
@@ -234,7 +234,7 @@ config AVR_USART2
 
 choice AVR_USART2_MUX
        prompt "USART2 pin selection"
-       depends on AVR_USART2
+       depends on AVR_USART2 || AVR_AVRDX_WS2812_USART2
        default AVR_USART2_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART2 interface.
@@ -269,7 +269,7 @@ config AVR_USART3
 
 choice AVR_USART3_MUX
        prompt "USART3 pin selection"
-       depends on AVR_USART3
+       depends on AVR_USART3 || AVR_AVRDX_WS2812_USART3
        default AVR_USART3_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART3 interface.
@@ -304,7 +304,7 @@ config AVR_USART4
 
 choice AVR_USART4_MUX
        prompt "USART4 pin selection"
-       depends on AVR_USART4
+       depends on AVR_USART4 || AVR_AVRDX_WS2812_USART4
        default AVR_USART4_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART4 interface.
@@ -339,7 +339,7 @@ config AVR_USART5
 
 choice AVR_USART5_MUX
        prompt "USART5 pin selection"
-       depends on AVR_USART5
+       depends on AVR_USART5 || AVR_AVRDX_WS2812_USART5
        default AVR_USART5_DEFAULT
        ---help---
                Select which I/O pins will be driven by USART5 interface.
@@ -542,6 +542,12 @@ endif
 
 endmenu # AVR DA/DB Peripheral Selections
 
+menu "AVR DA/DB Drivers"
+
+source "arch/avr/src/avrdx/Kconfig-drivers"
+
+endmenu # AVR DA/DB Drivers
+
 config AVR_AVRDX_SWINT
        bool "Enable software interrupt emulation"
        default false
diff --git a/arch/avr/src/avrdx/Kconfig-drivers 
b/arch/avr/src/avrdx/Kconfig-drivers
new file mode 100644
index 00000000000..95d0e3ef993
--- /dev/null
+++ b/arch/avr/src/avrdx/Kconfig-drivers
@@ -0,0 +1,126 @@
+config AVR_AVRDX_DRIVER_WS2812
+       bool "Enable driver for WS2812 LEDs"
+       default false
+       depends on AVR_AVRDX_SWINT
+       depends on AVRDX_HIGH_PRIO_INTERRUPTS
+       depends on WS2812
+       depends on WS2812_NON_SPI_DRIVER
+       depends on BOARD_LATE_INITIALIZE
+       ---help---
+               Enable this to build lower-half driver for WS2812 LED.
+
+               This driver uses a concept of "lanes". A lane is a set of WS2812
+               LEDs connected serially and driven from single hardware device
+               (peripheral) on the MCU side.
+
+               You need to select at least one peripheral to use this driver.
+               Selected peripheral cannot be used by its regular driver and
+               high priority interrupts will be enabled for it. (Or at least
+               for its output-related interrupt.)
+
+config AVR_AVRDX_WS2812_USART0
+       bool "Enable WS2812 lane on USART0"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_2
+       depends on !AVR_USART0
+       select AVRDX_HIGH_PRIO_USART0_DRE
+
+config AVR_AVRDX_WS2812_USART1
+       bool "Enable WS2812 lane on USART1"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_2
+       depends on !AVR_USART1
+       select AVRDX_HIGH_PRIO_USART1_DRE
+
+config AVR_AVRDX_WS2812_USART2
+       bool "Enable WS2812 lane on USART2"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_2
+       depends on !AVR_USART2
+       select AVRDX_HIGH_PRIO_USART2_DRE
+
+config AVR_AVRDX_WS2812_USART3
+       bool "Enable WS2812 lane on USART3"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_4 # this is correct, means 3 and 4
+       depends on !AVR_USART3
+       select AVRDX_HIGH_PRIO_USART3_DRE
+
+config AVR_AVRDX_WS2812_USART4
+       bool "Enable WS2812 lane on USART4"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_4
+       depends on !AVR_USART4
+       select AVRDX_HIGH_PRIO_USART4_DRE
+
+config AVR_AVRDX_WS2812_USART5
+       bool "Enable WS2812 lane on USART5"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on AVR_HAVE_USART_5
+       depends on !AVR_USART5
+       select AVRDX_HIGH_PRIO_USART5_DRE
+
+config AVR_AVRDX_WS2812_SHORT_LANE
+       bool "Limit number of LEDs in lane for faster execution"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       ---help---
+               When the LED data is submitted by the application, the bits
+               are converted to whatever data the hardware needs to be able
+               to transmit those bits. Generally, that means using more
+               memory - for example, the UART lanes convert each 3 bytes
+               of LED data into 8 + 1 bytes of UART data (more if the LEDs
+               have white component.) Walking through this data needs
+               an iterator variable (index) - data type needed for this
+               variable is determined by LED count.
+
+               If you select this option, the LED count is limited to make
+               uint8_t large enough to hold the index. The limitation is
+               31 LEDs without support for white LED (see WS2812_HAS_WHITE)
+               or 21 LEDs with it.
+
+               If you do not select it, the LED count is limited to a hardcoded
+               limit of 255 LEDs (per lane.)
+
+config AVR_AVRDX_WS2812_NO_READS
+       bool "Disable reading the pixel buffer"
+       default false
+       depends on AVR_AVRDX_DRIVER_WS2812
+       ---help---
+               The upper half driver expects to be able to read data present
+               in the pixel buffer. That means that the lower half driver
+               needs to store what it receives in write call.
+
+               The data is, however, also stored in the form suitable
+               for transmission by the hardware. In essence, everything
+               is stored twice.
+
+               If your application never reads data from the pixel buffer,
+               you can enable this option. Any attempt to read will return
+               ENOSYS and the pixel buffer will not be allocated.
+
+config AVR_AVRDX_WS2812_NO_WHITE_LED
+       bool "Suppress warning for white LED"
+       depends on AVR_AVRDX_DRIVER_WS2812
+       depends on WS2812_HAS_WHITE
+       default false
+       ---help---
+               This driver implements support for LEDs with white component
+               but the author of the code did not have any such device
+               to test. Therefore, support for these LEDs is disabled
+               and the compiler will issue a warning. Any attempt to register
+               LED with has_white set will return an error.
+
+               It is preferred to disable WS2812_HAS_WHITE altogether but
+               if you need it on (for example because the system has some other
+               WS2812 device using different lower-half), you can use this
+               option to suppress the compile-time warning and reduce
+               code size.
+
+               The code will be compiled as if WS2812_HAS_WHITE was unset.
diff --git a/arch/avr/src/avrdx/Make.defs b/arch/avr/src/avrdx/Make.defs
index 67f734b0be3..3a3c0b9a5b0 100644
--- a/arch/avr/src/avrdx/Make.defs
+++ b/arch/avr/src/avrdx/Make.defs
@@ -77,3 +77,8 @@ endif
 ifeq ($(CONFIG_AVR_AVRDX_SWINT),y)
 CHIP_CSRCS += avrdx_swint.c
 endif
+
+ifeq ($(CONFIG_AVR_AVRDX_DRIVER_WS2812),y)
+CHIP_CSRCS += avrdx_ws2812.c
+CHIP_ASRCS += avrdx_ws2812_uart_store.S avrdx_ws2812_uart_xmit.S
+endif
diff --git a/arch/avr/src/avrdx/avrdx_swint.h b/arch/avr/src/avrdx/avrdx_swint.h
index f4c6184a664..cb177963d3c 100644
--- a/arch/avr/src/avrdx/avrdx_swint.h
+++ b/arch/avr/src/avrdx/avrdx_swint.h
@@ -33,6 +33,18 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
+#if (CONFIG_AVRDX_SWINT_MAX_VECTORS <= 8)
+#  define AVRDX_SWINT_VECTORS_SIZE 1
+#elif (CONFIG_AVRDX_SWINT_MAX_VECTORS <= 16)
+#  define AVRDX_SWINT_VECTORS_SIZE 2
+#elif (CONFIG_AVRDX_SWINT_MAX_VECTORS <= 24)
+#  define AVRDX_SWINT_VECTORS_SIZE 3
+#elif (CONFIG_AVRDX_SWINT_MAX_VECTORS <= 32)
+#  define AVRDX_SWINT_VECTORS_SIZE 4
+#else
+#  error Kconfig should allow 32 vectors at most
+#endif
+
 /****************************************************************************
  * Public Types
  ****************************************************************************/
diff --git a/arch/avr/src/avrdx/avrdx_ws2812.c 
b/arch/avr/src/avrdx/avrdx_ws2812.c
new file mode 100644
index 00000000000..657b93e38ae
--- /dev/null
+++ b/arch/avr/src/avrdx/avrdx_ws2812.c
@@ -0,0 +1,1342 @@
+/****************************************************************************
+ * arch/avr/src/avrdx/avrdx_ws2812.c
+ * Driver for programmable RGB LED WS12812
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * You can read on design philosophy of this driver in the Documentation
+ * directory (platforms/avr/avrdx/docs/ws2812)
+ *
+ * The gist is that the implementation strives to be as fast as possible
+ * because with 800kHz LED bit frequency, the chip has 30 clock cycles
+ * to process each bit.
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <stdint.h>
+#include <nuttx/leds/ws2812.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "avrdx_ws2812.h"
+#include "avrdx_serial.h"
+#include "avrdx_swint.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* This code assumes that WS2812_RW_PIXEL_SIZE == 4 */
+
+static_assert(WS2812_RW_PIXEL_SIZE == 4,
+              "This code assumes that WS2812_RW_PIXEL_SIZE == 4");
+
+#if (defined CONFIG_WS2812_HAS_WHITE) && \
+    (defined CONFIG_AVR_AVRDX_WS2812_NO_WHITE_LED)
+#  undef CONFIG_WS2812_HAS_WHITE
+#endif
+
+#ifdef CONFIG_WS2812_HAS_WHITE
+
+/* Author of this code does not have any such LED on hand and did not
+ * succeed in finding a datasheet for one. Color component ordering
+ * is unknown and was simply copied from driver for another chip.
+ * (arch/risc-v/src/rp23xx-rv/rp23xx_ws2812.c)
+ *
+ * Since the code cannot be tested, it is DISABLED. Calling register
+ * method avrdx_ws2812_register with has_white set will cause
+ * it to return ENOTSUP. To save program memory space, disable
+ * WS2812_HAS_WHITE in Kconfig if you can.
+ *
+ * In case someone with such a LED tests this code, please submit
+ * a pull request that removes this warning and re-enables has_white
+ * in avrdx_ws2812_register.
+ */
+
+#  warning CONFIG_WS2812_HAS_WHITE is untested and is disabled.\
+ (see comment in source)
+#endif
+
+/* Note - position of data in various arrays is static for each lane;
+ * for unused lanes, the position is left empty. Size is therefore
+ * determined solely from highest used number.)
+ *
+ * For future edits: the ordering of existing items MUST be preserved,
+ * code all around uses these values as offsets for various calculations.
+ */
+
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART5)
+#  define AVRDX_WS2812_LANE_SIZE 6
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART4)
+#  define AVRDX_WS2812_LANE_SIZE 5
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART3)
+#  define AVRDX_WS2812_LANE_SIZE 4
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART2)
+#  define AVRDX_WS2812_LANE_SIZE 3
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART1)
+#  define AVRDX_WS2812_LANE_SIZE 2
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART0)
+#  define AVRDX_WS2812_LANE_SIZE 1
+#else
+#  error There is no enabled lane
+#endif
+
+/* This is specific for UART lanes - arrays that are not relevant
+ * for other lane types do not need to allocate space for them
+ */
+
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART5)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 6
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART4)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 5
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART3)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 4
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART2)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 3
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART1)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 2
+#elif defined(CONFIG_AVR_AVRDX_WS2812_USART0)
+#  define AVRDX_WS2812_LANE_ANY_USART
+#  define AVRDX_WS2812_LANE_USART_SIZE 1
+#else
+#  define AVRDX_WS2812_LANE_USART_SIZE 0
+#endif
+
+/* Avoid magic numbers in the code */
+
+#ifdef CONFIG_WS2812_HAS_WHITE
+#  define WS2812_BYTES_PER_LED_WITH_WHITE 12
+#endif
+
+#define WS2812_BYTES_PER_LED_NO_WHITE 8
+
+#define WS2812_MAX_LED_COUNT 255
+
+/* This is 5 * 65536 - allows the compiler to simplify some comparisons.
+ * The value of T_reset specified in the datasheet is 280us. (Value
+ * given in nanoseconds to be suitable for tv_nsec in struct timespec.)
+ */
+
+#define WS2812_T_RESET (327680)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Size of index needed to address all data for transmission.
+ * Use smallest data type if the user asked for it.
+ */
+
+#ifdef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+typedef uint8_t avrdx_ws2812_idx_t;
+#else
+/* Needs to be able to index transmit data - which is not the same
+ * as pixel buffer - for hardcoded limit of 255 LEDs
+ */
+
+typedef uint16_t avrdx_ws2812_idx_t;
+#endif
+
+/* This is stored in inode and we get it to recognize which character
+ * device file we are working with at the moment. There should be more
+ * information in the struct but we need to store most data in flat
+ * arrays so they are accessible from assembler.
+ */
+
+struct avrdx_ws2812_s
+{
+  enum avrdx_ws2812_lane_e lane;   /* Lane - used hardware */
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  /* There is a room for improvement - if CONFIG_WS2812_HAS_WHITE
+   * is not set, we only need 24 bits to store the pixel (uint24_t
+   * is supported by GCC.)
+   *
+   * Not implemented though - better memory savings can be achieved
+   * by not storing the data altogether.
+   */
+
+  uint32_t *pixel_buffer;
+#endif
+};
+
+#if (AVRDX_WS2812_LANE_SIZE <= 8)
+typedef uint8_t avrdx_ws2812_bitmap_t;
+#elif (AVRDX_WS2812_LANE_SIZE <= 16)
+typedef uint16_t avrdx_ws2812_bitmap_t;
+#elif (AVRDX_WS2812_LANE_SIZE <= 32)
+typedef uint32_t avrdx_ws2812_bitmap_t;
+#else
+#  error Having more than 32 lines is not supported
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void trigger_transmission_hardware(void);
+
+/* Technically not private since we load it from different file
+ * but it is only supposed to be used by store_uart_bytes()
+ * (Ie. it would be in this file if not written in assembler.)
+ */
+
+void avrdx_ws2812_store_uart_bytes_rgb(enum avrdx_ws2812_lane_e lane,
+                                       uint32_t *data,
+                                       uint8_t offset,
+                                       uint8_t len);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Values here correspond to the Documentation file describing this driver
+ * (platforms/avr/avrdx/docs/ws2812.rst) Declared in private data section
+ * despite not being static - used by a function written in assembler.
+ */
+
+const uint8_t IOBJ avrdx_ws2812_uart_white[4] =
+{
+  0xf7, /* 0b11110111 */
+  0xc7, /* 0b11000111 */
+  0xf4, /* 0b11110100 */
+  0xc4  /* 0b11000100 */
+};
+
+static avrdx_ws2812_bitmap_t avrdx_ws2812_dirty_lanes;
+static avrdx_ws2812_bitmap_t avrdx_ws2812_active_lane;
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/* Array of pointers to uint8_t. Each lane allocates appropriate amount
+ * of bytes and stores the pointer here. Used by assembler code, needs
+ * to be outside of struct.
+ */
+
+uint8_t *g_avrdx_ws2812_xmit_bytes[AVRDX_WS2812_LANE_SIZE];
+
+/* Next byte to be sent. Hardware for a lane may need additional data,
+ * defined below.
+ */
+
+avrdx_ws2812_idx_t g_avrdx_ws2812_xmit_next[AVRDX_WS2812_LANE_SIZE];
+
+/* Total count of bytes to be sent */
+
+avrdx_ws2812_idx_t g_avrdx_ws2812_xmit_count[AVRDX_WS2812_LANE_SIZE];
+
+/* Software interrupt vectors to signal completion */
+
+avrdx_swint_size_t g_avrdx_ws2812_swint_vector[AVRDX_WS2812_LANE_SIZE];
+
+/* Array of pointers to struct timespec which records the last time
+ * the lane data was transmitted. Used to ensure that the T_reset
+ * period elapses between retransmissions.
+ */
+
+struct timespec *g_avrdx_ws2812_xmit_ts[AVRDX_WS2812_LANE_SIZE];
+
+#ifdef AVRDX_WS2812_LANE_ANY_USART
+
+/* UART lanes use 9 bit transmissions. 9th bits are stored here.
+ * For each group of 8 bytes, one byte is allocated here and
+ * 8x 9th bits are stored in it. LSB holds 9th bit for byte
+ * with lowest index.
+ */
+
+uint8_t *g_avrdx_ws2812_uart9_bytes[AVRDX_WS2812_LANE_USART_SIZE];
+
+/* This is a working copy from g_avrdx_ws2812_uart9_bytes. Fetched
+ * in ISR every time xmit_next % 8 == 0, shifted to the right
+ * after each UART byte is transmitted.
+ *
+ * The highest bit cannot be set in this value except between
+ * the fetch and first shift. This means that it cannot be set
+ * when we enter the transmit ISR (because it does the shift
+ * after fetch.) We are therefore using this highest bit as a signal.
+ * If set, the LEDs have white component and 9 bit transmission
+ * is actually not in use. This check is skipped
+ * if CONFIG_WS2812_HAS_WHITE is unset.
+ */
+
+uint8_t g_avrdx_ws2812_uart9_current[AVRDX_WS2812_LANE_USART_SIZE];
+
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: transmission_completed
+ *
+ * Description:
+ *   IRQ callback called by software interrupt triggered by lane transmit
+ *   handler (for example UART Data Register Empty ISR) when it transmits
+ *   all bytes to LEDs
+ *
+ * Input Parameters:
+ *   irq, context - unused
+ *   arg - lane number (given as pointer, type cast is needed)
+ *
+ * Returned Value: OK
+ *
+ * Assumptions/Limitations:
+ *   Running in interrupt context.
+ *
+ ****************************************************************************/
+
+static int transmission_completed(int irq, void *context, FAR void *arg)
+{
+  enum avrdx_ws2812_lane_e lane;
+
+  /* Record the time - lane must not retransmit its data until
+   * T_reset period in the LEDs elapses.
+   */
+
+  lane = (enum avrdx_ws2812_lane_e) arg;
+  up_timer_gettime(g_avrdx_ws2812_xmit_ts[lane]);
+
+  /* Trigger next transmission from next lane. Or not if no lane is dirty,
+   * the function takes care of both cases.
+   */
+
+  trigger_transmission_hardware();
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: trigger_transmission_hardware
+ *
+ * Description:
+ *   Start transmission. If currently transmitting lane is not set, start
+ *   transmission from lowest dirty lane. (Dirty means lane that has new
+ *   data not transmitted to the LEDs.)
+ *
+ *   If it is set, transmission of that (currently transmitting) lane just
+ *   finished, find next dirty lane to transmit.
+ *
+ * Input Parameters: none
+ *
+ * Returned Value: void
+ *
+ * Assumptions/Limitations:
+ *   - may be running in interrupt context
+ *   - interrupts are disabled by the caller or by the (interrupt) context
+ *   - only called if there is no active transmission or if it
+ *     just finished
+ *
+ ****************************************************************************/
+
+static void trigger_transmission_hardware(void)
+{
+  uint8_t lane_idx;
+  avrdx_ws2812_bitmap_t lane_calc;
+
+  if (avrdx_ws2812_active_lane == 0)
+    {
+      /* No active lane - find first dirty */
+
+      if (avrdx_ws2812_dirty_lanes == 0)
+        {
+          /* This function is not supposed to be called
+           * under these conditions.
+           */
+
+          return;
+        }
+
+      /* Lowest lane in bitmap. (This expression finds lowest
+       * bit that is set in the value.)
+       */
+
+      avrdx_ws2812_active_lane = \
+          avrdx_ws2812_dirty_lanes & ((~avrdx_ws2812_dirty_lanes) + 1);
+    }
+  else
+    {
+      /* Active lane set means that some transmission finished.
+       * Move on to the next one.
+       */
+
+      if (avrdx_ws2812_dirty_lanes == 0)
+        {
+          /* There is no next one, everything is up-to-date */
+
+          avrdx_ws2812_active_lane = 0;
+          return;
+        }
+
+      while (1)
+        {
+          avrdx_ws2812_active_lane <<= 1;
+          if (avrdx_ws2812_active_lane == 0)
+            {
+              avrdx_ws2812_active_lane = 1; /* wrap */
+            }
+
+          if (avrdx_ws2812_active_lane & avrdx_ws2812_dirty_lanes)
+            {
+              /* Found dirty lane, transmit that */
+
+              break;
+            }
+        }
+    }
+
+  /* At this point the function either returned or the lane
+   * to be transmitted next is set in avrdx_ws2812_active_lane.
+   *
+   * Mark the lane as not dirty now. This will allow write calls to submit
+   * new data. But only after T_reset elapses, which the write must check.
+   */
+
+  avrdx_ws2812_dirty_lanes &= ~avrdx_ws2812_active_lane;
+
+  /* Convert active lane - bitmap - to index. Bitshifting to the right
+   * will eventually make the value 0 and that's the index we want.
+   * (Bitshift in the initialization compensates for the fact that
+   * value of 1 means lane_idx == 0 and the for loop must not run.)
+   */
+
+  lane_idx = 0;
+  for (lane_calc = avrdx_ws2812_active_lane >> 1; lane_calc; lane_calc >>= 1)
+    {
+      lane_idx++;
+    }
+
+  /* Reset byte counter */
+
+  g_avrdx_ws2812_xmit_next[lane_idx] = 0;
+
+  /* Hardware-specific action. Currently only UART */
+
+  if (lane_idx < AVRDX_WS2812_LANE_USART_SIZE)
+    {
+      AVRDX_USART(lane_idx).CTRLA |= USART_DREIE_bm;
+    }
+}
+
+/****************************************************************************
+ * Name: trigger_transmission
+ *
+ * Description:
+ *   Trigger transmission on a lane. Intended to be called after a write
+ *   (or other operation that changed pixel data.) Only flags the lane
+ *   data as dirty, then calls common method (if needed.)
+ *
+ * Input Parameters:
+ *   - lane - lane with dirty data to be written out
+ *
+ * Returned Value: void
+ *
+ * Assumptions/Limitations:
+ *   - it is assumed that the caller holds mutex but the implementation also
+ *     disables interrupts.
+ *   - this function may sleep - interrupts must not be disabled
+ *     by the caller
+ *
+ ****************************************************************************/
+
+static void trigger_transmission(enum avrdx_ws2812_lane_e lane)
+{
+  irqstate_t irqstate;
+  struct timespec ts;              /* Temporary value for calculations */
+  avrdx_ws2812_bitmap_t lane_bit;  /* Cache that avoids recomputing
+                                    * a bitshift */
+  uint32_t u32;                    /* Computation variables */
+  uint16_t u16;
+
+  /* Pointer to a lane timestamp. The compiler will re-read the value
+   * fetched from the g_avrdx_ws2812_xmit_ts array even if not declared
+   * volatile but let's make sure it does it.
+   */
+
+  volatile struct timespec *lane_xmit_ts;
+
+  /* Pointer to active lane value made volatile to ensure the compiler
+   * will re-read it on each loop pass (it did that in testing even
+   * without the volatile but again, let's make sure.)
+   */
+
+  volatile avrdx_ws2812_bitmap_t *active_lane;
+
+  irqstate = up_irq_save();
+
+  /* If this lane is currently being transmitted, it must not be marked
+   * dirty again - that would cause immediate retransmission. Instead, wait
+   * until it is not.
+   */
+
+  lane_bit = (1 << lane);
+  active_lane = &avrdx_ws2812_active_lane;
+  for (; (*active_lane) & lane_bit; )
+    {
+      /* Sleep for how long? 1.25us per bit means 30us per 24bits.
+       * For a millisecond wait, we need 34 LEDs at least. (Ignoring LEDs
+       * with white component.) Just use 2ms and let the loop handle the
+       * rest.
+       */
+
+      nxsched_usleep(2);
+    }
+
+  /* Now we know the lane is currently not being transmitted. But we must
+   * block the trigger if the transmission happened recently and T_reset
+   * period is in progress
+   */
+
+  lane_xmit_ts = g_avrdx_ws2812_xmit_ts[lane];
+  for (; ; )
+    {
+      up_timer_gettime(&ts);
+      clock_timespec_subtract(&ts, lane_xmit_ts, &ts);
+
+      /* Reset time is a microsecond value, if a second elapsed,
+       * there is no wait.
+       */
+
+      if (ts.tv_sec > 0)
+        {
+          break;
+        }
+
+      /* tv_nsec should be positive, current time minus past,
+       * tell this to the compiler.
+       */
+
+      u32 = (ts.tv_nsec < 0) ? 0 : ts.tv_nsec;
+
+      /* Is this value higher than WS2812_T_RESET? Value of WS2812_T_RESET
+       * is chosen to have 2 least significant bytes set to 0, simplify
+       * the comparison using this knowledge.
+       */
+
+#if (WS2812_T_RESET & 0xffff)
+#  error Value of WS2812_T_RESET does not meet expectations here
+#endif
+
+      u16 = u32 >> 16;
+      if (u16 > (WS2812_T_RESET >> 16))
+        {
+          break;
+        }
+
+      /* Alright, we will be waiting. For how long?
+       *
+       * tv_nsec (u32) is a nanosecond value but we will call usleep
+       * with a microsecond parameter. We can obtain the latter
+       * from the former by dividing it by 1000. We do not like such
+       * solution though because it does a division.
+       *
+       * So, we just bitshift u16 6 bits to the left (u16 is value
+       * of tv_nsec bitshifted by 16 to the right.) That gives us
+       * tv_nsec >> 10 and if we accept that a microsecond is 1024
+       * nanoseconds long, we are done. Or at least close to done,
+       * lower 6 bits of the resulting value are truncated and we need
+       * to compensate for that. Add 1 << 6 - waiting for more time
+       * is fine.
+       *
+       * Also - make sure that WS2812_T_RESET is set to a 24 bit value,
+       * all of this depends on that. (The next check is even more
+       * strict than that.)
+       */
+
+#if WS2812_T_RESET > 0x7ffff
+#  error This code assumes WS2812_T_RESET to be 5 * 65536
+#endif
+
+      nxsched_usleep((u16 << 6) + (1 << 6));
+    }
+
+  avrdx_ws2812_dirty_lanes |= lane_bit;
+
+  if (avrdx_ws2812_active_lane == 0)
+    {
+      /* No outgoing transmission currently, start it for this lane. */
+
+      trigger_transmission_hardware();
+    }
+
+  up_irq_restore(irqstate);
+}
+
+/****************************************************************************
+ * Name: store_uart_bytes
+ *
+ * Description:
+ *   Receives data from write syscall and encodes them into bytes
+ *   to be transmitted through the UART.
+ *
+ * Input Parameters:
+ *   - lane - lane which is being written to
+ *   - data - pointer to uint32_t array of RGB data
+ *   - offset - first LED to change with data, given in items
+ *   - len - data length in items
+ *   - has_white - set if the lane has LEDs with white component,
+ *     ignored if CONFIG_WS2812_HAS_WHITE is not set
+ *
+ * Returned Value: void
+ *
+ * Assumptions/Limitations:
+ *   - len is truncated so offset+len does not exceed the data buffer
+ *
+ ****************************************************************************/
+
+static void store_uart_bytes(enum avrdx_ws2812_lane_e lane,
+                             uint32_t *data,
+                             uint8_t offset,
+                             uint8_t len,
+                             bool has_white)
+{
+#ifdef CONFIG_WS2812_HAS_WHITE
+
+  uint8_t *uart_bytes = g_avrdx_ws2812_xmit_bytes[lane];
+  uint16_t temp_u16;
+  uint8_t temp_u8;
+  uint8_t i;
+
+  /* UART lane with support for white component. */
+
+  if (has_white)
+    {
+      /* Data is received as WWRRGGBB, the LED wants GGRRBBWW,
+       * need to keep that in mind.
+       */
+
+      uart_bytes += WS2812_BYTES_PER_LED_WITH_WHITE * offset;
+
+      for (; len; len--)
+        {
+          /* Little endian architecture, green is stored in byte +1,
+           * LED wants it first. The compiler should pick the byte we
+           * want without bitshifting.
+           *
+           * (From disassembly: no bitshifting but all four bytes
+           * are loaded from memory. Can probably be improved
+           * using some pointers and type casts.)
+           */
+
+#  ifdef CONFIG_ENDIAN_BIG
+#    error AVR is not big endian
+#  endif
+          temp_u8 = ((*data) & 0xff00) >> 8;
+          temp_u16 = temp_u8;
+          for (i = 4; i; i--)
+            {
+              /* Except this bitshift with temp_u16, the compiler
+               * recognizes that the rest can be done
+               * without shifting. Saves CPU time.
+               */
+
+              temp_u16 <<= 2;
+              *uart_bytes = \
+                  avrdx_ws2812_uart_white[(temp_u16 & 0x0300) >> 8];
+              uart_bytes++;
+            }
+
+          /* Red now. That one is at byte +2. The compiler only reads
+           * one byte from the memory... but gets the address from base
+           * value - 4 + 2 (sbiw, adiw)
+           */
+
+          temp_u8 = ((*data) & 0xff0000) >> 16;
+          temp_u16 = temp_u8;
+          for (i = 4; i; i--)
+            {
+              temp_u16 <<= 2;
+              *uart_bytes = \
+                  avrdx_ws2812_uart_white[(temp_u16 & 0x0300) >> 8];
+              uart_bytes++;
+            }
+
+          /* Blue - byte 0 */
+
+          temp_u8 = (*data) & 0xff;
+          temp_u16 = temp_u8;
+          for (i = 4; i; i--)
+            {
+              temp_u16 <<= 2;
+              *uart_bytes = \
+                  avrdx_ws2812_uart_white[(temp_u16 & 0x0300) >> 8];
+              uart_bytes++;
+            }
+
+          /* And white at byte + 3. Compiler still does not use bitshifts
+           * but it loads all bytes and then resets 3 of them
+           * (in registers) to zero.
+           */
+
+          temp_u8 = ((*data) & 0xff000000) >> 24;
+          temp_u16 = temp_u8;
+          for (i = 4; i; i--)
+            {
+              temp_u16 <<= 2;
+              *uart_bytes = \
+                  avrdx_ws2812_uart_white[(temp_u16 & 0x0300) >> 8];
+              uart_bytes++;
+            }
+
+          /* Done with this pixel, move onto the next one */
+
+          data++;
+        }
+    }
+  else
+#endif /* ifdef CONFIG_WS2812_HAS_WHITE */
+    {
+      /* No support for LEDs with white component or this lane
+       * does not have those. Conversion from RGB data to UART
+       * bytes is going to take a lot of bitshift and similar,
+       * we do this in assembly to save CPU time.
+       *
+       * Note - this method expects len to be non-zero.
+       */
+
+      if (len > 0)
+        {
+          avrdx_ws2812_store_uart_bytes_rgb(lane, data, offset, len);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: avrdx_ws2812_open
+ *
+ * Description:
+ *   Character device open handler. We do not track open count so it does
+ *   nothing. Must exist though, upper half does not check it for NULL.
+ *
+ * Input Parameters:
+ *   filep - struct file pointer
+ *
+ * Returned Value: OK
+ *
+ ****************************************************************************/
+
+static int avrdx_ws2812_open(struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: avrdx_ws2812_close
+ *
+ * Description:
+ *   Character device close handler. Upper half does check for NULL
+ *   pointer here but we just return OK so let's have it.
+ *
+ * Input Parameters:
+ *   filep - struct file pointer
+ *
+ * Returned Value:
+ *   OK
+ *
+ ****************************************************************************/
+
+static int avrdx_ws2812_close(struct file *filep)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Name: avrdx_ws2812_write
+ *
+ * Description:
+ *   Writes RGB data to pixel buffer (unless configured not to do so)
+ *   and to the transmit buffer.
+ *
+ * Input Parameters:
+ *   filep - struct file pointer
+ *   data - pointer to buffer with data to be written
+ *   len - size of the buffer
+ *
+ * Returned Value:
+ *   Number of bytes written.
+ *
+ * Assumptions/Limitations:
+ *   - value of len was checked by the caller to be multiple
+ *     of WS2812_RW_PIXEL_SIZE.
+ *   - value of data was checked by the call chain and is not NULL
+ *     (fs/vfs/fs_read.c does that in file_readv())
+ *   - same for len != 0 (done by fs/vfs/fs_read.c in file_readv_compat())
+ *
+ ****************************************************************************/
+
+static ssize_t avrdx_ws2812_write(struct file *filep,
+                                  const char *data, size_t len)
+{
+  struct ws2812_dev_s *dev_data;
+  struct avrdx_ws2812_s *priv_data;
+  uint32_t *data_u32;
+  size_t pos;
+
+  data_u32 = (uint32_t *) data;
+  dev_data = filep->f_inode->i_private;
+  priv_data = dev_data->private;
+  nxmutex_lock(&dev_data->lock);
+
+  pos = filep->f_pos;
+
+  /* Restrict len to not go beyond the buffer. */
+
+  if (len + pos > dev_data->nleds * WS2812_RW_PIXEL_SIZE)
+    {
+      len = dev_data->nleds * WS2812_RW_PIXEL_SIZE - pos;
+    }
+
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  /* Write a copy to the pixel buffer */
+
+  memcpy(((uint8_t *) priv_data->pixel_buffer) + pos, data, len);
+
+#endif
+
+  store_uart_bytes(priv_data->lane, data_u32,
+                   pos >> 2, len >> 2,
+                   dev_data->has_white);
+
+  pos += len;
+  filep->f_pos = pos;
+
+  trigger_transmission(priv_data->lane);
+
+  nxmutex_unlock(&dev_data->lock);
+
+  return len;
+}
+
+/****************************************************************************
+ * Name: avrdx_ws2812_read
+ *
+ * Description:
+ *   Reads data from pixel buffer to provided pointer and returns them.
+ *   Not compiled if CONFIG_AVR_AVRDX_WS2812_NO_READS is set.
+ *
+ * Input Parameters:
+ *   filep - struct file pointer
+ *   data - pointer to buffer which receives read data
+ *   len - size of the buffer
+ *
+ * Returned Value:
+ *   Number of bytes read.
+ *
+ * Assumptions/Limitations:
+ *   Identical to avrdx_ws2812_write
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+
+static ssize_t avrdx_ws2812_read(struct file *filep,
+                                 char *data, size_t len)
+{
+  struct ws2812_dev_s   *dev_data;
+  struct avrdx_ws2812_s *priv_data;
+  size_t pos;
+
+  dev_data = filep->f_inode->i_private;
+  priv_data = dev_data->private;
+
+  nxmutex_lock(&dev_data->lock);
+
+  pos = filep->f_pos;
+
+  /* Restrict len to not go beyond the buffer. */
+
+  if (len + pos > dev_data->nleds * WS2812_RW_PIXEL_SIZE)
+    {
+      len = dev_data->nleds * WS2812_RW_PIXEL_SIZE - pos;
+    }
+
+  memcpy(data, ((uint8_t *) priv_data->pixel_buffer) + pos, len);
+  pos += len;
+  filep->f_pos = pos;
+
+  nxmutex_unlock(&dev_data->lock);
+
+  return len;
+}
+
+#endif /* ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: avrdx_ws2812_register
+ *
+ * Description:
+ *   Initializer for one WS2812 lane. Lane is a set of WS2812 LEDs
+ *   connected serially and driven from single hardware device
+ *   on the MCU side.
+ *
+ *   Note that this function triggers transmission of its freshly
+ *   allocated and zeroed buffer, thus resetting the LEDs
+ *   and turning them all of. Since the function does not know
+ *   if it runs after a power-on or after a reset (and, therefore,
+ *   how much time elapsed from last transmission), it will simply
+ *   wait.
+ *
+ * Input Parameters:
+ *   path - device path for this LED lane
+ *   count - LED count. Limited to 255 regardless of upper half setting
+ *           (The device does not have memory for more.)
+ *   lane - enum avrdx_ws2812_lane_e value. Selects peripheral to which
+ *          are the LEDs connected on the board
+ *   has_white - bool. True if the lane has LEDs with white component.
+ *               Ignored if CONFIG_WS2812_HAS_WHITE is unset. (Or if
+ *               CONFIG_AVR_AVRDX_WS2812_NO_WHITE_LED is set.)
+ *
+ * Returned Value:
+ *   OK or negated errno.
+ *   ENOMEM - if the LED count exceeds limit (in addition to memory
+ *            allocation failure.)
+ *   ENOTSUP - if support for white LED is requested and not disabled
+ *             (see #warning at the beginning of the file or description
+ *             of AVR_AVRDX_WS2812_NO_WHITE_LED configuration option)
+ *   EINVAL - count requests more LEDs in chain than is supported
+ *
+ * Assumptions/Limitations:
+ *   Running in a context which allows sleeping.
+ *
+ ****************************************************************************/
+
+int avrdx_ws2812_register(const char *path, uint8_t count,
+                          enum avrdx_ws2812_lane_e lane,
+                          bool has_white)
+{
+  int ret = OK;
+  struct ws2812_dev_s   *dev_data = NULL;
+  struct avrdx_ws2812_s *priv_data = NULL;
+  avr_usart_t *usart;
+  uint16_t lane_bytes;
+  volatile uint8_t *portmux_reg;
+  struct timespec ts;
+
+#ifdef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  uint32_t *temp_pixel_buffer = NULL;
+#endif
+
+  if (count > WS2812_MAX_LED_COUNT)
+    {
+      return -EINVAL;
+    }
+
+  /* Deal with reset time (T_reset) of the LEDs. We do not know if this
+   * code runs during a power-up or after a reset. For the latter case,
+   * we have no way of knowing what was happening before the reset
+   * and if the T_reset period of the LEDs already expired (if any
+   * transmission was going out just when the MCU restarted.)
+   *
+   * This function will trigger a transmission to LEDs (to turn them
+   * off in case this is a reset and they have previous values)
+   * so it needs to make sure T_reset did expire.
+   *
+   * Datasheet says T_reset = 280us, we use 5 * 65536ns to save few
+   * program memory bytes. T_reset did expire if our uptime is greater.
+   */
+
+  up_timer_gettime(&ts);
+
+  if ((ts.tv_sec == 0) && (ts.tv_nsec < WS2812_T_RESET))
+    {
+      nxsched_usleep(WS2812_T_RESET - ts.tv_nsec);
+    }
+
+  /* * * * * * * Start of hardware-specific block * * * * * * *
+   * * * * * * * * * * * * * *  * * * * * * * * * * * * * * * */
+
+  /* * * * * * * Start of UART-specific block     * * * * * * */
+
+#ifdef AVRDX_WS2812_LANE_ANY_USART
+  if (
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART0
+      (lane == AVRDX_WS2812_LANE_USART0) ||
+#  endif
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART1
+      (lane == AVRDX_WS2812_LANE_USART1) ||
+#  endif
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART2
+      (lane == AVRDX_WS2812_LANE_USART2) ||
+#  endif
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART3
+      (lane == AVRDX_WS2812_LANE_USART3) ||
+#  endif
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART4
+      (lane == AVRDX_WS2812_LANE_USART4) ||
+#  endif
+#  ifdef CONFIG_AVR_AVRDX_WS2812_USART5
+      (lane == AVRDX_WS2812_LANE_USART5) ||
+#  endif
+      0)
+    {
+      /* This is an UART lane. Configure the hardware. This code
+       * is mostly a copy of what serial driver does. (PORTMUX_USARTROUTEB
+       * is a macro defined in io.h if the MCU has more than 4 UARTs.)
+       */
+
+#  ifdef PORTMUX_USARTROUTEB
+      if (lane < 4)
+#  endif
+        {
+          portmux_reg = &(PORTMUX.USARTROUTEA);
+        }
+#  ifdef PORTMUX_USARTROUTEB
+      else
+        {
+          portmux_reg = &(PORTMUX.USARTROUTEB);
+        }
+#  endif
+
+      /* In theory, only the kernel should manipulate this register
+       * and the value written is static (chosen by configuration.)
+       * That means there should be no need to reset it. Still doing it,
+       * runaway application may do accidental writes.
+       */
+
+      *(portmux_reg) = \
+        (\
+          *(portmux_reg) & \
+          ~(avrdx_usart_portmux_masks[lane]) \
+        ) | \
+        avrdx_usart_portmux_bits[lane];
+
+      /* Difference from serial driver - we must enable the pin
+       * inverter (and we must do it before the pin switches to output.)
+       */
+
+      AVRDX_USART_PORT(lane).PINCONFIG = PORT_INVEN_bm;
+      AVRDX_USART_PORT(lane).PINCTRLUPD = \
+        avrdx_usart_tx_pins[lane];
+
+      /* AVR128DA28/32/48/64 datasheet rev C, section 25.3.1 lists
+       * changing the pin direction before activating the device itself
+       * but that leads to short zero pulse. Change output value to serial
+       * line idle state to prevent that.
+       */
+
+      AVRDX_USART_PORT(lane).OUTSET = \
+        avrdx_usart_tx_pins[lane];
+      AVRDX_USART_PORT(lane).DIRSET = \
+        avrdx_usart_tx_pins[lane];
+
+      usart = &(AVRDX_USART(lane));
+      usart->BAUD = 64;
+      usart->CTRLB = (USART_RXMODE_CLK2X_GC | USART_TXEN_bm);
+
+      /* Default configuration of CTRLC is acceptable for LEDs
+       * with white component; the opposite case is handled
+       * below - amends the configuration.
+       */
+
+#  ifdef CONFIG_WS2812_HAS_WHITE
+
+      /* UART lane with support for white component */
+
+      if (has_white)
+        {
+          return -ENOTSUP;
+
+#    ifdef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+
+          /* Has white component and uint8_t index is enforced.
+           * 255 (UINT8_MAX) / 12 (bytes per LED) = 21 LEDs
+           */
+
+          if (count > (UINT8_MAX / WS2812_BYTES_PER_LED_WITH_WHITE))
+            {
+              ret = -EINVAL;
+              goto errout;
+            }
+#    endif
+
+          lane_bytes = count * WS2812_BYTES_PER_LED_WITH_WHITE;
+
+          /* Top bit in this variable serves as a signal for the ISR
+           * that the LED has white component and 9th UART data bit
+           * is not used.
+           */
+
+          g_avrdx_ws2812_uart9_current[lane] = 0xff;
+        }
+      else
+#  endif /* ifdef CONFIG_WS2812_HAS_WHITE*/
+        {
+          /* Does not have white component or white components
+           * not enabled at all.
+           */
+
+#  ifdef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+
+          /* 255 (UINT8_MAX) / 8 (bytes per LED ) = 31 LEDs */
+
+          if (count > (UINT8_MAX / WS2812_BYTES_PER_LED_NO_WHITE))
+            {
+              ret = -EINVAL;
+              goto errout;
+            }
+#  endif
+
+          lane_bytes = count * WS2812_BYTES_PER_LED_NO_WHITE;
+
+          /* Will be populated in the ISR but must not have top bit
+           * set when the ISR is entered. That would be interpreted
+           * as a signal that 9th UART data bit is not in use.
+           * If CONFIG_WS2812_HAS_WHITE is unset, 9th UART bit is
+           * always used, the check in the ISR is skipped and we
+           * don't need to do this.
+           */
+
+#  ifdef CONFIG_WS2812_HAS_WHITE
+          g_avrdx_ws2812_uart9_current[lane] = 0;
+#  endif
+
+          /* Allocate array for 9th bits. Each LED needs exactly
+           * one byte (24 bits packed into groups of 3, each group
+           * needs one 9th bit.)
+           */
+
+          g_avrdx_ws2812_uart9_bytes[lane] = (uint8_t *) kmm_zalloc(count);
+          if (g_avrdx_ws2812_uart9_bytes[lane] == NULL)
+            {
+              ret = -ENOMEM;
+              goto errout;
+            }
+
+          /* Initialize the hardware - only the 9th bit */
+
+          usart->CTRLC = USART_PMODE_EVEN_GC \
+              | USART_CHSIZE_9BITH_GC;
+        }
+    }
+  else
+
+  /* When support for other hardware is added, we want this to continue
+   * with else if. Doing it like this gives us an unterminated else though,
+   * needs to be taken care of at the end of the hardware-specific section.
+   */
+
+  /* * * * * * * End of UART-specific block       * * * * * * */
+
+#else
+
+  /* No UART lane - since to other lane type exists, that's an error.
+   * Should not happen though, checked elsewhere.
+   */
+
+#  error No lane selected
+#endif
+    {
+      /* Every hardware-specific block ends with else to allow
+       * the next block to start with if, forming else if.
+       * This empty block executes after the last else.
+       *
+       * The compiler is unable to recognize that the list of enums
+       * in the condition above is exhaustive and complaints (not always!)
+       * that lane_bytes may be used uninitialized. Silence it.
+       */
+
+      lane_bytes = 0;
+    }
+
+  g_avrdx_ws2812_xmit_bytes[lane] = (uint8_t *) kmm_zalloc(lane_bytes);
+  if (g_avrdx_ws2812_xmit_bytes[lane] == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+  g_avrdx_ws2812_xmit_count[lane] = lane_bytes;
+  g_avrdx_ws2812_xmit_next[lane] = 0;
+
+  g_avrdx_ws2812_xmit_ts[lane] = \
+      (struct timespec *) kmm_zalloc(sizeof(struct timespec));
+  if (g_avrdx_ws2812_xmit_ts[lane] == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+  dev_data = (struct ws2812_dev_s *) kmm_zalloc(sizeof(struct ws2812_dev_s));
+  if (dev_data == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+  priv_data = \
+      (struct avrdx_ws2812_s *) kmm_zalloc(sizeof(struct avrdx_ws2812_s));
+  if (priv_data == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  priv_data->pixel_buffer = (uint32_t *) \
+      kmm_zalloc(sizeof(uint32_t) * count);
+  if (priv_data->pixel_buffer == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+  /* #else - pixel_buffer member is not part of the struct,
+   * and it would be set to zero by kmm_zalloc above anyway
+   */
+#endif
+
+  priv_data->lane = lane;
+
+  nxmutex_init(&dev_data->lock);
+
+  /* File operation handlers */
+
+  dev_data->open = avrdx_ws2812_open;
+  dev_data->close = avrdx_ws2812_close;
+  dev_data->write = avrdx_ws2812_write;
+
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  dev_data->read = avrdx_ws2812_read;
+
+  /* #else - initialized to NULL by kmm_zalloc */
+#endif
+
+  /* Other data */
+
+  dev_data->private = priv_data;
+  dev_data->clock = 0;            /* not explained
+                                   * in include/nuttx/leds/ws2812.h,
+                                   * not present in drivers/leds/ws2812.c,
+                                   * set to zero and see what breaks.
+                                   */
+  dev_data->port = 0;             /* same here */
+  dev_data->nleds = count;
+
+#ifdef CONFIG_WS2812_HAS_WHITE
+  dev_data->has_white = has_white;
+
+  /* #else - left unset. The struct is allocated with kmm_zalloc
+   * so FALSE is already set
+   */
+#endif
+
+  g_avrdx_ws2812_swint_vector[lane] = \
+    avrdx_swint_allocate(transmission_completed, (void *)lane);
+  if (g_avrdx_ws2812_swint_vector[lane] == 0)
+    {
+      goto errout; /* No vector available */
+    }
+
+  /* xmit_bytes are initialized to zero but we can't just put zero bytes
+   * into the buffer - that value is not interpreted as zero by the LED.
+   * Instead, we populate the buffer as if it was written by userspace.
+   */
+
+#ifdef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  /* No reads - no pixel buffer. We need to allocate a temporary one */
+
+  temp_pixel_buffer = (uint32_t *) kmm_zalloc(sizeof(uint32_t) * count);
+  if (temp_pixel_buffer == NULL)
+    {
+      ret = -ENOMEM;
+      goto errout;
+    }
+
+  store_uart_bytes(lane, temp_pixel_buffer, 0, count, has_white);
+  kmm_free(temp_pixel_buffer);
+#else
+  store_uart_bytes(lane, priv_data->pixel_buffer, 0, count, has_white);
+#endif
+
+  ret = ws2812_register(path, dev_data);
+  if (ret < 0)
+    {
+      goto errout;
+    }
+
+  trigger_transmission(lane);
+
+  return ret;
+
+errout:
+
+  /* Free memory that was allocated, preferably in reverse order. */
+
+#ifdef CONFIG_AVR_AVRDX_WS2812_NO_READS
+  if (temp_pixel_buffer)
+    {
+      kmm_free(temp_pixel_buffer);
+    }
+#endif
+
+  if (priv_data)
+    {
+#ifndef CONFIG_AVR_AVRDX_WS2812_NO_READS
+      if (priv_data->pixel_buffer)
+        {
+          kmm_free(priv_data->pixel_buffer);
+        }
+
+#endif
+      kmm_free(priv_data);
+    }
+
+  if (dev_data)
+    {
+      kmm_free(dev_data);
+    }
+
+#ifdef AVRDX_WS2812_LANE_ANY_USART
+  if (g_avrdx_ws2812_uart9_bytes[lane])
+    {
+      kmm_free(g_avrdx_ws2812_uart9_bytes[lane]);
+    }
+#endif
+
+  if (g_avrdx_ws2812_xmit_ts[lane])
+    {
+      kmm_free(g_avrdx_ws2812_xmit_ts[lane]);
+    }
+
+  if (g_avrdx_ws2812_xmit_bytes[lane])
+    {
+      kmm_free(g_avrdx_ws2812_xmit_bytes[lane]);
+    }
+
+  return ret;
+}
diff --git a/arch/avr/src/avrdx/avrdx_ws2812.h 
b/arch/avr/src/avrdx/avrdx_ws2812.h
new file mode 100644
index 00000000000..2aec6bb051b
--- /dev/null
+++ b/arch/avr/src/avrdx/avrdx_ws2812.h
@@ -0,0 +1,174 @@
+/****************************************************************************
+ * arch/avr/src/avrdx/avrdx_ws2812.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_AVR_SRC_AVRDX_AVRDX_WS2812_H
+#define __ARCH_AVR_SRC_AVRDX_AVRDX_WS2812_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <assert.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* Lane size 1 is guaranteed, others are not. (That's why first #if
+ * does not check the size but others do.) Each lane has a static
+ * value (or is not defined if not enabled.)
+ *
+ * For future edits: lane numbers MUST NOT be redefined, the code uses
+ * the numeric values for various decisions, offset calculations etc.
+ *
+ * This enum is internally converted to a pointer, its size must fit
+ * pointer size. Asserted below the definition.
+ */
+
+enum avrdx_ws2812_lane_e
+{
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART0
+  AVRDX_WS2812_LANE_USART0 = 0,
+#endif
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART1
+  AVRDX_WS2812_LANE_USART1 = 1,
+#endif
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART2
+  AVRDX_WS2812_LANE_USART2 = 2,
+#endif
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART3
+  AVRDX_WS2812_LANE_USART3 = 3,
+#endif
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART4
+  AVRDX_WS2812_LANE_USART4 = 4,
+#endif
+#ifdef CONFIG_AVR_AVRDX_WS2812_USART5
+  AVRDX_WS2812_LANE_USART5 = 5
+#endif
+};
+
+static_assert(sizeof(enum avrdx_ws2812_lane_e) <= sizeof(FAR void *),
+              "Lane enum does not fit into pointer size.");
+
+#ifndef __ASSEMBLY__
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/****************************************************************************
+ * Inline Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: <Inline function name>
+ *
+ * Description:
+ *   Description of the operation of the inline function.
+ *
+ * Input Parameters:
+ *   A list of input parameters, one-per-line, appears here along with a
+ *   description of each input parameter.
+ *
+ * Returned Value:
+ *   Description of the value returned by this function (if any),
+ *   including an enumeration of all possible error values.
+ *
+ * Assumptions/Limitations:
+ *   Anything else that one might need to know to use this function.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: <Global function name>
+ *
+ * Description:
+ *   Description of the operation of the function.
+ *
+ * Input Parameters:
+ *   A list of input parameters, one-per-line, appears here along with a
+ *   description of each input parameter.
+ *
+ * Returned Value:
+ *   Description of the value returned by this function (if any),
+ *   including an enumeration of all possible error values.
+ *
+ * Assumptions/Limitations:
+ *   Anything else that one might need to know to use this function.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: avrdx_ws2812_register
+ *
+ *   Initializer for one WS2812 lane. Lane is a set of WS2812 LEDs
+ *   connected serially and driven from single hardware device
+ *   on the MCU side.
+ *
+ *   Note that this function triggers transmission of its freshly
+ *   allocated and zeroed buffer, thus resetting the LEDs
+ *   and turning them all of. Since the function does not know
+ *   if it runs after a power-on or after a reset (and, therefore,
+ *   how much time elapsed from last transmission), it will simply
+ *   wait.
+ *
+ * Input Parameters:
+ *   path - device path for this LED lane
+ *   count - LED count. Limited to 255 regardless of upper half setting
+ *           (The device does not have memory for more.)
+ *   lane - enum avrdx_ws2812_lane_e value. Selects peripheral to which
+ *          are the LEDs connected on the board
+ *   has_white - bool. True if the lane has LEDs with white component.
+ *               Ignored if CONFIG_WS2812_HAS_WHITE is unset.
+ *
+ * Returned Value:
+ *   OK or negated errno.
+ *   ENOMEM - if the LED count exceeds limit (in addition to memory
+ *            allocation failure.)
+ *
+ ****************************************************************************/
+
+int avrdx_ws2812_register(const char *path, uint8_t count,
+                          enum avrdx_ws2812_lane_e lane,
+                          bool has_white);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+
+#endif
diff --git a/arch/avr/src/avrdx/avrdx_ws2812_uart_store.S 
b/arch/avr/src/avrdx/avrdx_ws2812_uart_store.S
new file mode 100644
index 00000000000..4fbe7bd816e
--- /dev/null
+++ b/arch/avr/src/avrdx/avrdx_ws2812_uart_store.S
@@ -0,0 +1,280 @@
+/****************************************************************************
+ * arch/avr/src/avrdx/avrdx_ws2812_uart_store.S
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#if (defined CONFIG_WS2812_HAS_WHITE) && \
+    (defined CONFIG_AVR_AVRDX_WS2812_NO_WHITE_LED)
+#  undef CONFIG_WS2812_HAS_WHITE
+#endif
+
+/****************************************************************************
+ * External Symbols
+ ****************************************************************************/
+
+       .file   "avrdx_ws2812_uart_store.S"
+
+/****************************************************************************
+ * Name: avrdx_ws2812_store_uart_bytes_rgb
+ *
+ * Description:
+ *   Stores given RGB data (with offset and length limited to len
+ *   to UART transmit buffer, encoded for direct transmission to WS2812
+ *   LED.)
+ *
+ * Input Parameters:
+ *   lane - enum avrdx_ws2812_lane_e, interpreted as uint8_t
+ *          (register r24)
+ *   data - pointer to uint32_t array with RGB data (register r22:r23)
+ *   offset - this is the first pixel to be written. LED count
+ *            is limited to 255, data type is uint8_t (register r20)
+ *   length - length of data array, same as above for type (register r18)
+ *
+ * Return value: void
+ *
+ * Assumptions/Limitations:
+ *   - parameters are checked by the call chain (VFS) and len is not zero
+ *
+ ****************************************************************************/
+
+       .global avrdx_ws2812_store_uart_bytes_rgb
+
+avrdx_ws2812_store_uart_bytes_rgb:
+
+       ; Register usage
+       ; r1      - zero register (part of ABI, no need to clear it first)
+       ;
+       ; In prologue
+       ; (Mostly just storing data, not doing anything with it. Treat
+       ; this list as free-for-use/do-not-touch.)
+       ;
+       ; Do not touch
+       ; r2      - copy of length
+       ; r22:r23 - pointer to destination data
+       ; r24:r25 - pointer to destination data
+       ; r28:r29 - pointer to source data
+       ;
+       ; Free for use (when, if not always)
+       ; r20     - function argument (after prologue ends)
+       ; r24     - function argument (after prologue ends)
+       ; Z       - temporary pointer
+       ;
+       ; In main loop
+       ; r2      - copy of length, decremented after each pass, return
+       ;           when reduced to zero
+       ; r18     - current pixel component
+       ; r19     - index register for UART byte
+       ; r20     - temporary byte - do not use for anything that takes
+       ;           more than few lines of assembly
+       ; r21     - working copy of 9th bits (value assembled here, then
+       ;           stored when all 8 bits are ready.)
+       ; r22:r23 - pointer to g_avrdx_ws2812_uart9_bytes, ie. to current
+       ;           byte in array for 9th bits.
+       ; r24:r25 - pointer to current byte in UART transmit buffer.
+       ;           The pointer is usually stored in X but if X needs
+       ;           to address something else, copy is stored here.
+       ; r26:r27 - pointer to one of the transmit buffers. Code uses
+       ;           XL:XH notation.
+       ; r28:r29 - pointer to source data. Code uses YL:YH notation
+       ; r30:r31 - pointer to UART bytes. Code uses ZL:ZH notation
+       ;
+
+       ; prologue
+       push r2
+       push YL
+       push YH
+
+       ; move len to r2 to keep registers with more functionality free
+       mov r2, r18
+
+       ; Y stores pointer to source data and is not used for anything
+       ; else, free r22:r23 for other use (see above)
+       movw YL, r22
+
+       ; load address of current byte in 9th bits transmit buffer
+       ; we start with this one because this it doesn't need offset
+       ; multiplied.
+       ;
+       ; This is an array of pointers
+       ldi ZL, lo8(g_avrdx_ws2812_uart9_bytes)
+       ldi ZH, hi8(g_avrdx_ws2812_uart9_bytes)
+
+       ; add lane number to select the correct pointer;
+       ; (pointer is 16 bit, add twice)
+       add ZL, r24   ; r24 lane function argument, keep, still needed
+       adc ZH, r1
+       add ZL, r24
+       adc ZH, r1
+
+       ; Z now points to the address of UART buffer for 9th bits.
+       ; Load the address to r22:r23.
+       ld r22, Z
+       ldd r23, Z+1
+
+       ; apply offset - 1 byte per offset in this array
+       add r22, r20  ; r20 offset function argument, keep, still needed
+       adc r23, r1   ; max 255 LEDs, high byte is zero
+
+       ; now load address of current byte of regular transmit buffer
+       ldi ZL, lo8(g_avrdx_ws2812_xmit_bytes)
+       ldi ZH, hi8(g_avrdx_ws2812_xmit_bytes)
+
+       ; similar as above, add lane number, load the address
+       add ZL, r24
+       adc ZH, r1
+       add ZL, r24   ; lane number no longer needed
+       adc ZH, r1
+       ld r24, Z
+       ldd r25, Z+1
+
+       ; apply offset - this array has 8 bytes per offset
+       ; we can clobber the offset, do not need it after this
+       ;
+       ; note - compiler tends to do additions (bitshifts)
+       ; when multiplying by constant but multiplying is faster
+       ; here (6 instructions instead of 7)
+       ldi r21, 8
+       mul r21, r20
+       add r24, r0
+       adc r25, r1
+       eor r1, r1    ; ABI - r1 is zero
+
+       ; Z points to UART bytes (ie. precalculated values that encode
+       ; pixel data into bytes that are transmitted by UART.)
+       ;
+       ; In case you are wondering: yes, values in this array indeed
+       ; have incorrect bit 7. We will deal with that on load.
+       ldi ZL, lo8(avrdx_ws2812_uart_white)
+       ldi ZH, hi8(avrdx_ws2812_uart_white)
+
+       ; store g_avrdx_ws2812_xmit_bytes pointer here, that's what
+       ; is needed most of the time
+       movw XL, r24
+       ; initialization phase done
+
+1:
+       ; green component is first in the UART data (ie. LED wants it
+       ; first, MSB first) but not first in xxRRGGBB pixel
+       ldd r18, Y+1
+       ; load UART byte for G7 G6 LED bits
+       rcall 8f
+       ; G5 goes to 9th bits. note that r21 is not cleared.
+       ; 8 bits will be shifted to it so no reason to do that.
+       lsl r18
+       ror r21       ; counting bits shifted in: 1
+       ; same for G4 G3 G2
+       rcall 8f
+       lsl r18
+       ror r21       ; 2 bits in
+       ; G1 G0 can be dealt with using the function too
+       rcall 8f
+       ; need the next byte now though; the LED wants red
+       ldd r18, Y+2
+       lsl r18
+       ror r21       ; 3 bits in
+       ; that was R7, R6-R4 now
+       rcall 8f
+       lsl r18
+       ror r21       ; 4 bits in
+       ; and R3-R1
+       rcall 8f
+       lsl r18
+       ror r21       ; 5 bits in
+       ; need R0 and B7 for the function call. Manually doing
+       ; what the function does otherwise. We skip that part
+       ; of the function but can use the rest
+       lsl r18
+       rol r19
+       ld r18, Y
+       lsl r18
+       rol r19
+       ; note - skipping the part that sets r19
+       rcall 7f
+       ; B6 goes to the 9th bits byte
+       lsl r18
+       ror r21       ; 6 bits in
+       ; B5-B3 the usual way
+       rcall 8f
+       lsl r18
+       ror r21       ; 7 bits in
+       ; same for B2-B0
+       rcall 8f
+       lsl r18
+       ror r21       ; 8 bits in
+       ; we are done with the 9th bits but they are inverted
+       ; (0 in data is now 0 in r21 but requires short pulse,
+       ; ie. log.1 parity bit.) fix by inverting
+       com r21
+
+       ; store the pointer for g_avrdx_ws2812_xmit_bytes, need X
+       ; register usable
+       movw r24, XL
+       movw XL, r22
+       ; store 9th bits byte to the new X
+       st X+, r21
+       ; set X back to the main buffer
+       movw r22, XL
+       movw XL, r24
+
+       dec r2        ; decrement len
+       breq 9f       ; next loop unless len == 0
+
+       adiw YL, 4    ; next item in source data
+       rjmp 1b
+
+       ; epilogue
+9:
+       pop YH
+       pop YL
+       pop r2
+       ret
+
+8:
+       ; bitshift top bits from the current component
+       lsl r18
+       rol r19
+       lsl r18
+       rol r19
+       ; trim bits from previous call
+7:     andi r19, 0x03
+       ; r19 is now offset in avrdx_ws2812_uart_white
+       add ZL, r19
+       adc ZH, r1
+       ; load UART byte
+       ld r20, Z
+       ; correct its 8th bit (always 0)
+       andi r20, 0x7f
+       ; store it, post-increment
+       st X+, r20
+       ; reset Z to original position
+       sub ZL, r19
+       sbc ZH, r1
+
+       ret
diff --git a/arch/avr/src/avrdx/avrdx_ws2812_uart_xmit.S 
b/arch/avr/src/avrdx/avrdx_ws2812_uart_xmit.S
new file mode 100644
index 00000000000..27c116701d7
--- /dev/null
+++ b/arch/avr/src/avrdx/avrdx_ws2812_uart_xmit.S
@@ -0,0 +1,479 @@
+/****************************************************************************
+ * arch/avr/src/avrdx/avrdx_ws2812_uart_xmit.S
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <avrdx_swint.h>
+
+#include <avr/io.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#if (defined CONFIG_WS2812_HAS_WHITE) && \
+    (defined CONFIG_AVR_AVRDX_WS2812_NO_WHITE_LED)
+#  undef CONFIG_WS2812_HAS_WHITE
+#endif
+
+/****************************************************************************
+ * External Symbols
+ ****************************************************************************/
+
+       .global avrdx_swint_raise
+       .file   "avrdx_ws2812_uart_xmit.S"
+
+/****************************************************************************
+ * Name: __vector_avrdx_usart2_dre
+ *
+ * Description:
+ *   Interrupt handler for LED data transmission.
+ *
+ *   The compiler does pretty decent job if this function is compiled
+ *   from C - execution of the handler takes 66 clock cycles (compiled
+ *   with avr-gcc 14.2, calculated by hand.) That's with an implementation
+ *   that takes next byte and transmits it.
+ *
+ *   Such an implementation has a room for improvement though: every
+ *   transmitted byte has 12 bits (start + 9 bits + parity + stop), that is
+ *   96 CPU clock cycles. If any other interrupt request comes in, it is
+ *   almost sure to take more than 30 CPU cycles (which is 96 - 66 ie. time
+ *   needed to transmit minus time needed to prepare the transmission.)
+ *   That is going to leave the UART hardware idle almost for sure.
+ *
+ *   Now, as far as author of this code understands it, there are two
+ *   levels of buffering within the UART hardware. When a byte is written
+ *   to the TX data register, it is moved to the TX Buffer if it is free.
+ *   Then it is moved from TX Buffer to the shift register if it is free.
+ *   This means that if the hardware is idle (for example because another
+ *   interrupt handler was running for more than 3*96 = 288 CPU cycles),
+ *   it can receive 3 new bytes.
+ *
+ *   This is where the improvement comes from. If the interrupt handler
+ *   waits for few CPU clock cycles after pushing a byte - and that happens
+ *   naturally because of bookkeeping (incrementing next byte index
+ *   for example) - it can check if the TX data register became available
+ *   for next write (and write another byte if possible.)
+ *
+ *   Making such decision is not free but on the other hand, it saves
+ *   some 33 clock cycles worth of push/pop, initialization, jmp/reti
+ *   and decision about white LEDs.
+ *
+ *   Unfortunately, this is where the compiler stops doing good job.
+ *   It suddenly decides to use more registers (more push/pop), variable
+ *   that takes care of limiting loop count before the interrupt handler
+ *   cedes the CPU to other code is forced to be 16 bits etc. Overall,
+ *   the optimized implementation becomes slower than the simple one.
+ *
+ *   That's why an assembly implementation is used. (It is mostly based
+ *   on the compiled C code though, with some tweaks and with simplified
+ *   implementation of the counter.)
+ *
+ *   Loop count is chosen to be 4. That is 3 bytes of levels of buffering
+ *   in the hardware plus accounting for the fact that when the 3rd byte
+ *   if prepared, the 1st one is probably already out. (We give other
+ *   handlers a chance to run after that.)
+ *
+ *   This handler varies depending on CONFIG_AVR_AVRDX_WS2812_SHORT_LANE.
+ *   If this option is unset, next byte index is 16 bit.
+ *
+ *   Numbers (calculated by hand, for 8 bit next byte index):
+ *   The handler takes 70 clock ticks to run (out of 96 clock ticks
+ *   needed to transmit the byte.) Sending single byte therefore causes
+ *   73% CPU load.
+ *
+ *   If looping, every loop only needs 37 clock ticks. Therefore,
+ *   if the handler succeeds in pushing more than one byte, the load
+ *   is reduced (pushed bytes - CPU load):
+ *     2 bytes - CPU 56% load
+ *     3 bytes - CPU 50% load
+ *     4 bytes - CPU 47% load
+ *
+ *   Numbers (16 bit next byte index):
+ *   The handler takes 77 clock ticks, a 7 tick increase, one tick of that
+ *   in prologue, 6 ticks in loop. CPU load of single pass increases
+ *   to 80%. Loop needs 43 clock ticks, load reduction is as follows:
+ *     2 bytes - CPU 63% load
+ *     3 bytes - CPU 57% load
+ *     4 bytes - CPU 54% load
+ *
+ ****************************************************************************/
+
+       .macro WS2812_HANDLER_UART, uartnum
+       .global __vector_avrdx_usart\uartnum\()_dre
+__vector_avrdx_usart\uartnum\()_dre:
+
+       ; Register usage
+       ; r1      - zero register (part of ABI but need to clear it first)
+       ;           note - if CONFIG_AVR_AVRDX_WS2812_SHORT_LANE is not set,
+       ;           then r1 is not used and NOT SET TO ZERO
+       ;
+       ; r19     - loop counter
+       ; r22     - temporary value
+       ; r23     - value of current uart9 byte until processing of 9th bits
+       ;           is done (that is, until label 2 is reached)
+       ;         - temporary value after that
+       ; r24     - value of next byte (the one processed by this run)
+       ;           (see r25 below)
+       ; r30:r31 - various pointers
+       ; SREG(T) - flag that is set if support for LEDs with white component
+       ;           is enabled and in use for this lane (unused if
+       ;           CONFIG_WS2812_HAS_WHITE is not set)
+       ;
+       ; If CONFIG_AVR_AVRDX_WS2812_SHORT_LANE is not set:
+       ; r1       - not used and NOT SET TO ZERO
+       ; r25      - value of next byte (the one processed by this run),
+       ;            high byte
+
+       ; Prologue.
+       ;
+       ; Note that interrupt handler generated by C compiler saves RAMPZ.
+       ; Since there currently is no available AVR Dx device with more
+       ; than 64kB data space, we do not do that.
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       ; Zero register is not used if byte counter has 16 bits
+       push r1
+       in r1, SREG
+       push r1
+       eor r1, r1
+#else
+       ; If the zero register is not used, SREG is not saved. Use r19
+       ; (Doing it like this to preserve push order.)
+       push r19
+       in r19, SREG
+#endif
+       push r19
+       push r22
+       push r23
+       push r24
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       push r25
+#endif
+       push r30
+       push r31
+       ; total loop count + 1
+       ldi r19, 5
+       ; next byte to transmit - will be needed later
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       lds r24, g_avrdx_ws2812_xmit_next+\uartnum
+#else
+       lds r24, g_avrdx_ws2812_xmit_next+2*\uartnum
+       lds r25, g_avrdx_ws2812_xmit_next+2*\uartnum\()+1
+#endif
+
+       ; 13 ticks total (+1 with not short lane)
+
+    ; load current 9th bit byte
+    lds r23, g_avrdx_ws2812_uart9_current+\uartnum
+
+#if defined(CONFIG_WS2812_HAS_WHITE)
+       ; If support for LEDs with white component is enabled and the lane
+       ; has such LEDs, the current uart9 byte (a group of 8 9th bits)
+       ; is stored with highest bit set. That is normally not possible
+       ; (the byte is rotated to the right after load) and is used as
+       ; a signal.
+       ;
+       ; If support for LEDs with white component is not enabled, this
+       ; check is not needed.
+       ;
+       ; C code:
+    ; if ((g_avrdx_ws2812_uart9_current[2] & 0x80) == 0) {
+    ;
+    ; (Note for the C code - index 2 is hardcoded for USART2 ISR.)
+
+    ; Check for the signal that there is white component (therefore
+    ; 9th bits are not in use, therefore can be skipped);
+    ; store the information in T bit in SREG, will be used if looping
+    bst r23, 7
+    brts 2f
+    ; 4 ticks unless branch is taken (2 ticks if not checked)
+#endif
+
+3:
+       ; Is current byte divisible by 8? we need to load next 9th bits
+       ; byte in such case
+       ;
+       ; C code:
+       ; if ((g_avrdx_ws2812_xmit_next[2] & 0x07) == 0) {
+       ;g_avrdx_ws2812_uart9_current[2] = \
+       ;     g_avrdx_ws2812_uart9_bytes\
+       ;       [2] \
+       ;       [ \
+       ;         g_avrdx_ws2812_xmit_next[2] >> 3 \
+       ;       ];
+       ; }
+       ;
+       ; Note - r24 has correct value in loops. Never overwritten and
+       ; incremented below.
+       mov r22, r24
+       andi r22, 0x07
+       ; Note 1 - with short lane set, compiler compares r22 with r1
+       ; here - pointless (andi already set the zero flag.)
+       ; Note 2 - with short lane not set, compiler does eor
+       ; on a register and does logical or with r22 - also pointless
+       ; (same as above, andi already set the zero flag.)
+       breq 7f
+       ; 3 ticks if branch is not taken (7/8 cases), 4 ticks if it is
+
+1:
+       ; load 9th bit into TX register
+       ;
+       ; C code:
+       ; USART2.TXDATAH = g_avrdx_ws2812_uart9_current[2] & 0x01;
+    ; g_avrdx_ws2812_uart9_current[2] >>= 1;
+       mov r22, r23
+       andi r22, 0x01
+       ; avr/io.h provides USARTx_TXDATAH preprocessor macro
+       ; with address we need but we can't use it with the \uartnum
+       ; parameter because that is interpreted after the preprocessor
+       ; runs. we will work around this by applying offset to address
+       ; of first peripheral
+       sts USART0_TXDATAH+0x20*\uartnum\(), r22
+       ; prepare next bit and store it back to RAM
+       lsr r23
+       sts g_avrdx_ws2812_uart9_current+\uartnum\(), r23
+       ; 7 ticks
+2:
+       ; load current byte to TX register, trigger the transmission
+       ;
+       ; C code:
+       ; USART2.TXDATAL = g_avrdx_ws2812_xmit_bytes \
+       ;   [2] \
+       ;   [g_avrdx_ws2812_xmit_next[2]];
+       lds r30, g_avrdx_ws2812_xmit_bytes+2*\uartnum
+       lds r31, g_avrdx_ws2812_xmit_bytes+2*\uartnum\()+1
+       add r30, r24
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       adc r31, r1
+#else
+       adc r31, r25
+#endif
+       ld r23, Z
+       sts USART0_TXDATAL+0x20*\uartnum\(), r23
+       ; g_avrdx_ws2812_xmit_next[2]++;
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       subi r24, 0xff
+       sts g_avrdx_ws2812_xmit_next+\uartnum, r24
+#else
+       adiw r24, 1
+       sts g_avrdx_ws2812_xmit_next+2*\uartnum, r24
+       sts g_avrdx_ws2812_xmit_next+2*\uartnum\()+1, r25
+#endif
+
+       ; Are we done?
+       ;
+       ; C code:
+       ; if (g_avrdx_ws2812_xmit_next[2] == g_avrdx_ws2812_xmit_count[2]) {
+       ;   USART2.CTRLA &= ~USART_DREIE_bm;
+       ; }
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       lds r22, g_avrdx_ws2812_xmit_count+\uartnum
+#else
+       lds r22, g_avrdx_ws2812_xmit_count+2*\uartnum
+       lds r23, g_avrdx_ws2812_xmit_count+2*\uartnum\()+1
+#endif
+       cp r24, r22
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       cpc r25, r23
+#endif
+       breq 8f
+       ; 16/17 ticks (branch not/taken; mostly not)
+       ; (+6 with not short lane)
+
+       ; We are not done. By this time, value loaded to TXDATA should be
+       ; shifted to transmit buffer, if there was space available. Maybe
+       ; push next byte?
+       dec r19
+       ; Do not push next byte if we looped multiple times
+       breq 9f
+       ; 2/3 ticks (expected to take the branch once out of three cases)
+
+       ; Check for DREIF flag in status register. If set, data register
+       ; is empty
+       lds r23, USART0_STATUS+0x20*\uartnum\()
+       andi r23, USART_DREIF_bm
+       ; not set, no more loops
+       breq 9f
+       ; 4/5 ticks (not expected to take the branch)
+#if defined(CONFIG_WS2812_HAS_WHITE)
+       ; Same as above. If T in SREG is set, LEDs with white component
+       ; are supported and in use here. (Skipping 9th bits load.)
+       ; Note - r24 has correct value of next byte, incremented above
+       brts 2b
+#endif
+       ; reload uart9 back to r23
+       lds r23, g_avrdx_ws2812_uart9_current+\uartnum
+       rjmp 3b
+
+9:
+       ; Epilogue
+       pop r31
+       pop r30
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       pop r25
+#endif
+       pop r24
+       pop r23
+       pop r22
+       pop r19
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       out SREG, r19
+       pop r19
+#else
+       pop r1
+       out SREG, r1
+       pop r1
+#endif
+       reti
+       ; 9 ticks
+
+7:
+       ; Load new uart9 byte from the array
+       ;
+       ; note - this code clobbers r23 but at this point, that value
+       ; is used and new needs to be loaded (which is what this does.)
+       ; g_avrdx_ws2812_uart9_bytes is array of pointers to arrays
+       ; of 9th bits bytes. locate pointer for this handler
+       ; (Note - \uartnum is replaced by parameter, \() is ending
+       ; delimiter for the parameter - needed if something follows.)
+       lds r30, g_avrdx_ws2812_uart9_bytes+2*\uartnum
+       lds r31, g_avrdx_ws2812_uart9_bytes+2*\uartnum\()+1
+       ; offset in this handler's array
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       mov r22, r24
+       lsr r22
+       lsr r22
+       lsr r22
+#else
+       movw r22, r24
+       lsr r23
+       ror r22
+       lsr r23
+       ror r22
+       lsr r23
+       ror r22
+#endif
+       add r30, r22
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       adc r31, r1
+#else
+       adc r31, r23
+#endif
+       ; previous byte is replaced in r23
+       ld r23, Z
+       rjmp 1b
+       ; 13 ticks (+4 with not short lane)
+
+8:
+       ; Transmission finished
+       ;
+       ; clear DREIE - Data Register Empty Interrupt Enabled
+       lds r24, USART0_CTRLA+0x20*\uartnum\()
+       andi r24, ~USART_DREIE_bm
+       sts USART0_CTRLA+0x20*\uartnum\(), r24
+
+       ; We are calling C function now. We cannot assume anything about
+       ; its implementation and need to adhere to the ABI. Registers
+       ; R18–R27, R30, R31, R0 are call-clobbered and so is T flag
+       ; in SREG.
+       ;
+       ; All of these must be preserved from the interrupted code's
+       ; point of view but as long as we do nothing else after we
+       ; call the function, we can lose our own data.
+
+       ; push what was not pushed in the prologue
+       push r0
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       ; Zero register was not pushed nor zeroed
+       push r1
+       eor r1, r1
+#endif
+       push r18
+       push r20
+       push r21
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       push r25
+#endif
+       push r26
+       push r27
+
+       ; load software interrupt vector number
+#if (AVRDX_SWINT_VECTORS_SIZE == 1)
+       lds r24, g_avrdx_ws2812_swint_vector+\uartnum
+#elif (AVRDX_SWINT_VECTORS_SIZE == 2)
+       lds r24, g_avrdx_ws2812_swint_vector+2*\uartnum
+       lds r25, g_avrdx_ws2812_swint_vector+2*\uartnum\()+1
+#elif (AVRDX_SWINT_VECTORS_SIZE == 3)
+       lds r22, g_avrdx_ws2812_swint_vector+3*\uartnum
+       lds r23, g_avrdx_ws2812_swint_vector+3*\uartnum\()+1
+       lds r24, g_avrdx_ws2812_swint_vector+3*\uartnum\()+2
+#elif (AVRDX_SWINT_VECTORS_SIZE == 4)
+       lds r22, g_avrdx_ws2812_swint_vector+4*\uartnum
+       lds r23, g_avrdx_ws2812_swint_vector+4*\uartnum\()+1
+       lds r24, g_avrdx_ws2812_swint_vector+4*\uartnum\()+2
+       lds r25, g_avrdx_ws2812_swint_vector+4*\uartnum\()+3
+#else
+#  error Unhandled AVRDX_SWINT_VECTORS_SIZE
+#endif
+
+       ; raise the interrupt
+       call avrdx_swint_raise
+
+       ; now pop what we pushed here
+       pop r27
+       pop r26
+#if defined(CONFIG_AVR_AVRDX_WS2812_SHORT_LANE)
+       pop r25
+#endif
+       pop r21
+       pop r20
+       pop r18
+#ifndef CONFIG_AVR_AVRDX_WS2812_SHORT_LANE
+       pop r1
+#endif
+       pop r0
+       rjmp 9b
+
+       .endm
+
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART0)
+       WS2812_HANDLER_UART 0
+#endif
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART1)
+       WS2812_HANDLER_UART 1
+#endif
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART2)
+       WS2812_HANDLER_UART 2
+#endif
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART3)
+       WS2812_HANDLER_UART 3
+#endif
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART4)
+       WS2812_HANDLER_UART 4
+#endif
+#if defined(CONFIG_AVR_AVRDX_WS2812_USART5)
+       WS2812_HANDLER_UART 5
+#endif
+

Reply via email to