// Raspberry Pi WS2812 LED driver using SMI
// For detailed description, see https://iosoft.blog
//
// Copyright (c) 2020 Jeremy P Bentham
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// v0.01 JPB 16/7/20 Adapted from rpi_smi_adc_test v0.06
// v0.02 JPB 15/9/20 Addded RGB to GRB conversion
// v0.03 JPB 15/9/20 Added red-green flashing
// v0.04 JPB 16/9/20 Added test mode
// v0.05 JPB 19/9/20 Changed test mode colours
// v0.06 JPB 20/9/20 Outlined command-line data input
// v0.07 JPB 25/9/20 Command-line data input if not in test mode
// v0.08 JPB 26/9/20 Changed from 4 to 3 pulses per LED bit
//                   Added 4-bit zero preamble
//                   Added raw Tx data test
// v0.09 JPB 27/9/20 Added 16-channel option
// v0.10 JPB 28/9/20 Corrected Pi Zero caching problem
// v0.11 JPB 29/9/20 Added enable_dma before transfer (in case still active)
//                   Corrected DMA nsamp value (was byte count)

#include "rpi_pixleds.h"

#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "rpi_smi_defs.h"

// Structures for mapped I/O devices, and non-volatile memory
extern MEM_MAP gpio_regs, dma_regs;
MEM_MAP vc_mem, clk_regs, smi_regs;

// Pointers to SMI registers
volatile SMI_CS_REG *smi_cs;
volatile SMI_L_REG *smi_l;
volatile SMI_A_REG *smi_a;
volatile SMI_D_REG *smi_d;
volatile SMI_DMC_REG *smi_dmc;
volatile SMI_DSR_REG *smi_dsr;
volatile SMI_DSW_REG *smi_dsw;
volatile SMI_DCS_REG *smi_dcs;
volatile SMI_DCA_REG *smi_dca;
volatile SMI_DCD_REG *smi_dcd;

// RGB values for test mode (1 value for each of 16 channels)
// uint32_t on_rgbs[] = {0xef0000, 0x00ef00, 0x0000ef, 0xefef00, 0xef00ef, 0x00efef, 0xefefef};
// uint32_t off_rgbs = 0x000000;

// TXDATA_T *txdata;                              // Pointer to uncached Tx data buffer
// TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data
// int testmode, chan_ledcount = 1;               // Command-line parameters
int rgb_data[CHAN_MAXLEDS][LED_NCHANS]; // RGB data
int chan_num;

// Current channel for data I/P

// int main(int argc, char *argv[]) {
//   // setup
//   int args = 0, n;

//   while (argc > ++args) // Process command-line args
//   {
//     if (argv[args][0] == '-') {
//       switch (toupper(argv[args][1])) {
//       case 'N': // -N: number of LEDs per channel
//         if (args >= argc - 1)
//           fprintf(stderr, "Error: no numeric value\n");
//         else
//           chan_ledcount = atoi(argv[++args]);
//         break;
//       case 'T': // -T: test mode
//         testmode = 1;
//         break;
//       default: // Otherwise error
//         printf("Unrecognised option '%c'\n", argv[args][1]);
//         printf("Options:\n"
//                "  -n num    number of LEDs per channel\n"
//                "  -t        Test mode (flash LEDs)\n");
//         return (1);
//       }
//     } else if (chan_num < LED_NCHANS && hexdig(argv[args][0]) >= 0 &&
//                (n = str_rgb(argv[args], rgb_data, chan_num)) > 0) {
//       chan_ledcount = n > chan_ledcount ? n : chan_ledcount;
//       chan_num++;
//     }
//   }
//   signal(SIGINT, terminate_smi);
//   map_devices();
//   init_smi(LED_NCHANS > 8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING);
//   map_uncached_mem(&vc_mem, VC_MEM_SIZE);

//   setup_smi_dma(&vc_mem, TX_BUFF_LEN(chan_ledcount));
//   printf("%s %u LED%s per channel, %u channels\n", testmode ? "Testing" : "Setting",
//   chan_ledcount,
//          chan_ledcount == 1 ? "" : "s", LED_NCHANS);

//   for (size_t colorIndex = 0; colorIndex < 3; ++colorIndex) {
//     for (size_t i = 0; i < chan_ledcount; ++i) {
//       set_color(on_rgbs[colorIndex], &tx_buffer[LED_TX_OSET(i)]);
//     }

// #if LED_NCHANS <= 8
//     swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount));
// #endif

//     memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount));
//     start_smi(&vc_mem);
//     usleep(CHASE_MSEC * 1000);
//     sleep(1);
//   }

//   artnet_init();

//   // loops
//   if (testmode) {
//     while (1) {
//       test_leds();
//       sleep(3);
//     }
//   } else {
//     while (1) {
//       artDmx_t *artDmx;
//       if (artnet_read(&artDmx) == OpDmx) {
//         uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo;
//         unsigned int ledCountInFrame = dmxLength / 3;
//         uint16_t maxBound = ledCountInFrame < chan_ledcount ? ledCountInFrame :
//         chan_ledcount; unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); for (size_t
//         i = 0; i < maxBound; ++i) {
//           uint8_t *rgb = artDmx->data + (i * 3);
//           rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
//           rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]);
//         }

// #if LED_NCHANS <= 8
//         swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount));
// #endif

//         memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount));
//         // enable_dma(DMA_CHAN);
//         start_smi(&vc_mem);
//         // usleep(10);
//         // while (dma_active(DMA_CHAN))
//         //     usleep(10);
//       }
//     }
//   }
//   terminate_smi(0);
// }

// Convert RGB text string into integer data, for given channel
// Return number of data points for this channel
int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan) {
  int i = 0;
  char *p;

  while (chan < LED_NCHANS && i < CHAN_MAXLEDS && hexdig(*s) >= 0) {
    rgbs[i++][chan] = strtoul(s, &p, 16);
    s = *p ? p + 1 : p;
  }
  return (i);
}

// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan
// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low
void set_color(uint32_t rgb, TXDATA_T *txd) {
  int msk;

  // For each bit of the 24-bit RGB values..
  for (size_t n = 0; n < LED_NBITS; n++) {
    // Mask to convert RGB to GRB, M.S bit first
    msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
    // 1st byte or word is a high pulse on all lines
    txd[0] = (TXDATA_T)0xffff;
    // 2nd has high or low bits from data
    // 3rd is a low pulse
    txd[1] = txd[2] = 0;
    if (rgb & msk) {
      txd[1] = (TXDATA_T)0xffff;
    }
    txd += BIT_NPULSES;
  }
}

// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan
// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low
void rgb_txdata(int *rgbs, TXDATA_T *txd) {
  int i, n, msk;

  // For each bit of the 24-bit RGB values..
  for (n = 0; n < LED_NBITS; n++) {
    // Mask to convert RGB to GRB, M.S bit first
    msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
    // 1st byte or word is a high pulse on all lines
    txd[0] = (TXDATA_T)0xffff;
    // 2nd has high or low bits from data
    // 3rd is a low pulse
    txd[1] = txd[2] = 0;
    for (i = 0; i < LED_NCHANS; i++) {
      if (rgbs[i] & msk)
        txd[1] |= (1 << i);
    }
    txd += BIT_NPULSES;
  }
}

// Swap adjacent bytes in transmit data
void swap_bytes(void *data, int len) {
  uint16_t *wp = (uint16_t *)data;

  len = (len + 1) / 2;
  while (len-- > 0) {
    *wp = __builtin_bswap16(*wp);
    wp++;
  }
}

// Return hex digit value, -ve if not hex
int hexdig(char c) {
  c = toupper(c);
  return ((c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1);
}

// Map GPIO, DMA and SMI registers into virtual mem (user space)
// If any of these fail, program will be terminate_smid
void map_devices(void) {
  map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE);
  map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
  map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE);
  map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE);
}

// Catastrophic failure in initial setup
void fail(char *s) {
  printf(s);
  terminate_smi(0);
}

// Free memory segments and exit
void terminate_smi(int sig) {
  int i;
  printf("Closing\n");
  if (gpio_regs.virt) {
    for (i = 0; i < LED_NCHANS; i++)
      gpio_mode(LED_D0_PIN + i, GPIO_IN);
  }
  if (smi_regs.virt)
    *REG32(smi_regs, SMI_CS) = 0;
  stop_dma(DMA_CHAN);
  unmap_periph_mem(&vc_mem);
  unmap_periph_mem(&smi_regs);
  unmap_periph_mem(&dma_regs);
  unmap_periph_mem(&gpio_regs);
  exit(0);
}

// Initialise SMI, given data width, time step, and setup/hold/strobe counts
// Step value is in nanoseconds: even numbers, 2 to 30
void init_smi(int ledChan, int ns, int setup, int strobe, int hold) {
  int width = ledChan > 8 ? SMI_16_BITS : SMI_8_BITS;
  int i, divi = ns / 2;

  smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS);
  smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L);
  smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A);
  smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D);
  smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC);
  smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0);
  smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0);
  smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS);
  smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA);
  smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD);
  smi_cs->value = smi_l->value = smi_a->value = 0;
  smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0;
  if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) {
    *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5);
    usleep(10);
    while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7))
      ;
    usleep(10);
    *REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12);
    usleep(10);
    *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4);
    usleep(10);
    while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0)
      ;
    usleep(100);
  }
  if (smi_cs->seterr)
    smi_cs->seterr = 1;
  smi_dsr->rsetup = smi_dsw->wsetup = setup;
  smi_dsr->rstrobe = smi_dsw->wstrobe = strobe;
  smi_dsr->rhold = smi_dsw->whold = hold;
  smi_dmc->panicr = smi_dmc->panicw = 8;
  smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH;
  smi_dsr->rwidth = smi_dsw->wwidth = width;
  for (i = 0; i < LED_NCHANS; i++)
    gpio_mode(LED_D0_PIN + i, GPIO_ALT1);
}

// Set up SMI transfers using DMA
void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T **txdata) {
  DMA_CB *cbs = mp->virt;

  *txdata = (TXDATA_T *)(cbs + 1);
  smi_dmc->dmaen = 1;
  smi_cs->enable = 1;
  smi_cs->clear = 1;
  smi_cs->pxldat = 1;
  smi_l->len = nsamp * sizeof(TXDATA_T);
  smi_cs->write = 1;
  enable_dma(DMA_CHAN);
  cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP;
  cbs[0].tfr_len = nsamp;
  cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata);
  cbs[0].dest_ad = REG_BUS_ADDR(smi_regs, SMI_D);
}

// Start SMI DMA transfers
void start_smi(MEM_MAP *mp) {
  DMA_CB *cbs = mp->virt;

  start_dma(mp, DMA_CHAN, &cbs[0], 0);
  smi_cs->start = 1;
}

// EOF
