/*
 * led_sk6x_spi.c
 *
 *  Created on: Jun 19, 2023
 *      Author: true
 *
 * communicates with SK68xx LEDs using the SPI peripheral.
 *
 * this code operates the SPI at 4MHz and uses 5 bits to represent
 * each on-wire bit, resulting in 800KHz data rate.
 *
 * originally,
 * for the working buffer, each 5-bit word is stored in an 8-bit byte.
 * so, one byte per on-wire bit will be used. for RGB LEDs, 24 bits
 * per LED are required, so working memory use will be 24*LED_COUNT bytes.
 * the spi peripheral will only transfer the lower 5 bits of each byte.
 *
 * however,
 * this MCU supports 5-bit SPI mode, but has no DMA and isn't fast enough
 * to maintain continuous transfer at this rate. instead of trying to make
 * this work, we'll instead waste time and memory with a separate output
 * buffer. data will be generated for the output buffer from a separate
 * LEDvalue buffer by bit manipulating values into it.
 *
 * another note:
 * this would likely work perfectly fine with the weird delays between bits.
 * I was focused on making a very nice looking output waveform. I may test
 * and address this after con to reduce battery life, but considering the
 * draw of the LEDs and any addons, a worst case extra 1mA or so isn't shit.
 *
 */


#include <string.h>

#include "led_sk6x_spi.h"
#include "spi.h"

#include "adxl.h"



#define SK6X_FILL(COLOR) \
	for (uint8_t mask = 0x80; mask; mask >>= 1) { \
    	if (COLOR & mask) { \
            *p++ = SK6X_LO; \
        } else { \
            *p++ = SK6X_HI; \
        } \
    }



const uint8_t sk6x_map[8] = {0, 1, 2, 3, 4, 7, 6, 5};

static uint8_t sk6x_led[SK6X_LED_MAX_COUNT][3];	// G-R-B order
static uint8_t sk6x_buf[SK6X_BUF_SIZE];

static uint8_t sk6x_brightness = 0;
static uint8_t sk6x_led_enabled = 0;

static uint8_t sk6x_zone = SK6X_ZONE_ALL;



void led_sk6x_cb();



void led_sk6x_init()
{
	// configure ADXL to enable power
	adxl345_set_intr_polarity(ADXL345_INTR_ACTIVE_LO);

	// clear led buffers
	memset(sk6x_led, 0, sizeof(sk6x_led));
	memset(sk6x_buf, 0, sizeof(sk6x_buf));
}

__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_process()
{
	uint8_t  i, j;
	uint16_t n = 0;
	uint8_t  b[8];

	uint8_t v;

	j = 0;

	// do nothing if LEDs are turned off
	if (!sk6x_led_enabled) {
		return;
	}

	// pack RGB values into 5-bits-per-bit format
	for (i = 0; i < SK6X_LED_MAX_COUNT; i++) {
		for (j = 0; j < 3; j++) {
			// pack values for next color
			uint8_t *p = b;

			v = sk6x_led[sk6x_map[i]][j] >> sk6x_brightness;
			if (i <= 4) {
				if (!(sk6x_zone & SK6X_ZONE_MAIN)) v = 0;
			} else if (i <= 5) {
				if (!(sk6x_zone & SK6X_ZONE_REAR)) v = 0;
			} else {
				if (!(sk6x_zone & SK6X_ZONE_SIDE)) v = 0;
			}

			SK6X_FILL(v);

			// pack 8 bits into 5 bytes in final buffer
			sk6x_buf[n + 0] = (b[0] << 3) | (b[1] >> 2);
			sk6x_buf[n + 1] = (b[1] << 6) | (b[2] << 1) | (b[3] >> 4);
			sk6x_buf[n + 2] = (b[3] << 4) | (b[4] >> 1);
			sk6x_buf[n + 3] = (b[4] << 7) | (b[5] << 2) | (b[6] >> 3);
			sk6x_buf[n + 4] = (b[6] << 5) | (b[7]);
			n += 5;
		}
	}
}

__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_update()
{
	// turn on / off LEDs based on brightness setting
	if (sk6x_brightness >= 8) {
		// leds should be OFF

		// if already off, we're done
		if (!sk6x_led_enabled) return;
		// otherwise, blank the LEDs
		led_sk6x_process();
	} else {
		// leds should be ON
		if (!sk6x_led_enabled) {
			// they aren't on, so turn them on
			adxl345_set_intr_polarity(ADXL345_INTR_ACTIVE_LO);
			sk6x_led_enabled = 1;
		}
	}

	// configure MOSI pin and bit depth, then send LED data
	spi_mosi_sel(SPI_MOSI_LED);
	spi_xfer(sk6x_buf, 0, sizeof(sk6x_buf), SPI_NO_CALLBACK);

	// turn off LEDs based on brightness setting
	if (sk6x_brightness >= 8) {
		// leds should be OFF
		if (sk6x_led_enabled) {
			// LEDs should be blanked, so we can turn them off now
			adxl345_set_intr_polarity(ADXL345_INTR_ACTIVE_HI);
			sk6x_led_enabled = 0;
		}
	}
}

__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_set(uint8_t index, uint8_t r, uint8_t g, uint8_t b)
{
	/*
    uint8_t *p = &sk6x_buf[24 * index];
    SK6X_FILL(g);
    SK6X_FILL(r);
    SK6X_FILL(b);
    */
	sk6x_led[index][0] = g;
	sk6x_led[index][1] = r;
	sk6x_led[index][2] = b;
}

__attribute__ ((long_call, section(".ramfunc"))) void led_sk6x_set_all(uint8_t r, uint8_t g, uint8_t b)
{
	/*
    uint8_t *p = sk6x_buf;
    for (uint8_t i = 0; i < SK6X_LED_MAX_COUNT; i++) {
    	SK6X_FILL(g);
    	SK6X_FILL(r);
    	SK6X_FILL(b);
    }
    */

	uint8_t i;

	for (i = 0; i < SK6X_LED_MAX_COUNT; i++) {
		led_sk6x_set(i, r, g, b);
	}
}

void led_sk6x_cb()
{
	// there is nothing to handle after updating LEDs
}

void led_sk6x_zone_ena(uint8_t zone_mask)
{
	sk6x_zone = zone_mask & SK6X_ZONE_ALL;
}

void led_sk6x_brightness(uint8_t brightness)
{
	if (brightness >= 5) brightness = 5;

	if (brightness == 0) brightness = 8;
	else                 brightness = 5 - brightness;

	sk6x_brightness = brightness;
}
