#include <xorg-config.h>

#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glob.h>

#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif
#ifdef HAVE_SYS_MKDEV_H
#include <sys/mkdev.h>          /* for minor() on Solaris */
#endif

#include "xf86.h"
#include "xf86Modes.h"
#include "xf86_OSproc.h"

/* pci stuff */
#include "xf86Pci.h"

#include "xf86cmap.h"

#include "fbdevhw.h"
#include "fbpriv.h"
#include "globals.h"
#include <X11/extensions/dpmsconst.h>

#define PAGE_MASK               (~(getpagesize() - 1))

static XF86ModuleVersionInfo fbdevHWVersRec = {
    .modname      = "fbdevhw",
    .vendor       = MODULEVENDORSTRING,
    ._modinfo1_   = MODINFOSTRING1,
    ._modinfo2_   = MODINFOSTRING2,
    .xf86version  = XORG_VERSION_CURRENT,
    .majorversion = 0,
    .minorversion = 0,
    .patchlevel   = 2,
    .abiclass     = ABI_CLASS_VIDEODRV,
    .abiversion   = ABI_VIDEODRV_VERSION,
};

_X_EXPORT XF86ModuleData fbdevhwModuleData = {
    .vers = &fbdevHWVersRec
};

/* -------------------------------------------------------------------- */
/* our private data, and two functions to allocate/free this            */

#define FBDEVHWPTRLVAL(p) (p)->privates[fbdevHWPrivateIndex].ptr
#define FBDEVHWPTR(p) ((fbdevHWPtr)(FBDEVHWPTRLVAL(p)))

static int fbdevHWPrivateIndex = -1;

typedef struct {
    /* framebuffer device: filename (/dev/fb*), handle, more */
    char *device;
    int fd;
    void *fbmem;
    unsigned int fbmem_len;
    unsigned int fboff;
    char *mmio;
    unsigned int mmio_len;

    /* current hardware state */
    struct fb_fix_screeninfo fix;
    struct fb_var_screeninfo var;

    /* saved video mode */
    struct fb_var_screeninfo saved_var;
    uint32_t saved_accel;

    /* buildin video mode */
    DisplayModeRec buildin;

    /* disable non-fatal unsupported ioctls */
    CARD32 unsupported_ioctls;
} fbdevHWRec, *fbdevHWPtr;

enum {
    FBIOBLANK_UNSUPPORTED = 0,
};

static Bool
fbdevHWGetRec(ScrnInfoPtr pScrn)
{
    if (fbdevHWPrivateIndex < 0)
        fbdevHWPrivateIndex = xf86AllocateScrnInfoPrivateIndex();

    if (FBDEVHWPTR(pScrn) != NULL)
        return TRUE;

    FBDEVHWPTRLVAL(pScrn) = XNFcallocarray(1, sizeof(fbdevHWRec));
    return TRUE;
}

/* -------------------------------------------------------------------- */
/* some helpers for printing debug information                          */

#ifdef DEBUG
static void
print_fbdev_mode(const char *txt, struct fb_var_screeninfo *var)
{
    ErrorF("fbdev %s mode:\t%d   %d %d %d %d   %d %d %d %d   %d %d:%d:%d\n",
           txt, var->pixclock,
           var->xres, var->right_margin, var->hsync_len, var->left_margin,
           var->yres, var->lower_margin, var->vsync_len, var->upper_margin,
           var->bits_per_pixel,
           var->red.length, var->green.length, var->blue.length);
}

static void
print_xfree_mode(const char *txt, DisplayModePtr mode)
{
    ErrorF("xfree %s mode:\t%d   %d %d %d %d   %d %d %d %d\n",
           txt, mode->Clock,
           mode->HDisplay, mode->HSyncStart, mode->HSyncEnd, mode->HTotal,
           mode->VDisplay, mode->VSyncStart, mode->VSyncEnd, mode->VTotal);
}
#endif

/* -------------------------------------------------------------------- */
/* Convert timings between the XFree and the Frame Buffer Device        */

static void
xfree2fbdev_fblayout(ScrnInfoPtr pScrn, struct fb_var_screeninfo *var)
{
    var->xres_virtual = pScrn->displayWidth ? pScrn->displayWidth :
        pScrn->virtualX;
    var->yres_virtual = pScrn->virtualY;
    var->bits_per_pixel = pScrn->bitsPerPixel;
    if (pScrn->defaultVisual == TrueColor ||
        pScrn->defaultVisual == DirectColor) {
        var->red.length = pScrn->weight.red;
        var->green.length = pScrn->weight.green;
        var->blue.length = pScrn->weight.blue;
    }
    else {
        var->red.length = 8;
        var->green.length = 8;
        var->blue.length = 8;
    }
}

static void
xfree2fbdev_timing(DisplayModePtr mode, struct fb_var_screeninfo *var)
{
    var->xres = mode->HDisplay;
    var->yres = mode->VDisplay;
    if (var->xres_virtual < var->xres)
        var->xres_virtual = var->xres;
    if (var->yres_virtual < var->yres)
        var->yres_virtual = var->yres;
    var->xoffset = var->yoffset = 0;
    var->pixclock = mode->Clock ? 1000000000 / mode->Clock : 0;
    var->right_margin = mode->HSyncStart - mode->HDisplay;
    var->hsync_len = mode->HSyncEnd - mode->HSyncStart;
    var->left_margin = mode->HTotal - mode->HSyncEnd;
    var->lower_margin = mode->VSyncStart - mode->VDisplay;
    var->vsync_len = mode->VSyncEnd - mode->VSyncStart;
    var->upper_margin = mode->VTotal - mode->VSyncEnd;
    var->sync = 0;
    if (mode->Flags & V_PHSYNC)
        var->sync |= FB_SYNC_HOR_HIGH_ACT;
    if (mode->Flags & V_PVSYNC)
        var->sync |= FB_SYNC_VERT_HIGH_ACT;
    if (mode->Flags & V_PCSYNC)
        var->sync |= FB_SYNC_COMP_HIGH_ACT;
    if (mode->Flags & V_BCAST)
        var->sync |= FB_SYNC_BROADCAST;
    if (mode->Flags & V_INTERLACE)
        var->vmode = FB_VMODE_INTERLACED;
    else if (mode->Flags & V_DBLSCAN)
        var->vmode = FB_VMODE_DOUBLE;
    else
        var->vmode = FB_VMODE_NONINTERLACED;
}

static Bool
fbdev_modes_equal(struct fb_var_screeninfo *set, struct fb_var_screeninfo *req)
{
    return (set->xres_virtual >= req->xres_virtual &&
            set->yres_virtual >= req->yres_virtual &&
            set->bits_per_pixel == req->bits_per_pixel &&
            set->red.length == req->red.length &&
            set->green.length == req->green.length &&
            set->blue.length == req->blue.length &&
            set->xres == req->xres && set->yres == req->yres &&
            set->right_margin == req->right_margin &&
            set->hsync_len == req->hsync_len &&
            set->left_margin == req->left_margin &&
            set->lower_margin == req->lower_margin &&
            set->vsync_len == req->vsync_len &&
            set->upper_margin == req->upper_margin &&
            set->sync == req->sync && set->vmode == req->vmode);
}

static void
fbdev2xfree_timing(struct fb_var_screeninfo *var, DisplayModePtr mode)
{
    mode->Clock = var->pixclock ? 1000000000 / var->pixclock : 0;
    mode->HDisplay = var->xres;
    mode->HSyncStart = mode->HDisplay + var->right_margin;
    mode->HSyncEnd = mode->HSyncStart + var->hsync_len;
    mode->HTotal = mode->HSyncEnd + var->left_margin;
    mode->VDisplay = var->yres;
    mode->VSyncStart = mode->VDisplay + var->lower_margin;
    mode->VSyncEnd = mode->VSyncStart + var->vsync_len;
    mode->VTotal = mode->VSyncEnd + var->upper_margin;
    mode->Flags = 0;
    mode->Flags |= var->sync & FB_SYNC_HOR_HIGH_ACT ? V_PHSYNC : V_NHSYNC;
    mode->Flags |= var->sync & FB_SYNC_VERT_HIGH_ACT ? V_PVSYNC : V_NVSYNC;
    mode->Flags |= var->sync & FB_SYNC_COMP_HIGH_ACT ? V_PCSYNC : V_NCSYNC;
    if (var->sync & FB_SYNC_BROADCAST)
        mode->Flags |= V_BCAST;
    if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED)
        mode->Flags |= V_INTERLACE;
    else if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE)
        mode->Flags |= V_DBLSCAN;
    mode->SynthClock = mode->Clock;
    mode->CrtcHDisplay = mode->HDisplay;
    mode->CrtcHSyncStart = mode->HSyncStart;
    mode->CrtcHSyncEnd = mode->HSyncEnd;
    mode->CrtcHTotal = mode->HTotal;
    mode->CrtcVDisplay = mode->VDisplay;
    mode->CrtcVSyncStart = mode->VSyncStart;
    mode->CrtcVSyncEnd = mode->VSyncEnd;
    mode->CrtcVTotal = mode->VTotal;
    mode->CrtcHAdjusted = FALSE;
    mode->CrtcVAdjusted = FALSE;
}

/* -------------------------------------------------------------------- */
/* open correct framebuffer device                                      */


/* Wrapper around open() that also get the framebuffer name */
static int
fbdev_open_device(int scrnIndex, const char *dev, char **namep)
{
    int fd = dev ? open(dev, O_RDWR) : -1;

    if (!namep) {
        return fd;
    }

    if (fd == -1) {
        return -1;
    }

    struct fb_fix_screeninfo fix;

    if (ioctl(fd, FBIOGET_FSCREENINFO, (void *) (&fix)) == -1) {
        *namep = NULL;
        xf86DrvMsg(scrnIndex, X_ERROR,
                   "Not using framebuffer device %s: FBIOGET_FSCREENINFO: %s\n", dev, strerror(errno));
        close(fd);
        return -1;
    }
    *namep = malloc(16);
    if (*namep) {
        strncpy(*namep, fix.id, 16);
    }
    return fd;
}

static int
fbdev_check_user_devices(int scrnIndex, const char* dev, char **namep)
{
    int fd;

    /* try argument (from XF86Config) first */
    if (dev) {
        fd = fbdev_open_device(scrnIndex, dev, namep);
    } else {
        /* second: environment variable */
        dev = getenv("FRAMEBUFFER");
        fd = fbdev_open_device(scrnIndex, dev, namep);
    }

    if (dev && fd == -1) {
        xf86DrvMsg(scrnIndex, X_ERROR,
                   "Could not use the explicitly provided framebuffer: %s\n", dev);
    }
    return fd;
}

/**
 * Try to find the framebuffer device for a given PCI device
 * This probe works in the following way:
 *
 * 1. If we have device passed by the user, we store it's minor number.
 * We then look through the framebuffers associated to the pPci pci device.
 * If we find one that has the same minor as the one passed by the user, we
 * open the filename passed by the user and return an fd to it.
 * Otherwise, we return -1;
 *
 * 2. If we don't have a device passed by the user,
 * we look through the framebuffers associated to the pPci pci device.
 * If we find one that is valid, we return an fd to it.
 * Otherwise, we return -1;
 */
static int
fbdev_open_pci(int scrnIndex, struct pci_device *pPci, const char *device, char **namep)
{
    /*
     * We really don't care what pci slot we claim when using the fbdev driver
     * However, due to how the probe interface is designed,
     * we have to be careful to not claim the wrong pci slot.
     */
    char pattern[PATH_MAX];
    int fd;
    int fbdev_minor = -1;

    fd = fbdev_check_user_devices(scrnIndex, device, namep);

    int tfd;
    snprintf(pattern, sizeof(pattern),
             "/sys/bus/pci/devices/%04x:%02x:%02x.%d",
             pPci->domain, pPci->bus, pPci->dev, pPci->func);
    tfd = open(pattern, O_RDONLY);
    if (tfd == -1) {
        xf86DrvMsg(scrnIndex, X_WARNING,
                   "Sysfs interface cannot be used."
                   "Pci probe for framebuffer devices cannot function properly.\n");
        if (fd != -1) {
            xf86DrvMsg(scrnIndex, X_WARNING,
                       "Using device: %s without further checks\n", device);
            return fd;
        }
        xf86DrvMsg(scrnIndex, X_ERROR, "Unable to find a valid framebuffer device\n");
        return -1;
    }
    close(tfd);

    if (fd != -1) {
        struct stat res;
        if (fstat(fd, &res) == 0) {
            fbdev_minor = minor(res.st_rdev);
        }
        close(fd);
        fd = -1;
        if (namep) {
            free(*namep);
            *namep = NULL;
        }
    }

#define FBDEV_CHECK_PCI_GLOB(glob_pattern) \
    do { \
        glob_t res; \
        snprintf(pattern, sizeof(pattern), \
                 "/sys/bus/pci/devices/%04x:%02x:%02x.%d/" glob_pattern "/dev", \
                 pPci->domain, pPci->bus, pPci->dev, pPci->func); \
        if (!glob(pattern, GLOB_NOSORT | GLOB_NOESCAPE, NULL, &res)) { \
            char filename[PATH_MAX] = "/dev/"; \
            for (int i = 0; i < res.gl_pathc; i++) { \
                int maj, min = -1; \
                FILE *f = fopen(res.gl_pathv[i], "r"); \
                if (f) { \
                    (void)!fscanf(f, "%d:%d", &maj, &min); \
                    fclose(f); \
                } \
                if (fbdev_minor != -1) { \
                    if (fbdev_minor != min) { \
                        continue; \
                    } \
                    /* We have determined the the device the user gave us matches this pci device */ \
                    /* However, the name could be different than /dev/fb* */ \
                    /* Since we already have a filename from the user, use that instead of guessing */ \
                    return fbdev_check_user_devices(scrnIndex, device, namep); \
                } \
                char *src = strstr(res.gl_pathv[i], "graphics") + sizeof("graphics/") - 1; /* Has to match */ \
                char *dst = filename + sizeof("/dev/") - 1; \
                while (*src != '/') { \
                    *dst++ = *src++; \
                } \
                *dst = '\0'; \
                fd = fbdev_open_device(scrnIndex, filename, namep); \
                if (fd != -1) { \
                    return fd; \
                } \
            } \
        } \
        globfree(&res); \
    } while(0)

    FBDEV_CHECK_PCI_GLOB("graphics/fb*");
    FBDEV_CHECK_PCI_GLOB("graphics:fb*");
    FBDEV_CHECK_PCI_GLOB("*/graphics/fb*");
    FBDEV_CHECK_PCI_GLOB("*/graphics:fb*");

#undef FBDEV_CHECK_PCI_GLOB

    xf86DrvMsg(scrnIndex, X_ERROR, "Unable to find a valid framebuffer device\n");
    return -1;
}

static int
fbdev_open(int scrnIndex, const char *dev, char **namep)
{
    int fd;

    fd = fbdev_check_user_devices(scrnIndex, dev, namep);

    if (fd != -1) {
        /* fbdev was provided by the user and not guessed, just return it */
        return fd;
    }

    /* try the default device symlink */
    dev = "/dev/fb";
    fd = fbdev_open_device(scrnIndex, dev, namep);

    /* last tries, framebuffers 0 through 31 */
    char devbuf[] = "/dev/fbxx";
    for (int i = 0; i <= 31 && fd == -1; i++) {
        snprintf(devbuf, sizeof(devbuf),
                 "/dev/fb%d", i);
        fd = fbdev_open_device(scrnIndex, devbuf, namep);
    }

    if (fd == -1) {
        xf86DrvMsg(scrnIndex, X_ERROR, "Unable to find a valid framebuffer device\n");
    }

    return fd;
}

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

Bool
fbdevHWProbe(struct pci_device *pPci, const char *device, char **namep)
{
    int fd;

    if (pPci)
        fd = fbdev_open_pci(-1, pPci, device, namep);
    else
        fd = fbdev_open(-1, device, namep);

    if (-1 == fd)
        return FALSE;
    close(fd);
    return TRUE;
}

Bool
fbdevHWInit(ScrnInfoPtr pScrn, struct pci_device *pPci, const char *device)
{
    fbdevHWPtr fPtr;

    fbdevHWGetRec(pScrn);
    fPtr = FBDEVHWPTR(pScrn);

    /* open device */
    if (pPci)
        fPtr->fd = fbdev_open_pci(pScrn->scrnIndex, pPci, device, NULL);
    else
        fPtr->fd = fbdev_open(pScrn->scrnIndex, device, NULL);
    if (-1 == fPtr->fd) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "Failed to open framebuffer device, consult warnings"
                   " and/or errors above for possible reasons\n"
                   "\t(you may have to look at the server log to see"
                   " warnings)\n");
        return FALSE;
    }

    /* get current fb device settings */
    if (-1 == ioctl(fPtr->fd, FBIOGET_FSCREENINFO, (void *) (&fPtr->fix))) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "ioctl FBIOGET_FSCREENINFO: %s\n", strerror(errno));
        return FALSE;
    }
    if (-1 == ioctl(fPtr->fd, FBIOGET_VSCREENINFO, (void *) (&fPtr->var))) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
        return FALSE;
    }

    /* we can use the current settings as "buildin mode" */
    fbdev2xfree_timing(&fPtr->var, &fPtr->buildin);
    fPtr->buildin.name = "current";
    fPtr->buildin.next = &fPtr->buildin;
    fPtr->buildin.prev = &fPtr->buildin;
    fPtr->buildin.type |= M_T_BUILTIN;

    return TRUE;
}

char *
fbdevHWGetName(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    return fPtr->fix.id;
}

int
fbdevHWGetDepth(ScrnInfoPtr pScrn, int *fbbpp)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (fbbpp)
        *fbbpp = fPtr->var.bits_per_pixel;

    if (fPtr->fix.visual == FB_VISUAL_TRUECOLOR ||
        fPtr->fix.visual == FB_VISUAL_DIRECTCOLOR)
        return fPtr->var.red.length + fPtr->var.green.length +
            fPtr->var.blue.length;
    else
        return fPtr->var.bits_per_pixel;
}

int
fbdevHWGetLineLength(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (fPtr->fix.line_length)
        return fPtr->fix.line_length;
    else
        return fPtr->var.xres_virtual * fPtr->var.bits_per_pixel / 8;
}

int
fbdevHWGetType(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    return fPtr->fix.type;
}

int
fbdevHWGetVidmem(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    return fPtr->fix.smem_len;
}

static Bool
fbdevHWSetMode(ScrnInfoPtr pScrn, DisplayModePtr mode, Bool check)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);
    struct fb_var_screeninfo req_var = fPtr->var, set_var;

    xfree2fbdev_fblayout(pScrn, &req_var);
    xfree2fbdev_timing(mode, &req_var);

#ifdef DEBUG
    print_xfree_mode("init", mode);
    print_fbdev_mode("init", &req_var);
#endif

    set_var = req_var;

    if (check)
        set_var.activate = FB_ACTIVATE_TEST;

    if (0 != ioctl(fPtr->fd, FBIOPUT_VSCREENINFO, (void *) (&set_var))) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "FBIOPUT_VSCREENINFO: %s\n", strerror(errno));
        return FALSE;
    }

    if (!fbdev_modes_equal(&set_var, &req_var)) {
        if (!check)
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "FBIOPUT_VSCREENINFO succeeded but modified " "mode\n");
#ifdef DEBUG
        print_fbdev_mode("returned", &set_var);
#endif
        return FALSE;
    }

    if (!check)
        fPtr->var = set_var;

    return TRUE;
}

void
fbdevHWSetVideoModes(ScrnInfoPtr pScrn)
{
    const char **modename;
    DisplayModePtr mode, this, last = pScrn->modes;

    if (NULL == pScrn->display->modes)
        return;

    pScrn->virtualX = pScrn->display->virtualX;
    pScrn->virtualY = pScrn->display->virtualY;

    for (modename = pScrn->display->modes; *modename != NULL; modename++) {
        for (mode = pScrn->monitor->Modes; mode != NULL; mode = mode->next) {
            if (0 == strcmp(mode->name, *modename)) {
                if (fbdevHWSetMode(pScrn, mode, TRUE))
                    break;

                xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                           "\tmode \"%s\" test failed\n", *modename);
            }
        }

        if (NULL == mode) {
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "\tmode \"%s\" not found\n", *modename);
            continue;
        }

        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "\tmode \"%s\" ok\n", *modename);

        if (pScrn->virtualX < mode->HDisplay)
            pScrn->virtualX = mode->HDisplay;
        if (pScrn->virtualY < mode->VDisplay)
            pScrn->virtualY = mode->VDisplay;

        if (NULL == pScrn->modes) {
            this = pScrn->modes = xf86DuplicateMode(mode);
            this->next = this;
            this->prev = this;
        }
        else {
            this = xf86DuplicateMode(mode);
            this->next = pScrn->modes;
            this->prev = last;
            last->next = this;
            pScrn->modes->prev = this;
        }
        last = this;
    }
}

void
fbdevHWUseBuildinMode(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    pScrn->modes = &fPtr->buildin;
    pScrn->virtualX = pScrn->display->virtualX;
    pScrn->virtualY = pScrn->display->virtualY;
    if (pScrn->virtualX < fPtr->buildin.HDisplay)
        pScrn->virtualX = fPtr->buildin.HDisplay;
    if (pScrn->virtualY < fPtr->buildin.VDisplay)
        pScrn->virtualY = fPtr->buildin.VDisplay;
}

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

static void
calculateFbmem_len(fbdevHWPtr fPtr)
{
    fPtr->fboff = (unsigned long) fPtr->fix.smem_start & ~PAGE_MASK;
    fPtr->fbmem_len = (fPtr->fboff + fPtr->fix.smem_len + ~PAGE_MASK) &
        PAGE_MASK;
}

void *
fbdevHWMapVidmem(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (NULL == fPtr->fbmem) {
        calculateFbmem_len(fPtr);
        fPtr->fbmem = mmap(NULL, fPtr->fbmem_len, PROT_READ | PROT_WRITE,
                           MAP_SHARED, fPtr->fd, 0);
        if (-1 == (long) fPtr->fbmem) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "mmap fbmem: %s\n", strerror(errno));
            fPtr->fbmem = NULL;
        }
        else {
            /* Perhaps we'd better add fboff to fbmem and return 0 in
               fbdevHWLinearOffset()? Of course we then need to mask
               fPtr->fbmem with PAGE_MASK in fbdevHWUnmapVidmem() as
               well. [geert] */
        }
    }
    pScrn->memPhysBase =
        (unsigned long) fPtr->fix.smem_start & (unsigned long) (PAGE_MASK);
    pScrn->fbOffset =
        (unsigned long) fPtr->fix.smem_start & (unsigned long) (~PAGE_MASK);
    return fPtr->fbmem;
}

int
fbdevHWLinearOffset(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    return fPtr->fboff;
}

Bool
fbdevHWUnmapVidmem(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (NULL != fPtr->fbmem) {
        if (-1 == munmap(fPtr->fbmem, fPtr->fbmem_len))
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "munmap fbmem: %s\n", strerror(errno));
        fPtr->fbmem = NULL;
    }
    return TRUE;
}

void *
fbdevHWMapMMIO(ScrnInfoPtr pScrn)
{
    unsigned int mmio_off;

    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (NULL == fPtr->mmio) {
        /* tell the kernel not to use accels to speed up console scrolling */
        fPtr->saved_accel = fPtr->var.accel_flags;
        fPtr->var.accel_flags = 0;
        if (0 != ioctl(fPtr->fd, FBIOPUT_VSCREENINFO, (void *) (&fPtr->var))) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "FBIOPUT_VSCREENINFO: %s\n", strerror(errno));
            return FALSE;
        }
        mmio_off = (unsigned long) fPtr->fix.mmio_start & ~PAGE_MASK;
        fPtr->mmio_len = (mmio_off + fPtr->fix.mmio_len + ~PAGE_MASK) &
            PAGE_MASK;
        if (NULL == fPtr->fbmem)
            calculateFbmem_len(fPtr);
        fPtr->mmio = mmap(NULL, fPtr->mmio_len, PROT_READ | PROT_WRITE,
                          MAP_SHARED, fPtr->fd, fPtr->fbmem_len);
        if (-1 == (long) fPtr->mmio) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "mmap mmio: %s\n", strerror(errno));
            fPtr->mmio = NULL;
        }
        else
            fPtr->mmio += mmio_off;
    }
    return fPtr->mmio;
}

Bool
fbdevHWUnmapMMIO(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (NULL != fPtr->mmio) {
        if (-1 ==
            munmap((void *) ((unsigned long) fPtr->mmio & PAGE_MASK),
                   fPtr->mmio_len))
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "munmap mmio: %s\n",
                       strerror(errno));
        fPtr->mmio = NULL;
        fPtr->var.accel_flags = fPtr->saved_accel;
        if (0 != ioctl(fPtr->fd, FBIOPUT_VSCREENINFO, (void *) (&fPtr->var))) {
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "FBIOPUT_VSCREENINFO: %s\n", strerror(errno));
            return FALSE;
        }
    }
    return TRUE;
}

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

Bool
fbdevHWModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    pScrn->vtSema = TRUE;

    /* set */
    if (!fbdevHWSetMode(pScrn, mode, FALSE))
        return FALSE;

    /* read back */
    if (0 != ioctl(fPtr->fd, FBIOGET_FSCREENINFO, (void *) (&fPtr->fix))) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "FBIOGET_FSCREENINFO: %s\n", strerror(errno));
        return FALSE;
    }
    if (0 != ioctl(fPtr->fd, FBIOGET_VSCREENINFO, (void *) (&fPtr->var))) {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "FBIOGET_VSCREENINFO: %s\n", strerror(errno));
        return FALSE;
    }

    if (pScrn->defaultVisual == TrueColor ||
        pScrn->defaultVisual == DirectColor) {
        /* XXX: This is a hack, but it should be a NOP for all the setups that
         * worked before and actually seems to fix some others...
         */
        pScrn->offset.red = fPtr->var.red.offset;
        pScrn->offset.green = fPtr->var.green.offset;
        pScrn->offset.blue = fPtr->var.blue.offset;
        pScrn->mask.red =
            ((1 << fPtr->var.red.length) - 1) << fPtr->var.red.offset;
        pScrn->mask.green =
            ((1 << fPtr->var.green.length) - 1) << fPtr->var.green.offset;
        pScrn->mask.blue =
            ((1 << fPtr->var.blue.length) - 1) << fPtr->var.blue.offset;
    }

    return TRUE;
}

/* -------------------------------------------------------------------- */
/* video mode save/restore                                              */
void
fbdevHWSave(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (0 != ioctl(fPtr->fd, FBIOGET_VSCREENINFO, (void *) (&fPtr->saved_var)))
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "FBIOGET_VSCREENINFO: %s\n", strerror(errno));
}

void
fbdevHWRestore(ScrnInfoPtr pScrn)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (0 != ioctl(fPtr->fd, FBIOPUT_VSCREENINFO, (void *) (&fPtr->saved_var)))
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                   "FBIOPUT_VSCREENINFO: %s\n", strerror(errno));
}

/* -------------------------------------------------------------------- */
/* callback for xf86HandleColormaps                                     */

void
fbdevHWLoadPalette(ScrnInfoPtr pScrn, int numColors, int *indices,
                   LOCO * colors, VisualPtr pVisual)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);
    struct fb_cmap cmap;
    unsigned short red, green, blue;
    int i;

    cmap.len = 1;
    cmap.red = &red;
    cmap.green = &green;
    cmap.blue = &blue;
    cmap.transp = NULL;
    for (i = 0; i < numColors; i++) {
        cmap.start = indices[i];
        red = (colors[indices[i]].red << 8) | colors[indices[i]].red;
        green = (colors[indices[i]].green << 8) | colors[indices[i]].green;
        blue = (colors[indices[i]].blue << 8) | colors[indices[i]].blue;
        if (-1 == ioctl(fPtr->fd, FBIOPUTCMAP, (void *) &cmap))
            xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
                       "FBIOPUTCMAP: %s\n", strerror(errno));
    }
}

/* -------------------------------------------------------------------- */
/* these can be hooked directly into ScrnInfoRec                        */

ModeStatus
fbdevHWValidMode(ScrnInfoPtr pScrn, DisplayModePtr mode, Bool verbose, int flags)
{
    if (!fbdevHWSetMode(pScrn, mode, TRUE))
        return MODE_BAD;

    return MODE_OK;
}

Bool
fbdevHWSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    if (!fbdevHWSetMode(pScrn, mode, FALSE))
        return FALSE;

    return TRUE;
}

void
fbdevHWAdjustFrame(ScrnInfoPtr pScrn, int x, int y)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);

    if (x < 0 || x + fPtr->var.xres > fPtr->var.xres_virtual ||
        y < 0 || y + fPtr->var.yres > fPtr->var.yres_virtual)
        return;

    fPtr->var.xoffset = x;
    fPtr->var.yoffset = y;
    if (-1 == ioctl(fPtr->fd, FBIOPAN_DISPLAY, (void *) &fPtr->var))
        xf86DrvMsgVerb(pScrn->scrnIndex, X_WARNING, 5,
                       "FBIOPAN_DISPLAY: %s\n", strerror(errno));
}

Bool
fbdevHWEnterVT(ScrnInfoPtr pScrn)
{
    if (!fbdevHWModeInit(pScrn, pScrn->currentMode))
        return FALSE;
    fbdevHWAdjustFrame(pScrn, pScrn->frameX0, pScrn->frameY0);
    return TRUE;
}

void
fbdevHWLeaveVT(ScrnInfoPtr pScrn)
{
    fbdevHWRestore(pScrn);
}

void
fbdevHWDPMSSet(ScrnInfoPtr pScrn, int mode, int flags)
{
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);
    unsigned long fbmode;

    if (!pScrn->vtSema)
        return;

    if (fPtr->unsupported_ioctls & (1 << FBIOBLANK_UNSUPPORTED))
        return;

    switch (mode) {
    case DPMSModeOn:
        fbmode = 0;
        break;
    case DPMSModeStandby:
        fbmode = 2;
        break;
    case DPMSModeSuspend:
        fbmode = 3;
        break;
    case DPMSModeOff:
        fbmode = 4;
        break;
    default:
        return;
    }

RETRY:
    if (-1 == ioctl(fPtr->fd, FBIOBLANK, (void *) fbmode)) {
        switch (errno) {
        case EAGAIN:
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "FBIOBLANK: %s\n", strerror(errno));
	    break;
        case EINTR:
        case ERESTART:
            goto RETRY;
        default:
            fPtr->unsupported_ioctls |= (1 << FBIOBLANK_UNSUPPORTED);
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "FBIOBLANK: %s (Screen blanking not supported "
                       "by kernel - disabling)\n", strerror(errno));
        }
    }
}

Bool
fbdevHWSaveScreen(ScreenPtr pScreen, int mode)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    fbdevHWPtr fPtr = FBDEVHWPTR(pScrn);
    unsigned long unblank;

    if (!pScrn->vtSema)
        return TRUE;

    if (fPtr->unsupported_ioctls & (1 << FBIOBLANK_UNSUPPORTED))
        return FALSE;

    unblank = xf86IsUnblank(mode);

RETRY:
    if (-1 == ioctl(fPtr->fd, FBIOBLANK, (void *) (1 - unblank))) {
        switch (errno) {
        case EAGAIN:
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "FBIOBLANK: %s\n", strerror(errno));
            break;
        case EINTR:
        case ERESTART:
            goto RETRY;
        default:
            fPtr->unsupported_ioctls |= (1 << FBIOBLANK_UNSUPPORTED);
            xf86DrvMsg(pScrn->scrnIndex, X_INFO,
                       "FBIOBLANK: %s (Screen blanking not supported "
                       "by kernel - disabling)\n", strerror(errno));
        }
        return FALSE;
    }

    return TRUE;
}

xf86SwitchModeProc *
fbdevHWSwitchModeWeak(void)
{
    return fbdevHWSwitchMode;
}

xf86AdjustFrameProc *
fbdevHWAdjustFrameWeak(void)
{
    return fbdevHWAdjustFrame;
}

xf86LeaveVTProc *
fbdevHWLeaveVTWeak(void)
{
    return fbdevHWLeaveVT;
}

xf86ValidModeProc *
fbdevHWValidModeWeak(void)
{
    return fbdevHWValidMode;
}

xf86DPMSSetProc *
fbdevHWDPMSSetWeak(void)
{
    return fbdevHWDPMSSet;
}

xf86LoadPaletteProc *
fbdevHWLoadPaletteWeak(void)
{
    return fbdevHWLoadPalette;
}
