/*
   Copyright (c) 2003 Andreas Robinson, All rights reserved.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

#include <config.h>

#include <sys/io.h>

#include "unichrome.h"
#include "uc_overlay.h"
#include "vidregs.h"
#include "mmio.h"

#include <direct/messages.h>

#include <core/system.h>

/**
 * Set up the extended video FIFO.
 * @note It will be turned on if ucovl->scrwidth > 1024.
 */

void uc_ovl_setup_fifo(UcOverlayData* ucovl, int scrwidth)
{
     u8* mclk_save = ucovl->mclk_save;

     if (!iopl(3)) {
          if (scrwidth <= 1024) { // Disable
               if (ucovl->extfifo_on) {

                    dfb_layer_wait_vsync(dfb_layer_at(DLID_PRIMARY));

                    outb(0x16, 0x3c4); outb(mclk_save[0], 0x3c5);
                    outb(0x17, 0x3c4); outb(mclk_save[1], 0x3c5);
                    outb(0x18, 0x3c4); outb(mclk_save[2], 0x3c5);
                    ucovl->extfifo_on = false;
               }
          }
          else { // Enable
               if (!ucovl->extfifo_on) {

                    dfb_layer_wait_vsync(dfb_layer_at(DLID_PRIMARY));

                    // Save current setting
                    outb(0x16, 0x3c4); mclk_save[0] = inb(0x3c5);
                    outb(0x17, 0x3c4); mclk_save[1] = inb(0x3c5);
                    outb(0x18, 0x3c4); mclk_save[2] = inb(0x3c5);
                    // Enable extended FIFO
                    outb(0x17, 0x3c4); outb(0x2f, 0x3c5);
                    outb(0x16, 0x3c4); outb((mclk_save[0] & 0xf0) | 0x14, 0x3c5);
                    outb(0x18, 0x3c4); outb(0x56, 0x3c5);
                    ucovl->extfifo_on = true;
               }
          }
     }
     else {
          D_BUG( "Unichrome: could set io perissons\n" );
     }
     ucovl->scrwidth = scrwidth;
}

void uc_ovl_vcmd_wait(volatile u8* vio)
{
     while ((VIDEO_IN(vio, V_COMPOSE_MODE)
             & (V1_COMMAND_FIRE | V3_COMMAND_FIRE)));
}

/**
 * Update the video overlay.
 *
 * @param action        = UC_OVL_CHANGE: update everything
 *                      = UC_OVL_FLIP: only flip to the front surface buffer.
 * @param surface       source surface
 *
 * @note: Derived from ddmpeg.c, Upd_Video()
 */

DFBResult uc_ovl_update(UcDriverData* ucdrv,
                        UcOverlayData* ucovl,
                        int action,
                        CoreSurface* surface,
                        CoreSurfaceBufferLock* lock)
{
     int sw, sh, sp, sfmt;   // Source width, height, pitch and format
     int dx, dy;             // Destination position
     int dw, dh;             // Destination width and height
     int pfetch;             // Source pixels required for one line
     VideoMode *videomode;
     DFBRectangle scr;       // Screen size
     u32 dst_key = 0;        // Destination color key (hw format)

     bool write_buffers = false;
     bool write_settings = false;

     volatile u8* vio = ucdrv->hwregs;

     u32 win_start, win_end;   // Overlay register settings
     u32 zoom, mini;
     u32 dcount, falign, qwfetch;
     u32 y_start, u_start, v_start;
     u32 v_ctrl, fifo_ctrl;
     u32 alpha = 0;

     int offset = lock->offset;

     if (!ucovl->v1.isenabled) return DFB_OK;

     qwfetch = 0;

     // Get screen size
     videomode = dfb_system_current_mode();
     scr.w = videomode ? videomode->xres : 720;
     scr.h = videomode ? videomode->yres : 576;
     scr.x = 0;
     scr.y = 0;

     if (ucovl->scrwidth != scr.w) {
          // FIXME: fix uc_ovl_setup_fifo()
          //    uc_ovl_setup_fifo(ucovl, scr.w);
          action |= UC_OVL_CHANGE;
     }

     D_ASSERT(surface);

     sw = surface->config.size.w;
     sh = surface->config.size.h;
     sp = lock->pitch;
     sfmt = surface->config.format;

     if (ucovl->deinterlace) {
          /*if (ucovl->field)
               offset += sp;*/

          sh /= 2;
          //sp *= 2;
     }

     if (action & UC_OVL_CHANGE) {

          if ((sw > 4096) || (sh > 4096) ||
              (sw < 32) || (sh < 1) || (sp > 0x1fff)) {
               D_DEBUG("Layer surface size is out of bounds.");
               return DFB_INVAREA;
          }

          dx = ucovl->v1.win.x;
          dy = ucovl->v1.win.y;
          dw = ucovl->v1.win.w;
          dh = ucovl->v1.win.h;

          // Get image format, FIFO size, etc.

          uc_ovl_map_v1_control(sfmt, sw, ucovl->hwrev, ucovl->extfifo_on,
                                &v_ctrl, &fifo_ctrl);

          if (ucovl->deinterlace) {
               v_ctrl |= /*V1_BOB_ENABLE |*/ V1_FRAME_BASE;
          }

          // Get layer window.
          // The parts that fall outside the screen are clipped.

          uc_ovl_map_window(scr.w, scr.h, &(ucovl->v1.win), sw, sh,
                            &win_start, &win_end,
                            &ucovl->v1.ox, &ucovl->v1.oy, &pfetch);

          // Get scaling and data-fetch parameters

          // Note: the *_map_?zoom() functions return false if the scaling
          // is out of bounds. We don't act on it for now, because it only
          // makes the display look strange.

          zoom = 0;
          mini = 0;

          uc_ovl_map_vzoom(sh, dh, &zoom, &mini);
          uc_ovl_map_hzoom(sw, dw, &zoom, &mini, &falign, &dcount);
          qwfetch = uc_ovl_map_qwfetch(falign, sfmt, pfetch);

          // Prepare destination color key
          dst_key = uc_ovl_map_colorkey(&(ucovl->v1.dst_key));

          // prepare opacity
          if (ucovl->v1.level > 0)  // overlay
               alpha = uc_ovl_map_alpha(ucovl->v1.opacity);
          else
               alpha = uc_ovl_map_alpha(ucovl->opacity_primary);
          
          write_settings = true;
     }

     if (action & (UC_OVL_FIELD | UC_OVL_FLIP | UC_OVL_CHANGE)) {
          int field = 0;
          // Update the buffer pointers

          if (ucovl->deinterlace) {
               field = ucovl->field;
          }

          uc_ovl_map_buffer(sfmt, offset,
                            ucovl->v1.ox, ucovl->v1.oy, sw, surface->config.size.h, sp, 0/*field*/, &y_start,
                            &u_start, &v_start);

          if (field) {
               y_start |= 0x08000000;
          }

          write_buffers = true;
     }

     // Write to the hardware

/*    if (write_settings || write_buffers)
        uc_ovl_vcmd_wait(vio);*/

     if (write_settings) {

          VIDEO_OUT(vio, V1_CONTROL, v_ctrl);
          VIDEO_OUT(vio, V_FIFO_CONTROL, fifo_ctrl);

          VIDEO_OUT(vio, V1_WIN_START_Y, win_start);
          VIDEO_OUT(vio, V1_WIN_END_Y, win_end);

          VIDEO_OUT(vio, V1_SOURCE_HEIGHT, (sh << 16) | dcount);
          VIDEO_OUT(vio, V12_QWORD_PER_LINE, qwfetch);
          VIDEO_OUT(vio, V1_STRIDE, sp | ((sp >> 1) << 16));

          VIDEO_OUT(vio, V1_MINI_CONTROL, mini);
          VIDEO_OUT(vio, V1_ZOOM_CONTROL, zoom);

          VIDEO_OUT(vio, V_COLOR_KEY, dst_key);
          
          VIDEO_OUT(vio, V_ALPHA_CONTROL, alpha);
     }

     if (write_buffers) {

          VIDEO_OUT(vio, V1_STARTADDR_0, y_start);
          VIDEO_OUT(vio, V1_STARTADDR_CB0, u_start);
          VIDEO_OUT(vio, V1_STARTADDR_CR0, v_start);
     }

     if (write_settings || write_buffers) {
          VIDEO_OUT(vio, V_COMPOSE_MODE, V1_COMMAND_FIRE |
              (ucovl->v1.dstkey_enabled ? ENABLE_COLOR_KEYING : 0));
     }

     return DFB_OK;
}

DFBResult uc_ovl_set_adjustment(CoreLayer            *layer,
                                void                 *driver_data,
                                void                 *layer_data,
                                DFBColorAdjustment   *adj)
{
    UcOverlayData* ucovl = (UcOverlayData*) layer_data;
    UcDriverData*  ucdrv = (UcDriverData*) driver_data;
    DFBColorAdjustment* ucadj;
    u32 a1, a2;

    ucadj = &ucovl->v1.adj;

    if (adj->flags & DCAF_BRIGHTNESS)
        ucadj->brightness = adj->brightness;
    if (adj->flags & DCAF_CONTRAST)
        ucadj->contrast = adj->contrast;
    if (adj->flags & DCAF_HUE)
        ucadj->hue = adj->hue;
    if (adj->flags & DCAF_SATURATION)
        ucadj->saturation = adj->saturation;

    uc_ovl_map_adjustment(ucadj, &a1, &a2);

    VIDEO_OUT(ucdrv->hwregs, V1_ColorSpaceReg_1, a1);
    VIDEO_OUT(ucdrv->hwregs, V1_ColorSpaceReg_2, a2);

    return DFB_OK;
}
