/*
 * ssd1306.c
 * begin 20190505 true
 * modified 20241013
 */

#include "ssd1306.h"


SSD1306 oled;
uint8_t oled_fb[((SSD1306_HEIGHT >> 3) * SSD1306_WIDTH) + 1];

uint8_t ssd1306_buf[8];

volatile uint8_t ssd1306_cb_pending;
volatile uint8_t ssd1306_cb_state;


const uint8_t ssd1306_cmd_init[] = {
        0x00,   // command mode, all following bytes are command data
//      0xae,   // display off
        0xd5,   // set display clock div
        0x80,   //   suggested ratio
        0xa8,   // set multiplex
        0x1f,   //   128x32 (0x3f = 128x64)
        0xd3,   // set display offset
        0x00,   //   none
        0x40,   // set start line
        0x8d,   // set charge pump
        0x14,   //   internal VCC
        0x20,   // set memory addressing mode
        0x00,   //   horizontal mode
        0xa1,   // segremap
        0xc8,   // comscandec
        0xda,   // set compins
        0x02,   //
        0x81,   // set contrast
        0xa0,   //   good default value
        0xd9,   // set precharge
        0xf1,   //   internal vcc
        0xdb,   // set vcomdeselect
        0x30,   //
        0x2e,   // disable scroll
        0xa6,   // non-inverted display
        0xa4    // resume display operations
};

const uint8_t ssd1306_cmd_disp_window_def[] = {
        SSD1306_MODE_CMD,
        SSD1306_CMD_COLUMNADDR,     // columns: 0 to (width - 1)
        0,
        SSD1306_WIDTH - 1,
        SSD1306_CMD_PAGEADDR,       // pages (rows*8): 0 to ((height / 8) - 1)
        0,
        (SSD1306_HEIGHT >> 3) - 1
};


void ssd1306_cb(uint8_t p)
{
    ssd1306_cb_pending = 1;
    ssd1306_cb_state = p;
}

uint8_t ssd1306_cb_get()
{
    uint8_t ret;

    ret = ssd1306_cb_pending;
    ssd1306_cb_pending = 0;

    return ret;
}



void ssd1306_idle_wait()
{
/*
    uint16_t timeout = 2000;

    while (!ssd1306_idle()) {
        timeout--;
        if (!timeout) {
#ifdef SSD1306_RESET_ON_COMM_FAIL
            // todo: update reset source
            // RSTSRC |=  RSTSRC_SWRSF__SET;
#else
            // well fuck.
            // reboot the oled
            shiftreg0_auxpwr(0);
            shiftreg_update();
            timeout = 8000;
            while (timeout--) { asm("nop"); }
            shiftreg0_auxpwr(1);
            shiftreg_update();

            // and reset i2c bus (and eventually oled)
            i2c_err_isr(0);
#endif
        }
    }
*/
}

/*
 * sends a single command byte.
 */
void ssd1306_cmd(uint8_t cmd)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = cmd;

    ssd1306_write(ssd1306_buf, 2, 0);

    ssd1306_idle_wait();
}

/*
 * sets up the SSD1306.
 * does not clear the screen, turn on the screen, or send data.
 * you may want to use the callback for that.
 */
void ssd1306_init_step3()
{
    ssd1306_set_display(1);
    oled.callback = 0;
    oled.state = SSD1306_STATE_INITIALIZED | SSD1306_STATE_SET_PIXEL;
}
void ssd1306_init_step2()
{
    ssd1306_cls(&oled);
    ssd1306_update();
    oled.callback = ssd1306_init_step3;
}
void ssd1306_init(uint8_t display_off_during_init)
{
    oled.width = SSD1306_WIDTH;
    oled.height = SSD1306_HEIGHT;
    oled.state = 0;
    oled.mode = oled_fb;
    oled.fb = oled_fb + 1;

    if (display_off_during_init) {
        ssd1306_set_display(1);
    }

    ssd1306_write(ssd1306_cmd_init, (sizeof(ssd1306_cmd_init)), ssd1306_cb);

    if (display_off_during_init) {
        oled.callback = ssd1306_init_step2;
    } else {
        oled.callback = ssd1306_init_step3;
    }
}

void ssd1306_update()
{
    // reset our drawing window
    ssd1306_write(ssd1306_cmd_disp_window_def, (sizeof(ssd1306_cmd_disp_window_def)), 0);
    ssd1306_idle_wait();

    // write our framebuffer
    // since our i2c routine is state driven, and we let the state machine handle it,
    // we can't just send some static data (in this case, first byte selecting mode)
    // and then send our framebuffer. our display requires sending the comm mode
    // byte first before receiving framebuffer data. so we waste a byte to do this...
    // alternative would be to rewrite i2c routines to handle this...
    *oled.mode = SSD1306_MODE_DATA;
    ssd1306_write(oled.mode, ((oled.height >> 3) * oled.width) + 1, ssd1306_cb);
}

void ssd1306_cls(SSD1306 *o)
{
    uint16_t len;
    uint8_t *buf;

    len = ((o->height >> 3) * o->width);
    buf = o->fb;

    while (len--) {
        *buf++ = 0;
    }
}

void ssd1306_set_display(uint8_t display)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = display ? SSD1306_CMD_DISPLAY_ON : SSD1306_CMD_DISPLAY_OFF;

    ssd1306_write(ssd1306_buf, 2, 0);
    ssd1306_idle_wait();
}

void ssd1306_set_invert(uint8_t invert)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = invert ? SSD1306_CMD_INVERT_ON : SSD1306_CMD_INVERT_OFF;

    ssd1306_write(ssd1306_buf, 2, 0);
    ssd1306_idle_wait();
}

void ssd1306_set_contrast(uint8_t contrast)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = SSD1306_CMD_CONTRAST;
    *buf++ = contrast;

    ssd1306_write(ssd1306_buf, 3, 0);
    ssd1306_idle_wait();
}

void ssd1306_set_flip(uint8_t flip)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = flip ? SSD1306_CMD_COMSCANINC : SSD1306_CMD_COMSCANDEC;

    ssd1306_write(ssd1306_buf, 2, 0);
    ssd1306_idle_wait();

    if (flip) {
        oled.state |=  SSD1306_STATE_FLIP;
    } else {
        oled.state &= ~SSD1306_STATE_FLIP;
    }
}

void ssd1306_set_mirror(uint8_t mirror)
{
    uint8_t *buf = ssd1306_buf;

    *buf++ = SSD1306_MODE_CMD;
    *buf++ = mirror ? SSD1306_CMD_SEGREMAP : SSD1306_CMD_SEGREMAP_MIRROR;

    ssd1306_write(ssd1306_buf, 2, 0);
    ssd1306_idle_wait();

    if (mirror) {
        oled.state |=  SSD1306_STATE_MIRROR;
    } else {
        oled.state &= ~SSD1306_STATE_MIRROR;
    }
}

void ssd1306_set_flipmirror(uint8_t flipm)
{
    ssd1306_set_flip(flipm);
    ssd1306_set_mirror(flipm);
}
