#include "ST7789.h"


/** @brief soft reset controller. */
void st7789_sw_rst(){
    st7789_write_cmd_single(ST7789_SWRST);
    st7789_wait_ms(150);
}

/** @brief set sleep status.
 * @param status set to 1 if try to enter sleep in mode.
 * !!! after reset default is sleep in.
 * sleep status will be changed to sleep out during init.
*/
void st7789_sleep(uint8_t status){
    st7789_write_cmd_single(ST7789_SLPIN|(!status));
    if(!status) st7789_wait_ms(5);
    else st7789_wait_ms(120);
};

/** @brief set partial display.
 * @param status set to 1 to enter partial display mode.
 * after reset default normal display mode on.
*/
void st7789_partial_display(uint8_t status){
    if(status)st7789_display(0);   // avoid tearing effect. translate to blne, no loss.
    st7789_write_cmd_single(ST7789_PTLON|(!status));
    if(status)st7789_display(1);
}

/** @brief set display inversion.
 * @param status set to 1 display will invert.
 * after reset inversion is off.
*/
void st7789_inversion(uint8_t status){
    st7789_write_cmd_single(ST7789_INVOFF|(status));
}

/** @brief set gama value.
 *  @param gamma 4bit value GC[0:3].
 *  after reset gamma value is 0x1.
*/
void st7789_gamma(uint8_t gamma){
    st7789_write_cmd_single(ST7789_GAMSET);
    st7789_write_data_single(gamma & 0x0f);
}

/** @brief set display status.
 *  @param status set to 0 display will turn off.
 *  after reset value is display off.
 */
void st7789_display(uint8_t status){
    st7789_write_cmd_single(ST7789_DISPOFF | status);
}

/** @brief set mem write range.
 *  @param start top-left point.
 *  @param end   bottom-right point.
 *  used in partial refresh, the next step is ramwr.
 */
void st7789_setXY_range(struct rect* area){
    st7789_write_cmd_single(ST7789_CASET);
    st7789_write_data(&area->top_left.x, 2);
    st7789_write_data(&area->bottom_right.x, 2);
    st7789_write_cmd_single(ST7789_PASET);
    st7789_write_data(&area->top_left.y, 2);
    st7789_write_data(&area->bottom_right.y, 2);
}

/** @brief write buffer to screen
 *  @param buf frame buffer
 *  @param buflen frame buffer size
 */
void st7789_write_buf(uint8_t* buf, long buflen){
    st7789_write_cmd_single(ST7789_RAMWR);
    st7789_write_data_dma(buf, buflen);
}

#ifdef ST7789_ID_REQUIRED
/** @brief read buffer
 *  @param buf frame buffer
 *  @param buflen frame buffer size 
 */
void st7789_read_buf(uint8_t* buf, long buflen){
    st7789_write_cmd_single(ST7789_RAMRD);
    st7789_read_data(buf, buflen);
}
#endif 

/** @brief set partial display area.
 *  @param start top position.
 *  @param end   bottom position.
 */
void st7789_partial_area(uint16_t start, uint16_t end){
    st7789_write_cmd_single(ST7789_PTLAR);
    st7789_write_data(&start, 2);
    st7789_write_data(&end, 2);
    st7789_partial_display(0);
}

/** @brief set vertical display area.
 *  @param top top fixed area height.
 *  @param view scroll area height.
 *  @param bottom bottom fixed height.
 *  next step is ramwr. 
 *  !!!The condition is TFA+VSA+BFA = 320, otherwise Scrolling mode is undefined. 
 */
void st7789_vertical_scoll(uint16_t top, uint16_t view, uint16_t bottom){
    if(top+view+bottom > 320)return;
    else if(top+view+bottom < 320) bottom = 320 - view - top;
    st7789_write_cmd_single(ST7789_VSCRDEF);
    st7789_write_data(&top, 2);
    st7789_write_data(&view, 2);
    st7789_write_data(&bottom, 2);
    st7789_write_cmd_single(ST7789_PASET);
    st7789_write_data(&top, 2);
    view += top;
    st7789_write_data(&view, 2);
    st7789_write_cmd_single(ST7789_VSCRSADD);
    st7789_write_data_single(0x00);
}


/** @brief: change memory access.
*   @param access memory access value.
*   reset to 0x0000, sw reset not change.
*   MADCTL_*
*/
void st7789_memory_access(uint8_t access){
    st7789_write_cmd_single(ST7789_MADCTL);
    st7789_write_data_single(access);
}

/** @brief: idle mode.
*   @param status set to 0 to exit idle mode.
*   after reset value is 0.
*   in idle mode color reduce to 3bits.
*/
void st7789_idle_mode(uint8_t status){
    st7789_write_cmd_single(ST7789_IDMOFF | status);
}

/** @brief change interface pixel format.
*   @param data colmod byte.
*   PIXFMT_*
*/  
void st7789_pixel_format(uint8_t data){
    st7789_write_cmd_single(ST7789_COLMOD);
    st7789_write_data_single(data);
}

/** @brief change brightness.
*   @param data 0xff means the highest brightness. 
*   default is 0x00. 
*   this command will not change backlight brightness.
*/  
void st7789_brightness(uint8_t brightness){
    st7789_write_cmd_single(ST7789_WRDISBV);
    st7789_write_data_single(brightness);
}



/** @brief change ctrl dispaly register.
 *  @param data register value.
 *  DPCTRL_*
 */
void st7789_ctrl_display(uint8_t data){
    st7789_write_cmd_single(ST7789_WRCTRLD);
    st7789_write_data_single(data);
}


/** @brief change display color enhance.
 *  @param data register value.
 *  EHN_OFF EHN_USER EHN_STILL EHN_MOVING.
 *  EHN_LOW EHN_MEDIUM EHN_HIGH.
 *  default is 0x0000;
 */
void st7789_set_enhance(uint8_t data){
    st7789_write_cmd_single(ST7789_WRCACE);
    st7789_write_data_single(data);
}

/** @brief change minium brightness.
 *  @param data register value.
 *  default is 0x0000;
 */
void st7789_set_minium_brightness(uint8_t data){
    st7789_write_cmd_single(ST7789_WRCABCMB);
    st7789_write_data_single(data);
}


/** @brief ram control
*   @param data register value.
*   @note Little Endian only can be supported in 65K 8-bit and 9-bit interface
*   RAMCTL_MCU RAMCTL_RGB RAMCTL_VSYNC
*   RAMCTL_LE RAMCTL_BE
*   RAMCTL_18BIT RAMCTL_6BIT
*/
void st7789_ramctl(uint16_t data){
    st7789_write_cmd_single(ST7789_RAMCTRL);
    st7789_write_data_single(data | 0xf000);
}



/** @brief RGB Interface Control 
 *  @param data register value.
 *  HSYNC_BACK_PORCH have minium
 *  RGBCTL_*
 */
void st7789_rgbctl(uint32_t data){
    st7789_write_cmd_single(ST7789_RGBCTRL);
    st7789_write_data(&data, 3);
}

/** @brief Porch setting
 *  @param data register value 
 *  PORCTL_*
 *  defalut is 0x08, 0x08, disabled, 0x02, 0x02, 0x02, 0x02 
 */
void st7789_porctrl(uint64_t data){
    st7789_write_cmd_single(ST7789_PORCTRL);
    st7789_write_data(&data, 5);
}   



/** @brief framerate setting in idel mode and partial mode.
 *  @param data register value 
 *  FRCTL_*
 *  defalut is disabled, 60fps.
 */
void st7789_framerate_sp(uint32_t data){
    st7789_write_cmd_single(ST7789_FRCCTRL);
    st7789_write_data(&data, 3);
}

/** @brief framerate setting in idel mode and partial mode.
 *  @param data register value 
 *  defalut is 0x35: 13.26, -10.43 
 */
void st7789_gate_voltage(uint8_t data){
    st7789_write_cmd_single(ST7789_GATECTRL);
    st7789_write_data_single(data);
}

/** @brief vcom voltage setting
 *  @param voltage 0.1V ~ 1.675V
 *  default is 0.9V
 */
void st7789_com_voltage(float voltage){
    st7789_write_cmd_single(ST7789_VCOMS);
    st7789_write_data_single((int)((voltage - 0.1) / 0.025));
}

/** @brief frame rate setting in normal mode
 *  @param framerate FRAMERATE_*Hz
 *  default is 60Hz
 */
void st7789_framerate(uint8_t framerate){
    st7789_write_cmd_single(ST7789_FRCTRL2);
    st7789_write_data_single(framerate);
}

/** @brief fill area with color
 *  @param area  
 *  @param color rgb565 format color
 */
void st7789_fill(struct rect* area, uint16_t color){
    st7789_setXY_range(area);
    st7789_write_same_data_dma(color, (area->bottom_right.x - 
                                       area->top_left.x) * 
                                      (area->bottom_right.y - 
                                       area->top_left.y));
}

/** @brief fill area with color
*   @return dev->id
*/
void st7789_init(struct st7789* dev){
#ifdef ST7789_ID_REQUIRED
    uint8_t buf[20];
#endif

    struct rect area = {
        {0, 0},
        {dev->height, dev->width}
    };

    st7789_hw_rst();
    st7789_sw_rst();

#ifdef ST7789_ID_REQUIRED
    st7789_write_cmd_single(ST7789_RDDID);
    st7789_read_data(buf, 4);
    dev->id = (*(uint64_t*)buf) >> 8;
#endif

    st7789_sleep(0);
    st7789_memory_access(MADCTL_BIT_ORDER_RGB|
                         MADCTL_COLUMN_LEFT_TO_RIGHT|
                         MADCTL_ORDER_NORMAL|
                         MADCTL_PAGE_BOTTOM_TO_TOP);
    st7789_pixel_format(PIXFMT_RGB_65K | PIXFMT_RGB_16BITS);
    st7789_setXY_range(&area);
    st7789_fill(&area, 0x00);
    st7789_partial_display(0);
    st7789_display(1);
}

/** @brief display buffer at poistion.
*/
void st7789_disp_buf_at(struct rect* area, uint8_t* buf){
    st7789_setXY_range(area);
    st7789_write_buf(buf, ((area->bottom_right.x - 
                           area->top_left.x) * 
                          (area->bottom_right.y - 
                           area->top_left.y)) << 1);
}