/*
 * awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver
 *
 * originally written by true in 2022
 * while sleep deprived for a super constrained mcu
 *
 * some bugs fixed for defcon 32 on aug 6-7, 2024
 * - don't remember what was fixed
 *
 * some bugs fixed for supercon on oct 15, 2024
 * - fixed support for AW20054, which only supports 9 rows
 *   did this by respecting aw->rows
 *
 *
 * driver assumptions:
 * - rows and columns are as is ordered on the chip, lowest to highest
 *   (if any are skipped, just skip this data in your buffer)
 * - buffer size does not need to encompass all possible LEDs, only those
 *   which you have specified you are using.
 *   ensure sizeof(aw->fade) == aw->rows + aw->cols
 * - duty cycle will be set automatically according to aw->cols
 * - all AW20xxx chips will operate on the same i2c bus
 * - a static data buffer is used
 *
 * driver notices:
 * - updates only happen one column at a time, and are blocking
 *   (future version may implement a callback when each column is done)
 * - this driver has not yet implemented the pattern controller
 * - this driver has not yet implemented the GAIN register, except as
 *   a global configuration, so dimming only operates 8-bit
 *   (will be implemented later to allow for beyond-8-bit operation)
 * - this driver has not yet implemented FADEDIM mode
 *
 * if you need anything different, write it yourself
 */

#include "aw20xxx.h"



#define AW20X_THIS_PAGE         (aw->state & AW20X_STATE_PAGE_MASK)

#define AW20X_SET_PAGE(x)       if (AW20X_THIS_PAGE != (x)) { \
                                        aw20x_page(aw, x); \
                                        aw->state &= ~AW20X_STATE_PAGE_MASK; \
                                        aw->state |= x; }



static uint8_t aw_buf[25];      // enough bytes for register and single column FADEDIM update (1 + (12*2))



void aw20x_page(struct AW20x *aw, uint8_t page)
{
    // the datasheet isn't clear on this. the default is zero, and only the lower
    // three bits are specified. yet the DS says to send 0xCy where y is the page bits.
    // we'll just do what the DS says even though it contradicts itself.
    aw_buf[0] = 0xc0 | (page & AW20X_PAGE_MASK);
    AW20X_I2C_writereg(aw->addr, AW20X_REG_PAGE, aw_buf, 1);
}

void aw20x_init(struct AW20x *aw, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t imax)
{
    // set config register as specified
    aw->addr   = addr;
    aw->cols   = cols;
    aw->rows   = rows;
    aw->config = imax & AW20X_CONF_IMAX_MASK;

    // wake up (and set page to config page)
    aw20x_set_sleep(aw, 0);

    // enabled columns
    aw_buf[0] = cols - 1;
    while (AW20X_I2C_busy());
    AW20X_I2C_writereg(aw->addr, AW20X_REG_SIZE, aw_buf, 1);

    // general config
    aw_buf[0] = imax & AW20X_CONF_IMAX_MASK;
    while (AW20X_I2C_busy());
    AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1);
}

void aw20x_set_sleep(struct AW20x *aw, uint8_t sleep)
{
    // make sure we're on the config page
    AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);

    aw_buf[0] = sleep ? AW20X_SLPCR_SLEEP : 0;

    // send sleep bit
    while (AW20X_I2C_busy());
    AW20X_I2C_writereg(aw->addr, AW20X_REG_SLPCR, aw_buf, 1);

    // set state
    if (sleep) aw->state |=  AW20X_STATE_SLEEP_MASK;
    else       aw->state &= ~AW20X_STATE_SLEEP_MASK;

    // burn some cycles if we woke up
    if (!sleep) AW20X_INIT_DELAY();
}

void aw20x_set_imax(struct AW20x *aw, uint8_t imax)
{
    // make sure we're on the config page
    AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);

    aw_buf[0] = imax & AW20X_CONF_IMAX_MASK;

    // send imax
    while (AW20X_I2C_busy());
    AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1);

    aw->config = imax & AW20X_CONF_IMAX_MASK;
}

/*
 * sends LED values to the chip
 */
void aw20x_set_fade(struct AW20x *aw)
{
    uint8_t c;
    uint8_t row;
    uint8_t offset;

    // make sure we're on the fade page
    AW20X_SET_PAGE(AW20X_PAGE2_FADE);
    // don't touch the buffer until we are allowed
    while (AW20X_I2C_busy());

    row = offset = 0;
    for (c = 0; c < aw->cols; c++) {
        // write to chip
        AW20X_I2C_writereg(aw->addr, offset, aw->fade + row, aw->rows);
        while (AW20X_I2C_busy());
        row += aw->rows;
        offset += AW20X_MAX_ROWS;
    }
}

void aw20x_set_dim(struct AW20x *aw)
{
    // todo: implement
}

/*
 * sets all LEDs to the specified 6-bit DIM value.
 * used when just using FADE and 8-bit mode
 * to set initial and fine tune from IMAX the output current.
 */
void aw20x_set_dim_global(struct AW20x *aw, uint8_t dim)
{
    uint8_t i;
    uint8_t row;
    uint8_t offset;

    // ceil
    if (dim > 0x3f) dim = 0x3f;

    // make sure we're on the dim page
    AW20X_SET_PAGE(AW20X_PAGE1_DIM);
    // don't touch the buffer until we are allowed
    while (AW20X_I2C_busy());

    // clear buffer
    for (i = 0; i <= aw->rows; i++) aw_buf[i] = dim;

    // send buffer for each column
    row = offset = 0;
    for (i = 0; i < aw->cols; i++) {
        AW20X_I2C_writereg(aw->addr, offset, aw_buf, aw->rows);
        while (AW20X_I2C_busy());
        row += aw->rows;
        offset += AW20X_MAX_ROWS;

    }
}

void aw20x_commit_fadedim(struct AW20x *aw)
{

}

void aw20x_led_on(struct AW20x *aw, uint8_t first, uint8_t last, uint8_t on_bit)
{

}
/*
 * enables LEDs based on user LED count, zero-indexed
 * assumes AW20X_MAX_LEDON_BITS per row but will only turn on active defined rows
 * AW20036 would be 0-35, but AW20054 would be 0-71 and so on
 * for example, LEDs 8-12 on AW20054 with 9 roww enabled
 * would enable C0R8, C1R0, C1R1, C1R2. all other LEDs are disabled
 *
 * todo:
 * - read current state, and apply bitfields to the currently active state
 * - allow bypassing the readback for faster operation (such as setting all LEDs on at startup)
 * - make this more efficient (36 LEDs takes ~0.3ms on a 48MHz PIC!)
*/
void aw20x_led_enable_range(struct AW20x *aw, uint8_t first, uint8_t last)
{
    uint8_t c, r;
    uint8_t idx;
    uint8_t boff;
    uint8_t *buf;

    // make sure we're on the config page
    AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);
    // don't touch the buffer until we are allowed
    while (AW20X_I2C_busy());

    // bits are stored 6 bits per byte, 2 bytes per column, one bit for each row
    // for a maximum of the AW20X_MAX_ROWS of LED on bits
    // we only want to touch bits that exist on the chip and in the correct order
    idx = 0;
    for (c = 0; c < aw->cols; c++) {
        buf = &aw_buf[c*2];
        buf[0] = buf[1] = 0;
        boff = 0;
        for (r = 0; r < AW20X_MAX_LEDON_BITS*2; r++) {
            if (r + idx < first) continue;          // only start at the first led
            if (r + idx > last) break;              // and stop at the last

            if (r >= aw->rows) continue;            // max bits to process per row

            if (r == AW20X_MAX_LEDON_BITS) {        // only this many bits per byte
                boff += AW20X_MAX_LEDON_BITS;
                buf++;
            }

            *buf |= (1 << r - boff);
        }
        idx += AW20X_MAX_LEDON_BITS*2;
    }

    AW20X_I2C_writereg(aw->addr, AW20X_REG_LEDON0, aw_buf, c*2);
}

/*
 * disables LEDs based on user LED count, zero-indexed
 * (for example, LEDs 8-12 on AW20054 would enable C0R8, C0R9, C1R0, C1R1)
*/
void aw20x_led_disable(struct AW20x *aw, uint8_t first, uint8_t last)
{

}
