------------------------------------------------------------------------------
--                                                                          --
--                    Copyright (C) 2019-2020, AdaCore                      --
--                                                                          --
--  Redistribution and use in source and binary forms, with or without      --
--  modification, are permitted provided that the following conditions are  --
--  met:                                                                    --
--     1. Redistributions of source code must retain the above copyright    --
--        notice, this list of conditions and the following disclaimer.     --
--     2. Redistributions in binary form must reproduce the above copyright --
--        notice, this list of conditions and the following disclaimer in   --
--        the documentation and/or other materials provided with the        --
--        distribution.                                                     --
--     3. Neither the name of the copyright holder nor the names of its     --
--        contributors may be used to endorse or promote products derived   --
--        from this software without specific prior written permission.     --
--                                                                          --
--   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    --
--   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      --
--   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  --
--   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   --
--   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
--   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT       --
--   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  --
--   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  --
--   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT    --
--   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  --
--   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   --
--                                                                          --
------------------------------------------------------------------------------

--  Bit-bang a sequence of color LED values to a one-pin LED strip (WS2812B
--  or similar).

with System.Machine_Code; use System, System.Machine_Code;

with NRF_SVD.GPIO; use NRF_SVD.GPIO;
with MicroBit.Time;

package body MicroBit.IOs.NeoPixel is

   -----------
   -- Write --
   -----------

   procedure Write (Pin : Pin_Id; Values : UInt8_Array) is
      Point : constant GPIO_Point := Points (Pin);
      Mask  : constant UInt32 := 2 ** Point.Pin;
      EOL   : constant String := ASCII.LF & ASCII.HT;
   begin
      --  Set pin to digital out mode and reset strip

      Set (Pin, False);
      MicroBit.Time.Delay_Ms (1);

      --  The following loop is very time-sensitive. The bit frame is 1.25 µs,
      --  i.e. 20 cycles of the 16 MHz clock. So, we run it with IRQs disabled,
      --  and carefully account for every cycle.
      --  Code taken from:
      --  https://github.com/Microsoft/pxt-ws2812b/blob/master/sendBuffer.asm

      Asm ("    cpsid i             @  disable irq"    & EOL &
           "    b .start"                              & EOL &

           ".nextbit:               @             c0"  & EOL &
           "    str %2, [%1, #0]    @  pin := hi  c2"  & EOL &
           "    tst r6, r0          @             c3"  & EOL &
           "    bne .islate         @             c4"  & EOL &
           "    str %2, [%0, #0]    @  pin := lo  c6"  & EOL &

           ".islate:"                                  & EOL &
           "    lsr r6, r6, #1      @  r6 >>= 1   c7"  & EOL &
           "    bne .justbit        @             c8"  & EOL &

           "@ not just a bit - need new byte"          & EOL &

           "    add %3, #1          @ buf++       c9"  & EOL &
           "    sub %4, #1          @ len--       c10" & EOL &
           "    bcc .stop           @ if (len<0) goto .stop c11" & EOL &

           ".start:"                                   & EOL &
           "    movs r6, #0x80      @ reset mask  c12" & EOL &
           "    nop                 @             c13" & EOL &

           ".common:                @             c13" & EOL &
           "    str %2, [%0, #0]    @ pin := lo   c15" & EOL &

           "    @ always re-load byte - it just fits with the" & EOL &
           "    @ cycles better this way."                     & EOL &

           "    ldrb r0, [%3, #0]   @ r0 := *buf  c17" & EOL &
           "    b .nextbit          @             c20" & EOL &

           ".justbit:               @             c10" & EOL &

           "    @ no nops, branch taken is already 3 cycles" & EOL &
           "    b .common           @             c13" & EOL &

           ".stop:"                                    & EOL &
           "    str %2, [%0, #0]    @ pin := lo"       & EOL &
           "    cpsie i             @ enable irq",

           Inputs => (Address'Asm_Input ("l", GPIO_Periph_0.OUTCLR'Address), -- %0
                      Address'Asm_Input ("l", GPIO_Periph_0.OUTSET'Address), -- %1
                      UInt32'Asm_Input  ("l", Mask),                       -- %2
                      Address'Asm_Input ("l", Values'Address),             -- %3
                      Natural'Asm_Input ("l", Values'Length)),             -- %4
           Volatile => True,
           Clobber => "r0,r6");
   end Write;

end MicroBit.IOs.NeoPixel;
