/*
    zssd1306.c - SSD1306 128x64 display
    Copyright (C) 2018 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <libziaint.h>

#include <zssd1306.h>

#define ZSSD1306_CO 0x00
#define ZSSD1306_NOCO 0x80

#define ZSSD1306_COMMAND 0x00
#define ZSSD1306_DATA 0x40

#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_CHARGEPUMP 0x8d
#define SSD1306_SETSEGMENTREMAP 0xa0
#define SSD1306_DISENTIREON 0xa4
#define SSD1306_SETNORMAL 0xa6
#define SSD1306_SETINVERTED 0xa7
#define SSD1306_SETMULTIPLEX 0xa8
#define SSD1306_SETCOMSCANINC 0xc0
#define SSD1306_SETCOMSCANDEC 0xc8
#define SSD1306_SETDISPLAYOFFSET 0xd3
#define SSD1306_SETOSCFREQ 0xd5
#define SSD1306_SETCOMPINS 0xda
#define SSD1306_DISPLAYOFF 0xae         
#define SSD1306_DISPLAYON 0xaf

#include <zbus.h>
#include <zdebug.h>
#include <zi2c.h>
#include <zmisc.h>

#include <string.h>
#include <unistd.h>

void zssd1306_command(struct zbusdev *dev, unsigned char cmd){
    char buf[2];
    buf[0] = ZSSD1306_CO | ZSSD1306_COMMAND; // control byte
    buf[1] = cmd;
    zbus_write(dev, buf, 2);
}

void zssd1306_command2(struct zbusdev *dev, unsigned char cmd, unsigned char arg){
    char buf[3];
    buf[0] = ZSSD1306_CO | ZSSD1306_COMMAND; // control byte
    buf[1] = cmd;
    buf[2] = arg;
    zbus_write(dev, buf, 3);
}


    
int zssd1306_init(struct zbusdev *dev){
    char init[] = {
        0x00,       // command
        0xae,       // display off
        0xa8, 0x3f, // set multiplex
        0xd3, 0x00, // set display offset (vertical shift)
        0x40,       // set start line 0
        0xa0,       // set segment remap 0=seg0
//        0xa1,       // set segment remap 127=seg0
        0xc0,       // set com scan direction 0..N-1
//        0xc8,       // set com scan direction N-1..0
        0xda, 0x12, // set com pins alternative, disable remap
        0x81, 0x80, // set contrast
        0xa4,       // disable entire display on
        0xa6,       // set normal mode
//        0xa7,       // set inverted mode
        0xd5, 0x80, // set oscillator frequency
        0x8d, 0x14, // charge pump
        0x20, 0x02, // page addressing mode
        0xaf,       // display on
    };
    
    int i;
    int ret;
    
    //for (i = 0; i < 5; i++){
        ret = zi2c_write(dev, init, sizeof(init));
    //    if (ret >= 0) break;
    //    usleep(100000);
    //    dbg("zssd1306_init failed, retrying...\n");
    //}

    if (ret < 0) return ret;
    
    int y;
    for (i = 1; i < 2; i++)
    {
        for (y = 0; y < 8; y++){
            char buf[129];
            buf[0] = 0x40;
            memset(buf + 1, i % 2 == 0 ? 255 : 0, 128);
            zssd1306_goto(dev, 0, y);
            zi2c_write(dev, buf, 129);

        }
    }
    zssd1306_flip(dev, 1);
    /*zssd1306_goto(dev, 0, 0);
    zi2c_write(dev, "\x40\xff\x01\x01\x01\x01\x01\x01\x01\x01\xff", 11);
    zssd1306_goto(dev, 0, 1);
    zi2c_write(dev, "\x40\xff\x80\x80\x80\x80\x80\x80\x80\x80\xff", 11);

    zssd1306_goto(dev, 0, 2);
    zi2c_write(dev, "\x40\xff\x01\x01\x01\x01\x01\x01\x01\x01\xff", 11);
    zssd1306_goto(dev, 0, 3);
    zi2c_write(dev, "\x40\xff\x80\x80\x80\x80\x80\x80\x80\x80\xff", 11);*/
    return 0;
}

void zssd1306_free(struct zbusdev *dev){
    if (!dev) return;

    zssd1306_command(dev, 0xae);  // display off
    zbus_free(dev);
}


void zssd1306_flip(struct zbusdev *dev, char flip){
    if (flip){
        zssd1306_command(dev, 0xa1);  // set segment remap 127=seg0
        zssd1306_command(dev, 0xc8);  // set com scan direction N-1..0
    }else{
        zssd1306_command(dev, 0xa0);  // set segment remap 0=seg0 
        zssd1306_command(dev, 0xc0);  // set com scan direction 0..N-1
    }
}


void zssd1306_invert(struct zbusdev *dev, char invert){
    if (invert){
        zssd1306_command(dev, 0xa7);  // set inverted mode 
    }else{
        zssd1306_command(dev, 0xa6);  // set normal mode
    }
}

void zssd1306_contrast(struct zbusdev *dev, unsigned char contrast){
    zssd1306_command2(dev, 0x81, contrast);  // set contrast
}

void zssd1306_goto(struct zbusdev *dev, unsigned char x, unsigned char y){
    unsigned char buf[4];

    buf[0] = ZSSD1306_CO | ZSSD1306_COMMAND; // control byte
    buf[1] = 0xb0 | y;                 // page y
    buf[2] = 0x00 | (x & 0x0f);        // lower column
    buf[3] = 0x10 | ((x >> 4) & 0x0f); // upper column
    zbus_write(dev, buf, sizeof(buf));
}
