/*
 * LED lightness control
 * Target: ATtiny412 @ 20MHz (megaTinyCore)
 *
 * Board settings (Tools menu):
 *   - Board:    ATtiny412
 *   - Clock:    20MHz internal
 *   - Programmer: Adafruit HV UPDI Friend
 *
 * PA7 (ATtiny412 pin 2) - PWM output (~1220Hz)
 * PA6 (ATtiny412 pin 3) - Current set DAC output (0 - 0.55V)
 * PA1 (ATtiny412 pin 4) - potentiometer ADC input (0 - VCC)
 */

#include <avr/io.h>

// --- ADC / oversampling ---
#define POT_PIN         PIN_PA1
#define OSR_EXTRA_BITS  3
#define OSR_SAMPLES     (1 << (2 * OSR_EXTRA_BITS))    // 64 samples
#define ADC_RESULT_BITS (10 + OSR_EXTRA_BITS)          // 13-bit result
#define ADC_MAX         ((1UL << ADC_RESULT_BITS) - 1) // 8191

// 2% / 98% deadzone on the ADC range
#define DEADZONE_LOW    (ADC_MAX * 2  / 100)
#define DEADZONE_HIGH   (ADC_MAX * 98 / 100)

// --- PWM ---
#define PWM_PIN  PIN_PA7
#define PWM_TOP  16384  // 14-bit resolution

// --- DAC / current control ---
#define CC_PIN PIN_PA6 // LED Current control output

// --- Setup ---
void setup() {
    analogReference(VDD);

    pinMode(PWM_PIN, OUTPUT);
    digitalWrite(PWM_PIN, LOW);

    takeOverTCA0();
    TCA0.SINGLE.PER   = PWM_TOP;
    TCA0.SINGLE.CMP0  = 0;
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
    TCA0.SINGLE.CTRLA = TCA_SINGLE_ENABLE_bm;

    // Set point for current limit circuit
    digitalWrite(CC_PIN, LOW);
    pinMode(CC_PIN, OUTPUT);
    VREF.CTRLA = VREF.CTRLA & (0xF8 | 0x1); // 1.1V
    DAC0.CTRLA = 0x41; // enable
    DAC0.DATA = 0xff; 
}

// --- ADC ---
uint16_t adc_read_oversampled() {
    uint32_t sum = 0;
    for (uint8_t i = 0; i < OSR_SAMPLES; i++)
        sum += analogRead(POT_PIN);
    return (uint16_t)(sum >> OSR_EXTRA_BITS);
}

// Maps ADC reading to sRGB LUT input range (0-7863), with end deadzones
uint16_t adc_deadzone(uint16_t adc_raw) {
    if (adc_raw <= DEADZONE_LOW)  return 0;
    if (adc_raw >= DEADZONE_HIGH) return ADC_MAX - DEADZONE_LOW;
    return adc_raw - DEADZONE_LOW;
}

// --- sRGB gamma LUT ---
static const uint16_t srgb_lut[64] = {
        0,    20,    40,    61,    86,   117,   152,   194,
      241,   295,   355,   422,   495,   575,   663,   758,
      860,   970,  1087,  1213,  1346,  1488,  1639,  1797,
     1965,  2141,  2326,  2520,  2723,  2935,  3157,  3388,
     3628,  3879,  4139,  4409,  4689,  4980,  5280,  5591,
     5912,  6244,  6586,  6939,  7303,  7678,  8063,  8460,
     8868,  9287,  9718, 10160, 10613, 11078, 11555, 12043,
    12544, 13056, 13580, 14116, 14665, 15226, 15799, 16384,
};

// 0-7863 -> 0-16384
uint16_t srgb_to_linear(uint16_t v) {
    v = min(v, 7863);
    uint32_t scaled = (uint32_t)v * 63;
    uint8_t  idx    = scaled / 7863;
    if (idx >= 63) return srgb_lut[63];
    uint16_t a    = srgb_lut[idx];
    uint16_t b    = srgb_lut[idx + 1];
    uint16_t frac = scaled - (uint32_t)idx * 7863;
    return a + (uint16_t)((uint32_t)(b - a) * frac / 7863);
}

// --- PWM output ---
void pwm_write(uint16_t pwm_duty) {
    if (pwm_duty == 0) {
        TCA0.SINGLE.CMP0  = 0;
    } else if (pwm_duty >= PWM_TOP) {
        TCA0.SINGLE.CMP0  = PWM_TOP + 1;
    } else {
        TCA0.SINGLE.CMP0  = pwm_duty;
    }
}

// --- Main loop ---
void loop() {
    uint16_t adc_raw  = adc_read_oversampled();
    uint16_t srgb_in  = adc_deadzone(adc_raw);
    uint16_t pwm_duty = srgb_to_linear(srgb_in);

    if (pwm_duty > 16384) {
      pwm_write(0);
    } else {
      pwm_write(16384 - pwm_duty);
    }
}
