/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
   @file   phStbFB.c
   @brief  PNX8335 Framebuffer Implementation.

   @b  Component:  phStbFB.

   Set your editor for 4 space indentation.
*/
/*
Rev Date        Author      Comments
--------------------------------------------------------------------------------
1   20060322    laird       First Version
2   20060327    laird       Xcheck rework.
3   20060404    laird       Making it work.
4   20060405    laird       Adding sysfs and removing broken setcolreg.
5   20060406    laird       Using our own video mode database not the standard.
6   20060406    laird       Use double requested memory.
7   20060406    laird       XCheck rework.
8   20060419    neill       Allow allocation of larger chuncks of memory
9   20060420    laird       Added double buffering.
10  20060627    iwillis     Fixed sync
11  20060918    iwillis     Added hd support to Vmix component
10.1.1 20060919 batelaan    Temporarily remove module_init/exit calls, until it is separated from ph833xVidUseCase.
10.2.1 20060913 burningh    Supporting HD OSD on 8335
12  20060925    iwillis     merge 11 and 10.1.1
13  20060929    burningh    merge 12 and 10.2.1
14  20061005    NK          Added displayMode to ph833xVidUseCase_Vo_Create
15  20061016    neill       Added support for scaling
16  20061017    neill       Corrections after crosscheck
17  20061031    iwillis     Added some very silly debug code
18  20061101    iwillis     Removed the very silly debug code
19  20061113    burningh    Modify to use phStbVideoRenderer
20  20061115    burningh    Tidy up
21  20061207    burningh    Use exclusive maths for rectangles
22  20070109    steel       update for linux_VARIANT support
23  20070122    laird       Updating to new kernel (2.6.19.*)
24  20070124    neill       Remove compile time warnings
25  20070131    iwillis     Fix for contiguous memory allocation
26  20070202    iwillis     Crosscheck rework
27  20070212    neill       Allow output scaling control
28  20070214    neill       Allow dynamic scaling control
29  20070216    neill       Changes after crosscheck
30  20070227    neill       Add support for 2 framebuffer devices
31  20070323    neill       Correct num_fb parameter name
30.1.2 20070517    steel    remove scaling restrictions
33  20070807    neill       Add support for single buffered framebuffers
34  20070816    neill       Improve display resolution checking
35  20070914    neill       Simplify initialisation include destination
                            rectangle knowledge within the driver
36  20070917    neill       Changes after crosscheck
37  20070921    neill       Add support for HD framebuffer using 2 OSD planes
                            Only allocate the memory required for the display mode
                            Re-allocate memory when output mode is higher resolution
38  20070924    neill       Force pan display to wait for field fly-back for std FBs
39  20071101    neill       Correct pan display to always wait for field fly-back
40  20071207    neill       Only update the display when necessary
41  20071207    neill       Ensure flicker filtering setting persists
41.1.1 20080128 neill       Set up both graphics layers during probe
41.1.2 20080205 batelaan    Update file copyright/license text
42  20080331    neill       Improve HD exact scan output
43  20080410    neill       Add 1920x1080p mode
44  20080415    neill       Fix bug with setting HD frame buffer resolution
45  20080502    neill       Add destination rectangle module parameter
46  20080514    neill       Add support for logo display
47  20080514    neill       Check interlaced setting when changing mode
48  20080519    neill       Ensure destination rectangle edits can change mode
49  20080522    neill       Modify Logo display to use pre-existing OSD information
50  20080611    neill       Initialise frame buffer memory BEFORE displaying it
51  20080619    griffiths   Add option to allocate phStbFB_alloc_contiguous from KBufMgr
52  20080623    griffiths   Use all of alternate DirectFB kBufMgr region
53  20080623    neill       Support multiple full resolution SD graphics buffers
54  20080707    neill       Add support for framebuffer downscale
55  20080708    neill       Force the OSD to be re-created when the renderer mode changes
56  20080709    neill       Add force param to blank output
56.1.1 20070720 laird       Adding DirectFB 1.2 support
57  20080718    neill       Improve scaling for single HD framebuffers
58  20080725    laird       Make DirectfB 1.2 support easier.
59  20080728    neill       Compensate for Z ordering layer shift
60  20080804    neill       Check renderer refresh rate
61  20080805    neill       Add VGA support
62  20080902    neill       Set up OSD memory correctly after mode change
63  20080908    neill       Update OSD memory size after mode change
61.1.2  20080918 sjones     Convert device_driver to platform_driver
64  20080918    batelaan    Improve error reporting
65  20080925    batelaan    Merge 61.1.2 and 64
66  20081021    neill       Update OSD memory start and memory available information
                            after mode changes
66.1.1 20081127 estarkie    Improve 1080i OSD dest_rect_offset
67  20081124    steel       use cached double bufferd fb
68  20081127    estarkie    Merge with 67
69  20081202    steel       flush every pan operation
69.1.1  20090106    neill   Support multiple framebuffers and scaling in VGA mode
70  20081209    laird       Merged from 'phStbFB.c-69:csrc:SOUsb225#2' and 'phStbFB.c-CFR_STB#53.1.2:csrc:SOUsb225#2' by laird.
71  20081219    neill       Add support for multiple graphics formats
72  20090106    neill       Merge with 69.1.1
73  20090109    neill       Do not support preservation of flicker filtering during
                            resolution changes
74  20090112    neill       Add support for on-the-fly graphics format change
75  20090121    batelaan    Update license.
76  20090211    neill       Improve 1080i OSD placement
77  20090220    neill       Fix single HD framebuffer startup
78  20090223    neill       Unify method used to calculate position on RHS of HD framebuffer
79  20090224    neill       Correct scale factor calculation and OSD layer positioning
80  20090225    neill       More minor positioning fixes
81  20090319    neill       Improve HD framebuffer positioning
--------------------------------------------------------------------------------
For consistency and standardisation retain the Section Separators.
*/

/***********************************************
 * INCLUDE FILES                                *
 ************************************************/

#include <linux/version.h>

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>

#include "tmNxTypes.h"
#include "tmCompId.h"
#include "phStbFB.h"
#include "phStbVideoRenderer.h"
#include "phStbVideoRenderer_Layer.h"
#include "phStbKBufMgr.h"

/***********************************************
 * LOCAL MACROS                                 *
 * recommendation only: PHSTBFB_<WORD>+         *
 ************************************************/

/**
 * @brief Number of FB devices on PNX8335
 *
 *        Organisation in memory when using DirectFB 1.0 is:
 *
 *        +--------+----------+---------+
 *        |        |          |         |
 *        | First  | DirectFB | Second  |
 *        | Frame  | Scratch  | Frame   |
 *        | Buffer | Memory   | Buffer  |
 *        |        |          |         |
 *        +--------+----------+---------+
 *
 *        Note: The DirectFB scratch memory is associated with
 *              the 'fixed' data for the first frame buffer
 *
 *        Organisation in memory when using DirectFB 1.2.X is:
 *
 *        +--------+--------+---------+
 *        |        |        |         |
 *        | First  | Second | DirectFB|
 *        | Frame  | Frame  | Scratch |
 *        | Buffer | Buffer | Memory  |
 *        |        |        |         |
 *        +--------+--------+---------+
 */
#define PHSTB_NUM_FB_DEVS 2

/**
 * @brief Default GFX memory allocation size
 */
#define GFX_MEM_ALLOC_SIZE (2*1024*1024)

/**
 * @brief Maximum amount of memory in the system. Set to 256MB to be safe.
 */
#define GFX_MEM_TOTAL      (256*1024*1024)

/** Total amount of FB memory to allocate. */
#define GFX_TOTAL_MEM_TO_ALLOC (gfx_memory_size + (num_fb * gphStbFB_info.fb_size[0]))

/* The maximum number of blocks that can be allocated in order to
 * get contiguous memory.
 */
#define GFX_MAX_BLOCKS_TO_ALLOC     (GFX_MEM_TOTAL / GFX_MEM_ALLOC_SIZE)

#define FB_ALIGNMENT (0x00010000UL)

#define MAX_OSD_WIDTH        (960)

#define DISPLAY_WIDTH_SD         (720)

#define DISPLAY_WIDTH_720P       (1280)
#define DISPLAY_HEIGHT_720P      (720)
#define DISPLAY_WIDTH_1080I      (1920)
#define DISPLAY_HEIGHT_1080I     (1080)
#define DISPLAY_WIDTH_640_480    (640)
#define DISPLAY_HEIGHT_640_480   (480)
#define DISPLAY_WIDTH_800_600    (800)
#define DISPLAY_HEIGHT_800_600   (600)
#define DISPLAY_WIDTH_1024_768   (1024)
#define DISPLAY_HEIGHT_1024_768  (768)
#define DISPLAY_WIDTH_1360_768   (1360)
#define DISPLAY_HEIGHT_1360_768  (768)

#define VIDEO_MODE_IS_720P(width, height, interlaced) ((width == DISPLAY_WIDTH_720P) && (height == DISPLAY_HEIGHT_720P) && !interlaced)
#define VIDEO_MODE_IS_1080I(width, height, interlaced) ((width == DISPLAY_WIDTH_1080I) && (height == DISPLAY_HEIGHT_1080I) && interlaced)
#define VIDEO_MODE_IS_1080P(width, height, interlaced) ((width == DISPLAY_WIDTH_1080I) && (height == DISPLAY_HEIGHT_1080I) && !interlaced)
#define VIDEO_MODE_IS_640_480P(width, height, interlaced) ((width == DISPLAY_WIDTH_640_480) && (height == DISPLAY_HEIGHT_640_480) && !interlaced)
#define VIDEO_MODE_IS_800_600P(width, height, interlaced) ((width == DISPLAY_WIDTH_800_600) && (height == DISPLAY_HEIGHT_800_600) && !interlaced)
#define VIDEO_MODE_IS_1024_768P(width, height, interlaced) ((width == DISPLAY_WIDTH_1024_768) && (height == DISPLAY_HEIGHT_1024_768) && !interlaced)
#define VIDEO_MODE_IS_1360_768P(width, height, interlaced) ((width == DISPLAY_WIDTH_1360_768) && (height == DISPLAY_HEIGHT_1360_768) && !interlaced)

#define PHSTBFB_REQUIRED_MEMORY(_width, _height, _bpp) ((num_fb * _width * _height  * (double_buffered ? 2 : 1) * _bpp) / 8)

#define OSD_BLOCK_REGISTER (0xb7e12000)

#define NUM_CLUT8_RED   3
#define NUM_CLUT8_GREEN 3
#define NUM_CLUT8_BLUE  2
#define NUM_CLUT8_TRANS 0

#define NUM_ARGB1555_RED   5
#define NUM_ARGB1555_GREEN 5
#define NUM_ARGB1555_BLUE  5
#define NUM_ARGB1555_TRANS 1

#define NUM_ARGB4444_RED   4
#define NUM_ARGB4444_GREEN 4
#define NUM_ARGB4444_BLUE  4
#define NUM_ARGB4444_TRANS 4

#define NUM_RGB565_TRANS 0
#define NUM_RGB565_RED   5
#define NUM_RGB565_GREEN 6
#define NUM_RGB565_BLUE  5

#define NUM_ARGB8888_RED   8
#define NUM_ARGB8888_GREEN 8
#define NUM_ARGB8888_BLUE  8
#define NUM_ARGB8888_TRANS 8

#define CLUT_SIZE (256)

/******************************************************************
 * LOCAL TYPEDEFS                                                 *
 * recommendation only:                [p]phStbFB_<Word>+_t       *
 ******************************************************************/
/**
 * Here we define the default fb_fix_screeninfo
 * We do use modedb to get a fb_var_screeninfo.
 * see phStbFB_init how to use it
 */
static struct fb_fix_screeninfo gphStbFB_fix =
{
    .id =       "PNX8335 FB",
    .type =     FB_TYPE_PACKED_PIXELS,
    .visual =   FB_VISUAL_TRUECOLOR,
    .xpanstep = 0,
    .ypanstep = 1,
    .ywrapstep = 1,
    .accel =    FB_ACCEL_NONE,
};

/**
 *  Local structure holding all fb info.
 */
struct fb_info_pnx8335
{
    /* Standard FB info structure*/
    struct fb_info *pfb_info[PHSTB_NUM_FB_DEVS];

    /* Useful numbers*/
    unsigned long fb_size[PHSTB_NUM_FB_DEVS];
    uint8_t *membase_virt;
    unsigned long fb_allocated;

    /* PNX8335 implementation specific details*/
    phStbVideoRenderer_Layer_t *pLayer[PHSTB_NUM_FB_DEVS];
};

static bool modeChange = false;
static bool colourChange = false;
static unsigned int num_fb = 2;
static unsigned int width = 0;
static unsigned int height = 0;
static unsigned int dest_rect[4] = { 0, 0, 0, 0 }; /* No destination specified */
static unsigned int dest_rect_offset[2] = { 0, 0};     /* No offset */
static bool interlaced;
static uint32_t renderer_width;
static uint32_t renderer_height;
static phStbVideoRenderer_FrameRate_t renderer_framerate;

/******************************************************************
 * STATIC FUNCTION PROTOTYPES                  phStbFB_<Word>+    *
 ******************************************************************/
/**
 * The Module initialisation function.
 */
int phStbFB_init(void);

/**
 * Local function for parsing commandline options passed to the framebuffer.
 *
 * @param @b IN param String of used defined options.
 *
 * @return 0 on success
 *         negative on failure.
 */
int phStbFB_setup(char* param);

/******************************************************************
 * STATIC DATA                  g[k|p|kp|pk|kpk]phStbFB_<Word>+   *
 ******************************************************************/
/**
 * @brief Framebuffer device information.
 */
static struct fb_info_pnx8335 gphStbFB_info;

/**
 * @brief Background colour to be displayed.
 */
static unsigned int background = 0;

/**
 * @brief Decide if frame buffer should be double buffered.
 */
static int double_buffered = 1;

/**
 * @brief Decide if frame buffer memory should be cached.
 */
static int cached_memory = 1;

/**
 * @brief Decide if memory allocation should come from the kernel buffer manager
 */
static int use_kernel_buffmgr = 0;

/**
 * @brief Default amount of GFX memory available for use by DirectFB.
 *
 * @see   GFX_MEM_ALLOC_SIZE
 */
static int gfx_memory_size = 2 * GFX_MEM_ALLOC_SIZE;

/**
 * @brief The amount of GFX memory that is not mapped to a framebuffer.
 */
static unsigned int gfx_memory_available = 0;

/**
 * @brief Default starting address of GFX memory available for use by DirectFB.
 */
static char* gfx_memory_start = "0x00000000";

/**
 * @brief Default graphics display mode.
 */
static char* gfx_display_mode = "ARGB8888";

/**
 * @brief Should FB size be larger than is required (for resolution * buffering) to allow for offscreen surfaces.
 *        In DirectFB 1.0 This is true as we use the memory of the framebuffer for offscreen surfaces.
 *        In DirectFB 1.2.x this is false as we use the framebuffer memory for framebuffers only.  The memory that is left
 *        over (gfx_memory - Fb[0].size - fb[1].size) is mapped by DirectFB directly.
 */
static int extended_fb_mem = 1;

/**
 * @brief CLUTs associated with the 2 framebuffers
 */
static phStbVideoRenderer_GfxClut_t phStbFB_clut[2];

/**************************************************************************
 * EXPORTED DATA                     g[k|p|kp|pk|kpk]phStbFB_<Word>+      *
 **************************************************************************/

/* Allocate a contiguous region of memory.
 */
static void phStbFB_alloc_contiguous(
        unsigned long requested_size,   /* IN:  Amount of memory to allocate (in bytes). */
        uint8_t       **virt_addr,      /* OUT: Virtual start address of allocated memory. 0 on Failure. */
        unsigned long *allocated_size   /* OUT: Size of region actually allocated (in bytes) */
    )
{
    if (requested_size <= GFX_MEM_ALLOC_SIZE)
    {
        *virt_addr = (void *)__get_free_pages(GFP_KERNEL, get_order(requested_size));
        *allocated_size = 1<<(get_order(requested_size)+PAGE_SHIFT);
    }
    else
    {
        /* Get the memory in multiples of GFX_MEM_ALLOC_SIZE */

        /* Each time we get a block we set a flag in blockAllocated to indicate
         * that that block has been allocated. We can then check the blockAllocated
         * array for contiguous regions. We repeat this until we find a contiguous
         * region large enough. Then free off the unused blocks.
         */
        unsigned char blockAllocated[GFX_MAX_BLOCKS_TO_ALLOC];
        int i;
        unsigned long addr;
        unsigned long contiguous = 0;
        unsigned long maxContiguous = 0;
        unsigned long memBase = 0;

        memset(blockAllocated, 0, sizeof(blockAllocated));

        // Repeat until we find a block large enough
        while(maxContiguous < requested_size)
        {
            addr = __get_free_pages(GFP_KERNEL, get_order(GFX_MEM_ALLOC_SIZE));
            if (addr == 0)
            {
                printk(KERN_ERR "Unable to allocate contiguous Framebuffer memory %lu KB\n",
                    GFX_TOTAL_MEM_TO_ALLOC/1024);
                break;
            }
            // Set the flag to indicate that we have allocated this block
            blockAllocated[virt_to_phys((void *)addr) / GFX_MEM_ALLOC_SIZE] = 1;

            // Work out the largest contiguous memory region
            maxContiguous = 0;
            contiguous = 0;
            for (i = 0; i < GFX_MAX_BLOCKS_TO_ALLOC; i++)
            {
                if (blockAllocated[i])
                {
                    // Increment the contiguous size
                    contiguous += GFX_MEM_ALLOC_SIZE;
                    if (contiguous > maxContiguous)
                    {
                        // Keep the maximum that we have found so far.
                        maxContiguous = contiguous;
                        if (maxContiguous >= requested_size)
                        {
                            // No need to keep searching
                            break;
                        }
                    }
                }
                else
                {
                    // Block not allocated. Reset the contiguous size
                    contiguous = 0;
                }
            }
        }

        if (maxContiguous >= requested_size)
        {
            // We found a block large enough. Find out where it is.
            contiguous = 0;
            for (i = 0; i < GFX_MAX_BLOCKS_TO_ALLOC; i++)
            {
                if (blockAllocated[i])
                {
                    contiguous += GFX_MEM_ALLOC_SIZE;
                    if (contiguous >= requested_size)
                    {
                        // Found it. Work out the memory base address.
                        memBase = ((i + 1) * GFX_MEM_ALLOC_SIZE) - contiguous;

                        // Clear the blockAllocated on the contiguous memory we
                        // are going to use so that we do not free it off.
                        while (contiguous > 0)
                        {
                            blockAllocated[i] = 0;
                            i --;
                            contiguous -= GFX_MEM_ALLOC_SIZE;
                        }
                        break;
                    }
                }
                else
                {
                    contiguous = 0;
                }
            }
        }

        // Free off any blocks we do not need.
        for (i = 0; i < GFX_MAX_BLOCKS_TO_ALLOC; i++)
        {
            if (blockAllocated[i])
            {
                unsigned long blockBaseAddr;

                blockBaseAddr = i * GFX_MEM_ALLOC_SIZE;
                free_pages((int)phys_to_virt(blockBaseAddr), get_order(GFX_MEM_ALLOC_SIZE-1));
            }
        }

        if (maxContiguous < requested_size)
        {
            *virt_addr = 0;
            *allocated_size = 0;
        }
        else
        {
            // Store the address and size of the contiguous region
            *virt_addr = phys_to_virt(memBase);
            *allocated_size = maxContiguous;
        }
    }
}

/* Allocate a contiguous region of memory from the kernel buffer.
 */
static void phStbFB_alloc_kbufmgr(
        unsigned long requested_size,   /* IN:  Amount of memory to allocate (in bytes). */
        uint8_t       **virt_addr,      /* OUT: Virtual start address of allocated memory. 0 on Failure. */
        unsigned long *allocated_size   /* OUT: Size of region actually allocated (in bytes) */
    )
{
        uint32_t KBufMgrAltDFBAddr;
        uint32_t altDFBSize;
        tmErrorCode_t res;

        *virt_addr = 0;
        *allocated_size = 0;

        res = phStbKBufMgr_GetBuffer(phStbKBufMgr_Buffer_ALT_DIRECTFB, &KBufMgrAltDFBAddr, &altDFBSize);

        if(res != TM_OK)
        {
            printk(KERN_ERR "phStbFB: phStbKBufMgr_GetBuffer failed %x\n", res);
            return;
        }

        if(altDFBSize < requested_size)
        {
            printk(KERN_ERR "phStbFB: phStbKBufMgr not enough directFB space %lu KB.\n"
                   "Currently %d KB available. Increase in prjconfig\n",
                   requested_size/1024, altDFBSize/1024);
        }
        else
        {
            // Store the address and size of the contiguous region
            *virt_addr = (uint8_t *)KBufMgrAltDFBAddr;
            *allocated_size = altDFBSize;
        }
}

/**
 * phStbFB_init_memory - Allocates and sets up framebuffer memory.
 *
 * @fbIndex: Which frame buffer is being initialised
 *
 */
static int phStbFB_init_memory(void)
{
    struct fb_info *pFbInfo;
    uint32_t pgAlignedStartAddr = 0;

    pFbInfo = gphStbFB_info.pfb_info[0];

    if (gphStbFB_info.membase_virt == NULL)
    {
        unsigned long page;

        /*Need to malloc some memory for use by the framebuffer
          Its size is pFbInfo->var.xres * pFbInfo->var.yres * bpp * PH_STBFB_BUFFERING*/
        gphStbFB_info.fb_size[0] = gphStbFB_info.fb_size[1] = (pFbInfo->var.xres_virtual *
                                                               pFbInfo->var.yres_virtual *
                                                               (pFbInfo->var.bits_per_pixel / 8));

        /*
         * Allocate the framebuffer memory.
         * Allocate as much as possible (GFX_TOTAL_MEM_TO_ALLOC).
         * Must use get_free_pages as kmalloc is limited to 128K
         */

        gphStbFB_info.fb_allocated = 0;

        if(use_kernel_buffmgr)
        {
            phStbFB_alloc_kbufmgr(GFX_TOTAL_MEM_TO_ALLOC,
                                  &gphStbFB_info.membase_virt,
                                  &gphStbFB_info.fb_allocated);

            if (gphStbFB_info.membase_virt == NULL)
            {
                return -ENOMEM;
            }

        }
        else
        {
            phStbFB_alloc_contiguous(GFX_TOTAL_MEM_TO_ALLOC,
                                     &gphStbFB_info.membase_virt,
                                     &gphStbFB_info.fb_allocated);

            if (gphStbFB_info.membase_virt == NULL)
            {
                return -ENOMEM;
            }
            /*
             * Set page reserved so that mmap will work. This is necessary
             * since we'll be remapping normal memory.
             */
            for (page = (int)gphStbFB_info.membase_virt;
                 page < PAGE_ALIGN((unsigned long)gphStbFB_info.membase_virt + gphStbFB_info.fb_allocated);
                 page += PAGE_SIZE)
            {
                SetPageReserved(virt_to_page((void *)page));
            }

        }

        /*Store the virtual and physical addresses in the correct structure members*/
        pFbInfo->screen_base = gphStbFB_info.membase_virt;

        pFbInfo->fix.smem_start = virt_to_phys(pFbInfo->screen_base);
        pFbInfo->fix.line_length = (pFbInfo->var.bits_per_pixel / 8) * pFbInfo->var.xres_virtual;
        /*Store the maximum amount of memory allowed for use by the FB*/
        pFbInfo->screen_size = gphStbFB_info.fb_size[0];

        printk("phStbFB : Allocated %ld bytes (at 0x%08lx) for frame buffer and DirectFB\n", gphStbFB_info.fb_allocated, gphStbFB_info.pfb_info[0]->fix.smem_start);
    }

    if (num_fb == 2)
    {
        if(extended_fb_mem)
        {
            /* Allow for the memory used by the seconds framebuffer (same as for 1st frambuffer!) */
            pFbInfo->fix.smem_len = gphStbFB_info.fb_allocated - gphStbFB_info.fb_size[0] - FB_ALIGNMENT;
        }
        else
        {
            pFbInfo->fix.smem_len = gphStbFB_info.fb_size[0];
        }

        /* Set up pointer to 2nd framebuffer data structure */
        pFbInfo = gphStbFB_info.pfb_info[1];

        if (pFbInfo)
        {
            if(extended_fb_mem)
            {
                pFbInfo->screen_base = gphStbFB_info.membase_virt + gphStbFB_info.fb_allocated - gphStbFB_info.fb_size[1];
            }
            else
            {
                pFbInfo->screen_base = gphStbFB_info.membase_virt + gphStbFB_info.fb_size[0] + FB_ALIGNMENT;
            }
            /* Ensure that the graphics buffer is correctly aligned */
            pFbInfo->screen_base = (void *) ((unsigned long)pFbInfo->screen_base & ~(FB_ALIGNMENT-1));
            pFbInfo->fix.smem_len = gphStbFB_info.fb_size[1];

            pFbInfo->fix.smem_start = virt_to_phys(pFbInfo->screen_base);
            pFbInfo->fix.line_length = (pFbInfo->var.bits_per_pixel / 8) * pFbInfo->var.xres_virtual;
            /*Store the maximum amount of memory allowed for use by the FB*/
            pFbInfo->screen_size = gphStbFB_info.fb_size[1];
            if(!extended_fb_mem)
            {
                /* Page align the second framebuffer address*/
                pgAlignedStartAddr = PAGE_ALIGN((pFbInfo->fix.smem_start + gphStbFB_info.fb_size[1]));
            }
        }
    }
    else
    {
        if(extended_fb_mem)
        {
            /* We have extended the FB memory to allow for offscreen surfaces.*/
            pFbInfo->fix.smem_len = gphStbFB_info.fb_allocated;
        }
        else
        {
            pFbInfo->fix.smem_len = gphStbFB_info.fb_size[0];
            /* Page align the second framebuffer address*/
            pgAlignedStartAddr = PAGE_ALIGN((pFbInfo->fix.smem_start + gphStbFB_info.fb_size[0]));
        }
    }

    /* Fill in the module params so that DirectFB 1.2.x can use them.*/
    if(!extended_fb_mem)
    {
        /* Set the start of GFX memory to be the end of FB1.*/
        sprintf(gfx_memory_start, "0x%08x", pgAlignedStartAddr);
        /* Say how much memory can be used for Application level.*/
        gfx_memory_available = ((gphStbFB_info.pfb_info[0]->fix.smem_start + gphStbFB_info.fb_allocated) - pgAlignedStartAddr);

    }

    return 0;
}

/* Free the framebuffer memory.
 */
static void phStbFB_free_memory(void)
{
    unsigned long page;

    if(!use_kernel_buffmgr) {
        for (page = (int)gphStbFB_info.membase_virt;
             page < PAGE_ALIGN((unsigned long)gphStbFB_info.membase_virt + gphStbFB_info.fb_allocated);
             page += PAGE_SIZE)
        {
            ClearPageReserved(virt_to_page((void *)page));
        }

        if (gphStbFB_info.fb_allocated <= GFX_MEM_ALLOC_SIZE)
        {
            /*Free pages*/
            free_pages((unsigned int)gphStbFB_info.membase_virt, get_order(gphStbFB_info.fb_allocated - 1));
        }
        else
        {
            unsigned int physicalAddress;

            physicalAddress = virt_to_phys(gphStbFB_info.membase_virt);
            /* Return the memory in multiples of 4MB */
            while(gphStbFB_info.fb_allocated > 0)
            {
                free_pages((int)phys_to_virt(physicalAddress), get_order(GFX_MEM_ALLOC_SIZE - 1));
                physicalAddress += GFX_MEM_ALLOC_SIZE;
                gphStbFB_info.fb_allocated -= GFX_MEM_ALLOC_SIZE;
            }
        }
    }
    gphStbFB_info.membase_virt = NULL;
    gphStbFB_info.fb_allocated = 0;
}

/**
 * phStbFB_checkWidthAndHeight - Validates prosed FB width and height.
 *
 * @pWidth: Pointer to the proposed modified frame buffer width
 * @pHeight: Pointer to the proposed modified frame buffer height
 * @pRendererWidth: Pointer to hold the renderer output resolution width
 * @pRendererHeight: Pointer to hold the renderer output resolution height
 *
 * Checks to see if the display can support the specified width and
 * height values, and modifies them as necessary
 *
 */
static int phStbFB_checkWidthAndHeight(unsigned int *pWidth, unsigned int *pHeight, uint32_t *pRendererWidth, uint32_t *pRendererHeight)
{
    /* Check to see if the framebuffer width and height have been specified */
    if (*pWidth == 0 && *pHeight == 0)
    {
        /* Due to a hardware restriction in PNX8335 vertical scaling capabilities are poor :-( */
        /* This means that only certain resolutions work well - so they are fixed here!        */
        if (VIDEO_MODE_IS_720P(renderer_width, renderer_height, interlaced))
        {
            /* Set width and height to 720 pixels -> simple Y scaling! */
            *pWidth = *pHeight = renderer_height;
        }
        else if (VIDEO_MODE_IS_640_480P(renderer_width, renderer_height, interlaced) ||
                 VIDEO_MODE_IS_800_600P(renderer_width, renderer_height, interlaced))
        {
            /* Assume no scaling required */
            *pWidth = renderer_width;
            *pHeight = renderer_height;
        }
        else if (VIDEO_MODE_IS_1024_768P(renderer_width, renderer_height, interlaced) ||
                 VIDEO_MODE_IS_1360_768P(renderer_width, renderer_height, interlaced))
        {
            /* Set width to 960 pixels (maximum posible) and height to 768 pixels */
            *pWidth  = MAX_OSD_WIDTH;
            *pHeight = renderer_height;
        }
        else if (VIDEO_MODE_IS_1080I(renderer_width, renderer_height, interlaced))
        {
            /* Set width to 960 pixels (maximum posible) and height to 540 pixels -> simple Y scaling */
            *pWidth  = MAX_OSD_WIDTH;
            *pHeight = renderer_height/2;
        }
        else if (VIDEO_MODE_IS_1080P(renderer_width, renderer_height, interlaced))
        {
            /* Set width to 960 pixels (maximum posible) and height to 540 pixels -> simple Y scaling */
            *pWidth  = MAX_OSD_WIDTH;
            *pHeight = renderer_height/2;
        }
        else
        {
            /* Assume no scaling required */
            *pWidth = renderer_width;
            *pHeight = renderer_height;
        }
    }

    if (pRendererWidth)
    {
        *pRendererWidth = renderer_width;
    }
    if (pRendererHeight)
    {
        *pRendererHeight = renderer_height;
    }

    return 0;
}

/**
 * phStbFB_checkDimensions - Validates a var passed in.
 *
 * @pWidth: Pointer to the proposed modified frame buffer width
 * @pHeight: Pointer to the proposed modified frame buffer height
 *
 * Checks to see if the display can support the specified width and
 * height values, and modifies them as necessary - takes into account
 * the destination scaling rectangle.
 *
 */
static int phStbFB_checkDimensions(unsigned int *pWidth, unsigned int *pHeight)
{
    uint32_t output_renderer_width;
    uint32_t output_renderer_height;

    if (phStbFB_checkWidthAndHeight(pWidth, pHeight, &output_renderer_width, &output_renderer_height) != 0)
    {
        return -1;
    }
    /* Correct destination rectangle */

    /* Invalid start of destination rectangle */
    if (dest_rect[0] > output_renderer_width)
    {
        dest_rect[0] = 0;
    }
    if (dest_rect[1] > output_renderer_height)
    {
        dest_rect[1] = 0;
    }

    /* Invalid size of destination rectangle */
    if (dest_rect[2] == 0)
    {
        dest_rect[2] = output_renderer_width;
    }
    if (dest_rect[3] == 0)
    {
        dest_rect[3] = output_renderer_height;
    }
    if (dest_rect[2] > output_renderer_width)
    {
        dest_rect[2] = output_renderer_width;
    }
    if (dest_rect[3] > output_renderer_height)
    {
        dest_rect[3] = output_renderer_height;
    }

    return 0;
}

/**
 * phStbFB_setup_destination_offset
 *
 * Resets the offsets for onscreen rectangle that the frame buffer is displayed in
 *
 */
static void phStbFB_setup_destination_offsets(void)
{
    tmErrorCode_t status = TM_OK;

    status = phStbVideoRenderer_GetOutputResolution(phStbVideoRenderer_DisplayChain_Main, &renderer_width, &renderer_height);
    status = phStbVideoRenderer_GetOutputScanType(phStbVideoRenderer_DisplayChain_Main, &interlaced);
    status = phStbVideoRenderer_GetOutputFrameRate(phStbVideoRenderer_DisplayChain_Main, &renderer_framerate);

    /* Due to a hardware restriction in PNX8335 vertical scaling capabilities are poor :-( */
    /* This means that only certain resolutions work well - so they are fixed here!        */
    if (VIDEO_MODE_IS_720P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 229;
        dest_rect_offset[1] = 0;
    }
    else if (VIDEO_MODE_IS_1080I(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 160;
        if (renderer_framerate == phStbVideoRenderer_FrameRate_25)
        {
            dest_rect_offset[1] = 5;
        }
        else
        {
            dest_rect_offset[1] = 4;
        }
    }
    else if (VIDEO_MODE_IS_1080P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 160;
        dest_rect_offset[1] = 4;
    }
    else if (VIDEO_MODE_IS_640_480P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 258;
        dest_rect_offset[1] = 0;
    }
    else if (VIDEO_MODE_IS_800_600P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 157;
        dest_rect_offset[1] = 0;
    }
    else if (VIDEO_MODE_IS_1024_768P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 157;
        dest_rect_offset[1] = 0;
    }
    else if (VIDEO_MODE_IS_1360_768P(renderer_width, renderer_height, interlaced))
    {
        dest_rect_offset[0] = 157;
        dest_rect_offset[1] = 0;
    }
    else
    {
        dest_rect_offset[0] = 252;
        dest_rect_offset[1] = 0;
    }
}

/**
 * phStbFB_reset_destination_rect
 *
 * Resets the onscreen rectangle that the frame buffer is displayed in
 *
 */
static void phStbFB_reset_destination_rect(void)
{
    dest_rect[0] = 0;
    dest_rect[1] = 0;
    dest_rect[2] = renderer_width;
    dest_rect[3] = renderer_height;
}

/**
 * phStbFB_check_resolution
 *
 * Checks to see if the video renderer resolution has changed
 *
 */
static void phStbFB_check_resolution(void)
{
    uint32_t current_renderer_width;
    uint32_t current_renderer_height;
    bool     current_interlaced;
    phStbVideoRenderer_FrameRate_t current_framerate;
    tmErrorCode_t status;

    status = phStbVideoRenderer_GetOutputResolution(phStbVideoRenderer_DisplayChain_Main, &current_renderer_width, &current_renderer_height);
    status = phStbVideoRenderer_GetOutputScanType(phStbVideoRenderer_DisplayChain_Main, &current_interlaced);
    status = phStbVideoRenderer_GetOutputFrameRate(phStbVideoRenderer_DisplayChain_Main, &current_framerate);

    if ((current_renderer_width != renderer_width) ||
        (current_renderer_height != renderer_height) ||
        (current_framerate != renderer_framerate) ||
        (current_interlaced != interlaced))
    {
        /* Renderer output changed ... reset the destination rectangle */
        phStbFB_setup_destination_offsets();
        phStbFB_reset_destination_rect();
    }
}

static void phStbFb_setDefaultCLUT(struct fb_info *pFbInfo)
{
    /* Set up the CLUT */
    uint32_t red;
    uint32_t green;
    uint32_t blue;
    __u16 *pRed = pFbInfo->cmap.red;
    __u16 *pGreen = pFbInfo->cmap.green;
    __u16 *pBlue = pFbInfo->cmap.blue;
    __u16 *pTransp = pFbInfo->cmap.transp;

    pFbInfo->cmap.start = 0;
    pFbInfo->cmap.len = 256;

    /* Create a 8-8-4 palette */
    for(red = 0; red < 8; red++)
    {
        for(green = 0; green < 8; green++)
        {
            for(blue = 0; blue < 4; blue++)
            {
                *pRed++    = (__u16)((red * 0xFFFF) / 8);
                *pGreen++  = (__u16)((green * 0xFFFF) / 8);
                *pBlue++   = (__u16)((blue * 0xFFFF) / 4);
                *pTransp++ = 0xFFFF;
            }
        }
    }
}

static phStbVideoRenderer_GfxMode_t phStbFB_type(struct fb_var_screeninfo *var)
{
    phStbVideoRenderer_GfxMode_t mode;

    switch (var->bits_per_pixel)
    {
        case(32) :
            if ((var->transp.length == NUM_ARGB8888_TRANS) &&
                (var->red.length    == NUM_ARGB8888_RED)   &&
                (var->green.length  == NUM_ARGB8888_GREEN) &&
                (var->blue.length   == NUM_ARGB8888_BLUE))
            {
                mode = phStbVideoRenderer_GfxMode_ARGB8888;
            }
            else
            {
                mode = phStbVideoRenderer_GfxMode_Invalid;
            }
            break;
        case(16) :
            if ((var->transp.length == NUM_ARGB1555_TRANS) &&
                (var->red.length    == NUM_ARGB1555_RED)   &&
                (var->green.length  == NUM_ARGB1555_GREEN) &&
                (var->blue.length   == NUM_ARGB1555_BLUE))
            {
                mode = phStbVideoRenderer_GfxMode_ARGB1555;
            }
            else
            if ((var->transp.length == NUM_ARGB4444_TRANS) &&
                (var->red.length    == NUM_ARGB4444_RED)   &&
                (var->green.length  == NUM_ARGB4444_GREEN) &&
                (var->blue.length   == NUM_ARGB4444_BLUE))
            {
                mode = phStbVideoRenderer_GfxMode_ARGB4444;
            }
            else
            {
                if ((var->transp.length == NUM_RGB565_TRANS) &&
                    (var->red.length    == NUM_RGB565_RED)   &&
                    (var->green.length  == NUM_RGB565_GREEN) &&
                    (var->blue.length   == NUM_RGB565_BLUE))
                {
                    mode = phStbVideoRenderer_GfxMode_RGB565;
                }
                else
                {
                    mode = phStbVideoRenderer_GfxMode_Invalid;
                }
            }
            break;
        case(8) :
            if ((var->transp.length == NUM_CLUT8_TRANS) &&
                (var->red.length    == NUM_CLUT8_RED)   &&
                (var->green.length  == NUM_CLUT8_GREEN) &&
                (var->blue.length   == NUM_CLUT8_BLUE))
            {
                mode = phStbVideoRenderer_GfxMode_Clut256;
            }
            else
            {
                mode = phStbVideoRenderer_GfxMode_Invalid;
            }
            break;
        default:
            mode = phStbVideoRenderer_GfxMode_Invalid;
            break;
    }

    return mode;
}

/**
 * phStbFB_check_var - Validates a var passed in.
 *
 * @var: frame buffer variable screen structure
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Checks to see if the hardware supports the state requested by
 * var passed in. This function does not alter the hardware state!!!
 * This means the data stored in struct fb_info does not change.
 *
 * @return Returns negative errno on error, or zero on success.
 *
 * @note You can not change the resolution of the Framebuffer
 * @note We do not support FBConsole with regards to FB_VMODE_YWRAP etc
 */
static int phStbFB_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
    static   uint32_t prevRendererWidth[PHSTB_NUM_FB_DEVS] = {0,0};
    static   uint32_t prevRendererHeight[PHSTB_NUM_FB_DEVS] = {0,0};
    static   bool prevRendererInterlaced[PHSTB_NUM_FB_DEVS] = {false, false};
    static   phStbVideoRenderer_FrameRate_t prevRendererFramerate[PHSTB_NUM_FB_DEVS] = {phStbVideoRenderer_FrameRateNone, phStbVideoRenderer_FrameRateNone};
    
    phStbFB_check_resolution();

    /* Check to see if something has changed: */
    /*   Framebuffer resolution               */
    /*   Interlaced / Progressive output      */
    /*   Output framerate                     */
    /*   Output resolution                    */
    /*   Colour depth / format                */

    colourChange = colourChange ||
                   ((info->var.bits_per_pixel != var->bits_per_pixel) ||
                    (info->var.red.length     != var->red.length) ||
                    (info->var.red.offset     != var->red.offset) ||
                    (info->var.green.length   != var->green.length) ||
                    (info->var.green.offset   != var->green.offset) ||
                    (info->var.blue.length    != var->blue.length) ||
                    (info->var.blue.offset    != var->blue.offset) ||
                    (info->var.transp.length  != var->transp.length) ||
                    (info->var.transp.offset  != var->transp.offset));

    if (colourChange)
    {
        if (phStbFB_type(var) == phStbVideoRenderer_GfxMode_Invalid)
        {
            colourChange = false;
            return -1;
        }

        info->var.bits_per_pixel = var->bits_per_pixel;
        info->var.red.length     = var->red.length;
        info->var.red.offset     = var->red.offset;
        info->var.green.length   = var->green.length;
        info->var.green.offset   = var->green.offset;
        info->var.blue.length    = var->blue.length; 
        info->var.blue.offset    = var->blue.offset; 
        info->var.transp.length  = var->transp.length;
        info->var.transp.offset  = var->transp.offset;
    }

    modeChange = modeChange ||
                 (info->var.xres     != var->xres) ||
                 (info->var.yres     != var->yres) ||
                 (interlaced         != prevRendererInterlaced[info->node]) ||
                 (renderer_framerate != prevRendererFramerate[info->node])  ||
                 (renderer_width     != prevRendererWidth[info->node])      ||
                 (renderer_height    != prevRendererHeight[info->node]);

    /*If something has changed look at values*/
    if(modeChange)
    {
        if (phStbFB_checkWidthAndHeight(&var->xres, &var->yres, NULL, NULL) != 0)
        {
            modeChange = false;
            return -1;
        }

        prevRendererInterlaced[info->node] = interlaced;
        prevRendererWidth[info->node] = renderer_width;
        prevRendererHeight[info->node] = renderer_height;
        prevRendererFramerate[info->node] = renderer_framerate;

        info->var.xres = var->xres;
        info->var.yres = var->yres;
    }
    return 0;
}

static phStbVideoRenderer_GfxClut_t* phStbFB_convertClut(struct fb_info *info)
{
    if (phStbFB_type(&info->var) == phStbVideoRenderer_GfxMode_Clut256)
    {
        uint32_t i;
        __u16 *pRed    = info->cmap.red;
        __u16 *pGreen  = info->cmap.green;
        __u16 *pBlue   = info->cmap.blue;
        __u16 *pTransp = info->cmap.transp;

        phStbFB_clut[info->node].gfxClutSize  = info->cmap.len;
        phStbFB_clut[info->node].gfxClutStart = info->cmap.start;

        for(i = info->cmap.start; ((i < info->cmap.len) && (i < CLUT_SIZE)); i++)
        {
            phStbFB_clut[info->node].gfxClutData[i] = (((uint32_t)(*pTransp & 0xFF00)) << 16) |
                                                      (((uint32_t)(*pGreen  & 0xFF00)) << 8) |
                                                       ((uint32_t)(*pBlue   & 0xFF00)) |
                                                      (((uint32_t)(*pRed    & 0xFF00)) >> 8);
            pRed++;
            pGreen++;
            pBlue++;
            pTransp++;
        }

        return &phStbFB_clut[info->node];
    }

    return NULL;
}

/**
 * phStbFb_calculateScaleFactor
 * 
 * Calculates the scale factor required to scale between 2 values
 *
 * @original: Original dimension to scale from
 * @final:    Final (required) dimension to scale to
 * @factor:   Scaling multiplication factor
 * 
 */
static int32_t phStbFb_calculateScaleFactor(int32_t original, int32_t final, int32_t factor)
{
    int32_t scale;

    /* Calculate the possible scale value */
    scale = ((original * factor) / final);

    /* Check for overscaling */
    if (((original * factor)/scale) > final)
    {
        scale++;
    }

    return scale - 1;
}

/**
 * phStbFB_set_OSD - Sets up the OSD layers.
 *
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Using the fb_var_screeninfo in fb_info we set up the OSD layer information
 *
 */
static void phStbFB_set_OSD(struct fb_info *info)
{
    tmErrorCode_t status = TM_OK;
    int fbHorzOffsetStart;
    int fbHorzOffsetEnd;
    int fbVertOffsetStart;
    int fbVertOffsetEnd;
    int multiplier;

    if (renderer_width <= DISPLAY_WIDTH_SD)
    {
        /* Clock speeds are doubled for SD/ED modes - modify offsets appropriately */
        multiplier = 2;
    }
    else
    {
        multiplier = 1;
    }
    fbHorzOffsetStart = dest_rect_offset[0] + (dest_rect[0] * multiplier);
    fbHorzOffsetEnd = fbHorzOffsetStart + (dest_rect[2] * multiplier);

    if (interlaced)
    {
        fbVertOffsetStart = dest_rect_offset[1] + dest_rect[1];
    }
    else
    {
        fbVertOffsetStart = dest_rect_offset[1] + (dest_rect[1] * 2);
    }
    fbVertOffsetEnd = fbVertOffsetStart + dest_rect[3];

    status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node],
                                                       0,
                                                       0,
                                                       NULL,
                                                       phStbFB_type(&info->var),
                                                       NULL);

    status = phStbVideoRenderer_Layer_SetSrcRectangle(gphStbFB_info.pLayer[info->node],
                                                      0,
                                                      0,
                                                      info->var.xres > MAX_OSD_WIDTH ? (info->var.xres/2) : info->var.xres,
                                                      info->var.yres,
                                                      false);

    status = phStbVideoRenderer_Layer_SetDestRectangle(gphStbFB_info.pLayer[info->node],
                                                       fbHorzOffsetStart,
                                                       fbVertOffsetStart,
                                                       fbHorzOffsetEnd,
                                                       fbVertOffsetEnd);

    status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node],
                                                       info->var.xres,
                                                       info->var.yres,
                                                       (void*)(gphStbFB_info.pfb_info[info->node]->fix.smem_start),
                                                       phStbFB_type(&info->var),
                                                       phStbFB_convertClut(info));

    if (info->var.xres > MAX_OSD_WIDTH)
    {
        int32_t dstOffset;

        /* Calculate the actual width of the scaled output and the starting point of the second half of the source data */
        if (info->var.xres == dest_rect[2])
        {
            dstOffset = info->var.xres / 2;
        }
        else
        {
            int32_t scale;
            scale = phStbFb_calculateScaleFactor(info->var.xres, (dest_rect[2] * multiplier), 0x100);
            dstOffset = ((((0x100 * info->var.xres) / (scale + 1))) / 2);
            /* Upscaling requires an additional column of pixels! */
            if (info->var.xres < dest_rect[2])
            {
                dstOffset++;
            }
        }

        status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node+1],
                                                           0,
                                                           0,
                                                           NULL,
                                                           phStbFB_type(&info->var),
                                                           NULL);

        status = phStbVideoRenderer_Layer_SetSrcRectangle(gphStbFB_info.pLayer[info->node+1],
                                                          0,
                                                          0,
                                                          info->var.xres/2,
                                                          info->var.yres,
                                                          false);

        status = phStbVideoRenderer_Layer_SetDestRectangle(gphStbFB_info.pLayer[info->node+1],
                                                           fbHorzOffsetStart + dstOffset,
                                                           fbVertOffsetStart,
                                                           fbHorzOffsetEnd + dstOffset,
                                                           fbVertOffsetEnd);

        status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node+1],
                                                           info->var.xres,
                                                           info->var.yres,
                                                           (void*)(gphStbFB_info.pfb_info[info->node]->fix.smem_start + ((info->var.xres/2) * (info->var.bits_per_pixel / 8))),
                                                           phStbFB_type(&info->var),
                                                           phStbFB_convertClut(info));

    }
}

/**
 * phStbFB_set_par - Alters the hardware state.
 *
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Using the fb_var_screeninfo in fb_info we set the resolution of the
 * this particular framebuffer. This function alters the par AND the
 * fb_fix_screeninfo stored in fb_info. It doesn't not alter var in
 * fb_info since we are using that data.
 *
 * This means we depend on the data in var inside fb_info to be
 * supported by the hardware.  phStbFB_check_var is always called
 * before phStbFB_set_par to ensure this.
 *
 * @note You can not change the resolution of the Framebuffer
 * @note We do not support FBConsole with regards to FB_VMODE_YWRAP etc
 *
 */
static int phStbFB_set_par(struct fb_info *info)
{
    tmErrorCode_t status = TM_OK;

    /* Check to see if a mode change has been requested */
    if (!modeChange && 
        !colourChange)
    {
        return 0;
    }
    modeChange = false;

    /* Check to see if a colour mode change (to CLUT) has been requested */
    if (colourChange &&
        info->var.bits_per_pixel == 8)
    {
        phStbFb_setDefaultCLUT(info);
    }
    colourChange = false;

    phStbFB_check_resolution();
    if (phStbFB_checkDimensions(&info->var.xres, &info->var.yres) != 0)
    {
        return -1;
    }

    height = info->var.yres;

    info->var.yres_virtual = double_buffered ? info->var.yres*2 : info->var.yres;
    info->var.xres_virtual = info->var.xres;

    gphStbFB_info.pfb_info[info->node]->fix.line_length = (info->var.bits_per_pixel / 8) *
                                                           info->var.xres_virtual;

    gphStbFB_info.pfb_info[info->node]->screen_size = gphStbFB_info.fb_size[info->node]
                                                    = (info->var.xres_virtual *
                                                       info->var.yres_virtual *
                                                      (info->var.bits_per_pixel / 8));

    if (info->node == 0)
    {
        if (PHSTBFB_REQUIRED_MEMORY(info->var.xres, info->var.yres, info->var.bits_per_pixel) > gphStbFB_info.fb_allocated)
        {
            int error;
            phStbFB_free_memory();
            error = phStbFB_init_memory();
            if (error < 0)
            {
                return error;
            }
            width = info->var.xres;
            height = info->var.yres;
        }
        else
        {
            if(!extended_fb_mem)
            {
                gphStbFB_info.fb_size[0] = gphStbFB_info.fb_size[1] = (info->var.xres_virtual *
                                                                       info->var.yres_virtual *
                                                                       (info->var.bits_per_pixel / 8));
                gphStbFB_info.pfb_info[0]->fix.smem_len = gphStbFB_info.fb_size[0];

                sprintf(gfx_memory_start, "0x%08lx", PAGE_ALIGN((gphStbFB_info.pfb_info[0]->fix.smem_start + gphStbFB_info.fb_size[0])));

                /* Say how much memory can be used for Application level.*/
                gfx_memory_available = ((gphStbFB_info.pfb_info[0]->fix.smem_start + gphStbFB_info.fb_allocated) - 
                                        PAGE_ALIGN((gphStbFB_info.pfb_info[0]->fix.smem_start + gphStbFB_info.fb_size[0])));
            }
        }
        /* Clear the second OSD layer if only 1 framebuffer is being displayed */
        if (num_fb==1 && gphStbFB_info.pLayer[info->node+1])
        {
            status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node+1],
                                                               0,
                                                               0,
                                                               NULL,
                                                               phStbFB_type(&info->var),
                                                               NULL);
        }
        gphStbFB_info.pfb_info[info->node]->screen_base = gphStbFB_info.membase_virt;
    }
    else
    {
        if(extended_fb_mem)
        {
            gphStbFB_info.pfb_info[info->node]->screen_base = gphStbFB_info.membase_virt + gphStbFB_info.fb_allocated - gphStbFB_info.fb_size[info->node];
        }
        else
        {
            gphStbFB_info.pfb_info[info->node]->screen_base = gphStbFB_info.membase_virt + gphStbFB_info.fb_size[0] + FB_ALIGNMENT;
        }
        /* Ensure that the graphics buffer is correctly alligned */
        gphStbFB_info.pfb_info[info->node]->screen_base = (void *) ((unsigned long)gphStbFB_info.pfb_info[info->node]->screen_base & ~(FB_ALIGNMENT-1));
        gphStbFB_info.pfb_info[info->node]->fix.smem_len = gphStbFB_info.fb_size[info->node];

        sprintf(gfx_memory_start, "0x%08lx", PAGE_ALIGN((gphStbFB_info.pfb_info[1]->fix.smem_start + gphStbFB_info.fb_size[1])));

        /* Say how much memory can be used for Application level.*/
        gfx_memory_available = ((gphStbFB_info.pfb_info[0]->fix.smem_start + gphStbFB_info.fb_allocated) - 
                                PAGE_ALIGN((gphStbFB_info.pfb_info[1]->fix.smem_start + gphStbFB_info.fb_size[1])));
    }
    gphStbFB_info.pfb_info[info->node]->fix.smem_start = virt_to_phys(gphStbFB_info.pfb_info[info->node]->screen_base);

    phStbFB_set_OSD(info);

    if (num_fb == 2)
    {
        if(extended_fb_mem)
        {
            /* Modify the memory length taking into account the position of the second framebuffer */
            gphStbFB_info.pfb_info[0]->fix.smem_len = gphStbFB_info.pfb_info[1]->fix.smem_start - gphStbFB_info.pfb_info[0]->fix.smem_start;
        }
    }



    return 0;
}

/**
 *  phStbFB_setcolreg - Sets a color register in palette mode.
 *
 *  @note This has been copied form the skeletonfb.c code and should
 *        be looked at to see if it is actaully useful or not.
 *
 *  @regno: Which register in the CLUT we are programming
 *  @red: The red value which can be up to 16 bits wide
 *  @green: The green value which can be up to 16 bits wide
 *  @blue:  The blue value which can be up to 16 bits wide.
 *  @transp: If supported the alpha value which can be up to 16 bits wide.
 *  @info: frame buffer info structure
 *
 *  Set a single color register. The values supplied have a 16 bit
 *  magnitude which needs to be scaled in this function for the hardware.
 *  Things to take into consideration are how many color registers, if
 *  any, are supported with the current color visual. With truecolor mode
 *  no color palettes are supported. Here a psuedo palette is created
 *  which we store the value in pseudo_palette in struct fb_info. For
 *  pseudocolor mode we have a limited color palette. To deal with this
 *  we can program what color is displayed for a particular pixel value.
 *  DirectColor is similar in that we can program each color field. If
 *  we have a static colormap we don't need to implement this function.
 *
 *  @return Returns negative errno on error, or zero on success.
 */
static int phStbFB_setcolreg(unsigned regno, unsigned red, unsigned green,
               unsigned blue, unsigned transp,
               struct fb_info *info)
{
    tmErrorCode_t status;
    __u16 *pRed = info->cmap.red;
    __u16 *pGreen = info->cmap.green;
    __u16 *pBlue = info->cmap.blue;
    __u16 *pTransp = info->cmap.transp;

    pRed[regno]    = (__u16)red;
    pGreen[regno]  = (__u16)green;
    pBlue[regno]   = (__u16)blue;
    pTransp[regno] = (__u16)transp;

    status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node],
                                                       info->var.xres,
                                                       info->var.yres,
                                                       (void*)(gphStbFB_info.pfb_info[info->node]->fix.smem_start),
                                                       phStbFB_type(&info->var),
                                                       phStbFB_convertClut(info));
    return 0;
}

extern int phStbFB_setcmap(struct fb_cmap *cmap, struct fb_info *info)
{
    tmErrorCode_t status;
    fb_copy_cmap(cmap, &info->cmap);

    status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[info->node],
                                                       info->var.xres,
                                                       info->var.yres,
                                                       (void*)(gphStbFB_info.pfb_info[info->node]->fix.smem_start),
                                                       phStbFB_type(&info->var),
                                                       phStbFB_convertClut(info));
    return 0;
}

/**
 * phStbFB_pan_display - Pans the display.
 *
 * @var: frame buffer variable screen structure
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Pan (or wrap, depending on the `vmode' field) the display using the
 *      `xoffset' and `yoffset' fields of the `var' structure.
 *      If the values don't fit, return -EINVAL.
 *
 * Returns negative errno on error, or zero on success.
 *
 * @note currently not implemented will be later.
 */
static int phStbFB_pan_display(struct fb_var_screeninfo *var,
                               struct fb_info *info)
{
    tmErrorCode_t errCode = TM_OK;

    if (((var->xoffset + var->xres) > var->xres_virtual)||
        ((var->yoffset + var->yres) > var->yres_virtual))
    {
        return -EINVAL;
    }

    dma_map_single(NULL,
                  (void*)((gphStbFB_info.pfb_info[info->node]->fix.smem_start + (var->yoffset * var->xres_virtual)) | KSEG0),
                  (var->yres * var->xres_virtual), DMA_TO_DEVICE);

    if (var->xres > MAX_OSD_WIDTH)
    {
        errCode = phStbVideoRenderer_Layer_SetSrcRectangle(
                gphStbFB_info.pLayer[info->node+1],
                var->xoffset,
                var->yoffset,
                var->xoffset + (var->xres / 2),
                var->yoffset + var->yres,
                true);
    }

    /* Pan the display, by moving the source rectangle. */
    errCode = phStbVideoRenderer_Layer_SetSrcRectangle(
            gphStbFB_info.pLayer[info->node],
            var->xoffset,
            var->yoffset,
            var->xoffset + (var->xres > MAX_OSD_WIDTH ? var->xres/2 : var->xres),
            var->yoffset + var->yres,
            false);

    if(errCode != TM_OK)
    {
        printk(KERN_WARNING "phStbVideoRenderer_Layer_SetSrcRectangle failed Errcode = 0x%08x\n", errCode);
        return -EINVAL;
    }

    /*Update local settings*/
    info->var.xoffset = var->xoffset;
    info->var.yoffset = var->yoffset;

    return 0;
}

/**
 * phStbFB_blank - Blanks the display.
 *
 * @blank_mode: the blank mode we want.
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Blank the screen if blank_mode != 0, else unblank. Return 0 if
 * blanking succeeded, != 0 if un-/blanking failed due to e.g. a
 * video mode which doesn't support it.
 *
 * Implements VESA suspend
 * and powerdown modes on hardware that supports disabling hsync/vsync:
 * blank_mode == 2: suspend vsync
 * blank_mode == 3: suspend hsync
 * blank_mode == 4: powerdown
 *
 * Returns negative errno on error, or zero on success.
 *
 * @note used by DirectFB SetPowerMode.
 */
static int phStbFB_blank(int blank_mode, struct fb_info *info)
{
    tmErrorCode_t errCode = TM_ERR_BAD_PARAMETER;

    /* If blank == 0 then show layer*/
    if (blank_mode == 0)
    {
        errCode = phStbVideoRenderer_Layer_BlankOutput(gphStbFB_info.pLayer[info->node], false, false);
    }
    /* If blank == 1 then hide layer*/
    else if (blank_mode == 1)
    {
        errCode = phStbVideoRenderer_Layer_BlankOutput(gphStbFB_info.pLayer[info->node], true, false);
    }

    /* If we did not succesfully hide / show or
     * blankmode!=0/1 then retuen err
     */
    if(errCode != TM_OK)
    {
        return -EINVAL;
    }
    return 0;
}

/**
 * Allows various FB parameters to be set like width and height
 * We do not check them as the parameters will be valid as they should
 * have been checked by fb_check_var
 *
 * @param info @b IN The new/modified fb_info structure
 *
 * @return 0 on success else error
 */
static int phStbFB_ioctl(struct fb_info *info,
                  unsigned int cmd,
                  unsigned long arg)
{
    switch (cmd)
    {
        /* Return the current destination scale rectangle */
        case (PHSTBFB_IOCTL_GET_DEST_RECT) :
            {
                unsigned int * pDest = (unsigned int *)arg;
                pDest[0] = dest_rect[0];
                pDest[1] = dest_rect[1];
                pDest[2] = dest_rect[2];
                pDest[3] = dest_rect[3];
            }
            break;
            /* Modify the current destination scale rectangle */
        case (PHSTBFB_IOCTL_SET_DEST_RECT) :
            {
                modeChange = true;
                copy_from_user(&dest_rect, (unsigned int *) arg, sizeof(unsigned int) * 4);
                phStbFB_set_par(gphStbFB_info.pfb_info[info->node]);
            }
            break;
    }
    return 0;
}

static int phStbFB_mmap(struct fb_info *info, struct vm_area_struct * vma)
{
	unsigned long off;
	unsigned long start;
	u32 len;

	off = vma->vm_pgoff << PAGE_SHIFT;
	start = gphStbFB_info.pfb_info[info->node]->fix.smem_start;
	len = PAGE_ALIGN((start & ~PAGE_MASK) + gphStbFB_info.pfb_info[info->node]->fix.smem_len);
	start &= PAGE_MASK;
	if ((vma->vm_end - vma->vm_start + off) > len)
	{
		return -EINVAL;
	}
	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;
	vma->vm_flags |= VM_IO | VM_RESERVED;

	if (!(double_buffered && cached_memory))
	{
		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	}

	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
	{
		return -EAGAIN;
    }
    return 0;
}

/* ------------------------------------------------------------------------- */
/*
 * Frame buffer operations that we have implmented and support.
 * Some functions might be useful here but no one seems to use them
 * i.e cursor, sync, and imageblit etc.
 */
static struct fb_ops gphStbFB_ops = {
    .owner          = THIS_MODULE,
    .fb_check_var   = phStbFB_check_var,
    .fb_set_par     = phStbFB_set_par,
    .fb_setcolreg   = phStbFB_setcolreg,
    .fb_setcmap     = phStbFB_setcmap,
    .fb_blank       = phStbFB_blank,
    .fb_pan_display = phStbFB_pan_display,
    .fb_ioctl       = phStbFB_ioctl,
    .fb_mmap        = phStbFB_mmap
};

/**
 * sysfs entries
 */
static ssize_t phStbFB_show_memsize(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct platform_device *p_dev = to_platform_device(dev);

    return snprintf(buf, PAGE_SIZE, "%lu\n", gphStbFB_info.fb_size[p_dev->id]);
}

static DEVICE_ATTR(size, S_IRUGO, phStbFB_show_memsize, NULL);

static ssize_t phStbFB_show_rev(struct device *dev, struct device_attribute *attr, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "%d\n", 0);
}

static DEVICE_ATTR(revision, S_IRUGO, phStbFB_show_rev, NULL);


static ssize_t phStbFB_show_vendor(struct device *dev, struct device_attribute *attr, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "0x%04x", 0xDEAF);
}

static DEVICE_ATTR(vendor, S_IRUGO, phStbFB_show_vendor, NULL);

static ssize_t phStbFB_show_device(struct device *dev, struct device_attribute *attr, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "0x%04x", 0xABBA);
}

static DEVICE_ATTR(device, S_IRUGO, phStbFB_show_device, NULL);

static void __devexit phStbFB_remove_sysfs(struct device *dev)
{
    device_remove_file(dev, &dev_attr_size);
    device_remove_file(dev, &dev_attr_revision);
    device_remove_file(dev, &dev_attr_device);
    device_remove_file(dev, &dev_attr_vendor);
}

static void phStbFb_create_sysfs(struct device *dev)
{
    int ret;
    ret = device_create_file(dev, &dev_attr_size);
    ret = device_create_file(dev, &dev_attr_revision);
    ret = device_create_file(dev, &dev_attr_device);
    ret = device_create_file(dev, &dev_attr_vendor);
}

static inline void phStbFB_setPixel(phStbVideoRenderer_GfxMode_t mode, u32 value, u8 *dst)
{
    switch (mode)
    {
        case(phStbVideoRenderer_GfxMode_ARGB4444) :
            *((u16*)dst) = (((value & 0xF0000000) >> 16) | 
                            ((value & 0x00F00000) >> 12) |
                            ((value & 0x0000F000) >> 8)  |
                            ((value & 0x000000F0) >> 4));
            break;
        case(phStbVideoRenderer_GfxMode_RGB565) :
            *((u16*)dst) = (((value & 0x00F80000) >> 8) |
                            ((value & 0x0000FC00) >> 5) |
                            ((value & 0x000000F8) >> 3));
            break;
        case(phStbVideoRenderer_GfxMode_ARGB1555) :
            *((u16*)dst) = (((value & 0x80000000) >> 16) | 
                            ((value & 0x00F80000) >> 9)  |
                            ((value & 0x0000F800) >> 6)  |
                            ((value & 0x000000F8) >> 3));
            break;
        case(phStbVideoRenderer_GfxMode_Clut256) :
            *dst = (((((value & 0x00FF0000) >> 16) / 32) * 32) + 
                    ((((value & 0x0000FF00) >> 8) /  32) * 4) + 
                     ((value & 0x000000FF) / 64)); 
            break;
        default:
            *((u32*)dst) = value;
            break;
    }
}

#if defined(CONFIG_LOGO)
/**
 * Allows an image to be scaled and blitted to the framebuffer - using to display logos
 *
 * @param info          @b IN The fb_info structure
 * @param width         @b IN The image width
 * @param height        @b IN The image height
 * @param pStartAddress @b IN The image memory location
 *
 * @return void
 */
static void phStbFB_imageBlit(struct fb_info *info, u32 width, u32 height, u32 *pStartAddress)
{
    int x;
    int y;
    phStbVideoRenderer_GfxMode_t mode;
    u8 *dst_row;

    mode = phStbFB_type(&info->var);

    dst_row = (u8 *)info->screen_base;

    /* Go through each line of the destination framebuffer */
    for(y=0; y<info->var.height; y++)
    {
        const u32 *src;
        u8 *dst;

        /* Select the source row that best matches the destination */
        src = &(pStartAddress[((y * height) / info->var.height) * width]);
        dst = dst_row;

        /* Got through each pixel on the line */
        for(x=0; x<info->var.width; x++)
        {
            /* Copy the source pixel that best matches the destination */
            u32 value = src[((x* width) / info->var.width)];

            phStbFB_setPixel(mode, value, dst);

            dst += (info->var.bits_per_pixel) / 8;
        }

        dst_row += (info->var.width * info->var.bits_per_pixel) / 8;
    }
}

/**
 * Obtains information about the currently display OSD layer
 *
 * @param pWidth         @b OUT Width of the OSD layer
 * @param pHeight        @b OUT Height of the OSD layer
 * @param pPStartAddress @b OUT Pointer to the start of the OSD memory
 *
 * @return void
 */
static void phStbFb_getOSD(u32 *pWidth, u32 *pHeight, u32 **pPStartAddress)
{
    volatile u32 *pOsdReg;
    u32 *pOsdAnchor;

    /* Get a pointer to the OSD block's registers */
    pOsdReg = (volatile u32 *)OSD_BLOCK_REGISTER;

    if (pOsdReg[1])
    {
        /* Get a pointer to the OSD descriptor */
        pOsdAnchor = (u32 *)(pOsdReg[1] | 0x80000000);

        /* Get the OSD dimensions */
        *pWidth = pOsdAnchor[1] & 0x7FF;
        *pHeight = (pOsdAnchor[2] >> 16) & 0x7FF;

        /* Check for window extraction */
        if ((pOsdAnchor[2] & 0x8000) == 0)
        {
            *pHeight /= 2;
        }

        /* Calculate the OSD memory start address */
        *pPStartAddress = (u32 *)(pOsdAnchor[4] + (u32)pOsdAnchor);
    }
    else
    {
        *pWidth = 0;
        *pHeight = 0;
        *pPStartAddress = 0;
    }
}
#endif

static phStbVideoRenderer_GfxMode_t phStbFb_setGraphicsMode(struct fb_info *pFbInfo)
{
    phStbVideoRenderer_GfxMode_t mode = phStbVideoRenderer_GfxMode_Invalid;

    if (0 == strcmp(gfx_display_mode, "RGB16"))
    {
        mode = phStbVideoRenderer_GfxMode_RGB565;

        pFbInfo->var.bits_per_pixel = NUM_RGB565_BLUE + NUM_RGB565_GREEN + NUM_RGB565_RED;
        pFbInfo->var.red.offset = NUM_RGB565_BLUE + NUM_RGB565_GREEN;
        pFbInfo->var.red.length = NUM_RGB565_RED;
        pFbInfo->var.green.offset = NUM_RGB565_BLUE;
        pFbInfo->var.green.length = NUM_RGB565_GREEN;
        pFbInfo->var.blue.offset =  0;
        pFbInfo->var.blue.length = NUM_RGB565_BLUE;
        pFbInfo->var.transp.offset = 0;
        pFbInfo->var.transp.length = 0;
    }
    else
    if (0 == strcmp(gfx_display_mode, "ARGB1555"))
    {
        mode = phStbVideoRenderer_GfxMode_ARGB1555;

        pFbInfo->var.bits_per_pixel = NUM_ARGB1555_BLUE + NUM_ARGB1555_GREEN + NUM_ARGB1555_RED + NUM_ARGB1555_TRANS;
        pFbInfo->var.red.offset = NUM_ARGB1555_BLUE + NUM_ARGB1555_GREEN;
        pFbInfo->var.red.length = NUM_ARGB1555_RED;
        pFbInfo->var.green.offset = NUM_ARGB1555_BLUE;
        pFbInfo->var.green.length = NUM_ARGB1555_GREEN;
        pFbInfo->var.blue.offset =  0;
        pFbInfo->var.blue.length = NUM_ARGB1555_BLUE;
        pFbInfo->var.transp.offset = NUM_ARGB1555_BLUE + NUM_ARGB1555_GREEN + NUM_ARGB1555_RED;
        pFbInfo->var.transp.length = NUM_ARGB1555_TRANS;
    }
    else
    if (0 == strcmp(gfx_display_mode, "ARGB4444"))
    {
        mode = phStbVideoRenderer_GfxMode_ARGB4444;

        pFbInfo->var.bits_per_pixel = NUM_ARGB4444_BLUE + NUM_ARGB4444_GREEN + NUM_ARGB4444_RED + NUM_ARGB4444_TRANS;
        pFbInfo->var.red.offset = NUM_ARGB4444_BLUE + NUM_ARGB4444_GREEN;
        pFbInfo->var.red.length = NUM_ARGB4444_RED;
        pFbInfo->var.green.offset = NUM_ARGB4444_BLUE;
        pFbInfo->var.green.length = NUM_ARGB4444_GREEN;
        pFbInfo->var.blue.offset =  0;
        pFbInfo->var.blue.length = NUM_ARGB4444_BLUE;
        pFbInfo->var.transp.offset = NUM_ARGB4444_BLUE + NUM_ARGB4444_GREEN + NUM_ARGB4444_RED;
        pFbInfo->var.transp.length = NUM_ARGB4444_TRANS;
    }
    else
    if (0 == strncmp(gfx_display_mode, "ARGB", 4))
    {
        mode = phStbVideoRenderer_GfxMode_ARGB8888;

        pFbInfo->var.bits_per_pixel = NUM_ARGB8888_BLUE + NUM_ARGB8888_GREEN + NUM_ARGB8888_RED + NUM_ARGB8888_TRANS;
        pFbInfo->var.red.offset = NUM_ARGB8888_BLUE + NUM_ARGB8888_GREEN;
        pFbInfo->var.red.length = NUM_ARGB8888_RED;
        pFbInfo->var.green.offset = NUM_ARGB8888_BLUE;
        pFbInfo->var.green.length = NUM_ARGB8888_GREEN;
        pFbInfo->var.blue.offset =  0;
        pFbInfo->var.blue.length = NUM_ARGB8888_BLUE;
        pFbInfo->var.transp.offset = NUM_ARGB8888_BLUE + NUM_ARGB8888_GREEN + NUM_ARGB8888_RED;
        pFbInfo->var.transp.length = NUM_ARGB8888_TRANS;
    }
    else
    if (0 == strcmp(gfx_display_mode, "LUT8"))
    {
        mode = phStbVideoRenderer_GfxMode_Clut256;

        pFbInfo->var.bits_per_pixel = 8;
        pFbInfo->var.red.offset = NUM_CLUT8_BLUE + NUM_CLUT8_GREEN;
        pFbInfo->var.red.length = NUM_CLUT8_RED;
        pFbInfo->var.green.offset = NUM_CLUT8_BLUE;
        pFbInfo->var.green.length = NUM_CLUT8_GREEN;
        pFbInfo->var.blue.offset =  0;
        pFbInfo->var.blue.length = NUM_CLUT8_BLUE;
        pFbInfo->var.transp.offset = NUM_CLUT8_BLUE + NUM_CLUT8_GREEN + NUM_CLUT8_RED;
        pFbInfo->var.transp.length = NUM_CLUT8_TRANS;

        phStbFb_setDefaultCLUT(pFbInfo);
    }
    else
    {
        printk(KERN_WARNING "Unrecognised graphics mode '%s'\n", gfx_display_mode);
    }
    return mode;
}

/*
 *  Initialization of the Framebuffer.
 */
static int __init phStbFB_probe(struct platform_device *p_dev)
{
    tmErrorCode_t status = TM_OK;
    unsigned long i;
    int fbIndex = 0;
    int error;
    char layerName[PH_STBVIDEORENDERER_MAX_NAME_LENGTH];
    struct fb_info *pFbInfo;
    phStbVideoRenderer_GfxMode_t mode;

#if defined(CONFIG_LOGO)
    u32 logoWidth = 0;
    u32 logoHeight = 0;
    u32* pLogoStartAddress = 0;

    /* Only put logo on 1st Framebuffer */
    if (p_dev->id == 0)
    {
        phStbFb_getOSD(&logoWidth, &logoHeight, &pLogoStartAddress);
    }
#endif

    /*Index into fb array is the device id*/
    fbIndex = p_dev->id;

    if ((p_dev->id < 0) || (p_dev->id >= PHSTB_NUM_FB_DEVS))
    {
        return -ENODEV;
    }

    pFbInfo = framebuffer_alloc(0, &p_dev->dev);
    gphStbFB_info.pfb_info[fbIndex] = pFbInfo;

    if (pFbInfo == NULL) {
        printk(KERN_ERR "Error framebuffer_alloc\n");
        return -ENOMEM;
    }

    /*
     * Function pointers and fixed values filled in here.
     */
    pFbInfo->fbops = &gphStbFB_ops;
    pFbInfo->fix = gphStbFB_fix;
    sprintf(pFbInfo->fix.id, "%s:%d", pFbInfo->fix.id, fbIndex);

    phStbFB_setup_destination_offsets();

    /* Check for a valid destination rectangle */
    if ((dest_rect[2] == 0) || (dest_rect[3] == 0))
    {
        /* Set up the Frame buffer destination */
        phStbFB_reset_destination_rect();
    }

    phStbFB_checkDimensions(&width, &height);

    pFbInfo->var.xres = pFbInfo->var.xres_virtual = width;
    pFbInfo->var.yres = height;
    pFbInfo->var.width = width;
    pFbInfo->var.height = height;

    /* Set up virtual resolution to enable double buffered display */
    pFbInfo->var.yres_virtual = double_buffered ? pFbInfo->var.yres * 2 : pFbInfo->var.yres;

    pFbInfo->var.vmode = FB_VMODE_NONINTERLACED;

    /* Allocate a colour map */
    if (fb_alloc_cmap(&pFbInfo->cmap, 256, 1) < 0)
    {
        printk(KERN_WARNING "Unable to allocate Colour Map\n");
        return -EINVAL;
    }

    /* Set up the graphics mode */
    mode = phStbFb_setGraphicsMode(pFbInfo);
    if (mode == phStbVideoRenderer_GfxMode_Invalid)
    {
        return -EINVAL;
    }

    /* Allocate and set up the framebuffer memory */
    error = phStbFB_init_memory();
    if (error < 0)
    {
        return error;
    }

    /*
     * Set up flags to indicate what sort of acceleration your
     * driver can provide (pan/wrap/copyarea/etc.) and whether it
     * is a module -- see FBINFO_* in include/linux/fb.h
     */

    pFbInfo->flags = (FBINFO_HWACCEL_DISABLED);

    /*Now register the framebuffer*/
    if (register_framebuffer(pFbInfo) < 0)
    {
        printk(KERN_WARNING "Unable to Register Framebuffer\n");
        return -EINVAL;
    }
    /*Now do sysfs stuff*/
	platform_set_drvdata(p_dev, pFbInfo);
    phStbFb_create_sysfs(&p_dev->dev);

    /*Fill with background colour*/
    {
        u8 *dst = pFbInfo->screen_base;
        u8 increment = (pFbInfo->var.bits_per_pixel) / 8;

        for(i = 0; i < gphStbFB_info.fb_size[fbIndex]; i+=increment)
        {
            phStbFB_setPixel(mode, background, dst + i);
        }
    }

#if defined(CONFIG_LOGO)
    if (pLogoStartAddress)
    {
        phStbFB_imageBlit(pFbInfo, logoWidth, logoHeight, pLogoStartAddress);
    }
#endif

    if (fbIndex == 0)
    {
        status = phStbVideoRenderer_GetVideoLayer(phStbVideoRenderer_Layers_Graphics1, &gphStbFB_info.pLayer[0], layerName);
        status = phStbVideoRenderer_GetVideoLayer(phStbVideoRenderer_Layers_Graphics2, &gphStbFB_info.pLayer[1], layerName);

        phStbVideoRenderer_OsdClearAndReset();
    }

    phStbFB_set_OSD(pFbInfo);

    printk(KERN_INFO "phStbFB : /dev/fb%d - %dx%d pixels (%s - %s; %s buffered)\n",
           pFbInfo->node,
           pFbInfo->var.xres,
           pFbInfo->var.yres,
           gfx_display_mode,
           ( (pFbInfo->var.xres == dest_rect[2]) &&
             (pFbInfo->var.yres == dest_rect[3]) ) ? "Not scaled" : "Scaled",
           pFbInfo->var.yres_virtual == (2*pFbInfo->var.yres) ? "Double" : "Single"
           );

    /*Success registering the devices so return 0*/
    return 0;
}

/*
 *  Cleanup
 */
static int __devexit phStbFB_remove(struct platform_device* p_dev)
{
    int fbIndex = p_dev->id;
    tmErrorCode_t status = TM_OK;

    /* Unregister fb*/
    unregister_framebuffer(gphStbFB_info.pfb_info[fbIndex]);

    /* Remove sysfs entries */
    phStbFB_remove_sysfs(&p_dev->dev);

    platform_set_drvdata(p_dev, NULL);

    /*
     * @todo
     * Should deallocate TMML regions here and deinit any code left over
     * However not sure what we are doing for this yet with regards to VideoRenderer so leaving it
     * for now.
     */

    /* deallocate colour map */
    fb_dealloc_cmap(&gphStbFB_info.pfb_info[fbIndex]->cmap);

    /* Set the buffer pointer to NULL, thus deactivating the layer. */
    status = phStbVideoRenderer_Layer_SetBufferPointer(gphStbFB_info.pLayer[fbIndex], 
                                                       0, 
                                                       0, 
                                                       NULL,
                                                       phStbFB_type(&gphStbFB_info.pfb_info[fbIndex]->var),
                                                       NULL);

    if (fbIndex == 0)
    {
        phStbFB_free_memory();
    }
    return 0;
}

/*
 * Allow PNX8335 driver to take special options.
 *
 * Used when we can not do module options.
 *
 * Parse special options.
 */
int __init phStbFB_setup(char *options)
{
    char* this_opt;

    if (!options || !*options)
    {
        return 0;
    }

    /* Parse user speficied options (`video=phStbFB:....') */
    while ((this_opt = strsep(&options, ",")) != NULL )
    {
        if (!*this_opt)
            continue;

        if (!strncmp(this_opt, "background::", 11))
        {
            background = simple_strtoul(this_opt+11, NULL, 0);
        }
        else if (!strncmp(this_opt, "gfx_memory_size:", 16))
        {
            gfx_memory_size = simple_strtoul(this_opt+11, NULL, 0);
        }
        else if (!strncmp(this_opt, "width:", 6))
        {
            width = simple_strtoul(this_opt+6, NULL, 0);
        }
        else if (!strncmp(this_opt, "num_fb:", 7))
        {
            num_fb = simple_strtoul(this_opt+7, NULL, 0);
        }
        else if (!strncmp(this_opt, "height:", 7))
        {
            height = simple_strtoul(this_opt+7, NULL, 0);
        }
    }
    return 0;
}

/**
 * Sysfs Structures.
 */
static struct platform_driver gphStbFB_driver[PHSTB_NUM_FB_DEVS] = {
{
    .driver = {
        .name = "fb0"
    },
    .probe = phStbFB_probe,
    .remove = __devexit_p(phStbFB_remove),
},
{
    .driver = {
        .name = "fb1"
    },
    .probe = phStbFB_probe,
    .remove = __devexit_p(phStbFB_remove),
}
};

static struct platform_device gphStbFB_device[PHSTB_NUM_FB_DEVS] = {
{
    .name = "fb0",
},
{
    .name = "fb1",
}
};

int __init phStbFB_init(void)
{
    int fbIndex;
    /*
     *  For kernel boot options (in 'video=phStbFB:<option>:<option>...' format)
     */
#ifndef MODULE
    char *option = NULL;

    if (fb_get_options("phStbFB", &option))
    {
        printk(KERN_WARNING "Failed to parse specified options\n");
        return -ENODEV;
    }
    phStbFB_setup(option);
#endif

    if (num_fb > 2)
    {
        printk(KERN_WARNING "Excessive number of Framebuffers requested (%d) - resetting to 2\n", num_fb);
        num_fb = 2;
    }
    if (num_fb < 1)
    {
        printk(KERN_WARNING "Insufficient number of Framebuffers requested (%d) - resetting to 1\n", num_fb);
        num_fb = 1;
    }

    for(fbIndex = 0; fbIndex < num_fb; fbIndex++)
    {
        int ret;
        ret = platform_driver_register(&gphStbFB_driver[fbIndex]);
        if (!ret)
        {
            gphStbFB_device[fbIndex].id = fbIndex;

            ret = platform_device_register(&gphStbFB_device[fbIndex]);
            if (ret)
            {
                platform_driver_unregister(&gphStbFB_driver[fbIndex]);
                return ret;
            }
        }
    }
    return 0;
}

void __exit phStbFB_exit(void)
{
    int fbIndex;

    for(fbIndex = 0; fbIndex < num_fb; fbIndex++)
    {
        platform_driver_unregister(&gphStbFB_driver[fbIndex]);
    }
}

/* ------------------------------------------------------------------------- */
/**
 * @brief Modularization Parameters etc.
 */

module_init(phStbFB_init);
module_exit(phStbFB_exit);

module_param(background, uint, S_IRUGO);
MODULE_PARM_DESC(background, "Framebuffer background colour to be used (32 bit ARGB) [default transparent 0x00000000]\n");

module_param(gfx_display_mode, charp, S_IRUGO);
MODULE_PARM_DESC(gfx_display_mode, "The default colour mode (ARGB, RGB16, ARGB4444, ARGB1555, LUT8) [default ARGB8888]\n");

module_param(gfx_memory_start, charp, S_IRUGO);
MODULE_PARM_DESC(gfx_memory_start, "The start address of the GFX memory allocated for use by DirectFB specified in Hex String.\n");

module_param(gfx_memory_size, int, S_IRUGO);
MODULE_PARM_DESC(gfx_memory_size, "The Maximum amount of GFX memory allowed for use by DirectFB specified in bytes\n");

module_param(gfx_memory_available, uint, S_IRUGO);
MODULE_PARM_DESC(gfx_memory_available, "How much memory is not mapped to either of the framebuffers (gfx_memory_size - fb_size[n]).\n");

module_param(num_fb,uint,S_IRUGO);
MODULE_PARM_DESC(num_fb, "Set the maximum number of framebuffers (default 2)");

module_param_array(dest_rect,uint,NULL,S_IRUGO);
MODULE_PARM_DESC(dest_rect, "Destination Rectangle of the framebuffer (x y width height)");

module_param(width,uint,S_IRUGO);
MODULE_PARM_DESC(width, "Set the width of the framebuffer");

module_param(height,uint,S_IRUGO);
MODULE_PARM_DESC(height, "Set the height of the framebuffer");

module_param(double_buffered, bool, S_IRUGO);
MODULE_PARM_DESC(double_buffered, "Enable double buffered framebuffer (default 1 - true)");

module_param(cached_memory, bool, S_IRUGO);
MODULE_PARM_DESC(cached_memory, "make framebuffer memory cached (default 1 - true if double buffered)");

module_param(use_kernel_buffmgr, bool, S_IRUGO);
MODULE_PARM_DESC(use_kernel_buffmgr, "Enable memory allocation from the kernel buffer manager (default 0 - false)");

module_param(extended_fb_mem, bool, S_IRUGO);
MODULE_PARM_DESC(extended_fb_mem, "Extend Framebuffer memory to allow for offscreen surfaces in DirectFB (default 1 - true)");

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("NXP B.V.");
