/*
 * Mesa 3-D graphics library
 * Version:  6.3
 *
 * Copyright (C) 1999-2005  Brian Paul   All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/* Minimal swrast-based dri loadable driver.
 *
 * Todo:
 *   -- Use malloced (rather than framebuffer) memory for backbuffer
 *   -- 32bpp is hardwared -- fix
 *
 * NOTES:
 *   -- No mechanism for cliprects or resize notification --
 *      assumes this is a fullscreen device.  
 *   -- No locking -- assumes this is the only driver accessing this 
 *      device.
 *   -- Doesn't (yet) make use of any acceleration or other interfaces
 *      provided by fb.  Would be entirely happy working against any 
 *	fullscreen interface.
 *   -- HOWEVER: only a small number of pixelformats are supported, and
 *      the mechanism for choosing between them makes some assumptions
 *      that may not be valid everywhere.
 */

#include "driver.h"
#include "drm.h"
#include "utils.h"
#include "drirenderbuffer.h"

#include "buffers.h"
#include "main/extensions.h"
#include "main/framebuffer.h"
#include "main/renderbuffer.h"
#include "vbo/vbo.h"
#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/tnl.h"
#include "tnl/tcontext.h"
#include "tnl/t_pipeline.h"
#include "drivers/common/driverfuncs.h"

void fbSetSpanFunctions(driRenderbuffer *drb, const GLvisual *vis);

typedef struct {
   GLcontext *glCtx;		/* Mesa context */

   struct {
      __DRIcontextPrivate *context;	
      __DRIscreenPrivate *screen;	
      __DRIdrawablePrivate *drawable; /* drawable bound to this ctx */
   } dri;
   
} fbContext, *fbContextPtr;

#define FB_CONTEXT(ctx)		((fbContextPtr)(ctx->DriverCtx))


static const GLubyte *
get_string(GLcontext *ctx, GLenum pname)
{
   (void) ctx;
   switch (pname) {
      case GL_RENDERER:
         return (const GLubyte *) "Mesa dumb framebuffer";
      default:
         return NULL;
   }
}


static void
update_state( GLcontext *ctx, GLuint new_state )
{
   /* not much to do here - pass it on */
   _swrast_InvalidateState( ctx, new_state );
   _swsetup_InvalidateState( ctx, new_state );
   _vbo_InvalidateState( ctx, new_state );
   _tnl_InvalidateState( ctx, new_state );
}


/**
 * Called by ctx->Driver.GetBufferSize from in core Mesa to query the
 * current framebuffer size.
 */
static void
get_buffer_size( GLframebuffer *buffer, GLuint *width, GLuint *height )
{
   GET_CURRENT_CONTEXT(ctx);
   fbContextPtr fbmesa = FB_CONTEXT(ctx);

   *width  = fbmesa->dri.drawable->w;
   *height = fbmesa->dri.drawable->h;
}


static void
updateFramebufferSize(GLcontext *ctx)
{
   fbContextPtr fbmesa = FB_CONTEXT(ctx);
   struct gl_framebuffer *fb = ctx->WinSysDrawBuffer;
   if (fbmesa->dri.drawable->w != fb->Width ||
       fbmesa->dri.drawable->h != fb->Height) {
      driUpdateFramebufferSize(ctx, fbmesa->dri.drawable);
   }
}

static void
viewport(GLcontext *ctx, GLint x, GLint y, GLsizei w, GLsizei h)
{
   /* XXX this should be called after we acquire the DRI lock, not here */
   updateFramebufferSize(ctx);
}


static void
init_core_functions( struct dd_function_table *functions )
{
   functions->GetString = get_string;
   functions->UpdateState = update_state;
   functions->GetBufferSize = get_buffer_size;
   functions->Viewport = viewport;

   functions->Clear = _swrast_Clear;  /* could accelerate with blits */
}


/*
 * Generate code for span functions.
 */

/* 24-bit BGR */
#define NAME(PREFIX) PREFIX##_B8G8R8
#define FORMAT GL_RGBA8
#define SPAN_VARS \
   driRenderbuffer *drb = (driRenderbuffer *) rb;
#define INIT_PIXEL_PTR(P, X, Y) \
   GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 3;
#define INC_PIXEL_PTR(P) P += 3
#define STORE_PIXEL(DST, X, Y, VALUE) \
   DST[0] = VALUE[BCOMP]; \
   DST[1] = VALUE[GCOMP]; \
   DST[2] = VALUE[RCOMP]
#define FETCH_PIXEL(DST, SRC) \
   DST[RCOMP] = SRC[2]; \
   DST[GCOMP] = SRC[1]; \
   DST[BCOMP] = SRC[0]; \
   DST[ACOMP] = 0xff

#include "swrast/s_spantemp.h"


/* 32-bit BGRA */
#define NAME(PREFIX) PREFIX##_B8G8R8A8
#define FORMAT GL_RGBA8
#define SPAN_VARS \
   driRenderbuffer *drb = (driRenderbuffer *) rb;
#define INIT_PIXEL_PTR(P, X, Y) \
   GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 4;
#define INC_PIXEL_PTR(P) P += 4
#define STORE_PIXEL(DST, X, Y, VALUE) \
   DST[0] = VALUE[BCOMP]; \
   DST[1] = VALUE[GCOMP]; \
   DST[2] = VALUE[RCOMP]; \
   DST[3] = VALUE[ACOMP]
#define STORE_PIXEL_RGB(DST, X, Y, VALUE) \
   DST[0] = VALUE[BCOMP]; \
   DST[1] = VALUE[GCOMP]; \
   DST[2] = VALUE[RCOMP]; \
   DST[3] = 0xff
#define FETCH_PIXEL(DST, SRC) \
   DST[RCOMP] = SRC[2]; \
   DST[GCOMP] = SRC[1]; \
   DST[BCOMP] = SRC[0]; \
   DST[ACOMP] = SRC[3]

#include "swrast/s_spantemp.h"


/* 16-bit BGR (XXX implement dithering someday) */
#define NAME(PREFIX) PREFIX##_B5G6R5
#define FORMAT GL_RGBA8
#define SPAN_VARS \
   driRenderbuffer *drb = (driRenderbuffer *) rb;
#define INIT_PIXEL_PTR(P, X, Y) \
   GLushort *P = (GLushort *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 2;
#define INC_PIXEL_PTR(P) P += 1
#define STORE_PIXEL(DST, X, Y, VALUE) \
   DST[0] = ( (((VALUE[RCOMP]) & 0xf8) << 8) | (((VALUE[GCOMP]) & 0xfc) << 3) | ((VALUE[BCOMP]) >> 3) )
#define FETCH_PIXEL(DST, SRC) \
   DST[RCOMP] = ( (((SRC[0]) >> 8) & 0xf8) | (((SRC[0]) >> 11) & 0x7) ); \
   DST[GCOMP] = ( (((SRC[0]) >> 3) & 0xfc) | (((SRC[0]) >>  5) & 0x3) ); \
   DST[BCOMP] = ( (((SRC[0]) << 3) & 0xf8) | (((SRC[0])      ) & 0x7) ); \
   DST[ACOMP] = 0xff

#include "swrast/s_spantemp.h"


/* 15-bit BGR (XXX implement dithering someday) */
#define NAME(PREFIX) PREFIX##_B5G5R5
#define FORMAT GL_RGBA8
#define SPAN_VARS \
   driRenderbuffer *drb = (driRenderbuffer *) rb;
#define INIT_PIXEL_PTR(P, X, Y) \
   GLushort *P = (GLushort *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 2;
#define INC_PIXEL_PTR(P) P += 1
#define STORE_PIXEL(DST, X, Y, VALUE) \
   DST[0] = ( (((VALUE[RCOMP]) & 0xf8) << 7) | (((VALUE[GCOMP]) & 0xf8) << 2) | ((VALUE[BCOMP]) >> 3) )
#define FETCH_PIXEL(DST, SRC) \
   DST[RCOMP] = ( (((SRC[0]) >> 7) & 0xf8) | (((SRC[0]) >> 10) & 0x7) ); \
   DST[GCOMP] = ( (((SRC[0]) >> 2) & 0xf8) | (((SRC[0]) >>  5) & 0x7) ); \
   DST[BCOMP] = ( (((SRC[0]) << 3) & 0xf8) | (((SRC[0])      ) & 0x7) ); \
   DST[ACOMP] = 0xff

#include "swrast/s_spantemp.h"


/* 8-bit color index */
#define NAME(PREFIX) PREFIX##_CI8
#define FORMAT GL_COLOR_INDEX8_EXT
#define SPAN_VARS \
   driRenderbuffer *drb = (driRenderbuffer *) rb;
#define INIT_PIXEL_PTR(P, X, Y) \
   GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X);
#define INC_PIXEL_PTR(P) P += 1
#define STORE_PIXEL(DST, X, Y, VALUE) \
   *DST = VALUE[0]
#define FETCH_PIXEL(DST, SRC) \
   DST = SRC[0]

#include "swrast/s_spantemp.h"



void
fbSetSpanFunctions(driRenderbuffer *drb, const GLvisual *vis)
{
   ASSERT(drb->Base.InternalFormat == GL_RGBA);
   if (drb->Base.InternalFormat == GL_RGBA) {
      if (vis->redBits == 5 && vis->greenBits == 6 && vis->blueBits == 5) {
         drb->Base.GetRow = get_row_B5G6R5;
         drb->Base.GetValues = get_values_B5G6R5;
         drb->Base.PutRow = put_row_B5G6R5;
         drb->Base.PutMonoRow = put_mono_row_B5G6R5;
         drb->Base.PutRowRGB = put_row_rgb_B5G6R5;
         drb->Base.PutValues = put_values_B5G6R5;
         drb->Base.PutMonoValues = put_mono_values_B5G6R5;
      }
      else if (vis->redBits == 5 && vis->greenBits == 5 && vis->blueBits == 5) {
         drb->Base.GetRow = get_row_B5G5R5;
         drb->Base.GetValues = get_values_B5G5R5;
         drb->Base.PutRow = put_row_B5G5R5;
         drb->Base.PutMonoRow = put_mono_row_B5G5R5;
         drb->Base.PutRowRGB = put_row_rgb_B5G5R5;
         drb->Base.PutValues = put_values_B5G5R5;
         drb->Base.PutMonoValues = put_mono_values_B5G5R5;
      }
      else if (vis->redBits == 8 && vis->greenBits == 8 && vis->blueBits == 8
               && vis->alphaBits == 8) {
         drb->Base.GetRow = get_row_B8G8R8A8;
         drb->Base.GetValues = get_values_B8G8R8A8;
         drb->Base.PutRow = put_row_B8G8R8A8;
         drb->Base.PutMonoRow = put_mono_row_B8G8R8A8;
         drb->Base.PutRowRGB = put_row_rgb_B8G8R8A8;
         drb->Base.PutValues = put_values_B8G8R8A8;
         drb->Base.PutMonoValues = put_mono_values_B8G8R8A8;
      }
      else if (vis->redBits == 8 && vis->greenBits == 8 && vis->blueBits == 8
               && vis->alphaBits == 0) {
         drb->Base.GetRow = get_row_B8G8R8;
         drb->Base.GetValues = get_values_B8G8R8;
         drb->Base.PutRow = put_row_B8G8R8;
         drb->Base.PutMonoRow = put_mono_row_B8G8R8;
         drb->Base.PutRowRGB = put_row_rgb_B8G8R8;
         drb->Base.PutValues = put_values_B8G8R8;
         drb->Base.PutMonoValues = put_mono_values_B8G8R8;
      }
      else if (vis->indexBits == 8) {
         drb->Base.GetRow = get_row_CI8;
         drb->Base.GetValues = get_values_CI8;
         drb->Base.PutRow = put_row_CI8;
         drb->Base.PutMonoRow = put_mono_row_CI8;
         drb->Base.PutValues = put_values_CI8;
         drb->Base.PutMonoValues = put_mono_values_CI8;
      }
   }
   else {
      /* hardware z/stencil/etc someday */
   }
}



/* Initialize the driver specific screen private data.
 */
static GLboolean
fbInitDriver( __DRIscreenPrivate *sPriv )
{
   sPriv->private = NULL;
   return GL_TRUE;
}

static void
fbDestroyScreen( __DRIscreenPrivate *sPriv )
{
}


/* Create the device specific context.
 */
static GLboolean
fbCreateContext( const __GLcontextModes *glVisual,
		 __DRIcontextPrivate *driContextPriv,
		 void *sharedContextPrivate)
{
   fbContextPtr fbmesa;
   GLcontext *ctx, *shareCtx;
   struct dd_function_table functions;

   assert(glVisual);
   assert(driContextPriv);

   /* Allocate the Fb context */
   fbmesa = (fbContextPtr) _mesa_calloc( sizeof(*fbmesa) );
   if ( !fbmesa )
      return GL_FALSE;

   /* Init default driver functions then plug in our FBdev-specific functions
    */
   _mesa_init_driver_functions(&functions);
   init_core_functions(&functions);

   /* Allocate the Mesa context */
   if (sharedContextPrivate)
      shareCtx = ((fbContextPtr) sharedContextPrivate)->glCtx;
   else
      shareCtx = NULL;

   ctx = fbmesa->glCtx = _mesa_create_context(glVisual, shareCtx, 
					      &functions, (void *) fbmesa);
   if (!fbmesa->glCtx) {
      _mesa_free(fbmesa);
      return GL_FALSE;
   }
   driContextPriv->driverPrivate = fbmesa;

   /* Create module contexts */
   _swrast_CreateContext( ctx );
   _vbo_CreateContext( ctx );
   _tnl_CreateContext( ctx );
   _swsetup_CreateContext( ctx );
   _swsetup_Wakeup( ctx );


   /* use default TCL pipeline */
   {
      TNLcontext *tnl = TNL_CONTEXT(ctx);
      tnl->Driver.RunPipeline = _tnl_run_pipeline;
   }

   _mesa_enable_sw_extensions(ctx);

   return GL_TRUE;
}


static void
fbDestroyContext( __DRIcontextPrivate *driContextPriv )
{
   GET_CURRENT_CONTEXT(ctx);
   fbContextPtr fbmesa = (fbContextPtr) driContextPriv->driverPrivate;
   fbContextPtr current = ctx ? FB_CONTEXT(ctx) : NULL;

   /* check if we're deleting the currently bound context */
   if (fbmesa == current) {
      _mesa_make_current(NULL, NULL, NULL);
   }

   /* Free fb context resources */
   if ( fbmesa ) {
      _swsetup_DestroyContext( fbmesa->glCtx );
      _tnl_DestroyContext( fbmesa->glCtx );
      _vbo_DestroyContext( fbmesa->glCtx );
      _swrast_DestroyContext( fbmesa->glCtx );

      /* free the Mesa context */
      fbmesa->glCtx->DriverCtx = NULL;
      _mesa_destroy_context( fbmesa->glCtx );

      _mesa_free( fbmesa );
   }
}


/* Create and initialize the Mesa and driver specific pixmap buffer
 * data.
 */
static GLboolean
fbCreateBuffer( __DRIscreenPrivate *driScrnPriv,
		__DRIdrawablePrivate *driDrawPriv,
		const __GLcontextModes *mesaVis,
		GLboolean isPixmap )
{
   struct gl_framebuffer *mesa_framebuffer;
   
   if (isPixmap) {
      return GL_FALSE; /* not implemented */
   }
   else {
      const GLboolean swDepth = mesaVis->depthBits > 0;
      const GLboolean swAlpha = mesaVis->alphaBits > 0;
      const GLboolean swAccum = mesaVis->accumRedBits > 0;
      const GLboolean swStencil = mesaVis->stencilBits > 0;
      
      mesa_framebuffer = _mesa_create_framebuffer(mesaVis);
      if (!mesa_framebuffer)
         return 0;

      /* XXX double-check these parameters (bpp vs cpp, etc) */
      {
         driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA,
                                                   driScrnPriv->pFB,
                                                   driScrnPriv->fbBPP / 8,
                                                   driScrnPriv->fbOrigin,
                                                   driScrnPriv->fbStride,
                                                   driDrawPriv);
         fbSetSpanFunctions(drb, mesaVis);
         _mesa_add_renderbuffer(mesa_framebuffer,
                                BUFFER_FRONT_LEFT, &drb->Base);
      }
      if (mesaVis->doubleBufferMode) {
         /* XXX what are the correct origin/stride values? */
         GLvoid *backBuf = _mesa_malloc(driScrnPriv->fbStride
                                        * driScrnPriv->fbHeight);
         driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA,
                                                   backBuf,
                                                   driScrnPriv->fbBPP /8,
                                                   driScrnPriv->fbOrigin,
                                                   driScrnPriv->fbStride,
                                                   driDrawPriv);
         fbSetSpanFunctions(drb, mesaVis);
         _mesa_add_renderbuffer(mesa_framebuffer,
                                BUFFER_BACK_LEFT, &drb->Base);
      }

      _mesa_add_soft_renderbuffers(mesa_framebuffer,
                                   GL_FALSE, /* color */
                                   swDepth,
                                   swStencil,
                                   swAccum,
                                   swAlpha, /* or always zero? */
                                   GL_FALSE /* aux */);
      
      driDrawPriv->driverPrivate = mesa_framebuffer;

      return 1;
   }
}


static void
fbDestroyBuffer(__DRIdrawablePrivate *driDrawPriv)
{
   _mesa_reference_framebuffer((GLframebuffer **)(&(driDrawPriv->driverPrivate)), NULL);
}



/* If the backbuffer is on a videocard, this is extraordinarily slow!
 */
static void
fbSwapBuffers( __DRIdrawablePrivate *dPriv )
{
   struct gl_framebuffer *mesa_framebuffer = (struct gl_framebuffer *)dPriv->driverPrivate;
   struct gl_renderbuffer * front_renderbuffer = mesa_framebuffer->Attachment[BUFFER_FRONT_LEFT].Renderbuffer;
   void *frontBuffer = front_renderbuffer->Data;
   int currentPitch = ((driRenderbuffer *)front_renderbuffer)->pitch;
   void *backBuffer = mesa_framebuffer->Attachment[BUFFER_BACK_LEFT].Renderbuffer->Data;

   if (dPriv->driContextPriv && dPriv->driContextPriv->driverPrivate) {
      fbContextPtr fbmesa = (fbContextPtr) dPriv->driContextPriv->driverPrivate;
      GLcontext *ctx = fbmesa->glCtx;
      
      if (ctx->Visual.doubleBufferMode) {
	 int i;
	 int offset = 0;
         char *tmp = _mesa_malloc(currentPitch);

         _mesa_notifySwapBuffers( ctx );  /* flush pending rendering comands */

         ASSERT(frontBuffer);
         ASSERT(backBuffer);

	 for (i = 0; i < dPriv->h; i++) {
            _mesa_memcpy(tmp, (char *) backBuffer + offset,
                         currentPitch);
            _mesa_memcpy((char *) frontBuffer + offset, tmp,
                          currentPitch);
            offset += currentPitch;
	 }
	    
	 _mesa_free(tmp);
      }
   }
   else {
      /* XXX this shouldn't be an error but we can't handle it for now */
      _mesa_problem(NULL, "fbSwapBuffers: drawable has no context!\n");
   }
}


/* Force the context `c' to be the current context and associate with it
 * buffer `b'.
 */
static GLboolean
fbMakeCurrent( __DRIcontextPrivate *driContextPriv,
	       __DRIdrawablePrivate *driDrawPriv,
	       __DRIdrawablePrivate *driReadPriv )
{
   if ( driContextPriv ) {
      fbContextPtr newFbCtx = 
            (fbContextPtr) driContextPriv->driverPrivate;

      newFbCtx->dri.drawable = driDrawPriv;

      _mesa_make_current( newFbCtx->glCtx, 
                           driDrawPriv->driverPrivate,
                           driReadPriv->driverPrivate);
   } else {
      _mesa_make_current( NULL, NULL, NULL );
   }

   return GL_TRUE;
}


/* Force the context `c' to be unbound from its buffer.
 */
static GLboolean
fbUnbindContext( __DRIcontextPrivate *driContextPriv )
{
   return GL_TRUE;
}

static struct __DriverAPIRec fbAPI = {
   .InitDriver      = fbInitDriver,
   .DestroyScreen   = fbDestroyScreen,
   .CreateContext   = fbCreateContext,
   .DestroyContext  = fbDestroyContext,
   .CreateBuffer    = fbCreateBuffer,
   .DestroyBuffer   = fbDestroyBuffer,
   .SwapBuffers     = fbSwapBuffers,
   .MakeCurrent     = fbMakeCurrent,
   .UnbindContext   = fbUnbindContext,
};



static int
__driValidateMode(const DRIDriverContext *ctx )
{
   return 1;
}

static int
__driInitFBDev( struct DRIDriverContextRec *ctx )
{
   /* Note that drmOpen will try to load the kernel module, if needed. */
   /* we need a fbdev drm driver - it will only track maps */
   ctx->drmFD = drmOpen("radeon", NULL );
   if (ctx->drmFD < 0) {
      fprintf(stderr, "[drm] drmOpen failed\n");
      return 0;
   }

   ctx->shared.SAREASize = SAREA_MAX;

   if (drmAddMap( ctx->drmFD,
       0,
       ctx->shared.SAREASize,
       DRM_SHM,
       DRM_CONTAINS_LOCK,
       &ctx->shared.hSAREA) < 0)
   {
      fprintf(stderr, "[drm] drmAddMap failed\n");
      return 0;
   }
   fprintf(stderr, "[drm] added %d byte SAREA at 0x%08lx\n",
           ctx->shared.SAREASize,
           (unsigned long) ctx->shared.hSAREA);

   if (drmMap( ctx->drmFD,
       ctx->shared.hSAREA,
       ctx->shared.SAREASize,
       (drmAddressPtr)(&ctx->pSAREA)) < 0)
   {
      fprintf(stderr, "[drm] drmMap failed\n");
      return 0;
   }
   memset(ctx->pSAREA, 0, ctx->shared.SAREASize);
   fprintf(stderr, "[drm] mapped SAREA 0x%08lx to %p, size %d\n",
           (unsigned long) ctx->shared.hSAREA, ctx->pSAREA,
           ctx->shared.SAREASize);
   
   /* Need to AddMap the framebuffer and mmio regions here:
   */
   if (drmAddMap( ctx->drmFD,
       (drm_handle_t)ctx->FBStart,
       ctx->FBSize,
       DRM_FRAME_BUFFER,
#ifndef _EMBEDDED
		  0,
#else
		  DRM_READ_ONLY,
#endif
		  &ctx->shared.hFrameBuffer) < 0)
   {
      fprintf(stderr, "[drm] drmAddMap framebuffer failed\n");
      return 0;
   }

   fprintf(stderr, "[drm] framebuffer handle = 0x%08lx\n",
           (unsigned long) ctx->shared.hFrameBuffer);

   return 1;
}

static void
__driHaltFBDev( struct DRIDriverContextRec *ctx )
{
}

struct DRIDriverRec __driDriver = {
   __driValidateMode,
   __driValidateMode,
   __driInitFBDev,
   __driHaltFBDev
};

static __GLcontextModes *
fbFillInModes( __DRIscreenPrivate *psp,
	       unsigned pixel_bits, unsigned depth_bits,
	       unsigned stencil_bits, GLboolean have_back_buffer )
{
   __GLcontextModes * modes;
   __GLcontextModes * m;
   unsigned num_modes;
   unsigned depth_buffer_factor;
   unsigned back_buffer_factor;
   GLenum fb_format;
   GLenum fb_type;

    /* Right now GLX_SWAP_COPY_OML isn't supported, but it would be easy
   * enough to add support.  Basically, if a context is created with an
   * fbconfig where the swap method is GLX_SWAP_COPY_OML, pageflipping
   * will never be used.
    */
   static const GLenum back_buffer_modes[] = {
      GLX_NONE, GLX_SWAP_UNDEFINED_OML /*, GLX_SWAP_COPY_OML */
   };

   uint8_t depth_bits_array[2];
   uint8_t stencil_bits_array[2];


   depth_bits_array[0] = depth_bits;
   depth_bits_array[1] = depth_bits;
    
    /* Just like with the accumulation buffer, always provide some modes
   * with a stencil buffer.  It will be a sw fallback, but some apps won't
   * care about that.
    */
   stencil_bits_array[0] = 0;
   stencil_bits_array[1] = (stencil_bits == 0) ? 8 : stencil_bits;

   depth_buffer_factor = ((depth_bits != 0) || (stencil_bits != 0)) ? 2 : 1;
   back_buffer_factor  = (have_back_buffer) ? 2 : 1;

   num_modes = depth_buffer_factor * back_buffer_factor * 4;

   if ( pixel_bits == 16 ) {
      fb_format = GL_RGB;
      fb_type = GL_UNSIGNED_SHORT_5_6_5;
   }
   else {
      fb_format = GL_RGBA;
      fb_type = GL_UNSIGNED_INT_8_8_8_8_REV;
   }

   modes = (*psp->contextModes->createContextModes)( num_modes, sizeof( __GLcontextModes ) );
   m = modes;
   if ( ! driFillInModes( & m, fb_format, fb_type,
          depth_bits_array, stencil_bits_array, depth_buffer_factor,
          back_buffer_modes, back_buffer_factor,
          GLX_TRUE_COLOR ) ) {
             fprintf( stderr, "[%s:%u] Error creating FBConfig!\n",
                      __func__, __LINE__ );
             return NULL;
          }

          if ( ! driFillInModes( & m, fb_format, fb_type,
                 depth_bits_array, stencil_bits_array, depth_buffer_factor,
                 back_buffer_modes, back_buffer_factor,
                 GLX_DIRECT_COLOR ) ) {
                    fprintf( stderr, "[%s:%u] Error creating FBConfig!\n",
                             __func__, __LINE__ );
                    return NULL;
                 }

    /* Mark the visual as slow if there are "fake" stencil bits.
    */
                 for ( m = modes ; m != NULL ; m = m->next ) {
                    if ( (m->stencilBits != 0) && (m->stencilBits != stencil_bits) ) {
                       m->visualRating = GLX_SLOW_CONFIG;
                    }
                 }

                 return modes;
}


/**
 * This is the bootstrap function for the driver.  libGL supplies all of the
 * requisite information about the system, and the driver initializes itself.
 * This routine also fills in the linked list pointed to by \c driver_modes
 * with the \c __GLcontextModes that the driver can support for windows or
 * pbuffers.
 * 
 * \return A pointer to a \c __DRIscreenPrivate on success, or \c NULL on 
 *         failure.
 */
PUBLIC
void * __driCreateNewScreen( __DRInativeDisplay *dpy, int scrn, __DRIscreen *psc,
                                   const __GLcontextModes * modes,
                                   const __DRIversion * ddx_version,
                                   const __DRIversion * dri_version,
                                   const __DRIversion * drm_version,
                                   const __DRIframebuffer * frame_buffer,
                                   drmAddress pSAREA, int fd, 
                                   int internal_api_version,
                                   __GLcontextModes ** driver_modes )
{
   __DRIscreenPrivate *psp;
   static const __DRIversion ddx_expected = { 4, 0, 0 };
   static const __DRIversion dri_expected = { 4, 0, 0 };
   static const __DRIversion drm_expected = { 1, 5, 0 };


   if ( ! driCheckDriDdxDrmVersions2( "fb",
          dri_version, & dri_expected,
          ddx_version, & ddx_expected,
          drm_version, & drm_expected ) ) {
             return NULL;
          }
      
          psp = __driUtilCreateNewScreen(dpy, scrn, psc, NULL,
                                         ddx_version, dri_version, drm_version,
                                         frame_buffer, pSAREA, fd,
                                         internal_api_version, &fbAPI);
          if ( psp != NULL ) {
	     *driver_modes = fbFillInModes( psp, psp->fbBPP,
					    (psp->fbBPP == 16) ? 16 : 24,
					    (psp->fbBPP == 16) ? 0  : 8,
					    1);
          }

          return (void *) psp;
}
