/*
 * iTVC15 Framebuffer driver
 *
 * This module presents the iTVC15 OSD (onscreen display) framebuffer memory 
 * as a standard Linux /dev/fb style framebuffer device. The framebuffer has
 * a 32 bpp packed pixel format with full alpha channel support. Depending
 * on the TV standard configured in the ivtv module at load time, resolution
 * is fixed at either 720x480 (NTSC) or 720x576 (PAL).
 *
 * Copyright (c) 2003 Matt T. Yourst <yourst@yourst.com>
 *
 * Derived from drivers/video/vesafb.c
 * Portions (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
 *
 * This file is licensed under the GNU General Public License, version 2.
 *
 */

/*
#
# Instructions for making ivtv-fb work with XFree86:
# Add the following sections and parts thereof to /etc/X11/XF86Config:
#

#
# NOTE: The monitor section is obtainable by running:
# fbset -fb /dev/fb1 -x
# (or /dev/fbX for whatever framebuffer ivtv-fb is on)
#
Section "Monitor"
    Identifier  "NTSC Monitor"
    HorizSync  30-68
    VertRefresh 50-120
    Mode "720x480"
      # D: 34.563 MHz, H: 37.244 kHz, V: 73.897 Hz
      DotClock 34.564
      HTimings 720 752 840 928
      VTimings 480 484 488 504
      Flags    "-HSync" "-VSync"
    EndMode
EndSection

Section "Device"
    Identifier  "Hauppauge PVR 350 iTVC15 Framebuffer"
    Driver      "fbdev"
    Option      "fbdev" "/dev/fb1"      # <-- modify if using another device
    BusID "0:10:0"
EndSection

Section "Screen"
  Identifier  "TV Screen"
  Device      "Hauppauge PVR 350 iTVC15 Framebuffer"
  Monitor     "NTSC Monitor"
  DefaultDepth 24
  DefaultFbbpp 32
  Subsection "Display"
    Depth 24
    FbBpp 32
    Modes "720x480"
  EndSubsection
EndSection

Section "ServerLayout"
  ...

  Screen 0 "Screen 1"                      # << (your computer monitor)

  # (add the following line)
  Screen 1 "TV Screen" RightOf "Screen 1"  # << (TV screen)

  ...
EndSection

#
# Then start X as usual; both your normal (computer) monitor and the
# NTSC or PAL TV monitor should display the default X background.
#
# Note the "RightOf" clause above: if you move the mouse off the right
# side of the computer screen, the pointer should appear on your TV
# screen. Keyboard events will go to windows in either screen.
#
# To start a program (e.g., xterm) on the TV only:
#
# export DISPLAY=:0.1         (i.e., X server #0, screen #1 = TV)
# xterm&
#
# There is also a way to join both the computer monitor and TV into
# one giant virtual screen using the Xinerama extension, but I haven't 
# tried it. Doing so may not be such a good idea anyway, as you obviously
# wouldn't want random X windows getting moved over the TV picture.


A note on unloading the fb driver:

If you want to be able to unload the framebuffer driver (and you aren't
already using fbcon),  add this to your lilo config:

video=vc:x-y

where x is the first fb device to allocate and y is the second. If you 
already have a fb driver loaded, fiddle with the numbers so all the consoles
are already allocated. For me, i just set it to 0-0, ie:

in lilo.conf:

image=/vmlinuz
        label=linux
        read-only
        append="root=/dev/hda1 video=vc:0-0"

--OR--
on bootup, do this
LILO: linux video=vc:0-0

according to how i read /usr/src/linux/drivers/video/fbmem.c and
/usr/src/linux/drivers/char/console.c, that should disable the
console hijacks, and allow you to unload the driver.

-tmk
#
#
#
#
#
#
#
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/fb.h>
#include <linux/console.h>
#include <linux/bitops.h>
#include <linux/pagemap.h>

#include <asm/io.h>
#include <asm/ioctl.h>

#ifdef CONFIG_MTRR
#include <asm/mtrr.h>
#endif

#include <video/fbcon.h>
#include <video/fbcon-cfb32.h>

#include "ivtv.h"

/*
 * card parameters
 */

static int ivtv_fb_card_id;

/* Card selected as framebuffer for this module instance: */
static struct ivtv *ivtv_fb;

/* card */
static unsigned long video_base;	/* physical addr */
static unsigned long video_rel_base;	/* address relative to base of decoder memory */
static int video_size;
static char *video_vbase;		/* mapped */

/* mode */
static int video_width;
static int video_height;
static int video_height_virtual;
static int video_linelength;
static unsigned long shadow_framebuf_offset;
static unsigned long shadow_framebuf_size;


/*
 * ivtv API calls for framebuffer related support
 */

static inline int ivtv_api_fb_get_framebuffer(struct ivtv *itv,
					      void **fbbase, int *fblength)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    int rc;

    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_framebuffer\n");

    rc = ivtv_api(itv->dec_mbox, &itv->sem_lock,
		 IVTV_API_FB_GET_FRAMEBUFFER, &result, 0, &data[0]);
    *fbbase = (void *) data[0];
    *fblength = data[1];
    return rc;
}

static inline int ivtv_api_fb_get_pixel_format(struct ivtv *itv)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_pixel_format\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_PIXEL_FORMAT,
	     &result, 0, &data[0]);
    return data[0];
}

static inline int ivtv_api_fb_set_pixel_format(struct ivtv *itv,
					       int format)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    data[0] = format;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_pixel_format\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock,
	     IVTV_API_FB_SET_PIXEL_FORMAT, &result, 1, &data[0]);
    return result;
}

static inline int ivtv_api_fb_get_state(struct ivtv *itv)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_state\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_STATE,
	     &result, 0, &data[0]);
    return data[0];
}

static inline int ivtv_api_fb_set_state(struct ivtv *itv, int enabled)
{
    u32 params[IVTV_MBOX_MAX_DATA], result;
    params[0] = enabled;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_state\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_SET_STATE,
	     &result, 1, &params[0]);
    return result;
}

static inline int ivtv_api_fb_set_framebuffer_window(struct ivtv *itv,
						     int left, int top,
						     int width, int height)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_framebuffer_window\n");
    data[0] = width;
    data[1] = height;
    data[2] = left;
    data[3] = top;

    ivtv_api(itv->dec_mbox, &itv->sem_lock,
	     IVTV_API_FB_SET_FRAMEBUFFER_WINDOW, &result, 4, &data[0]);
    return result;
}

static inline int ivtv_api_fb_get_osd_coords(struct ivtv *itv,
					     struct ivtv_osd_coords *osd)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_osd_coords\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_OSD_COORDS,
	     &result, 0, &data[0]);

    osd->offset = data[0] - video_rel_base;
    osd->max_offset = video_size;
    osd->pixel_stride = data[1];
    osd->lines = data[2];
    osd->x = data[3];
    osd->y = data[4];

    return result;
}

static inline int ivtv_api_fb_set_osd_coords(struct ivtv *itv,
					     const struct ivtv_osd_coords
					     *osd)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_osd_coords\n");
    data[0] = osd->offset + video_rel_base;
    data[1] = osd->pixel_stride;
    data[2] = osd->lines;
    data[3] = osd->x;
    data[4] = osd->y;

    // FIXME maybe wait on vsync?
    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_SET_OSD_COORDS,
	     &result, 5, &data[0]);
    return result;
}

static inline int ivtv_api_fb_get_screen_coords(struct ivtv *itv,
						struct rectangle *r)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_screen_coords\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_SCREEN_COORDS,
	     &result, 0, &data[0]);

    r->x0 = data[0];
    r->y0 = data[1];
    r->x1 = data[2];
    r->y1 = data[3];

    return result;
}

static inline int ivtv_api_fb_set_screen_coords(struct ivtv *itv,
						const struct rectangle *r)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_screen_coords\n");
    data[0] = r->x0;
    data[1] = r->y0;
    data[2] = r->x1;
    data[3] = r->y1;

    ivtv_api(itv->dec_mbox, &itv->sem_lock,
	     IVTV_API_FB_SET_SCREEN_COORDS, &result, 4, &data[0]);
    return result;
}

static inline int ivtv_api_fb_get_global_alpha(struct ivtv *itv)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_global_alpha\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_GLOBAL_ALPHA,
	     &result, 0, &data[0]);
    return data[1];
}

static inline int ivtv_api_fb_set_global_alpha(struct ivtv *itv,
					       int enable_global,
					       int alpha, int enable_local)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_global_alpha\n");
    data[0] = enable_global;
    data[1] = alpha;
    data[2] = !enable_local;
    ivtv_api(itv->dec_mbox, &itv->sem_lock,
	     IVTV_API_FB_SET_GLOBAL_ALPHA, &result, 3, &data[0]);
    return result;
}

static inline int ivtv_api_fb_get_flicker_state(struct ivtv *itv)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_get_flicker_state\n");

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_GET_FLICKER_STATE,
	     &result, 0, &data[0]);
    return data[0];
}

static inline int ivtv_api_fb_set_flicker_state(struct ivtv *itv,
						int enabled)
{
    u32 params[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_set_flicker_state\n");
    params[0] = enabled;

    ivtv_api(itv->dec_mbox, &itv->sem_lock,
	     IVTV_API_FB_SET_FLICKER_STATE, &result, 1, &params[0]);
    return result;
}

static inline int ivtv_api_fb_blt_fill(struct ivtv *itv, int rasterop,
				       int alpha_mode, int alpha_mask_mode,
				       int width, int height, int destmask,
				       u32 destaddr, int deststride,
				       u32 value)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_blt_fill\n");
    data[0] = rasterop;
    data[1] = alpha_mode;
    data[2] = alpha_mask_mode;
    data[3] = width;
    data[4] = height;
    data[5] = destmask;
    data[6] = destaddr;
    data[7] = deststride;
    data[8] = value;

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_BLT_FILL, &result,
	     9, &data[0]);
    return result;
}

static inline int ivtv_api_fb_blt_copy(struct ivtv *itv, int rasterop,
				       int alpha_mode, int alpha_mask_mode,
				       int width, int height, int destmask,
				       u32 destaddr, int deststride,
				       int sourcestride, int sourceaddr)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_api_fb_blt_copy: width = %d, height = %d, destaddr = %d, deststride = %d, sourcestride = %d, sourceaddr = %d\n",
	 width, height, destaddr, deststride, sourcestride, sourceaddr);

    data[0] = rasterop;
    data[1] = alpha_mode;
    data[2] = alpha_mask_mode;
    data[3] = width;
    data[4] = height;
    data[5] = destmask;
    data[6] = destaddr;
    data[7] = deststride;
    data[8] = sourcestride;
    data[9] = sourceaddr;

    ivtv_api(itv->dec_mbox, &itv->sem_lock, IVTV_API_FB_BLT_COPY, &result,
	     10, &data[0]);
    return result;
}


MODULE_PARM(ivtv_fb_card_id, "i");
MODULE_PARM_DESC(ivtv_fb_card_id,
		 "ID number of ivtv card to use as framebuffer device (0-2)");

MODULE_LICENSE("GPL");


/* --------------------------------------------------------------------- */

static struct fb_var_screeninfo ivtvfb_defined = {
    0, 0, 0, 0,			/* W,H, W, H (virtual) load xres,xres_virtual */
    0, 0,			/* virtual -> visible no offset */
    32,				/* depth -> load bits_per_pixel */
    0,				/* greyscale ? */
    {0, 0, 0},			/* R */
    {0, 0, 0},			/* G */
    {0, 0, 0},			/* B */
    {0, 0, 0},			/* transparency */
    0,				/* standard pixel format */
    FB_ACTIVATE_NOW,
    -1, -1,
    0,
    0L, 0L, 0L, 0L, 0L,
    0L, 0L, 0,			/* No sync info */
    FB_VMODE_NONINTERLACED,
    {0, 0, 0, 0, 0, 0}
};

static struct display disp;
static struct fb_info fb_info;
static union {
    u32 cfb32[16];
} fbcon_cmap;

#ifdef CONFIG_MTRR
static int mtrr = 1;		//++MTY
static unsigned long fb_start_aligned_physaddr;	/* video_base rounded down as required by hardware MTRRs */
static unsigned long fb_end_aligned_physaddr;	/* video_base rounded up as required by hardware MTRRs */
#endif

static struct display_switch ivtvfb_sw;

/* --------------------------------------------------------------------- */

static int ivtvfb_update_var(int con, struct fb_info *info)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_update_var\n");
    return 0;
}

static int ivtvfb_get_fix(struct fb_fix_screeninfo *fix, int con,
			  struct fb_info *info)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_get_fix\n");
    memset(fix, 0, sizeof(struct fb_fix_screeninfo));
    strcpy(fix->id, "iTVC15 TV out");

    fix->smem_start = video_base;
    fix->smem_len = video_size;
    fix->type = FB_TYPE_PACKED_PIXELS;
    fix->visual = FB_VISUAL_TRUECOLOR;
    fix->xpanstep = 0;
    fix->ypanstep = 0;
    fix->ywrapstep = 0;
    fix->line_length = video_linelength;
    return 0;
}

static int ivtvfb_get_var(struct fb_var_screeninfo *var, int con,
			  struct fb_info *info)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_get_var\n");
    if (con == -1)
	memcpy(var, &ivtvfb_defined, sizeof(struct fb_var_screeninfo));
    else
	*var = fb_display[con].var;
    return 0;
}

static void ivtvfb_set_disp(int con)
{
    struct fb_fix_screeninfo fix;
    struct display *display;
    struct display_switch *sw;

    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_set_disp\n");
    if (con >= 0)
	display = &fb_display[con];
    else
	display = &disp;	/* used during initialization */

    ivtvfb_get_fix(&fix, con, 0);

    memset(display, 0, sizeof(struct display));
    display->screen_base = video_vbase;
    display->visual = fix.visual;
    display->type = fix.type;
    display->type_aux = fix.type_aux;
    display->ypanstep = fix.ypanstep;
    display->ywrapstep = fix.ywrapstep;
    display->line_length = fix.line_length;
    display->next_line = fix.line_length;
    display->can_soft_blank = 0;
    display->inverse = 0;
    ivtvfb_get_var(&display->var, -1, &fb_info);

    sw = &fbcon_cfb32;
    display->dispsw_data = fbcon_cmap.cfb32;
    memcpy(&ivtvfb_sw, sw, sizeof(*sw));
    display->dispsw = &ivtvfb_sw;
    display->scrollmode = SCROLL_YREDRAW;
    ivtvfb_sw.bmove = fbcon_redraw_bmove;
}

static int ivtvfb_set_var(struct fb_var_screeninfo *var, int con,
			  struct fb_info *info)
{
    int first=0;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_set_var\n");
    if (con >= 0) first = 1;

    if (var->xres != ivtvfb_defined.xres ||
	var->yres != ivtvfb_defined.yres ||
	var->xres_virtual != ivtvfb_defined.xres_virtual ||
	var->yres_virtual > video_height_virtual ||
	var->yres_virtual < video_height ||
	var->xoffset ||
	var->bits_per_pixel != ivtvfb_defined.bits_per_pixel ||
	var->nonstd) {
	if (first) {
	    printk(KERN_ERR
		   "ivtvfb does not support changing the video mode\n");
	    first = 0;
	}
	return -EINVAL;
    }

    if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST)
	return 0;

    if (var->yoffset)
	return -EINVAL;
    return 0;
}

/*
 * ivtv never uses a colormap: it is always straight RGBA 8:8:8:8...
 */
static int ivtvfb_get_cmap(struct fb_cmap *cmap, int kspc, int con,
			   struct fb_info *info)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_get_cmap\n");
    return 0;
}

static int ivtvfb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
			   struct fb_info *info)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_set_cmap\n");
    return 0;
}

static int ivtv_fb_blt_copy(struct ivtv *itv, int x, int y, int width,
			    int height, int source_offset,
			    int source_stride)
{
    int rc;
    unsigned long destaddr = ((y * video_width) + x) * 4;

    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_fb_blt_copy\n");
    source_offset += shadow_framebuf_offset;

    rc = ivtv_api_fb_blt_copy(ivtv_fb, 0xa, 0x1, 0x0, width, height,
			      0xffffffff, destaddr, video_width,
			      source_stride, source_offset);
    return rc;
}

/*
 * Returns the physical location of the PTE associated with a given virtual address.
 */
static inline pte_t *virt_to_pte(struct mm_struct *mm, void *addr)
{
    return
	pte_offset(pmd_offset
		   (pgd_offset(mm, (unsigned long) addr),
		    (unsigned long) addr), (unsigned long) addr);
}

struct ivtvfb_user_dma_to_device ivtvfb_current_fb_dma;

// 4MB max buffer size (on IA32 at least: 1024 pages x 4KB/page = 4MB):
#define IVTV_MAX_FB_DMA_PAGES 1024

int ivtvfb_alloc_user_dma_to_device(struct ivtvfb_user_dma_to_device *dma)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_alloc_user_dma_to_device\n");
    dma->page_count = 0;
    dma->sglist = kmalloc(sizeof(struct ivtv_SG_element)*IVTV_MAX_FB_DMA_PAGES,
			  GFP_KERNEL);
    if (!dma->sglist) {
	printk(KERN_ERR
	       "ivtvfb: cannot allocate scatter/gather list for %d pages\n",
	       IVTV_MAX_FB_DMA_PAGES);
	return -ENOMEM;
    }

    dma->map = kmalloc(sizeof(struct page *) * IVTV_MAX_FB_DMA_PAGES, GFP_KERNEL);
    if (!dma->map) {
	IVTV_DEBUG_FB(IVTV_DEBUG_ERR, "can't alloc dma page array\n");
	kfree(dma->sglist);
	return -ENOMEM;
    }

    dma->sg_dma_handle =
	pci_map_single(ivtv_fb->dev, (void *) dma->sglist,
		       (sizeof(struct ivtv_SG_element) *
			IVTV_MAX_FB_DMA_PAGES), PCI_DMA_TODEVICE);

    return 0;
}

//++MTY This is pretty fast - fast enough to do around 30+ frames per second at NTSC 720x480x4 or 27 frames per second at PAL 720x576x4
int ivtvfb_prep_user_dma_to_device(struct ivtvfb_user_dma_to_device *dma,
				   unsigned long ivtv_dest_addr,
				   char *userbuf, int size_in_bytes)
{
    int i, offset;
    unsigned long uaddr;
    int size_in_pages = (size_in_bytes + (PAGE_SIZE - 1)) >> PAGE_SHIFT;

    IVTV_DEBUG_FB(IVTV_DEBUG_DMA, "ivtvfb_prep_user_dma_to_device, dst: 0x%08x\n",
		    (unsigned int) ivtv_dest_addr);

    uaddr = ((unsigned long) userbuf & PAGE_MASK);
    offset = uaddr & ~PAGE_MASK;

    down_read(&current->mm->mmap_sem);
    size_in_pages = get_user_pages(current, current->mm, uaddr, size_in_pages, 0, 0, dma->map, NULL);
    up_read(&current->mm->mmap_sem);

    if (size_in_pages < 0) {
	IVTV_DEBUG_FB(IVTV_DEBUG_ERR, "failed to map user pages\n");
	return size_in_pages;
    }

    dma->page_count = size_in_pages;
    for (i = 0; i < size_in_pages; i++) {
	dma->sglist[i].size = PAGE_SIZE;
	dma->sglist[i].src = pci_map_page(ivtv_fb->dev, dma->map[i], 0, offset, PCI_DMA_TODEVICE);
	dma->sglist[i].dst = ivtv_dest_addr + i * PAGE_SIZE + offset;
	offset = 0;
    }

    // Indicate the last element to the hardware, so we get an interrupt on completion...
    dma->sglist[size_in_pages - 1].size |= 0x80000000;

#ifdef IVTVFB_DEBUG_PER_FRAME
    printk(KERN_INFO
	   "ivtvfb: Allocated scatter/gather list of %d bytes (%d pages) at kva 0x%08x = physaddr 0x%08x:\n",
	   size_in_bytes, size_in_pages, dma->sglist, dma->sg_dma_handle);
    for (i = 0; i < size_in_pages; i++) {
	printk(KERN_INFO
	       "ivtvfb:   [%d] src 0x%08x -> dest 0x%08x, size 0x%08x bytes\n",
	       i, dma->sglist[i].src, dma->sglist[i].dst,
	       dma->sglist[i].size);
    }
#endif

    return 0;
}

int ivtvfb_free_user_dma_to_device(struct ivtvfb_user_dma_to_device *dma)
{
    int i;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_free_user_dma_to_device\n");

    for (i = 0; i < dma->page_count; i++) {
	pci_unmap_page(ivtv_fb->dev, dma->sglist[i].src, PAGE_SIZE, PCI_DMA_TODEVICE);
	page_cache_release(dma->map[i]);
    }
    kfree(dma->sglist);
    kfree(dma->map);
    dma->page_count = 0;
    return 0;
}

int ivtvfb_execute_user_dma_to_device(struct ivtvfb_user_dma_to_device
				      *dma)
{
    u32 data[IVTV_MBOX_MAX_DATA], result;
    int rc;
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtvfb_execute_user_dma_to_device\n");

    data[0] = dma->sg_dma_handle;
    data[1] = dma->page_count;
    data[2] = 0x1;		// 0x1 = OSD data

    IVTV_DEBUG_FB(IVTV_DEBUG_DMA, "Schedule FB DMA: physical address 0x%08x, "
	   "arraysize 0x%08x, type 0x%08x\n", data[0], data[1], data[2]);

    // Enable DMA complete interrupt:
    ivtv_clear_irq_mask(ivtv_fb, IVTV_IRQ_DEC_DMA_COMPLETE);

    set_bit(IVTV_F_I_OSD_DMA, &ivtv_fb->i_flags);

    rc = ivtv_api(ivtv_fb->dec_mbox, &ivtv_fb->sem_lock,
		  IVTV_API_DEC_DMA_FROM_HOST, &result, 3, &data[0]);

    if (rc) {
	IVTV_DEBUG_FB(IVTV_DEBUG_ERR, "error sending DMA info\n");
	clear_bit(IVTV_F_I_BUSY, &ivtv_fb->i_flags);
    }

    IVTV_DEBUG_FB(IVTV_DEBUG_DMA, "OK, scheduled FB DMA!");
    return 0;
}

static inline int ivtv_fb_prep_frame(struct ivtv *itv,
				     unsigned long destaddr, void *srcaddr,
				     int count)
{
    DECLARE_WAITQUEUE(wait, current);
    int rc;

    //if (!srcaddr || verify_area(...)) ...
    if ((destaddr + count) > video_size)
	return -E2BIG;

    rc = 0;
    add_wait_queue(&ivtv_fb->dec_master_w, &wait);
    do {
	set_current_state(TASK_INTERRUPTIBLE);
	/* FIXME mini-race still .. need to port to 'stream' format */
	if (!test_and_set_bit(IVTV_F_I_BUSY, &ivtv_fb->i_flags))
	    break;

	schedule();

	if (signal_pending(current))
	    rc = -ERESTARTSYS;
    } while (!rc);
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&ivtv_fb->dec_master_w, &wait);

    if (rc)
	goto out_dma_lock;

    destaddr = IVTV_DEC_MEM_START + video_rel_base + destaddr;

    if (0 != (rc = ivtvfb_prep_user_dma_to_device(&ivtvfb_current_fb_dma, destaddr,
					(char *) srcaddr, count)))
    {
	IVTV_DEBUG_FB(IVTV_DEBUG_DMA, "err prep user dma to device=%x\n",rc);
	goto out_dma_lock;
    }
    if (0 != (rc = ivtvfb_execute_user_dma_to_device(&ivtvfb_current_fb_dma)))
    {
	IVTV_DEBUG_FB(IVTV_DEBUG_DMA, "err exec user dma to device=%x\n",rc);
	goto out_dma_lock;
    }

    return 0;
out_dma_lock:
    clear_bit(IVTV_F_I_BUSY, &ivtv_fb->i_flags);
    wake_up(&ivtv_fb->dec_master_w);
    return rc;
}

int ivtv_fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
		  unsigned long arg, int con, struct fb_info *info)
{

    int rc;

    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "ivtv_fb_ioctl\n");
    switch (cmd) {
    case 0x7777:{
	    while (MOD_IN_USE) MOD_DEC_USE_COUNT;
	    MOD_INC_USE_COUNT;
	    return 0;
    }
    case IVTVFB_IOCTL_GET_STATE:{
	    struct ivtvfb_ioctl_state_info state;
	    state.status = (ivtv_api_fb_get_state(ivtv_fb) & 0x7);
	    state.status |= (ivtv_api_fb_get_flicker_state(ivtv_fb) << 3);
	    state.alpha = ivtv_api_fb_get_global_alpha(ivtv_fb);
	    IVTV_DEBUG_FB(IVTV_DEBUG_IOCTL, "IVTVFB_IOCTL_GET_STATE: status = %lu, alpha = %lu\n",
		 state.status, state.alpha);
	    if (copy_to_user((void *) arg, &state, sizeof(state)))
		return -EFAULT;
	    return 0;
	}
    case IVTVFB_IOCTL_SET_STATE:{
	    struct ivtvfb_ioctl_state_info state;
	    if (copy_from_user(&state, (void *) arg, sizeof(state)))
		return -EFAULT;
	    IVTV_DEBUG_FB(IVTV_DEBUG_IOCTL, "IVTVFB_IOCTL_SET_STATE: status = %lu, alpha = %lu\n",
		 state.status, state.alpha);
	    ivtv_api_fb_set_state(ivtv_fb,
				  (state.status && IVTVFB_STATUS_ENABLED));
	    ivtv_api_fb_set_global_alpha(ivtv_fb,
					 (state.
					  status &
					  IVTVFB_STATUS_GLOBAL_ALPHA) ? 1 :
					 0, state.alpha,
					 (state.
					  status &
					  IVTVFB_STATUS_LOCAL_ALPHA) ? 1 :
					 0);
	    ivtv_api_fb_set_flicker_state(ivtv_fb,
					  (state.
					   status &
					   IVTVFB_STATUS_FLICKER_REDUCTION)
					  ? 1 : 0);
	    IVTV_DEBUG_FB(IVTV_DEBUG_IOCTL, "new state = %d\n",
		   ivtv_api_fb_get_state(ivtv_fb));
	    IVTV_DEBUG_FB(IVTV_DEBUG_IOCTL, "global alpha now = %d\n",
		   ivtv_api_fb_get_global_alpha(ivtv_fb));
	    return 0;
	}
    case IVTVFB_IOCTL_PREP_FRAME:{
	    struct ivtvfb_ioctl_dma_host_to_ivtv_args args;
	    if (copy_from_user(&args, (void *) arg, sizeof(args)))
		return -EFAULT;
	    return ivtv_fb_prep_frame(ivtv_fb, args.dest_offset,
				      args.source, args.count);
	}
    case IVTVFB_IOCTL_BLT_COPY:{
	    struct ivtvfb_ioctl_blt_copy_args args;
	    if (copy_from_user(&args, (void *) arg, sizeof(args)))
		return -EFAULT;

	    return ivtv_fb_blt_copy(ivtv_fb, args.x, args.y, args.width,
				    args.height, args.source_stride,
				    args.source_offset);
	}
    case IVTVFB_IOCTL_GET_ACTIVE_BUFFER:{
	    struct ivtv_osd_coords bufinfo;
	    rc = ivtv_api_fb_get_osd_coords(ivtv_fb, &bufinfo);
	    return copy_to_user((void *) arg, &bufinfo, sizeof(bufinfo));
	}
    case IVTVFB_IOCTL_SET_ACTIVE_BUFFER:{
	    struct ivtv_osd_coords bufinfo;
	    if (copy_from_user(&bufinfo, (void *) arg, sizeof(bufinfo)))
		return -EFAULT;
	    return ivtv_api_fb_set_osd_coords(ivtv_fb, &bufinfo);
	}
    case IVTVFB_IOCTL_GET_FRAME_BUFFER:{
	    struct ivtvfb_ioctl_get_frame_buffer getfb;
	    getfb.mem  = (void *)video_vbase;
	    getfb.bytes =  video_size;
	    getfb.sizex =  video_width;
	    getfb.sizey =  video_height;

	    return copy_to_user((void *) arg, &getfb, sizeof(getfb));
	}
    default:
	return -EINVAL;
    }
    return 0;
}

static struct fb_ops ivtvfb_ops = {
    owner:THIS_MODULE,
    fb_get_fix:ivtvfb_get_fix,
    fb_get_var:ivtvfb_get_var,
    fb_set_var:ivtvfb_set_var,
    fb_get_cmap:ivtvfb_get_cmap,
    fb_set_cmap:ivtvfb_set_cmap,
    fb_ioctl:ivtv_fb_ioctl,
    fb_pan_display:NULL,
};

/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */

static void ivtvfb_blank(int blank, struct fb_info *info)
{
    /* Not supported */
}

int __init ivtvfb_init(void)
{
    int rc;
    u32 fbbase;
    u32 fblength;
    struct ivtv_osd_coords osd;
    struct rectangle rect;

    if ((ivtv_fb_card_id < 0) || (ivtv_fb_card_id >= ivtv_cards_active)) {
	printk(KERN_ERR
	       "Error! ivtv-fb: ivtv_fb_card_id parameter is out of range (valid range: 0-%d)\n",
	       ivtv_cards_active - 1);
	return -1;
    }

    ivtv_fb = &ivtv_cards[ivtv_fb_card_id];
    if (!ivtv_fb || (ivtv_fb->card_type != IVTV_350_V1)) {
	printk(KERN_ERR
	       "Error! ivtv-fb: Specified card (id %d) is either not present or does not support TV out (PVR350 only)\n",
	       ivtv_fb_card_id);
	return -1;
    }

    printk(KERN_INFO
	   "ivtv-fb: Framebuffer module loaded (attached to ivtv card id %d)\n",
	   ivtv_fb_card_id);

    rc = ivtv_api_fb_set_pixel_format(ivtv_fb, 4);	// 4 = AlphaRGB 8:8:8:8

    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "Current pixel format = %d\n",
	   ivtv_api_fb_get_pixel_format(ivtv_fb));

    rc = ivtv_api_fb_get_framebuffer(ivtv_fb, (void **) &fbbase,
				     &fblength);
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "Framebuffer is at decoder-relative address 0x%08x and has %d bytes.\n",
	   fbbase, fblength);

    rc = ivtv_api_fb_get_osd_coords(ivtv_fb, &osd);
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "OSD: offset = 0x%08x (max offset = 0x%08x), pixel_stride = %d, lines = %d, x = %d, y = %d\n",
	   (u32)osd.offset, (u32)osd.max_offset, osd.pixel_stride, osd.lines, osd.x,
	   osd.y);

    /* setup OSD and screen for PAL */
    if (ivtv_pal) {
    	osd.lines = 576;
    	rc = ivtv_api_fb_set_osd_coords(ivtv_fb, &osd);
	if (rc)
	    IVTV_DEBUG_FB(IVTV_DEBUG_ERR, "failed setting PAL osd\n");

	rect.x0 = 0;
	rect.x1 = 720;
	rect.y0 = 0;
	rect.y1 = 576;
	rc = ivtv_api_fb_set_screen_coords(ivtv_fb, &rect);
	if (rc)
	    IVTV_DEBUG_FB(IVTV_DEBUG_ERR, "failed setting PAL screen\n");
    }

     rc = ivtv_api_fb_get_screen_coords(ivtv_fb, &rect);
     printk(KERN_INFO "ivtv-fb: screen coords: [%d %d] -> [%d %d]\n",
	   rect.x0, rect.y0, rect.x1, rect.y1);

    printk(KERN_INFO "ivtv-fb: original global alpha = %d\n",
	   ivtv_api_fb_get_global_alpha(ivtv_fb));

    /*
     * Normally a 32-bit RGBA framebuffer would be fine, however XFree86's fbdev 
     * driver doesn't understand the concept of alpha channel and always sets
     * bits 24-31 to zero when using a 24bpp-on-32bpp framebuffer device. We fix
     * this behavior by enabling the iTVC15's global alpha feature, which causes 
     * the chip to ignore the per-pixel alpha data and instead use one value (e.g.,
     * full brightness = 255) for the entire framebuffer. The local alpha is also
     * disabled in this step.
     *
     *++MTY Need to update http://ivtv.sourceforge.net/ivtv/firmware-api.html 
     *      call 0x4b: param[2] says 1 = enable local alpha, when in reality
     *      it means *disable* local alpha...
     *      
     */
    ivtv_api_fb_set_global_alpha(ivtv_fb, 1, 255, 0);
    printk(KERN_INFO "ivtv-fb: new global alpha = %d\n",
	   ivtv_api_fb_get_global_alpha(ivtv_fb));

    rc = ivtv_api_fb_set_state(ivtv_fb, 1);	// 1 = enabled
    printk(KERN_INFO "ivtv-fb: current OSD state = %d\n",
	   ivtv_api_fb_get_state(ivtv_fb));

    video_rel_base = fbbase;
    video_base = ivtv_fb->base_addr + IVTV_DEC_MEM_START + video_rel_base;
    video_width = rect.x1 - rect.x0;
    video_height = rect.y1 - rect.y0;
    video_linelength = 4 * osd.pixel_stride;
    video_size = fblength;

    shadow_framebuf_size = (video_width * video_height * 4);
    shadow_framebuf_offset = (video_size - shadow_framebuf_size) & ~3;

    if (!request_mem_region(video_base, video_size, "ivtvfb")) {
	printk(KERN_WARNING
	       "ivtv-fb: warning: cannot reserve video memory at 0x%lx\n",
	       video_base);
	/* We cannot make this fatal. Sometimes this comes from magic spaces our resource handlers simply don't know about */
    }

    video_vbase = ioremap(video_base, video_size);
    if (!video_vbase) {
	release_mem_region(video_base, video_size);
	printk(KERN_ERR
	       "ivtv-fb: abort, cannot ioremap video memory 0x%x @ 0x%lx\n",
	       video_size, video_base);
	return -EIO;
    }

    printk(KERN_INFO
	   "ivtv-fb: framebuffer at 0x%lx, mapped to 0x%p, size %dk\n",
	   video_base, video_vbase, video_size / 1024);
    printk(KERN_INFO "ivtv-fb: mode is %dx%dx%d, linelength=%d\n",
	   video_width, video_height, 32, video_linelength);

    ivtvfb_defined.xres = video_width;
    ivtvfb_defined.yres = video_height;
    ivtvfb_defined.xres_virtual = video_width;
    ivtvfb_defined.yres_virtual = video_height;
    ivtvfb_defined.bits_per_pixel = 32;
    video_height_virtual = ivtvfb_defined.yres_virtual;

    /* some dummy values for timing to make fbset happy */
    ivtvfb_defined.pixclock = 10000000 / video_width * 1000 / video_height;
    ivtvfb_defined.left_margin = (video_width / 8) & 0xf8;
    ivtvfb_defined.right_margin = 32;
    ivtvfb_defined.upper_margin = 16;
    ivtvfb_defined.lower_margin = 4;
    ivtvfb_defined.hsync_len = (video_width / 8) & 0xf8;
    ivtvfb_defined.vsync_len = 4;

    ivtvfb_defined.red.offset = 0;
    ivtvfb_defined.red.length = 8;
    ivtvfb_defined.green.offset = 8;
    ivtvfb_defined.green.length = 8;
    ivtvfb_defined.blue.offset = 16;
    ivtvfb_defined.blue.length = 8;
    ivtvfb_defined.transp.offset = 24;
    ivtvfb_defined.transp.length = 8;

#ifdef CONFIG_MTRR
    if (mtrr) {
	/* Find the largest power of two that maps the whole buffer */
	int size_shift = 31;
	while (!(video_size & (1 << size_shift))) {
	    size_shift--;
	}
	size_shift++;

	fb_start_aligned_physaddr = video_base & ~((1 << size_shift) - 1);
	fb_end_aligned_physaddr =
	    (video_base + (1 << size_shift) - 1) & ~((1 << size_shift) -
						     1);
	if (mtrr_add
	    (fb_start_aligned_physaddr,
	     (fb_end_aligned_physaddr - fb_start_aligned_physaddr),
	     MTRR_TYPE_WRCOMB, 1) < 0) {
	    printk(KERN_WARNING
		   "ivtv-fb: warning: mtrr_add() failed to add write combining region 0x%08x-0x%08x\n",
		   (unsigned int) fb_start_aligned_physaddr,
		   (unsigned int) fb_end_aligned_physaddr);
	}
    }
#endif

    strcpy(fb_info.modename, "iTVC15 TV out");
    fb_info.fontname[0] = '\0';
    fb_info.changevar = NULL;
    fb_info.node = -1;
    fb_info.fbops = &ivtvfb_ops;
    fb_info.disp = &disp;
    fb_info.switch_con = NULL;	//&ivtvfb_switch; (fbcon will ignore it then)
    fb_info.updatevar = &ivtvfb_update_var;
    fb_info.blank = &ivtvfb_blank;
    fb_info.flags = FBINFO_FLAG_DEFAULT;
    ivtvfb_set_disp(-1);

    if (register_framebuffer(&fb_info) < 0)
	return -EINVAL;

    ivtv_fb->fb_id = GET_FB_IDX(fb_info.node);

    printk(KERN_INFO "fb%d: %s frame buffer device\n",
	   ivtv_fb->fb_id, fb_info.modename);

    /* Set up DMA and BLT copy structures */
    ivtvfb_alloc_user_dma_to_device(&ivtvfb_current_fb_dma);
    ivtv_fb->user_dma_to_device_state = &ivtvfb_current_fb_dma;
    return 0;
}


static void ivtvfb_cleanup(void)
{
    IVTV_DEBUG_FB(IVTV_DEBUG_INFO, "Unloading framebuffer module\n");
    unregister_framebuffer(&fb_info);
    iounmap(video_vbase);
#ifdef CONFIG_MTRR
    mtrr_del(-1, fb_start_aligned_physaddr,
	     (fb_end_aligned_physaddr - fb_start_aligned_physaddr));
#endif
    ivtv_fb->user_dma_to_device_state = NULL;
    ivtvfb_free_user_dma_to_device(&ivtvfb_current_fb_dma);
    ivtv_fb->fb_id = -1;
    //release_mem_region(video_base, video_size);
}

module_init(ivtvfb_init);
module_exit(ivtvfb_cleanup);
