/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020/12/31     Bernard      Add license info
 */
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include <rtthread.h>
#ifdef RT_USING_SMART
#include <lwp.h>
#include <lwp_user_mm.h>
#endif
#include <board.h>

#include "drv_clcd.h"
#include "rt_lcd.h"

#define CLCD_WIDTH  (BSP_LCD_WIDTH)
#define CLCD_HEIGHT (BSP_LCD_HEIGHT)

#define CLCD_DEVICE(dev)    (struct drv_clcd_device*)(dev)

#define PL111_CR_EN         0x001
#define PL111_CR_PWR        0x800
#define PL111_IOBASE        0x10020000
#define PL111_PALBASE       (PL111_IOBASE + 0x200)

typedef struct _PL111MMIO
{
    uint32_t      volatile tim0;      //0
    uint32_t      volatile tim1;      //4
    uint32_t      volatile tim2;      //8
    uint32_t      volatile tim3;      //c
    uint32_t      volatile upbase;    //10
    uint32_t      volatile f;         //14
    uint32_t      volatile control;   //18
    uint32_t      volatile g;         //1c
} PL111MMIO;

struct drv_clcd_device
{
    struct rt_device parent;

    int width;
    int height;

    uint8_t *fb;
};
struct drv_clcd_device _lcd;

static rt_err_t drv_clcd_init(struct rt_device *device)
{
    struct drv_clcd_device *lcd = CLCD_DEVICE(device);

    (void)lcd; /* nothing, right now */
    return RT_EOK;
}

static rt_err_t drv_clcd_control(struct rt_device *device, int cmd, void *args)
{
    struct drv_clcd_device *lcd = CLCD_DEVICE(device);

    switch (cmd)
    {
    case RTGRAPHIC_CTRL_RECT_UPDATE:
        {
            struct rt_device_rect_info *info = (struct rt_device_rect_info*)args;

            info = info; /* nothing, right now */
        }
        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_RGB565;
            info->bits_per_pixel= 16;
            info->width         = lcd->width;
            info->height        = lcd->height;
            info->framebuffer   = lcd->fb;
        }
        break;

    case FBIOGET_FSCREENINFO:
    {
#ifdef RT_USING_SMART
        struct fb_fix_screeninfo *info = (struct fb_fix_screeninfo *)args;
        strncpy(info->id, "lcd", sizeof(info->id));
        info->smem_len    = lcd->width * lcd->height * 2;
        info->smem_start  = (uint32_t)lwp_map_user_phy(lwp_self(), RT_NULL, lcd->fb,
            info->smem_len, 1);
        info->line_length = lcd->width * 2;
#endif
    }
        break;

    case FBIOGET_VSCREENINFO:
    {
        struct fb_var_screeninfo *info = (struct fb_var_screeninfo *)args;
        info->bits_per_pixel = 16;
        info->xres = lcd->width;
        info->yres = lcd->height;
    }
        break;

    case FBIOGET_DISPINFO:
        break;
    }

    return RT_EOK;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops clcd_ops =
{
    drv_clcd_init,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    drv_clcd_control
};
#endif

int drv_clcd_hw_init(void)
{
    PL111MMIO   *plio;
    struct rt_device *device = &_lcd.parent;

    /* memset _lcd to zero */
    memset(&_lcd, 0x0, sizeof(_lcd));

    _lcd.width  = CLCD_WIDTH;
    _lcd.height = CLCD_HEIGHT;
    rt_kprintf("try to allocate fb... | w - %d, h - %d | ", _lcd.width, _lcd.height);
#ifdef RT_USING_SMART
    _lcd.fb = rt_pages_alloc(rt_page_bits(_lcd.width * _lcd.height * 2));
#else
    _lcd.fb = rt_malloc(_lcd.width * _lcd.height * 2);
#endif
    rt_kprintf("done!\n");
    rt_kprintf("fb => 0x%08x\n", _lcd.fb);
    if (_lcd.fb == NULL)
    {
        rt_kprintf("initialize frame buffer failed!\n");
        return -1;
    }
    memset(_lcd.fb, 0xff, _lcd.width * _lcd.height * 2);

    plio = (PL111MMIO*)PL111_IOBASE;
#ifdef RT_USING_SMART
    plio = (PL111MMIO *)rt_ioremap((void*)PL111_IOBASE, 0x1000);
#endif
    plio->tim0 = 0x3F1F3C00 | ((CLCD_WIDTH / 16 - 1) << 2);
    plio->tim1 = 0x080B6000 | (CLCD_HEIGHT - 1);

    plio->upbase = (uint32_t)_lcd.fb;
#ifdef RT_USING_SMART
    plio->upbase += PV_OFFSET;
#endif
    /* 16-bit 565 color */
    plio->control = 0x1921 | (0x6 << 1);

    device->type    = RT_Device_Class_Graphic;
#ifdef RT_USING_DEVICE_OPS
    device->ops     = &clcd_ops;
#else
    device->init    = drv_clcd_init;
    device->control = drv_clcd_control;
#endif

    rt_device_register(device, "lcd", RT_DEVICE_FLAG_RDWR);

    return 0;
}
INIT_DEVICE_EXPORT(drv_clcd_hw_init);