/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author         Notes
 * 2019-08-29     zdzn           first version
 */

#include <rthw.h>
#include <stdint.h>
#include <rtthread.h>
#include <rtdevice.h>
#include "mbox.h"
#include "drv_fb.h"
#include "mmu.h"
#include "cache.h"

#define LCD_WIDTH     (800)
#define LCD_HEIGHT    (480)
#define LCD_DEPTH     (32)
#define LCD_BPP       (32)

#define TAG_ALLOCATE_BUFFER         0x00040001
#define TAG_SET_PHYS_WIDTH_HEIGHT   0x00048003
#define TAG_SET_VIRT_WIDTH_HEIGHT   0x00048004
#define TAG_SET_DEPTH               0x00048005
#define TAG_SET_PIXEL_ORDER         0x00048006
#define TAG_GET_PITCH               0x00040008
#define TAG_SET_VIRT_OFFSET         0x00048009
#define TAG_END                     0x00000000


enum {
    MBOX_TAG_FB_GET_GPIOVIRT        = 0x00040010,
    MBOX_TAG_FB_ALLOCATE_BUFFER     = 0x00040001,
    MBOX_TAG_FB_RELEASE_BUFFER      = 0x00048001,
    MBOX_TAG_FB_BLANK_SCREEN        = 0x00040002,
    MBOX_TAG_FB_GET_PHYS_WH         = 0x00040003,
    MBOX_TAG_FB_TEST_PHYS_WH        = 0x00044003,
    MBOX_TAG_FB_SET_PHYS_WH         = 0x00048003,
    MBOX_TAG_FB_GET_VIRT_WH         = 0x00040004,
    MBOX_TAG_FB_TEST_VIRT_WH        = 0x00044004,
    MBOX_TAG_FB_SET_VIRT_WH         = 0x00048004,
    MBOX_TAG_FB_GET_DEPTH           = 0x00040005,
    MBOX_TAG_FB_TEST_DEPTH          = 0x00044005,
    MBOX_TAG_FB_SET_DEPTH           = 0x00048005,
    MBOX_TAG_FB_GET_PIXEL_ORDER     = 0x00040006,
    MBOX_TAG_FB_TEST_PIXEL_ORDER    = 0x00044006,
    MBOX_TAG_FB_SET_PIXEL_ORDER     = 0x00048006,
    MBOX_TAG_FB_GET_ALPHA_MODE      = 0x00040007,
    MBOX_TAG_FB_TEST_ALPHA_MODE     = 0x00044007,
    MBOX_TAG_FB_SET_ALPHA_MODE      = 0x00048007,
    MBOX_TAG_FB_GET_PITCH           = 0x00040008,
    MBOX_TAG_FB_GET_VIRT_OFFSET     = 0x00040009,
    MBOX_TAG_FB_TEST_VIRT_OFFSET    = 0x00044009,
    MBOX_TAG_FB_SET_VIRT_OFFSET     = 0x00048009,
    MBOX_TAG_FB_GET_OVERSCAN        = 0x0004000a,
    MBOX_TAG_FB_TEST_OVERSCAN       = 0x0004400a,
    MBOX_TAG_FB_SET_OVERSCAN        = 0x0004800a,
    MBOX_TAG_FB_GET_PALETTE         = 0x0004000b,
    MBOX_TAG_FB_TEST_PALETTE        = 0x0004400b,
    MBOX_TAG_FB_SET_PALETTE         = 0x0004800b,
};

#define LCD_DEVICE(dev)    (struct rt_hdmi_fb_device*)(dev)

static struct rt_hdmi_fb_device _hdmi;

typedef rt_uint16_t color_t;

rt_err_t hdmi_fb_open(rt_device_t dev, rt_uint16_t oflag)
{
    return RT_EOK;
}

rt_err_t hdmi_fb_close(rt_device_t dev)
{
    return RT_EOK;
}

rt_size_t hdmi_fb_read(rt_device_t dev, rt_off_t pos, void *buf, rt_size_t size)
{
    return 0;
}

rt_size_t hdmi_fb_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    return size;
}

rt_err_t hdmi_fb_control(rt_device_t dev, int cmd, void *args)
{
    struct rt_hdmi_fb_device *lcd = LCD_DEVICE(dev);
    switch (cmd)
    {
    case RTGRAPHIC_CTRL_RECT_UPDATE:
        {
            struct rt_device_rect_info *info = (struct rt_device_rect_info*)args;
            info = info;
        }
        break;

    case RTGRAPHIC_CTRL_GET_INFO:
        {
           struct rt_device_graphic_info* info = (struct rt_device_graphic_info*)args;

            RT_ASSERT(info != RT_NULL);
            info->pixel_format  = RTGRAPHIC_PIXEL_FORMAT_RGB888;
            info->bits_per_pixel= LCD_DEPTH;
            info->width         = lcd->width;
            info->height        = lcd->height;
            info->framebuffer   = lcd->fb;
        }
        break;
    }
    return RT_EOK;
}

const static struct rt_device_ops hdmi_fb_ops =
{
    RT_NULL,
    hdmi_fb_open,
    hdmi_fb_close,
    hdmi_fb_read,
    hdmi_fb_write,
    hdmi_fb_control
};

rt_err_t rt_hdmi_fb_device_init(struct rt_hdmi_fb_device *hdmi_fb, const char *name)
{
    struct rt_device *device;
    RT_ASSERT(hdmi_fb != RT_NULL);

    device = &hdmi_fb->parent;

    /* set device type */
    device->type = RT_Device_Class_Graphic;
    /* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
    device->ops = &hdmi_fb_ops;
#else
    device->init = RT_NULL;
    device->open = hdmi_fb_open;
    device->close = hdmi_fb_close;
    device->read = hdmi_fb_read;
    device->write = hdmi_fb_write;
    device->control = hdmi_fb_control;
#endif

    /* register to device manager */
    rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);

    return RT_EOK;
}

rt_uint32_t bcm283x_mbox_fb_get_gpiovirt(void)
{
    mbox[0] = 8*4;                      // length of the message
    mbox[1] = MBOX_REQUEST;             // this is a request message

    mbox[2] = MBOX_TAG_FB_GET_GPIOVIRT;
    mbox[3] = 4;                        // buffer size
    mbox[4] = 0;                        // len

    mbox[5] = 0;                        // id
    mbox[6] = 0;

    mbox[7] = MBOX_TAG_LAST;
    mbox_call(8, MMU_DISABLE);
    return (mbox[5] & 0x3fffffff);
}

rt_uint32_t bcm283x_mbox_fb_get_pitch(void)
{
    mbox[0] = 8*4;                  // length of the message
    mbox[1] = MBOX_REQUEST;         // this is a request message

    mbox[2] = MBOX_TAG_FB_GET_PITCH;
    mbox[3] = 4;                    // buffer size
    mbox[4] = 0;                    // len

    mbox[5] = 0;                    // id
    mbox[6] = 0;

    mbox[7] = MBOX_TAG_LAST;
    mbox_call(8, MMU_DISABLE);
    return mbox[5];
}

void bcm283x_mbox_fb_set_porder(int rgb)
{
    mbox[0] = 8*4;                      // length of the message
    mbox[1] = MBOX_REQUEST;             // this is a request message

    mbox[2] = MBOX_TAG_FB_SET_PIXEL_ORDER;
    mbox[3] = 4;                        // buffer size
    mbox[4] = 4;                        // len

    mbox[5] = rgb;                      // id
    mbox[6] = 0;

    mbox[7] = MBOX_TAG_LAST;
    mbox_call(8, MMU_DISABLE);
}

void bcm283x_mbox_fb_setoffset(int xoffset, int yoffset)
{
    mbox[0] = 8*4;                      // length of the message
    mbox[1] = MBOX_REQUEST;             // this is a request message

    mbox[2] = MBOX_TAG_FB_SET_VIRT_OFFSET;
    mbox[3] = 8;                        // buffer size
    mbox[4] = 8;                        // len

    mbox[5] = xoffset;                  // id
    mbox[6] = yoffset;

    mbox[7] = MBOX_TAG_LAST;
    mbox_call(8, MMU_DISABLE);
}


void bcm283x_mbox_fb_setalpha(int alpha)
{

    mbox[0] = 8*4;                      // length of the message
    mbox[1] = MBOX_REQUEST;             // this is a request message

    mbox[2] = MBOX_TAG_FB_SET_ALPHA_MODE;
    mbox[3] = 4;                        // buffer size
    mbox[4] = 4;                        // len

    mbox[5] = alpha;                    // id
    mbox[6] = 0;

    mbox[7] = MBOX_TAG_LAST;
    mbox_call(8, MMU_DISABLE);
}

void *bcm283x_mbox_fb_alloc(int width, int height, int bpp, int nrender)
{
    mbox[0] = 4 * 35;
    mbox[1] = MBOX_REQUEST;

    mbox[2] = TAG_ALLOCATE_BUFFER;//get framebuffer, gets alignment on request
    mbox[3] = 8;                  //size
    mbox[4] = 4;                  //len
    mbox[5] = 4096;               //The design of MBOX driver forces us to give the virtual address 0x3C100000
    mbox[6] = 0;                  //FrameBufferInfo.size

    mbox[7] = TAG_SET_PHYS_WIDTH_HEIGHT;
    mbox[8] = 8;
    mbox[9] = 8;
    mbox[10] = width;
    mbox[11] = height;

    mbox[12] = TAG_SET_VIRT_WIDTH_HEIGHT;
    mbox[13] = 8;
    mbox[14] = 8;
    mbox[15] = width;
    mbox[16] = height * nrender;

    mbox[17] = TAG_SET_DEPTH;
    mbox[18] = 4;
    mbox[19] = 4;
    mbox[20] = bpp;

    mbox[21] = TAG_SET_PIXEL_ORDER;
    mbox[22] = 4;
    mbox[23] = 0;
    mbox[24] = 0;                    //RGB, not BGR preferably

    mbox[25] = TAG_GET_PITCH;
    mbox[26] = 4;
    mbox[27] = 0;
    mbox[28] = 0;

    mbox[29] = TAG_SET_VIRT_OFFSET;
    mbox[30] = 8;
    mbox[31] = 8;
    mbox[32] = 0;
    mbox[33] = 0;

    mbox[34] = TAG_END;

    mbox_call(MBOX_CH_PROP, MMU_DISABLE);

    return (void *)((rt_uint64_t)(mbox[5] & 0x3fffffff));
}

int hdmi_fb_init(void)
{
    _hdmi.fb = (rt_uint8_t *)bcm283x_mbox_fb_alloc(LCD_WIDTH, LCD_HEIGHT, LCD_BPP, 1);
    bcm283x_mbox_fb_setoffset(0, 0);
    bcm283x_mbox_fb_set_porder(0);
    _hdmi.width = LCD_WIDTH;
    _hdmi.height = LCD_HEIGHT;
    _hdmi.depth = LCD_DEPTH;
    _hdmi.pitch = 0;
    _hdmi.pixel_format = RTGRAPHIC_PIXEL_FORMAT_RGB888;

    // rt_hw_mmu_map(&mmu_info, (unsigned long)_hdmi.fb, (void *)0x200000, DEVICE_MEM);

    rt_hw_cpu_dcache_invalidate((unsigned long)_hdmi.fb,LCD_WIDTH * LCD_HEIGHT * 3);

    //rt_kprintf("_hdmi.fb is %p\n", _hdmi.fb);
    rt_hdmi_fb_device_init(&_hdmi, "lcd");

    return 0;
}

INIT_DEVICE_EXPORT(hdmi_fb_init);