aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/video/fbdev/core
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ti.com>2014-02-13 16:24:55 +0200
committerTomi Valkeinen <tomi.valkeinen@ti.com>2014-04-17 08:10:19 +0300
commit19757fc8432ac97a07a890d6310cccc1896a1b36 (patch)
tree67f2b9174d3246f6c65a8466ffa8079cab4c7cbf /drivers/video/fbdev/core
parentvideo: move fbdev to drivers/video/fbdev (diff)
downloadwireguard-linux-19757fc8432ac97a07a890d6310cccc1896a1b36.tar.xz
wireguard-linux-19757fc8432ac97a07a890d6310cccc1896a1b36.zip
fbdev: move fbdev core files to separate directory
Instead of having fbdev framework core files at the root fbdev directory, mixed with random fbdev device drivers, move the fbdev core files to a separate core directory. This makes it much clearer which of the files are actually part of the fbdev framework, and which are part of device drivers. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Geert Uytterhoeven <geert@linux-m68k.org> Acked-by: Rob Clark <robdclark@gmail.com> Acked-by: Jingoo Han <jg1.han@samsung.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Diffstat (limited to 'drivers/video/fbdev/core')
-rw-r--r--drivers/video/fbdev/core/Makefile16
-rw-r--r--drivers/video/fbdev/core/cfbcopyarea.c434
-rw-r--r--drivers/video/fbdev/core/cfbfillrect.c371
-rw-r--r--drivers/video/fbdev/core/cfbimgblt.c313
-rw-r--r--drivers/video/fbdev/core/fb_ddc.c119
-rw-r--r--drivers/video/fbdev/core/fb_defio.c245
-rw-r--r--drivers/video/fbdev/core/fb_draw.h186
-rw-r--r--drivers/video/fbdev/core/fb_notify.c47
-rw-r--r--drivers/video/fbdev/core/fb_sys_fops.c104
-rw-r--r--drivers/video/fbdev/core/fbcmap.c362
-rw-r--r--drivers/video/fbdev/core/fbcvt.c379
-rw-r--r--drivers/video/fbdev/core/fbmem.c2002
-rw-r--r--drivers/video/fbdev/core/fbmon.c1592
-rw-r--r--drivers/video/fbdev/core/fbsysfs.c586
-rw-r--r--drivers/video/fbdev/core/modedb.c1137
-rw-r--r--drivers/video/fbdev/core/svgalib.c672
-rw-r--r--drivers/video/fbdev/core/syscopyarea.c377
-rw-r--r--drivers/video/fbdev/core/sysfillrect.c335
-rw-r--r--drivers/video/fbdev/core/sysimgblt.c288
19 files changed, 9565 insertions, 0 deletions
diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile
new file mode 100644
index 000000000000..fa306538dac2
--- /dev/null
+++ b/drivers/video/fbdev/core/Makefile
@@ -0,0 +1,16 @@
+obj-y += fb_notify.o
+obj-$(CONFIG_FB) += fb.o
+fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
+ modedb.o fbcvt.o
+fb-objs := $(fb-y)
+
+obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o
+obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o
+obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o
+obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o
+obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o
+obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o
+obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o
+obj-$(CONFIG_FB_SVGALIB) += svgalib.o
+obj-$(CONFIG_FB_DDC) += fb_ddc.o
+obj-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o
diff --git a/drivers/video/fbdev/core/cfbcopyarea.c b/drivers/video/fbdev/core/cfbcopyarea.c
new file mode 100644
index 000000000000..bcb57235fcc7
--- /dev/null
+++ b/drivers/video/fbdev/core/cfbcopyarea.c
@@ -0,0 +1,434 @@
+/*
+ * Generic function for frame buffer with packed pixels of any depth.
+ *
+ * Copyright (C) 1999-2005 James Simmons <jsimmons@www.infradead.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * NOTES:
+ *
+ * This is for cfb packed pixels. Iplan and such are incorporated in the
+ * drivers that need them.
+ *
+ * FIXME
+ *
+ * Also need to add code to deal with cards endians that are different than
+ * the native cpu endians. I also need to deal with MSB position in the word.
+ *
+ * The two functions or copying forward and backward could be split up like
+ * the ones for filling, i.e. in aligned and unaligned versions. This would
+ * help moving some redundant computations and branches out of the loop, too.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+#include <asm/io.h>
+#include "fb_draw.h"
+
+#if BITS_PER_LONG == 32
+# define FB_WRITEL fb_writel
+# define FB_READL fb_readl
+#else
+# define FB_WRITEL fb_writeq
+# define FB_READL fb_readq
+#endif
+
+ /*
+ * Generic bitwise copy algorithm
+ */
+
+static void
+bitcpy(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx,
+ const unsigned long __iomem *src, unsigned src_idx, int bits,
+ unsigned n, u32 bswapmask)
+{
+ unsigned long first, last;
+ int const shift = dst_idx-src_idx;
+
+#if 0
+ /*
+ * If you suspect bug in this function, compare it with this simple
+ * memmove implementation.
+ */
+ fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8,
+ (char *)src + ((src_idx & (bits - 1))) / 8, n / 8);
+ return;
+#endif
+
+ first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask);
+ last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask);
+
+ if (!shift) {
+ // Same alignment for source and dest
+
+ if (dst_idx+n <= bits) {
+ // Single word
+ if (last)
+ first &= last;
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst);
+ } else {
+ // Multiple destination words
+
+ // Leading bits
+ if (first != ~0UL) {
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst);
+ dst++;
+ src++;
+ n -= bits - dst_idx;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 8) {
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ FB_WRITEL(FB_READL(src++), dst++);
+ n -= 8;
+ }
+ while (n--)
+ FB_WRITEL(FB_READL(src++), dst++);
+
+ // Trailing bits
+ if (last)
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst);
+ }
+ } else {
+ /* Different alignment for source and dest */
+ unsigned long d0, d1;
+ int m;
+
+ int const left = shift & (bits - 1);
+ int const right = -shift & (bits - 1);
+
+ if (dst_idx+n <= bits) {
+ // Single destination word
+ if (last)
+ first &= last;
+ d0 = FB_READL(src);
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ if (shift > 0) {
+ // Single source word
+ d0 <<= left;
+ } else if (src_idx+n <= bits) {
+ // Single source word
+ d0 >>= right;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src + 1);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+ d0 = d0 >> right | d1 << left;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
+ } else {
+ // Multiple destination words
+ /** We must always remember the last value read, because in case
+ SRC and DST overlap bitwise (e.g. when moving just one pixel in
+ 1bpp), we always collect one full long for DST and that might
+ overlap with the current long from SRC. We store this value in
+ 'd0'. */
+ d0 = FB_READL(src++);
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ // Leading bits
+ if (shift > 0) {
+ // Single source word
+ d1 = d0;
+ d0 <<= left;
+ n -= bits - dst_idx;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src++);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+
+ d0 = d0 >> right | d1 << left;
+ n -= bits - dst_idx;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
+ d0 = d1;
+ dst++;
+
+ // Main chunk
+ m = n % bits;
+ n /= bits;
+ while ((n >= 4) && !bswapmask) {
+ d1 = FB_READL(src++);
+ FB_WRITEL(d0 >> right | d1 << left, dst++);
+ d0 = d1;
+ d1 = FB_READL(src++);
+ FB_WRITEL(d0 >> right | d1 << left, dst++);
+ d0 = d1;
+ d1 = FB_READL(src++);
+ FB_WRITEL(d0 >> right | d1 << left, dst++);
+ d0 = d1;
+ d1 = FB_READL(src++);
+ FB_WRITEL(d0 >> right | d1 << left, dst++);
+ d0 = d1;
+ n -= 4;
+ }
+ while (n--) {
+ d1 = FB_READL(src++);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+ d0 = d0 >> right | d1 << left;
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(d0, dst++);
+ d0 = d1;
+ }
+
+ // Trailing bits
+ if (m) {
+ if (m <= bits - right) {
+ // Single source word
+ d0 >>= right;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src);
+ d1 = fb_rev_pixels_in_long(d1,
+ bswapmask);
+ d0 = d0 >> right | d1 << left;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
+ }
+ }
+ }
+}
+
+ /*
+ * Generic bitwise copy algorithm, operating backward
+ */
+
+static void
+bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx,
+ const unsigned long __iomem *src, unsigned src_idx, int bits,
+ unsigned n, u32 bswapmask)
+{
+ unsigned long first, last;
+ int shift;
+
+#if 0
+ /*
+ * If you suspect bug in this function, compare it with this simple
+ * memmove implementation.
+ */
+ fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8,
+ (char *)src + ((src_idx & (bits - 1))) / 8, n / 8);
+ return;
+#endif
+
+ dst += (dst_idx + n - 1) / bits;
+ src += (src_idx + n - 1) / bits;
+ dst_idx = (dst_idx + n - 1) % bits;
+ src_idx = (src_idx + n - 1) % bits;
+
+ shift = dst_idx-src_idx;
+
+ first = ~fb_shifted_pixels_mask_long(p, (dst_idx + 1) % bits, bswapmask);
+ last = fb_shifted_pixels_mask_long(p, (bits + dst_idx + 1 - n) % bits, bswapmask);
+
+ if (!shift) {
+ // Same alignment for source and dest
+
+ if ((unsigned long)dst_idx+1 >= n) {
+ // Single word
+ if (first)
+ last &= first;
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst);
+ } else {
+ // Multiple destination words
+
+ // Leading bits
+ if (first) {
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst);
+ dst--;
+ src--;
+ n -= dst_idx+1;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 8) {
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ FB_WRITEL(FB_READL(src--), dst--);
+ n -= 8;
+ }
+ while (n--)
+ FB_WRITEL(FB_READL(src--), dst--);
+
+ // Trailing bits
+ if (last != -1UL)
+ FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst);
+ }
+ } else {
+ // Different alignment for source and dest
+ unsigned long d0, d1;
+ int m;
+
+ int const left = shift & (bits-1);
+ int const right = -shift & (bits-1);
+
+ if ((unsigned long)dst_idx+1 >= n) {
+ // Single destination word
+ if (first)
+ last &= first;
+ d0 = FB_READL(src);
+ if (shift < 0) {
+ // Single source word
+ d0 >>= right;
+ } else if (1+(unsigned long)src_idx >= n) {
+ // Single source word
+ d0 <<= left;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src - 1);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+ d0 = d0 << left | d1 >> right;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
+ } else {
+ // Multiple destination words
+ /** We must always remember the last value read, because in case
+ SRC and DST overlap bitwise (e.g. when moving just one pixel in
+ 1bpp), we always collect one full long for DST and that might
+ overlap with the current long from SRC. We store this value in
+ 'd0'. */
+
+ d0 = FB_READL(src--);
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ // Leading bits
+ if (shift < 0) {
+ // Single source word
+ d1 = d0;
+ d0 >>= right;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src--);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+ d0 = d0 << left | d1 >> right;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), first), dst);
+ d0 = d1;
+ dst--;
+ n -= dst_idx+1;
+
+ // Main chunk
+ m = n % bits;
+ n /= bits;
+ while ((n >= 4) && !bswapmask) {
+ d1 = FB_READL(src--);
+ FB_WRITEL(d0 << left | d1 >> right, dst--);
+ d0 = d1;
+ d1 = FB_READL(src--);
+ FB_WRITEL(d0 << left | d1 >> right, dst--);
+ d0 = d1;
+ d1 = FB_READL(src--);
+ FB_WRITEL(d0 << left | d1 >> right, dst--);
+ d0 = d1;
+ d1 = FB_READL(src--);
+ FB_WRITEL(d0 << left | d1 >> right, dst--);
+ d0 = d1;
+ n -= 4;
+ }
+ while (n--) {
+ d1 = FB_READL(src--);
+ d1 = fb_rev_pixels_in_long(d1, bswapmask);
+ d0 = d0 << left | d1 >> right;
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(d0, dst--);
+ d0 = d1;
+ }
+
+ // Trailing bits
+ if (m) {
+ if (m <= bits - left) {
+ // Single source word
+ d0 <<= left;
+ } else {
+ // 2 source words
+ d1 = FB_READL(src);
+ d1 = fb_rev_pixels_in_long(d1,
+ bswapmask);
+ d0 = d0 << left | d1 >> right;
+ }
+ d0 = fb_rev_pixels_in_long(d0, bswapmask);
+ FB_WRITEL(comp(d0, FB_READL(dst), last), dst);
+ }
+ }
+ }
+}
+
+void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
+{
+ u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy;
+ u32 height = area->height, width = area->width;
+ unsigned long const bits_per_line = p->fix.line_length*8u;
+ unsigned long __iomem *base = NULL;
+ int bits = BITS_PER_LONG, bytes = bits >> 3;
+ unsigned dst_idx = 0, src_idx = 0, rev_copy = 0;
+ u32 bswapmask = fb_compute_bswapmask(p);
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ /* if the beginning of the target area might overlap with the end of
+ the source area, be have to copy the area reverse. */
+ if ((dy == sy && dx > sx) || (dy > sy)) {
+ dy += height;
+ sy += height;
+ rev_copy = 1;
+ }
+
+ // split the base of the framebuffer into a long-aligned address and the
+ // index of the first bit
+ base = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1));
+ dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1));
+ // add offset of source and target area
+ dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel;
+ src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel;
+
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+
+ if (rev_copy) {
+ while (height--) {
+ dst_idx -= bits_per_line;
+ src_idx -= bits_per_line;
+ bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits,
+ base + (src_idx / bits), src_idx % bits, bits,
+ width*p->var.bits_per_pixel, bswapmask);
+ }
+ } else {
+ while (height--) {
+ bitcpy(p, base + (dst_idx / bits), dst_idx % bits,
+ base + (src_idx / bits), src_idx % bits, bits,
+ width*p->var.bits_per_pixel, bswapmask);
+ dst_idx += bits_per_line;
+ src_idx += bits_per_line;
+ }
+ }
+}
+
+EXPORT_SYMBOL(cfb_copyarea);
+
+MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>");
+MODULE_DESCRIPTION("Generic software accelerated copyarea");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/fbdev/core/cfbfillrect.c b/drivers/video/fbdev/core/cfbfillrect.c
new file mode 100644
index 000000000000..ba9f58b2a5e8
--- /dev/null
+++ b/drivers/video/fbdev/core/cfbfillrect.c
@@ -0,0 +1,371 @@
+/*
+ * Generic fillrect for frame buffers with packed pixels of any depth.
+ *
+ * Copyright (C) 2000 James Simmons (jsimmons@linux-fbdev.org)
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * NOTES:
+ *
+ * Also need to add code to deal with cards endians that are different than
+ * the native cpu endians. I also need to deal with MSB position in the word.
+ *
+ */
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+#include "fb_draw.h"
+
+#if BITS_PER_LONG == 32
+# define FB_WRITEL fb_writel
+# define FB_READL fb_readl
+#else
+# define FB_WRITEL fb_writeq
+# define FB_READL fb_readq
+#endif
+
+ /*
+ * Aligned pattern fill using 32/64-bit memory accesses
+ */
+
+static void
+bitfill_aligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx,
+ unsigned long pat, unsigned n, int bits, u32 bswapmask)
+{
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask);
+ last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask);
+
+ if (dst_idx+n <= bits) {
+ // Single word
+ if (last)
+ first &= last;
+ FB_WRITEL(comp(pat, FB_READL(dst), first), dst);
+ } else {
+ // Multiple destination words
+
+ // Leading bits
+ if (first!= ~0UL) {
+ FB_WRITEL(comp(pat, FB_READL(dst), first), dst);
+ dst++;
+ n -= bits - dst_idx;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 8) {
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ FB_WRITEL(pat, dst++);
+ n -= 8;
+ }
+ while (n--)
+ FB_WRITEL(pat, dst++);
+
+ // Trailing bits
+ if (last)
+ FB_WRITEL(comp(pat, FB_READL(dst), last), dst);
+ }
+}
+
+
+ /*
+ * Unaligned generic pattern fill using 32/64-bit memory accesses
+ * The pattern must have been expanded to a full 32/64-bit value
+ * Left/right are the appropriate shifts to convert to the pattern to be
+ * used for the next 32/64-bit word
+ */
+
+static void
+bitfill_unaligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx,
+ unsigned long pat, int left, int right, unsigned n, int bits)
+{
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ // Single word
+ if (last)
+ first &= last;
+ FB_WRITEL(comp(pat, FB_READL(dst), first), dst);
+ } else {
+ // Multiple destination words
+ // Leading bits
+ if (first) {
+ FB_WRITEL(comp(pat, FB_READL(dst), first), dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ n -= bits - dst_idx;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 4) {
+ FB_WRITEL(pat, dst++);
+ pat = pat << left | pat >> right;
+ FB_WRITEL(pat, dst++);
+ pat = pat << left | pat >> right;
+ FB_WRITEL(pat, dst++);
+ pat = pat << left | pat >> right;
+ FB_WRITEL(pat, dst++);
+ pat = pat << left | pat >> right;
+ n -= 4;
+ }
+ while (n--) {
+ FB_WRITEL(pat, dst++);
+ pat = pat << left | pat >> right;
+ }
+
+ // Trailing bits
+ if (last)
+ FB_WRITEL(comp(pat, FB_READL(dst), last), dst);
+ }
+}
+
+ /*
+ * Aligned pattern invert using 32/64-bit memory accesses
+ */
+static void
+bitfill_aligned_rev(struct fb_info *p, unsigned long __iomem *dst,
+ int dst_idx, unsigned long pat, unsigned n, int bits,
+ u32 bswapmask)
+{
+ unsigned long val = pat, dat;
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask);
+ last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask);
+
+ if (dst_idx+n <= bits) {
+ // Single word
+ if (last)
+ first &= last;
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ val, dat, first), dst);
+ } else {
+ // Multiple destination words
+ // Leading bits
+ if (first!=0UL) {
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ val, dat, first), dst);
+ dst++;
+ n -= bits - dst_idx;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 8) {
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ n -= 8;
+ }
+ while (n--) {
+ FB_WRITEL(FB_READL(dst) ^ val, dst);
+ dst++;
+ }
+ // Trailing bits
+ if (last) {
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ val, dat, last), dst);
+ }
+ }
+}
+
+
+ /*
+ * Unaligned generic pattern invert using 32/64-bit memory accesses
+ * The pattern must have been expanded to a full 32/64-bit value
+ * Left/right are the appropriate shifts to convert to the pattern to be
+ * used for the next 32/64-bit word
+ */
+
+static void
+bitfill_unaligned_rev(struct fb_info *p, unsigned long __iomem *dst,
+ int dst_idx, unsigned long pat, int left, int right,
+ unsigned n, int bits)
+{
+ unsigned long first, last, dat;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ // Single word
+ if (last)
+ first &= last;
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ pat, dat, first), dst);
+ } else {
+ // Multiple destination words
+
+ // Leading bits
+ if (first != 0UL) {
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ pat, dat, first), dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ n -= bits - dst_idx;
+ }
+
+ // Main chunk
+ n /= bits;
+ while (n >= 4) {
+ FB_WRITEL(FB_READL(dst) ^ pat, dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ FB_WRITEL(FB_READL(dst) ^ pat, dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ FB_WRITEL(FB_READL(dst) ^ pat, dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ FB_WRITEL(FB_READL(dst) ^ pat, dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ n -= 4;
+ }
+ while (n--) {
+ FB_WRITEL(FB_READL(dst) ^ pat, dst);
+ dst++;
+ pat = pat << left | pat >> right;
+ }
+
+ // Trailing bits
+ if (last) {
+ dat = FB_READL(dst);
+ FB_WRITEL(comp(dat ^ pat, dat, last), dst);
+ }
+ }
+}
+
+void cfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
+{
+ unsigned long pat, pat2, fg;
+ unsigned long width = rect->width, height = rect->height;
+ int bits = BITS_PER_LONG, bytes = bits >> 3;
+ u32 bpp = p->var.bits_per_pixel;
+ unsigned long __iomem *dst;
+ int dst_idx, left;
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR )
+ fg = ((u32 *) (p->pseudo_palette))[rect->color];
+ else
+ fg = rect->color;
+
+ pat = pixel_to_pat(bpp, fg);
+
+ dst = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1));
+ dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8;
+ dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp;
+ /* FIXME For now we support 1-32 bpp only */
+ left = bits % bpp;
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+ if (!left) {
+ u32 bswapmask = fb_compute_bswapmask(p);
+ void (*fill_op32)(struct fb_info *p,
+ unsigned long __iomem *dst, int dst_idx,
+ unsigned long pat, unsigned n, int bits,
+ u32 bswapmask) = NULL;
+
+ switch (rect->rop) {
+ case ROP_XOR:
+ fill_op32 = bitfill_aligned_rev;
+ break;
+ case ROP_COPY:
+ fill_op32 = bitfill_aligned;
+ break;
+ default:
+ printk( KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n");
+ fill_op32 = bitfill_aligned;
+ break;
+ }
+ while (height--) {
+ dst += dst_idx >> (ffs(bits) - 1);
+ dst_idx &= (bits - 1);
+ fill_op32(p, dst, dst_idx, pat, width*bpp, bits,
+ bswapmask);
+ dst_idx += p->fix.line_length*8;
+ }
+ } else {
+ int right, r;
+ void (*fill_op)(struct fb_info *p, unsigned long __iomem *dst,
+ int dst_idx, unsigned long pat, int left,
+ int right, unsigned n, int bits) = NULL;
+#ifdef __LITTLE_ENDIAN
+ right = left;
+ left = bpp - right;
+#else
+ right = bpp - left;
+#endif
+ switch (rect->rop) {
+ case ROP_XOR:
+ fill_op = bitfill_unaligned_rev;
+ break;
+ case ROP_COPY:
+ fill_op = bitfill_unaligned;
+ break;
+ default:
+ printk(KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n");
+ fill_op = bitfill_unaligned;
+ break;
+ }
+ while (height--) {
+ dst += dst_idx / bits;
+ dst_idx &= (bits - 1);
+ r = dst_idx % bpp;
+ /* rotate pattern to the correct start position */
+ pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp));
+ fill_op(p, dst, dst_idx, pat2, left, right,
+ width*bpp, bits);
+ dst_idx += p->fix.line_length*8;
+ }
+ }
+}
+
+EXPORT_SYMBOL(cfb_fillrect);
+
+MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>");
+MODULE_DESCRIPTION("Generic software accelerated fill rectangle");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/cfbimgblt.c b/drivers/video/fbdev/core/cfbimgblt.c
new file mode 100644
index 000000000000..a2bb276a8b24
--- /dev/null
+++ b/drivers/video/fbdev/core/cfbimgblt.c
@@ -0,0 +1,313 @@
+/*
+ * Generic BitBLT function for frame buffer with packed pixels of any depth.
+ *
+ * Copyright (C) June 1999 James Simmons
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * NOTES:
+ *
+ * This function copys a image from system memory to video memory. The
+ * image can be a bitmap where each 0 represents the background color and
+ * each 1 represents the foreground color. Great for font handling. It can
+ * also be a color image. This is determined by image_depth. The color image
+ * must be laid out exactly in the same format as the framebuffer. Yes I know
+ * their are cards with hardware that coverts images of various depths to the
+ * framebuffer depth. But not every card has this. All images must be rounded
+ * up to the nearest byte. For example a bitmap 12 bits wide must be two
+ * bytes width.
+ *
+ * Tony:
+ * Incorporate mask tables similar to fbcon-cfb*.c in 2.4 API. This speeds
+ * up the code significantly.
+ *
+ * Code for depths not multiples of BITS_PER_LONG is still kludgy, which is
+ * still processed a bit at a time.
+ *
+ * Also need to add code to deal with cards endians that are different than
+ * the native cpu endians. I also need to deal with MSB position in the word.
+ */
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+#include "fb_draw.h"
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+static const u32 cfb_tab8_be[] = {
+ 0x00000000,0x000000ff,0x0000ff00,0x0000ffff,
+ 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff,
+ 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff,
+ 0xffff0000,0xffff00ff,0xffffff00,0xffffffff
+};
+
+static const u32 cfb_tab8_le[] = {
+ 0x00000000,0xff000000,0x00ff0000,0xffff0000,
+ 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00,
+ 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff,
+ 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff
+};
+
+static const u32 cfb_tab16_be[] = {
+ 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff
+};
+
+static const u32 cfb_tab16_le[] = {
+ 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff
+};
+
+static const u32 cfb_tab32[] = {
+ 0x00000000, 0xffffffff
+};
+
+#define FB_WRITEL fb_writel
+#define FB_READL fb_readl
+
+static inline void color_imageblit(const struct fb_image *image,
+ struct fb_info *p, u8 __iomem *dst1,
+ u32 start_index,
+ u32 pitch_index)
+{
+ /* Draw the penguin */
+ u32 __iomem *dst, *dst2;
+ u32 color = 0, val, shift;
+ int i, n, bpp = p->var.bits_per_pixel;
+ u32 null_bits = 32 - bpp;
+ u32 *palette = (u32 *) p->pseudo_palette;
+ const u8 *src = image->data;
+ u32 bswapmask = fb_compute_bswapmask(p);
+
+ dst2 = (u32 __iomem *) dst1;
+ for (i = image->height; i--; ) {
+ n = image->width;
+ dst = (u32 __iomem *) dst1;
+ shift = 0;
+ val = 0;
+
+ if (start_index) {
+ u32 start_mask = ~fb_shifted_pixels_mask_u32(p,
+ start_index, bswapmask);
+ val = FB_READL(dst) & start_mask;
+ shift = start_index;
+ }
+ while (n--) {
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR )
+ color = palette[*src];
+ else
+ color = *src;
+ color <<= FB_LEFT_POS(p, bpp);
+ val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask);
+ if (shift >= null_bits) {
+ FB_WRITEL(val, dst++);
+
+ val = (shift == null_bits) ? 0 :
+ FB_SHIFT_LOW(p, color, 32 - shift);
+ }
+ shift += bpp;
+ shift &= (32 - 1);
+ src++;
+ }
+ if (shift) {
+ u32 end_mask = fb_shifted_pixels_mask_u32(p, shift,
+ bswapmask);
+
+ FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
+ }
+ dst1 += p->fix.line_length;
+ if (pitch_index) {
+ dst2 += p->fix.line_length;
+ dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1));
+
+ start_index += pitch_index;
+ start_index &= 32 - 1;
+ }
+ }
+}
+
+static inline void slow_imageblit(const struct fb_image *image, struct fb_info *p,
+ u8 __iomem *dst1, u32 fgcolor,
+ u32 bgcolor,
+ u32 start_index,
+ u32 pitch_index)
+{
+ u32 shift, color = 0, bpp = p->var.bits_per_pixel;
+ u32 __iomem *dst, *dst2;
+ u32 val, pitch = p->fix.line_length;
+ u32 null_bits = 32 - bpp;
+ u32 spitch = (image->width+7)/8;
+ const u8 *src = image->data, *s;
+ u32 i, j, l;
+ u32 bswapmask = fb_compute_bswapmask(p);
+
+ dst2 = (u32 __iomem *) dst1;
+ fgcolor <<= FB_LEFT_POS(p, bpp);
+ bgcolor <<= FB_LEFT_POS(p, bpp);
+
+ for (i = image->height; i--; ) {
+ shift = val = 0;
+ l = 8;
+ j = image->width;
+ dst = (u32 __iomem *) dst1;
+ s = src;
+
+ /* write leading bits */
+ if (start_index) {
+ u32 start_mask = ~fb_shifted_pixels_mask_u32(p,
+ start_index, bswapmask);
+ val = FB_READL(dst) & start_mask;
+ shift = start_index;
+ }
+
+ while (j--) {
+ l--;
+ color = (*s & (1 << l)) ? fgcolor : bgcolor;
+ val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask);
+
+ /* Did the bitshift spill bits to the next long? */
+ if (shift >= null_bits) {
+ FB_WRITEL(val, dst++);
+ val = (shift == null_bits) ? 0 :
+ FB_SHIFT_LOW(p, color, 32 - shift);
+ }
+ shift += bpp;
+ shift &= (32 - 1);
+ if (!l) { l = 8; s++; }
+ }
+
+ /* write trailing bits */
+ if (shift) {
+ u32 end_mask = fb_shifted_pixels_mask_u32(p, shift,
+ bswapmask);
+
+ FB_WRITEL((FB_READL(dst) & end_mask) | val, dst);
+ }
+
+ dst1 += pitch;
+ src += spitch;
+ if (pitch_index) {
+ dst2 += pitch;
+ dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1));
+ start_index += pitch_index;
+ start_index &= 32 - 1;
+ }
+
+ }
+}
+
+/*
+ * fast_imageblit - optimized monochrome color expansion
+ *
+ * Only if: bits_per_pixel == 8, 16, or 32
+ * image->width is divisible by pixel/dword (ppw);
+ * fix->line_legth is divisible by 4;
+ * beginning and end of a scanline is dword aligned
+ */
+static inline void fast_imageblit(const struct fb_image *image, struct fb_info *p,
+ u8 __iomem *dst1, u32 fgcolor,
+ u32 bgcolor)
+{
+ u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel;
+ u32 ppw = 32/bpp, spitch = (image->width + 7)/8;
+ u32 bit_mask, end_mask, eorx, shift;
+ const char *s = image->data, *src;
+ u32 __iomem *dst;
+ const u32 *tab = NULL;
+ int i, j, k;
+
+ switch (bpp) {
+ case 8:
+ tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le;
+ break;
+ case 16:
+ tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le;
+ break;
+ case 32:
+ default:
+ tab = cfb_tab32;
+ break;
+ }
+
+ for (i = ppw-1; i--; ) {
+ fgx <<= bpp;
+ bgx <<= bpp;
+ fgx |= fgcolor;
+ bgx |= bgcolor;
+ }
+
+ bit_mask = (1 << ppw) - 1;
+ eorx = fgx ^ bgx;
+ k = image->width/ppw;
+
+ for (i = image->height; i--; ) {
+ dst = (u32 __iomem *) dst1, shift = 8; src = s;
+
+ for (j = k; j--; ) {
+ shift -= ppw;
+ end_mask = tab[(*src >> shift) & bit_mask];
+ FB_WRITEL((end_mask & eorx)^bgx, dst++);
+ if (!shift) { shift = 8; src++; }
+ }
+ dst1 += p->fix.line_length;
+ s += spitch;
+ }
+}
+
+void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
+{
+ u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0;
+ u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel;
+ u32 width = image->width;
+ u32 dx = image->dx, dy = image->dy;
+ u8 __iomem *dst1;
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ bitstart = (dy * p->fix.line_length * 8) + (dx * bpp);
+ start_index = bitstart & (32 - 1);
+ pitch_index = (p->fix.line_length & (bpl - 1)) * 8;
+
+ bitstart /= 8;
+ bitstart &= ~(bpl - 1);
+ dst1 = p->screen_base + bitstart;
+
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+
+ if (image->depth == 1) {
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR) {
+ fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];
+ bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color];
+ } else {
+ fgcolor = image->fg_color;
+ bgcolor = image->bg_color;
+ }
+
+ if (32 % bpp == 0 && !start_index && !pitch_index &&
+ ((width & (32/bpp-1)) == 0) &&
+ bpp >= 8 && bpp <= 32)
+ fast_imageblit(image, p, dst1, fgcolor, bgcolor);
+ else
+ slow_imageblit(image, p, dst1, fgcolor, bgcolor,
+ start_index, pitch_index);
+ } else
+ color_imageblit(image, p, dst1, start_index, pitch_index);
+}
+
+EXPORT_SYMBOL(cfb_imageblit);
+
+MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>");
+MODULE_DESCRIPTION("Generic software accelerated imaging drawing");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/fbdev/core/fb_ddc.c b/drivers/video/fbdev/core/fb_ddc.c
new file mode 100644
index 000000000000..94322ccfedde
--- /dev/null
+++ b/drivers/video/fbdev/core/fb_ddc.c
@@ -0,0 +1,119 @@
+/*
+ * drivers/video/fb_ddc.c - DDC/EDID read support.
+ *
+ * Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/fb.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/slab.h>
+
+#include "../edid.h"
+
+#define DDC_ADDR 0x50
+
+static unsigned char *fb_do_probe_ddc_edid(struct i2c_adapter *adapter)
+{
+ unsigned char start = 0x0;
+ unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL);
+ struct i2c_msg msgs[] = {
+ {
+ .addr = DDC_ADDR,
+ .flags = 0,
+ .len = 1,
+ .buf = &start,
+ }, {
+ .addr = DDC_ADDR,
+ .flags = I2C_M_RD,
+ .len = EDID_LENGTH,
+ .buf = buf,
+ }
+ };
+
+ if (!buf) {
+ dev_warn(&adapter->dev, "unable to allocate memory for EDID "
+ "block.\n");
+ return NULL;
+ }
+
+ if (i2c_transfer(adapter, msgs, 2) == 2)
+ return buf;
+
+ dev_warn(&adapter->dev, "unable to read EDID block.\n");
+ kfree(buf);
+ return NULL;
+}
+
+unsigned char *fb_ddc_read(struct i2c_adapter *adapter)
+{
+ struct i2c_algo_bit_data *algo_data = adapter->algo_data;
+ unsigned char *edid = NULL;
+ int i, j;
+
+ algo_data->setscl(algo_data->data, 1);
+
+ for (i = 0; i < 3; i++) {
+ /* For some old monitors we need the
+ * following process to initialize/stop DDC
+ */
+ algo_data->setsda(algo_data->data, 1);
+ msleep(13);
+
+ algo_data->setscl(algo_data->data, 1);
+ for (j = 0; j < 5; j++) {
+ msleep(10);
+ if (algo_data->getscl(algo_data->data))
+ break;
+ }
+ if (j == 5)
+ continue;
+
+ algo_data->setsda(algo_data->data, 0);
+ msleep(15);
+ algo_data->setscl(algo_data->data, 0);
+ msleep(15);
+ algo_data->setsda(algo_data->data, 1);
+ msleep(15);
+
+ /* Do the real work */
+ edid = fb_do_probe_ddc_edid(adapter);
+ algo_data->setsda(algo_data->data, 0);
+ algo_data->setscl(algo_data->data, 0);
+ msleep(15);
+
+ algo_data->setscl(algo_data->data, 1);
+ for (j = 0; j < 10; j++) {
+ msleep(10);
+ if (algo_data->getscl(algo_data->data))
+ break;
+ }
+
+ algo_data->setsda(algo_data->data, 1);
+ msleep(15);
+ algo_data->setscl(algo_data->data, 0);
+ algo_data->setsda(algo_data->data, 0);
+ if (edid)
+ break;
+ }
+ /* Release the DDC lines when done or the Apple Cinema HD display
+ * will switch off
+ */
+ algo_data->setsda(algo_data->data, 1);
+ algo_data->setscl(algo_data->data, 1);
+
+ adapter->class |= I2C_CLASS_DDC;
+ return edid;
+}
+
+EXPORT_SYMBOL_GPL(fb_ddc_read);
+
+MODULE_AUTHOR("Dennis Munsie <dmunsie@cecropia.com>");
+MODULE_DESCRIPTION("DDC/EDID reading support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
new file mode 100644
index 000000000000..900aa4ecd617
--- /dev/null
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -0,0 +1,245 @@
+/*
+ * linux/drivers/video/fb_defio.c
+ *
+ * Copyright (C) 2006 Jaya Kumar
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/list.h>
+
+/* to support deferred IO */
+#include <linux/rmap.h>
+#include <linux/pagemap.h>
+
+static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
+{
+ void *screen_base = (void __force *) info->screen_base;
+ struct page *page;
+
+ if (is_vmalloc_addr(screen_base + offs))
+ page = vmalloc_to_page(screen_base + offs);
+ else
+ page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT);
+
+ return page;
+}
+
+/* this is to find and return the vmalloc-ed fb pages */
+static int fb_deferred_io_fault(struct vm_area_struct *vma,
+ struct vm_fault *vmf)
+{
+ unsigned long offset;
+ struct page *page;
+ struct fb_info *info = vma->vm_private_data;
+
+ offset = vmf->pgoff << PAGE_SHIFT;
+ if (offset >= info->fix.smem_len)
+ return VM_FAULT_SIGBUS;
+
+ page = fb_deferred_io_page(info, offset);
+ if (!page)
+ return VM_FAULT_SIGBUS;
+
+ get_page(page);
+
+ if (vma->vm_file)
+ page->mapping = vma->vm_file->f_mapping;
+ else
+ printk(KERN_ERR "no mapping available\n");
+
+ BUG_ON(!page->mapping);
+ page->index = vmf->pgoff;
+
+ vmf->page = page;
+ return 0;
+}
+
+int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
+{
+ struct fb_info *info = file->private_data;
+ struct inode *inode = file_inode(file);
+ int err = filemap_write_and_wait_range(inode->i_mapping, start, end);
+ if (err)
+ return err;
+
+ /* Skip if deferred io is compiled-in but disabled on this fbdev */
+ if (!info->fbdefio)
+ return 0;
+
+ mutex_lock(&inode->i_mutex);
+ /* Kill off the delayed work */
+ cancel_delayed_work_sync(&info->deferred_work);
+
+ /* Run it immediately */
+ err = schedule_delayed_work(&info->deferred_work, 0);
+ mutex_unlock(&inode->i_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
+
+/* vm_ops->page_mkwrite handler */
+static int fb_deferred_io_mkwrite(struct vm_area_struct *vma,
+ struct vm_fault *vmf)
+{
+ struct page *page = vmf->page;
+ struct fb_info *info = vma->vm_private_data;
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct page *cur;
+
+ /* this is a callback we get when userspace first tries to
+ write to the page. we schedule a workqueue. that workqueue
+ will eventually mkclean the touched pages and execute the
+ deferred framebuffer IO. then if userspace touches a page
+ again, we repeat the same scheme */
+
+ file_update_time(vma->vm_file);
+
+ /* protect against the workqueue changing the page list */
+ mutex_lock(&fbdefio->lock);
+
+ /* first write in this cycle, notify the driver */
+ if (fbdefio->first_io && list_empty(&fbdefio->pagelist))
+ fbdefio->first_io(info);
+
+ /*
+ * We want the page to remain locked from ->page_mkwrite until
+ * the PTE is marked dirty to avoid page_mkclean() being called
+ * before the PTE is updated, which would leave the page ignored
+ * by defio.
+ * Do this by locking the page here and informing the caller
+ * about it with VM_FAULT_LOCKED.
+ */
+ lock_page(page);
+
+ /* we loop through the pagelist before adding in order
+ to keep the pagelist sorted */
+ list_for_each_entry(cur, &fbdefio->pagelist, lru) {
+ /* this check is to catch the case where a new
+ process could start writing to the same page
+ through a new pte. this new access can cause the
+ mkwrite even when the original ps's pte is marked
+ writable */
+ if (unlikely(cur == page))
+ goto page_already_added;
+ else if (cur->index > page->index)
+ break;
+ }
+
+ list_add_tail(&page->lru, &cur->lru);
+
+page_already_added:
+ mutex_unlock(&fbdefio->lock);
+
+ /* come back after delay to process the deferred IO */
+ schedule_delayed_work(&info->deferred_work, fbdefio->delay);
+ return VM_FAULT_LOCKED;
+}
+
+static const struct vm_operations_struct fb_deferred_io_vm_ops = {
+ .fault = fb_deferred_io_fault,
+ .page_mkwrite = fb_deferred_io_mkwrite,
+};
+
+static int fb_deferred_io_set_page_dirty(struct page *page)
+{
+ if (!PageDirty(page))
+ SetPageDirty(page);
+ return 0;
+}
+
+static const struct address_space_operations fb_deferred_io_aops = {
+ .set_page_dirty = fb_deferred_io_set_page_dirty,
+};
+
+static int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ vma->vm_ops = &fb_deferred_io_vm_ops;
+ vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+ if (!(info->flags & FBINFO_VIRTFB))
+ vma->vm_flags |= VM_IO;
+ vma->vm_private_data = info;
+ return 0;
+}
+
+/* workqueue callback */
+static void fb_deferred_io_work(struct work_struct *work)
+{
+ struct fb_info *info = container_of(work, struct fb_info,
+ deferred_work.work);
+ struct list_head *node, *next;
+ struct page *cur;
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+
+ /* here we mkclean the pages, then do all deferred IO */
+ mutex_lock(&fbdefio->lock);
+ list_for_each_entry(cur, &fbdefio->pagelist, lru) {
+ lock_page(cur);
+ page_mkclean(cur);
+ unlock_page(cur);
+ }
+
+ /* driver's callback with pagelist */
+ fbdefio->deferred_io(info, &fbdefio->pagelist);
+
+ /* clear the list */
+ list_for_each_safe(node, next, &fbdefio->pagelist) {
+ list_del(node);
+ }
+ mutex_unlock(&fbdefio->lock);
+}
+
+void fb_deferred_io_init(struct fb_info *info)
+{
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+
+ BUG_ON(!fbdefio);
+ mutex_init(&fbdefio->lock);
+ info->fbops->fb_mmap = fb_deferred_io_mmap;
+ INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
+ INIT_LIST_HEAD(&fbdefio->pagelist);
+ if (fbdefio->delay == 0) /* set a default of 1 s */
+ fbdefio->delay = HZ;
+}
+EXPORT_SYMBOL_GPL(fb_deferred_io_init);
+
+void fb_deferred_io_open(struct fb_info *info,
+ struct inode *inode,
+ struct file *file)
+{
+ file->f_mapping->a_ops = &fb_deferred_io_aops;
+}
+EXPORT_SYMBOL_GPL(fb_deferred_io_open);
+
+void fb_deferred_io_cleanup(struct fb_info *info)
+{
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct page *page;
+ int i;
+
+ BUG_ON(!fbdefio);
+ cancel_delayed_work_sync(&info->deferred_work);
+
+ /* clear out the mapping that we setup */
+ for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) {
+ page = fb_deferred_io_page(info, i);
+ page->mapping = NULL;
+ }
+
+ info->fbops->fb_mmap = NULL;
+ mutex_destroy(&fbdefio->lock);
+}
+EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/fb_draw.h b/drivers/video/fbdev/core/fb_draw.h
new file mode 100644
index 000000000000..624ee115f129
--- /dev/null
+++ b/drivers/video/fbdev/core/fb_draw.h
@@ -0,0 +1,186 @@
+#ifndef _FB_DRAW_H
+#define _FB_DRAW_H
+
+#include <asm/types.h>
+#include <linux/fb.h>
+#include <linux/bug.h>
+
+ /*
+ * Compose two values, using a bitmask as decision value
+ * This is equivalent to (a & mask) | (b & ~mask)
+ */
+
+static inline unsigned long
+comp(unsigned long a, unsigned long b, unsigned long mask)
+{
+ return ((a ^ b) & mask) ^ b;
+}
+
+ /*
+ * Create a pattern with the given pixel's color
+ */
+
+#if BITS_PER_LONG == 64
+static inline unsigned long
+pixel_to_pat( u32 bpp, u32 pixel)
+{
+ switch (bpp) {
+ case 1:
+ return 0xfffffffffffffffful*pixel;
+ case 2:
+ return 0x5555555555555555ul*pixel;
+ case 4:
+ return 0x1111111111111111ul*pixel;
+ case 8:
+ return 0x0101010101010101ul*pixel;
+ case 12:
+ return 0x1001001001001001ul*pixel;
+ case 16:
+ return 0x0001000100010001ul*pixel;
+ case 24:
+ return 0x0001000001000001ul*pixel;
+ case 32:
+ return 0x0000000100000001ul*pixel;
+ default:
+ WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp);
+ return 0;
+ }
+}
+#else
+static inline unsigned long
+pixel_to_pat( u32 bpp, u32 pixel)
+{
+ switch (bpp) {
+ case 1:
+ return 0xfffffffful*pixel;
+ case 2:
+ return 0x55555555ul*pixel;
+ case 4:
+ return 0x11111111ul*pixel;
+ case 8:
+ return 0x01010101ul*pixel;
+ case 12:
+ return 0x01001001ul*pixel;
+ case 16:
+ return 0x00010001ul*pixel;
+ case 24:
+ return 0x01000001ul*pixel;
+ case 32:
+ return 0x00000001ul*pixel;
+ default:
+ WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp);
+ return 0;
+ }
+}
+#endif
+
+#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE
+#if BITS_PER_LONG == 64
+#define REV_PIXELS_MASK1 0x5555555555555555ul
+#define REV_PIXELS_MASK2 0x3333333333333333ul
+#define REV_PIXELS_MASK4 0x0f0f0f0f0f0f0f0ful
+#else
+#define REV_PIXELS_MASK1 0x55555555ul
+#define REV_PIXELS_MASK2 0x33333333ul
+#define REV_PIXELS_MASK4 0x0f0f0f0ful
+#endif
+
+static inline unsigned long fb_rev_pixels_in_long(unsigned long val,
+ u32 bswapmask)
+{
+ if (bswapmask & 1)
+ val = comp(val >> 1, val << 1, REV_PIXELS_MASK1);
+ if (bswapmask & 2)
+ val = comp(val >> 2, val << 2, REV_PIXELS_MASK2);
+ if (bswapmask & 3)
+ val = comp(val >> 4, val << 4, REV_PIXELS_MASK4);
+ return val;
+}
+
+static inline u32 fb_shifted_pixels_mask_u32(struct fb_info *p, u32 index,
+ u32 bswapmask)
+{
+ u32 mask;
+
+ if (!bswapmask) {
+ mask = FB_SHIFT_HIGH(p, ~(u32)0, index);
+ } else {
+ mask = 0xff << FB_LEFT_POS(p, 8);
+ mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask;
+ mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask));
+#if defined(__i386__) || defined(__x86_64__)
+ /* Shift argument is limited to 0 - 31 on x86 based CPU's */
+ if(index + bswapmask < 32)
+#endif
+ mask |= FB_SHIFT_HIGH(p, ~(u32)0,
+ (index + bswapmask) & ~(bswapmask));
+ }
+ return mask;
+}
+
+static inline unsigned long fb_shifted_pixels_mask_long(struct fb_info *p,
+ u32 index,
+ u32 bswapmask)
+{
+ unsigned long mask;
+
+ if (!bswapmask) {
+ mask = FB_SHIFT_HIGH(p, ~0UL, index);
+ } else {
+ mask = 0xff << FB_LEFT_POS(p, 8);
+ mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask;
+ mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask));
+#if defined(__i386__) || defined(__x86_64__)
+ /* Shift argument is limited to 0 - 31 on x86 based CPU's */
+ if(index + bswapmask < BITS_PER_LONG)
+#endif
+ mask |= FB_SHIFT_HIGH(p, ~0UL,
+ (index + bswapmask) & ~(bswapmask));
+ }
+ return mask;
+}
+
+
+static inline u32 fb_compute_bswapmask(struct fb_info *info)
+{
+ u32 bswapmask = 0;
+ unsigned bpp = info->var.bits_per_pixel;
+
+ if ((bpp < 8) && (info->var.nonstd & FB_NONSTD_REV_PIX_IN_B)) {
+ /*
+ * Reversed order of pixel layout in bytes
+ * works only for 1, 2 and 4 bpp
+ */
+ bswapmask = 7 - bpp + 1;
+ }
+ return bswapmask;
+}
+
+#else /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */
+
+static inline unsigned long fb_rev_pixels_in_long(unsigned long val,
+ u32 bswapmask)
+{
+ return val;
+}
+
+#define fb_shifted_pixels_mask_u32(p, i, b) FB_SHIFT_HIGH((p), ~(u32)0, (i))
+#define fb_shifted_pixels_mask_long(p, i, b) FB_SHIFT_HIGH((p), ~0UL, (i))
+#define fb_compute_bswapmask(...) 0
+
+#endif /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */
+
+#define cpu_to_le_long _cpu_to_le_long(BITS_PER_LONG)
+#define _cpu_to_le_long(x) __cpu_to_le_long(x)
+#define __cpu_to_le_long(x) cpu_to_le##x
+
+#define le_long_to_cpu _le_long_to_cpu(BITS_PER_LONG)
+#define _le_long_to_cpu(x) __le_long_to_cpu(x)
+#define __le_long_to_cpu(x) le##x##_to_cpu
+
+static inline unsigned long rolx(unsigned long word, unsigned int shift, unsigned int x)
+{
+ return (word << shift) | (word >> (x - shift));
+}
+
+#endif /* FB_DRAW_H */
diff --git a/drivers/video/fbdev/core/fb_notify.c b/drivers/video/fbdev/core/fb_notify.c
new file mode 100644
index 000000000000..74c2da528884
--- /dev/null
+++ b/drivers/video/fbdev/core/fb_notify.c
@@ -0,0 +1,47 @@
+/*
+ * linux/drivers/video/fb_notify.c
+ *
+ * Copyright (C) 2006 Antonino Daplas <adaplas@pol.net>
+ *
+ * 2001 - Documented with DocBook
+ * - Brad Douglas <brad@neruo.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+#include <linux/fb.h>
+#include <linux/notifier.h>
+#include <linux/export.h>
+
+static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
+
+/**
+ * fb_register_client - register a client notifier
+ * @nb: notifier block to callback on events
+ */
+int fb_register_client(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&fb_notifier_list, nb);
+}
+EXPORT_SYMBOL(fb_register_client);
+
+/**
+ * fb_unregister_client - unregister a client notifier
+ * @nb: notifier block to callback on events
+ */
+int fb_unregister_client(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&fb_notifier_list, nb);
+}
+EXPORT_SYMBOL(fb_unregister_client);
+
+/**
+ * fb_notifier_call_chain - notify clients of fb_events
+ *
+ */
+int fb_notifier_call_chain(unsigned long val, void *v)
+{
+ return blocking_notifier_call_chain(&fb_notifier_list, val, v);
+}
+EXPORT_SYMBOL_GPL(fb_notifier_call_chain);
diff --git a/drivers/video/fbdev/core/fb_sys_fops.c b/drivers/video/fbdev/core/fb_sys_fops.c
new file mode 100644
index 000000000000..ff275d7f3eaf
--- /dev/null
+++ b/drivers/video/fbdev/core/fb_sys_fops.c
@@ -0,0 +1,104 @@
+/*
+ * linux/drivers/video/fb_sys_read.c - Generic file operations where
+ * framebuffer is in system RAM
+ *
+ * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+#include <linux/fb.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+ssize_t fb_sys_read(struct fb_info *info, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ void *src;
+ int err = 0;
+ unsigned long total_size;
+
+ if (info->state != FBINFO_STATE_RUNNING)
+ return -EPERM;
+
+ total_size = info->screen_size;
+
+ if (total_size == 0)
+ total_size = info->fix.smem_len;
+
+ if (p >= total_size)
+ return 0;
+
+ if (count >= total_size)
+ count = total_size;
+
+ if (count + p > total_size)
+ count = total_size - p;
+
+ src = (void __force *)(info->screen_base + p);
+
+ if (info->fbops->fb_sync)
+ info->fbops->fb_sync(info);
+
+ if (copy_to_user(buf, src, count))
+ err = -EFAULT;
+
+ if (!err)
+ *ppos += count;
+
+ return (err) ? err : count;
+}
+EXPORT_SYMBOL_GPL(fb_sys_read);
+
+ssize_t fb_sys_write(struct fb_info *info, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ void *dst;
+ int err = 0;
+ unsigned long total_size;
+
+ if (info->state != FBINFO_STATE_RUNNING)
+ return -EPERM;
+
+ total_size = info->screen_size;
+
+ if (total_size == 0)
+ total_size = info->fix.smem_len;
+
+ if (p > total_size)
+ return -EFBIG;
+
+ if (count > total_size) {
+ err = -EFBIG;
+ count = total_size;
+ }
+
+ if (count + p > total_size) {
+ if (!err)
+ err = -ENOSPC;
+
+ count = total_size - p;
+ }
+
+ dst = (void __force *) (info->screen_base + p);
+
+ if (info->fbops->fb_sync)
+ info->fbops->fb_sync(info);
+
+ if (copy_from_user(dst, buf, count))
+ err = -EFAULT;
+
+ if (!err)
+ *ppos += count;
+
+ return (err) ? err : count;
+}
+EXPORT_SYMBOL_GPL(fb_sys_write);
+
+MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>");
+MODULE_DESCRIPTION("Generic file read (fb in system RAM)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/fbcmap.c b/drivers/video/fbdev/core/fbcmap.c
new file mode 100644
index 000000000000..f89245b8ba8e
--- /dev/null
+++ b/drivers/video/fbdev/core/fbcmap.c
@@ -0,0 +1,362 @@
+/*
+ * linux/drivers/video/fbcmap.c -- Colormap handling for frame buffer devices
+ *
+ * Created 15 Jun 1997 by Geert Uytterhoeven
+ *
+ * 2001 - Documented with DocBook
+ * - Brad Douglas <brad@neruo.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+static u16 red2[] __read_mostly = {
+ 0x0000, 0xaaaa
+};
+static u16 green2[] __read_mostly = {
+ 0x0000, 0xaaaa
+};
+static u16 blue2[] __read_mostly = {
+ 0x0000, 0xaaaa
+};
+
+static u16 red4[] __read_mostly = {
+ 0x0000, 0xaaaa, 0x5555, 0xffff
+};
+static u16 green4[] __read_mostly = {
+ 0x0000, 0xaaaa, 0x5555, 0xffff
+};
+static u16 blue4[] __read_mostly = {
+ 0x0000, 0xaaaa, 0x5555, 0xffff
+};
+
+static u16 red8[] __read_mostly = {
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa
+};
+static u16 green8[] __read_mostly = {
+ 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa
+};
+static u16 blue8[] __read_mostly = {
+ 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa
+};
+
+static u16 red16[] __read_mostly = {
+ 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa,
+ 0x5555, 0x5555, 0x5555, 0x5555, 0xffff, 0xffff, 0xffff, 0xffff
+};
+static u16 green16[] __read_mostly = {
+ 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa,
+ 0x5555, 0x5555, 0xffff, 0xffff, 0x5555, 0x5555, 0xffff, 0xffff
+};
+static u16 blue16[] __read_mostly = {
+ 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa,
+ 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff
+};
+
+static const struct fb_cmap default_2_colors = {
+ .len=2, .red=red2, .green=green2, .blue=blue2
+};
+static const struct fb_cmap default_8_colors = {
+ .len=8, .red=red8, .green=green8, .blue=blue8
+};
+static const struct fb_cmap default_4_colors = {
+ .len=4, .red=red4, .green=green4, .blue=blue4
+};
+static const struct fb_cmap default_16_colors = {
+ .len=16, .red=red16, .green=green16, .blue=blue16
+};
+
+
+
+/**
+ * fb_alloc_cmap - allocate a colormap
+ * @cmap: frame buffer colormap structure
+ * @len: length of @cmap
+ * @transp: boolean, 1 if there is transparency, 0 otherwise
+ * @flags: flags for kmalloc memory allocation
+ *
+ * Allocates memory for a colormap @cmap. @len is the
+ * number of entries in the palette.
+ *
+ * Returns negative errno on error, or zero on success.
+ *
+ */
+
+int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags)
+{
+ int size = len * sizeof(u16);
+ int ret = -ENOMEM;
+
+ if (cmap->len != len) {
+ fb_dealloc_cmap(cmap);
+ if (!len)
+ return 0;
+
+ cmap->red = kmalloc(size, flags);
+ if (!cmap->red)
+ goto fail;
+ cmap->green = kmalloc(size, flags);
+ if (!cmap->green)
+ goto fail;
+ cmap->blue = kmalloc(size, flags);
+ if (!cmap->blue)
+ goto fail;
+ if (transp) {
+ cmap->transp = kmalloc(size, flags);
+ if (!cmap->transp)
+ goto fail;
+ } else {
+ cmap->transp = NULL;
+ }
+ }
+ cmap->start = 0;
+ cmap->len = len;
+ ret = fb_copy_cmap(fb_default_cmap(len), cmap);
+ if (ret)
+ goto fail;
+ return 0;
+
+fail:
+ fb_dealloc_cmap(cmap);
+ return ret;
+}
+
+int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp)
+{
+ return fb_alloc_cmap_gfp(cmap, len, transp, GFP_ATOMIC);
+}
+
+/**
+ * fb_dealloc_cmap - deallocate a colormap
+ * @cmap: frame buffer colormap structure
+ *
+ * Deallocates a colormap that was previously allocated with
+ * fb_alloc_cmap().
+ *
+ */
+
+void fb_dealloc_cmap(struct fb_cmap *cmap)
+{
+ kfree(cmap->red);
+ kfree(cmap->green);
+ kfree(cmap->blue);
+ kfree(cmap->transp);
+
+ cmap->red = cmap->green = cmap->blue = cmap->transp = NULL;
+ cmap->len = 0;
+}
+
+/**
+ * fb_copy_cmap - copy a colormap
+ * @from: frame buffer colormap structure
+ * @to: frame buffer colormap structure
+ *
+ * Copy contents of colormap from @from to @to.
+ */
+
+int fb_copy_cmap(const struct fb_cmap *from, struct fb_cmap *to)
+{
+ int tooff = 0, fromoff = 0;
+ int size;
+
+ if (to->start > from->start)
+ fromoff = to->start - from->start;
+ else
+ tooff = from->start - to->start;
+ size = to->len - tooff;
+ if (size > (int) (from->len - fromoff))
+ size = from->len - fromoff;
+ if (size <= 0)
+ return -EINVAL;
+ size *= sizeof(u16);
+
+ memcpy(to->red+tooff, from->red+fromoff, size);
+ memcpy(to->green+tooff, from->green+fromoff, size);
+ memcpy(to->blue+tooff, from->blue+fromoff, size);
+ if (from->transp && to->transp)
+ memcpy(to->transp+tooff, from->transp+fromoff, size);
+ return 0;
+}
+
+int fb_cmap_to_user(const struct fb_cmap *from, struct fb_cmap_user *to)
+{
+ int tooff = 0, fromoff = 0;
+ int size;
+
+ if (to->start > from->start)
+ fromoff = to->start - from->start;
+ else
+ tooff = from->start - to->start;
+ size = to->len - tooff;
+ if (size > (int) (from->len - fromoff))
+ size = from->len - fromoff;
+ if (size <= 0)
+ return -EINVAL;
+ size *= sizeof(u16);
+
+ if (copy_to_user(to->red+tooff, from->red+fromoff, size))
+ return -EFAULT;
+ if (copy_to_user(to->green+tooff, from->green+fromoff, size))
+ return -EFAULT;
+ if (copy_to_user(to->blue+tooff, from->blue+fromoff, size))
+ return -EFAULT;
+ if (from->transp && to->transp)
+ if (copy_to_user(to->transp+tooff, from->transp+fromoff, size))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * fb_set_cmap - set the colormap
+ * @cmap: frame buffer colormap structure
+ * @info: frame buffer info structure
+ *
+ * Sets the colormap @cmap for a screen of device @info.
+ *
+ * Returns negative errno on error, or zero on success.
+ *
+ */
+
+int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+ int i, start, rc = 0;
+ u16 *red, *green, *blue, *transp;
+ u_int hred, hgreen, hblue, htransp = 0xffff;
+
+ red = cmap->red;
+ green = cmap->green;
+ blue = cmap->blue;
+ transp = cmap->transp;
+ start = cmap->start;
+
+ if (start < 0 || (!info->fbops->fb_setcolreg &&
+ !info->fbops->fb_setcmap))
+ return -EINVAL;
+ if (info->fbops->fb_setcmap) {
+ rc = info->fbops->fb_setcmap(cmap, info);
+ } else {
+ for (i = 0; i < cmap->len; i++) {
+ hred = *red++;
+ hgreen = *green++;
+ hblue = *blue++;
+ if (transp)
+ htransp = *transp++;
+ if (info->fbops->fb_setcolreg(start++,
+ hred, hgreen, hblue,
+ htransp, info))
+ break;
+ }
+ }
+ if (rc == 0)
+ fb_copy_cmap(cmap, &info->cmap);
+
+ return rc;
+}
+
+int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)
+{
+ int rc, size = cmap->len * sizeof(u16);
+ struct fb_cmap umap;
+
+ if (size < 0 || size < cmap->len)
+ return -E2BIG;
+
+ memset(&umap, 0, sizeof(struct fb_cmap));
+ rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL,
+ GFP_KERNEL);
+ if (rc)
+ return rc;
+ if (copy_from_user(umap.red, cmap->red, size) ||
+ copy_from_user(umap.green, cmap->green, size) ||
+ copy_from_user(umap.blue, cmap->blue, size) ||
+ (cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) {
+ rc = -EFAULT;
+ goto out;
+ }
+ umap.start = cmap->start;
+ if (!lock_fb_info(info)) {
+ rc = -ENODEV;
+ goto out;
+ }
+
+ rc = fb_set_cmap(&umap, info);
+ unlock_fb_info(info);
+out:
+ fb_dealloc_cmap(&umap);
+ return rc;
+}
+
+/**
+ * fb_default_cmap - get default colormap
+ * @len: size of palette for a depth
+ *
+ * Gets the default colormap for a specific screen depth. @len
+ * is the size of the palette for a particular screen depth.
+ *
+ * Returns pointer to a frame buffer colormap structure.
+ *
+ */
+
+const struct fb_cmap *fb_default_cmap(int len)
+{
+ if (len <= 2)
+ return &default_2_colors;
+ if (len <= 4)
+ return &default_4_colors;
+ if (len <= 8)
+ return &default_8_colors;
+ return &default_16_colors;
+}
+
+
+/**
+ * fb_invert_cmaps - invert all defaults colormaps
+ *
+ * Invert all default colormaps.
+ *
+ */
+
+void fb_invert_cmaps(void)
+{
+ u_int i;
+
+ for (i = 0; i < ARRAY_SIZE(red2); i++) {
+ red2[i] = ~red2[i];
+ green2[i] = ~green2[i];
+ blue2[i] = ~blue2[i];
+ }
+ for (i = 0; i < ARRAY_SIZE(red4); i++) {
+ red4[i] = ~red4[i];
+ green4[i] = ~green4[i];
+ blue4[i] = ~blue4[i];
+ }
+ for (i = 0; i < ARRAY_SIZE(red8); i++) {
+ red8[i] = ~red8[i];
+ green8[i] = ~green8[i];
+ blue8[i] = ~blue8[i];
+ }
+ for (i = 0; i < ARRAY_SIZE(red16); i++) {
+ red16[i] = ~red16[i];
+ green16[i] = ~green16[i];
+ blue16[i] = ~blue16[i];
+ }
+}
+
+
+ /*
+ * Visible symbols for modules
+ */
+
+EXPORT_SYMBOL(fb_alloc_cmap);
+EXPORT_SYMBOL(fb_dealloc_cmap);
+EXPORT_SYMBOL(fb_copy_cmap);
+EXPORT_SYMBOL(fb_set_cmap);
+EXPORT_SYMBOL(fb_default_cmap);
+EXPORT_SYMBOL(fb_invert_cmaps);
diff --git a/drivers/video/fbdev/core/fbcvt.c b/drivers/video/fbdev/core/fbcvt.c
new file mode 100644
index 000000000000..7cb715dfc0e1
--- /dev/null
+++ b/drivers/video/fbdev/core/fbcvt.c
@@ -0,0 +1,379 @@
+/*
+ * linux/drivers/video/fbcvt.c - VESA(TM) Coordinated Video Timings
+ *
+ * Copyright (C) 2005 Antonino Daplas <adaplas@pol.net>
+ *
+ * Based from the VESA(TM) Coordinated Video Timing Generator by
+ * Graham Loveridge April 9, 2003 available at
+ * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#define FB_CVT_CELLSIZE 8
+#define FB_CVT_GTF_C 40
+#define FB_CVT_GTF_J 20
+#define FB_CVT_GTF_K 128
+#define FB_CVT_GTF_M 600
+#define FB_CVT_MIN_VSYNC_BP 550
+#define FB_CVT_MIN_VPORCH 3
+#define FB_CVT_MIN_BPORCH 6
+
+#define FB_CVT_RB_MIN_VBLANK 460
+#define FB_CVT_RB_HBLANK 160
+#define FB_CVT_RB_V_FPORCH 3
+
+#define FB_CVT_FLAG_REDUCED_BLANK 1
+#define FB_CVT_FLAG_MARGINS 2
+#define FB_CVT_FLAG_INTERLACED 4
+
+struct fb_cvt_data {
+ u32 xres;
+ u32 yres;
+ u32 refresh;
+ u32 f_refresh;
+ u32 pixclock;
+ u32 hperiod;
+ u32 hblank;
+ u32 hfreq;
+ u32 htotal;
+ u32 vtotal;
+ u32 vsync;
+ u32 hsync;
+ u32 h_front_porch;
+ u32 h_back_porch;
+ u32 v_front_porch;
+ u32 v_back_porch;
+ u32 h_margin;
+ u32 v_margin;
+ u32 interlace;
+ u32 aspect_ratio;
+ u32 active_pixels;
+ u32 flags;
+ u32 status;
+};
+
+static const unsigned char fb_cvt_vbi_tab[] = {
+ 4, /* 4:3 */
+ 5, /* 16:9 */
+ 6, /* 16:10 */
+ 7, /* 5:4 */
+ 7, /* 15:9 */
+ 8, /* reserved */
+ 9, /* reserved */
+ 10 /* custom */
+};
+
+/* returns hperiod * 1000 */
+static u32 fb_cvt_hperiod(struct fb_cvt_data *cvt)
+{
+ u32 num = 1000000000/cvt->f_refresh;
+ u32 den;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) {
+ num -= FB_CVT_RB_MIN_VBLANK * 1000;
+ den = 2 * (cvt->yres/cvt->interlace + 2 * cvt->v_margin);
+ } else {
+ num -= FB_CVT_MIN_VSYNC_BP * 1000;
+ den = 2 * (cvt->yres/cvt->interlace + cvt->v_margin * 2
+ + FB_CVT_MIN_VPORCH + cvt->interlace/2);
+ }
+
+ return 2 * (num/den);
+}
+
+/* returns ideal duty cycle * 1000 */
+static u32 fb_cvt_ideal_duty_cycle(struct fb_cvt_data *cvt)
+{
+ u32 c_prime = (FB_CVT_GTF_C - FB_CVT_GTF_J) *
+ (FB_CVT_GTF_K) + 256 * FB_CVT_GTF_J;
+ u32 m_prime = (FB_CVT_GTF_K * FB_CVT_GTF_M);
+ u32 h_period_est = cvt->hperiod;
+
+ return (1000 * c_prime - ((m_prime * h_period_est)/1000))/256;
+}
+
+static u32 fb_cvt_hblank(struct fb_cvt_data *cvt)
+{
+ u32 hblank = 0;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
+ hblank = FB_CVT_RB_HBLANK;
+ else {
+ u32 ideal_duty_cycle = fb_cvt_ideal_duty_cycle(cvt);
+ u32 active_pixels = cvt->active_pixels;
+
+ if (ideal_duty_cycle < 20000)
+ hblank = (active_pixels * 20000)/
+ (100000 - 20000);
+ else {
+ hblank = (active_pixels * ideal_duty_cycle)/
+ (100000 - ideal_duty_cycle);
+ }
+ }
+
+ hblank &= ~((2 * FB_CVT_CELLSIZE) - 1);
+
+ return hblank;
+}
+
+static u32 fb_cvt_hsync(struct fb_cvt_data *cvt)
+{
+ u32 hsync;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
+ hsync = 32;
+ else
+ hsync = (FB_CVT_CELLSIZE * cvt->htotal)/100;
+
+ hsync &= ~(FB_CVT_CELLSIZE - 1);
+ return hsync;
+}
+
+static u32 fb_cvt_vbi_lines(struct fb_cvt_data *cvt)
+{
+ u32 vbi_lines, min_vbi_lines, act_vbi_lines;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) {
+ vbi_lines = (1000 * FB_CVT_RB_MIN_VBLANK)/cvt->hperiod + 1;
+ min_vbi_lines = FB_CVT_RB_V_FPORCH + cvt->vsync +
+ FB_CVT_MIN_BPORCH;
+
+ } else {
+ vbi_lines = (FB_CVT_MIN_VSYNC_BP * 1000)/cvt->hperiod + 1 +
+ FB_CVT_MIN_VPORCH;
+ min_vbi_lines = cvt->vsync + FB_CVT_MIN_BPORCH +
+ FB_CVT_MIN_VPORCH;
+ }
+
+ if (vbi_lines < min_vbi_lines)
+ act_vbi_lines = min_vbi_lines;
+ else
+ act_vbi_lines = vbi_lines;
+
+ return act_vbi_lines;
+}
+
+static u32 fb_cvt_vtotal(struct fb_cvt_data *cvt)
+{
+ u32 vtotal = cvt->yres/cvt->interlace;
+
+ vtotal += 2 * cvt->v_margin + cvt->interlace/2 + fb_cvt_vbi_lines(cvt);
+ vtotal |= cvt->interlace/2;
+
+ return vtotal;
+}
+
+static u32 fb_cvt_pixclock(struct fb_cvt_data *cvt)
+{
+ u32 pixclock;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
+ pixclock = (cvt->f_refresh * cvt->vtotal * cvt->htotal)/1000;
+ else
+ pixclock = (cvt->htotal * 1000000)/cvt->hperiod;
+
+ pixclock /= 250;
+ pixclock *= 250;
+ pixclock *= 1000;
+
+ return pixclock;
+}
+
+static u32 fb_cvt_aspect_ratio(struct fb_cvt_data *cvt)
+{
+ u32 xres = cvt->xres;
+ u32 yres = cvt->yres;
+ u32 aspect = -1;
+
+ if (xres == (yres * 4)/3 && !((yres * 4) % 3))
+ aspect = 0;
+ else if (xres == (yres * 16)/9 && !((yres * 16) % 9))
+ aspect = 1;
+ else if (xres == (yres * 16)/10 && !((yres * 16) % 10))
+ aspect = 2;
+ else if (xres == (yres * 5)/4 && !((yres * 5) % 4))
+ aspect = 3;
+ else if (xres == (yres * 15)/9 && !((yres * 15) % 9))
+ aspect = 4;
+ else {
+ printk(KERN_INFO "fbcvt: Aspect ratio not CVT "
+ "standard\n");
+ aspect = 7;
+ cvt->status = 1;
+ }
+
+ return aspect;
+}
+
+static void fb_cvt_print_name(struct fb_cvt_data *cvt)
+{
+ u32 pixcount, pixcount_mod;
+ int cnt = 255, offset = 0, read = 0;
+ u8 *buf = kzalloc(256, GFP_KERNEL);
+
+ if (!buf)
+ return;
+
+ pixcount = (cvt->xres * (cvt->yres/cvt->interlace))/1000000;
+ pixcount_mod = (cvt->xres * (cvt->yres/cvt->interlace)) % 1000000;
+ pixcount_mod /= 1000;
+
+ read = snprintf(buf+offset, cnt, "fbcvt: %dx%d@%d: CVT Name - ",
+ cvt->xres, cvt->yres, cvt->refresh);
+ offset += read;
+ cnt -= read;
+
+ if (cvt->status)
+ snprintf(buf+offset, cnt, "Not a CVT standard - %d.%03d Mega "
+ "Pixel Image\n", pixcount, pixcount_mod);
+ else {
+ if (pixcount) {
+ read = snprintf(buf+offset, cnt, "%d", pixcount);
+ cnt -= read;
+ offset += read;
+ }
+
+ read = snprintf(buf+offset, cnt, ".%03dM", pixcount_mod);
+ cnt -= read;
+ offset += read;
+
+ if (cvt->aspect_ratio == 0)
+ read = snprintf(buf+offset, cnt, "3");
+ else if (cvt->aspect_ratio == 3)
+ read = snprintf(buf+offset, cnt, "4");
+ else if (cvt->aspect_ratio == 1 || cvt->aspect_ratio == 4)
+ read = snprintf(buf+offset, cnt, "9");
+ else if (cvt->aspect_ratio == 2)
+ read = snprintf(buf+offset, cnt, "A");
+ else
+ read = 0;
+ cnt -= read;
+ offset += read;
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) {
+ read = snprintf(buf+offset, cnt, "-R");
+ cnt -= read;
+ offset += read;
+ }
+ }
+
+ printk(KERN_INFO "%s\n", buf);
+ kfree(buf);
+}
+
+static void fb_cvt_convert_to_mode(struct fb_cvt_data *cvt,
+ struct fb_videomode *mode)
+{
+ mode->refresh = cvt->f_refresh;
+ mode->pixclock = KHZ2PICOS(cvt->pixclock/1000);
+ mode->left_margin = cvt->h_back_porch;
+ mode->right_margin = cvt->h_front_porch;
+ mode->hsync_len = cvt->hsync;
+ mode->upper_margin = cvt->v_back_porch;
+ mode->lower_margin = cvt->v_front_porch;
+ mode->vsync_len = cvt->vsync;
+
+ mode->sync &= ~(FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT);
+
+ if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
+ mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ else
+ mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+}
+
+/*
+ * fb_find_mode_cvt - calculate mode using VESA(TM) CVT
+ * @mode: pointer to fb_videomode; xres, yres, refresh and vmode must be
+ * pre-filled with the desired values
+ * @margins: add margin to calculation (1.8% of xres and yres)
+ * @rb: compute with reduced blanking (for flatpanels)
+ *
+ * RETURNS:
+ * 0 for success
+ * @mode is filled with computed values. If interlaced, the refresh field
+ * will be filled with the field rate (2x the frame rate)
+ *
+ * DESCRIPTION:
+ * Computes video timings using VESA(TM) Coordinated Video Timings
+ */
+int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb)
+{
+ struct fb_cvt_data cvt;
+
+ memset(&cvt, 0, sizeof(cvt));
+
+ if (margins)
+ cvt.flags |= FB_CVT_FLAG_MARGINS;
+
+ if (rb)
+ cvt.flags |= FB_CVT_FLAG_REDUCED_BLANK;
+
+ if (mode->vmode & FB_VMODE_INTERLACED)
+ cvt.flags |= FB_CVT_FLAG_INTERLACED;
+
+ cvt.xres = mode->xres;
+ cvt.yres = mode->yres;
+ cvt.refresh = mode->refresh;
+ cvt.f_refresh = cvt.refresh;
+ cvt.interlace = 1;
+
+ if (!cvt.xres || !cvt.yres || !cvt.refresh) {
+ printk(KERN_INFO "fbcvt: Invalid input parameters\n");
+ return 1;
+ }
+
+ if (!(cvt.refresh == 50 || cvt.refresh == 60 || cvt.refresh == 70 ||
+ cvt.refresh == 85)) {
+ printk(KERN_INFO "fbcvt: Refresh rate not CVT "
+ "standard\n");
+ cvt.status = 1;
+ }
+
+ cvt.xres &= ~(FB_CVT_CELLSIZE - 1);
+
+ if (cvt.flags & FB_CVT_FLAG_INTERLACED) {
+ cvt.interlace = 2;
+ cvt.f_refresh *= 2;
+ }
+
+ if (cvt.flags & FB_CVT_FLAG_REDUCED_BLANK) {
+ if (cvt.refresh != 60) {
+ printk(KERN_INFO "fbcvt: 60Hz refresh rate "
+ "advised for reduced blanking\n");
+ cvt.status = 1;
+ }
+ }
+
+ if (cvt.flags & FB_CVT_FLAG_MARGINS) {
+ cvt.h_margin = (cvt.xres * 18)/1000;
+ cvt.h_margin &= ~(FB_CVT_CELLSIZE - 1);
+ cvt.v_margin = ((cvt.yres/cvt.interlace)* 18)/1000;
+ }
+
+ cvt.aspect_ratio = fb_cvt_aspect_ratio(&cvt);
+ cvt.active_pixels = cvt.xres + 2 * cvt.h_margin;
+ cvt.hperiod = fb_cvt_hperiod(&cvt);
+ cvt.vsync = fb_cvt_vbi_tab[cvt.aspect_ratio];
+ cvt.vtotal = fb_cvt_vtotal(&cvt);
+ cvt.hblank = fb_cvt_hblank(&cvt);
+ cvt.htotal = cvt.active_pixels + cvt.hblank;
+ cvt.hsync = fb_cvt_hsync(&cvt);
+ cvt.pixclock = fb_cvt_pixclock(&cvt);
+ cvt.hfreq = cvt.pixclock/cvt.htotal;
+ cvt.h_back_porch = cvt.hblank/2 + cvt.h_margin;
+ cvt.h_front_porch = cvt.hblank - cvt.hsync - cvt.h_back_porch +
+ 2 * cvt.h_margin;
+ cvt.v_back_porch = 3 + cvt.v_margin;
+ cvt.v_front_porch = cvt.vtotal - cvt.yres/cvt.interlace -
+ cvt.v_back_porch - cvt.vsync;
+ fb_cvt_print_name(&cvt);
+ fb_cvt_convert_to_mode(&cvt, mode);
+
+ return 0;
+}
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
new file mode 100644
index 000000000000..b6d5008f361f
--- /dev/null
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -0,0 +1,2002 @@
+/*
+ * linux/drivers/video/fbmem.c
+ *
+ * Copyright (C) 1994 Martin Schaller
+ *
+ * 2001 - Documented with DocBook
+ * - Brad Douglas <brad@neruo.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+
+#include <linux/compat.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/vt.h>
+#include <linux/init.h>
+#include <linux/linux_logo.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/console.h>
+#include <linux/kmod.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/efi.h>
+#include <linux/fb.h>
+
+#include <asm/fb.h>
+
+
+ /*
+ * Frame buffer device initialization and setup routines
+ */
+
+#define FBPIXMAPSIZE (1024 * 8)
+
+static DEFINE_MUTEX(registration_lock);
+
+struct fb_info *registered_fb[FB_MAX] __read_mostly;
+EXPORT_SYMBOL(registered_fb);
+
+int num_registered_fb __read_mostly;
+EXPORT_SYMBOL(num_registered_fb);
+
+static struct fb_info *get_fb_info(unsigned int idx)
+{
+ struct fb_info *fb_info;
+
+ if (idx >= FB_MAX)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&registration_lock);
+ fb_info = registered_fb[idx];
+ if (fb_info)
+ atomic_inc(&fb_info->count);
+ mutex_unlock(&registration_lock);
+
+ return fb_info;
+}
+
+static void put_fb_info(struct fb_info *fb_info)
+{
+ if (!atomic_dec_and_test(&fb_info->count))
+ return;
+ if (fb_info->fbops->fb_destroy)
+ fb_info->fbops->fb_destroy(fb_info);
+}
+
+int lock_fb_info(struct fb_info *info)
+{
+ mutex_lock(&info->lock);
+ if (!info->fbops) {
+ mutex_unlock(&info->lock);
+ return 0;
+ }
+ return 1;
+}
+EXPORT_SYMBOL(lock_fb_info);
+
+/*
+ * Helpers
+ */
+
+int fb_get_color_depth(struct fb_var_screeninfo *var,
+ struct fb_fix_screeninfo *fix)
+{
+ int depth = 0;
+
+ if (fix->visual == FB_VISUAL_MONO01 ||
+ fix->visual == FB_VISUAL_MONO10)
+ depth = 1;
+ else {
+ if (var->green.length == var->blue.length &&
+ var->green.length == var->red.length &&
+ var->green.offset == var->blue.offset &&
+ var->green.offset == var->red.offset)
+ depth = var->green.length;
+ else
+ depth = var->green.length + var->red.length +
+ var->blue.length;
+ }
+
+ return depth;
+}
+EXPORT_SYMBOL(fb_get_color_depth);
+
+/*
+ * Data padding functions.
+ */
+void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch, u32 height)
+{
+ __fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height);
+}
+EXPORT_SYMBOL(fb_pad_aligned_buffer);
+
+void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height,
+ u32 shift_high, u32 shift_low, u32 mod)
+{
+ u8 mask = (u8) (0xfff << shift_high), tmp;
+ int i, j;
+
+ for (i = height; i--; ) {
+ for (j = 0; j < idx; j++) {
+ tmp = dst[j];
+ tmp &= mask;
+ tmp |= *src >> shift_low;
+ dst[j] = tmp;
+ tmp = *src << shift_high;
+ dst[j+1] = tmp;
+ src++;
+ }
+ tmp = dst[idx];
+ tmp &= mask;
+ tmp |= *src >> shift_low;
+ dst[idx] = tmp;
+ if (shift_high < mod) {
+ tmp = *src << shift_high;
+ dst[idx+1] = tmp;
+ }
+ src++;
+ dst += d_pitch;
+ }
+}
+EXPORT_SYMBOL(fb_pad_unaligned_buffer);
+
+/*
+ * we need to lock this section since fb_cursor
+ * may use fb_imageblit()
+ */
+char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size)
+{
+ u32 align = buf->buf_align - 1, offset;
+ char *addr = buf->addr;
+
+ /* If IO mapped, we need to sync before access, no sharing of
+ * the pixmap is done
+ */
+ if (buf->flags & FB_PIXMAP_IO) {
+ if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
+ info->fbops->fb_sync(info);
+ return addr;
+ }
+
+ /* See if we fit in the remaining pixmap space */
+ offset = buf->offset + align;
+ offset &= ~align;
+ if (offset + size > buf->size) {
+ /* We do not fit. In order to be able to re-use the buffer,
+ * we must ensure no asynchronous DMA'ing or whatever operation
+ * is in progress, we sync for that.
+ */
+ if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
+ info->fbops->fb_sync(info);
+ offset = 0;
+ }
+ buf->offset = offset + size;
+ addr += offset;
+
+ return addr;
+}
+EXPORT_SYMBOL(fb_get_buffer_offset);
+
+#ifdef CONFIG_LOGO
+
+static inline unsigned safe_shift(unsigned d, int n)
+{
+ return n < 0 ? d >> -n : d << n;
+}
+
+static void fb_set_logocmap(struct fb_info *info,
+ const struct linux_logo *logo)
+{
+ struct fb_cmap palette_cmap;
+ u16 palette_green[16];
+ u16 palette_blue[16];
+ u16 palette_red[16];
+ int i, j, n;
+ const unsigned char *clut = logo->clut;
+
+ palette_cmap.start = 0;
+ palette_cmap.len = 16;
+ palette_cmap.red = palette_red;
+ palette_cmap.green = palette_green;
+ palette_cmap.blue = palette_blue;
+ palette_cmap.transp = NULL;
+
+ for (i = 0; i < logo->clutsize; i += n) {
+ n = logo->clutsize - i;
+ /* palette_cmap provides space for only 16 colors at once */
+ if (n > 16)
+ n = 16;
+ palette_cmap.start = 32 + i;
+ palette_cmap.len = n;
+ for (j = 0; j < n; ++j) {
+ palette_cmap.red[j] = clut[0] << 8 | clut[0];
+ palette_cmap.green[j] = clut[1] << 8 | clut[1];
+ palette_cmap.blue[j] = clut[2] << 8 | clut[2];
+ clut += 3;
+ }
+ fb_set_cmap(&palette_cmap, info);
+ }
+}
+
+static void fb_set_logo_truepalette(struct fb_info *info,
+ const struct linux_logo *logo,
+ u32 *palette)
+{
+ static const unsigned char mask[] = { 0,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff };
+ unsigned char redmask, greenmask, bluemask;
+ int redshift, greenshift, blueshift;
+ int i;
+ const unsigned char *clut = logo->clut;
+
+ /*
+ * We have to create a temporary palette since console palette is only
+ * 16 colors long.
+ */
+ /* Bug: Doesn't obey msb_right ... (who needs that?) */
+ redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8];
+ greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
+ bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8];
+ redshift = info->var.red.offset - (8 - info->var.red.length);
+ greenshift = info->var.green.offset - (8 - info->var.green.length);
+ blueshift = info->var.blue.offset - (8 - info->var.blue.length);
+
+ for ( i = 0; i < logo->clutsize; i++) {
+ palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
+ safe_shift((clut[1] & greenmask), greenshift) |
+ safe_shift((clut[2] & bluemask), blueshift));
+ clut += 3;
+ }
+}
+
+static void fb_set_logo_directpalette(struct fb_info *info,
+ const struct linux_logo *logo,
+ u32 *palette)
+{
+ int redshift, greenshift, blueshift;
+ int i;
+
+ redshift = info->var.red.offset;
+ greenshift = info->var.green.offset;
+ blueshift = info->var.blue.offset;
+
+ for (i = 32; i < 32 + logo->clutsize; i++)
+ palette[i] = i << redshift | i << greenshift | i << blueshift;
+}
+
+static void fb_set_logo(struct fb_info *info,
+ const struct linux_logo *logo, u8 *dst,
+ int depth)
+{
+ int i, j, k;
+ const u8 *src = logo->data;
+ u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
+ u8 fg = 1, d;
+
+ switch (fb_get_color_depth(&info->var, &info->fix)) {
+ case 1:
+ fg = 1;
+ break;
+ case 2:
+ fg = 3;
+ break;
+ default:
+ fg = 7;
+ break;
+ }
+
+ if (info->fix.visual == FB_VISUAL_MONO01 ||
+ info->fix.visual == FB_VISUAL_MONO10)
+ fg = ~((u8) (0xfff << info->var.green.length));
+
+ switch (depth) {
+ case 4:
+ for (i = 0; i < logo->height; i++)
+ for (j = 0; j < logo->width; src++) {
+ *dst++ = *src >> 4;
+ j++;
+ if (j < logo->width) {
+ *dst++ = *src & 0x0f;
+ j++;
+ }
+ }
+ break;
+ case 1:
+ for (i = 0; i < logo->height; i++) {
+ for (j = 0; j < logo->width; src++) {
+ d = *src ^ xor;
+ for (k = 7; k >= 0; k--) {
+ *dst++ = ((d >> k) & 1) ? fg : 0;
+ j++;
+ }
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors),
+ * linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on
+ * the visual format and color depth of the framebuffer, the DAC, the
+ * pseudo_palette, and the logo data will be adjusted accordingly.
+ *
+ * Case 1 - linux_logo_clut224:
+ * Color exceeds the number of console colors (16), thus we set the hardware DAC
+ * using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set.
+ *
+ * For visuals that require color info from the pseudo_palette, we also construct
+ * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
+ * will be set.
+ *
+ * Case 2 - linux_logo_vga16:
+ * The number of colors just matches the console colors, thus there is no need
+ * to set the DAC or the pseudo_palette. However, the bitmap is packed, ie,
+ * each byte contains color information for two pixels (upper and lower nibble).
+ * To be consistent with fb_imageblit() usage, we therefore separate the two
+ * nibbles into separate bytes. The "depth" flag will be set to 4.
+ *
+ * Case 3 - linux_logo_mono:
+ * This is similar with Case 2. Each byte contains information for 8 pixels.
+ * We isolate each bit and expand each into a byte. The "depth" flag will
+ * be set to 1.
+ */
+static struct logo_data {
+ int depth;
+ int needs_directpalette;
+ int needs_truepalette;
+ int needs_cmapreset;
+ const struct linux_logo *logo;
+} fb_logo __read_mostly;
+
+static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height)
+{
+ u32 size = width * height, i;
+
+ out += size - 1;
+
+ for (i = size; i--; )
+ *out-- = *in++;
+}
+
+static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height)
+{
+ int i, j, h = height - 1;
+
+ for (i = 0; i < height; i++)
+ for (j = 0; j < width; j++)
+ out[height * j + h - i] = *in++;
+}
+
+static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height)
+{
+ int i, j, w = width - 1;
+
+ for (i = 0; i < height; i++)
+ for (j = 0; j < width; j++)
+ out[height * (w - j) + i] = *in++;
+}
+
+static void fb_rotate_logo(struct fb_info *info, u8 *dst,
+ struct fb_image *image, int rotate)
+{
+ u32 tmp;
+
+ if (rotate == FB_ROTATE_UD) {
+ fb_rotate_logo_ud(image->data, dst, image->width,
+ image->height);
+ image->dx = info->var.xres - image->width - image->dx;
+ image->dy = info->var.yres - image->height - image->dy;
+ } else if (rotate == FB_ROTATE_CW) {
+ fb_rotate_logo_cw(image->data, dst, image->width,
+ image->height);
+ tmp = image->width;
+ image->width = image->height;
+ image->height = tmp;
+ tmp = image->dy;
+ image->dy = image->dx;
+ image->dx = info->var.xres - image->width - tmp;
+ } else if (rotate == FB_ROTATE_CCW) {
+ fb_rotate_logo_ccw(image->data, dst, image->width,
+ image->height);
+ tmp = image->width;
+ image->width = image->height;
+ image->height = tmp;
+ tmp = image->dx;
+ image->dx = image->dy;
+ image->dy = info->var.yres - image->height - tmp;
+ }
+
+ image->data = dst;
+}
+
+static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
+ int rotate, unsigned int num)
+{
+ unsigned int x;
+
+ if (rotate == FB_ROTATE_UR) {
+ for (x = 0;
+ x < num && image->dx + image->width <= info->var.xres;
+ x++) {
+ info->fbops->fb_imageblit(info, image);
+ image->dx += image->width + 8;
+ }
+ } else if (rotate == FB_ROTATE_UD) {
+ for (x = 0; x < num && image->dx >= 0; x++) {
+ info->fbops->fb_imageblit(info, image);
+ image->dx -= image->width + 8;
+ }
+ } else if (rotate == FB_ROTATE_CW) {
+ for (x = 0;
+ x < num && image->dy + image->height <= info->var.yres;
+ x++) {
+ info->fbops->fb_imageblit(info, image);
+ image->dy += image->height + 8;
+ }
+ } else if (rotate == FB_ROTATE_CCW) {
+ for (x = 0; x < num && image->dy >= 0; x++) {
+ info->fbops->fb_imageblit(info, image);
+ image->dy -= image->height + 8;
+ }
+ }
+}
+
+static int fb_show_logo_line(struct fb_info *info, int rotate,
+ const struct linux_logo *logo, int y,
+ unsigned int n)
+{
+ u32 *palette = NULL, *saved_pseudo_palette = NULL;
+ unsigned char *logo_new = NULL, *logo_rotate = NULL;
+ struct fb_image image;
+
+ /* Return if the frame buffer is not mapped or suspended */
+ if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
+ info->flags & FBINFO_MODULE)
+ return 0;
+
+ image.depth = 8;
+ image.data = logo->data;
+
+ if (fb_logo.needs_cmapreset)
+ fb_set_logocmap(info, logo);
+
+ if (fb_logo.needs_truepalette ||
+ fb_logo.needs_directpalette) {
+ palette = kmalloc(256 * 4, GFP_KERNEL);
+ if (palette == NULL)
+ return 0;
+
+ if (fb_logo.needs_truepalette)
+ fb_set_logo_truepalette(info, logo, palette);
+ else
+ fb_set_logo_directpalette(info, logo, palette);
+
+ saved_pseudo_palette = info->pseudo_palette;
+ info->pseudo_palette = palette;
+ }
+
+ if (fb_logo.depth <= 4) {
+ logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL);
+ if (logo_new == NULL) {
+ kfree(palette);
+ if (saved_pseudo_palette)
+ info->pseudo_palette = saved_pseudo_palette;
+ return 0;
+ }
+ image.data = logo_new;
+ fb_set_logo(info, logo, logo_new, fb_logo.depth);
+ }
+
+ image.dx = 0;
+ image.dy = y;
+ image.width = logo->width;
+ image.height = logo->height;
+
+ if (rotate) {
+ logo_rotate = kmalloc(logo->width *
+ logo->height, GFP_KERNEL);
+ if (logo_rotate)
+ fb_rotate_logo(info, logo_rotate, &image, rotate);
+ }
+
+ fb_do_show_logo(info, &image, rotate, n);
+
+ kfree(palette);
+ if (saved_pseudo_palette != NULL)
+ info->pseudo_palette = saved_pseudo_palette;
+ kfree(logo_new);
+ kfree(logo_rotate);
+ return logo->height;
+}
+
+
+#ifdef CONFIG_FB_LOGO_EXTRA
+
+#define FB_LOGO_EX_NUM_MAX 10
+static struct logo_data_extra {
+ const struct linux_logo *logo;
+ unsigned int n;
+} fb_logo_ex[FB_LOGO_EX_NUM_MAX];
+static unsigned int fb_logo_ex_num;
+
+void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n)
+{
+ if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX)
+ return;
+
+ fb_logo_ex[fb_logo_ex_num].logo = logo;
+ fb_logo_ex[fb_logo_ex_num].n = n;
+ fb_logo_ex_num++;
+}
+
+static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height,
+ unsigned int yres)
+{
+ unsigned int i;
+
+ /* FIXME: logo_ex supports only truecolor fb. */
+ if (info->fix.visual != FB_VISUAL_TRUECOLOR)
+ fb_logo_ex_num = 0;
+
+ for (i = 0; i < fb_logo_ex_num; i++) {
+ if (fb_logo_ex[i].logo->type != fb_logo.logo->type) {
+ fb_logo_ex[i].logo = NULL;
+ continue;
+ }
+ height += fb_logo_ex[i].logo->height;
+ if (height > yres) {
+ height -= fb_logo_ex[i].logo->height;
+ fb_logo_ex_num = i;
+ break;
+ }
+ }
+ return height;
+}
+
+static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
+{
+ unsigned int i;
+
+ for (i = 0; i < fb_logo_ex_num; i++)
+ y += fb_show_logo_line(info, rotate,
+ fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
+
+ return y;
+}
+
+#else /* !CONFIG_FB_LOGO_EXTRA */
+
+static inline int fb_prepare_extra_logos(struct fb_info *info,
+ unsigned int height,
+ unsigned int yres)
+{
+ return height;
+}
+
+static inline int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
+{
+ return y;
+}
+
+#endif /* CONFIG_FB_LOGO_EXTRA */
+
+
+int fb_prepare_logo(struct fb_info *info, int rotate)
+{
+ int depth = fb_get_color_depth(&info->var, &info->fix);
+ unsigned int yres;
+
+ memset(&fb_logo, 0, sizeof(struct logo_data));
+
+ if (info->flags & FBINFO_MISC_TILEBLITTING ||
+ info->flags & FBINFO_MODULE)
+ return 0;
+
+ if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
+ depth = info->var.blue.length;
+ if (info->var.red.length < depth)
+ depth = info->var.red.length;
+ if (info->var.green.length < depth)
+ depth = info->var.green.length;
+ }
+
+ if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
+ /* assume console colormap */
+ depth = 4;
+ }
+
+ /* Return if no suitable logo was found */
+ fb_logo.logo = fb_find_logo(depth);
+
+ if (!fb_logo.logo) {
+ return 0;
+ }
+
+ if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
+ yres = info->var.yres;
+ else
+ yres = info->var.xres;
+
+ if (fb_logo.logo->height > yres) {
+ fb_logo.logo = NULL;
+ return 0;
+ }
+
+ /* What depth we asked for might be different from what we get */
+ if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
+ fb_logo.depth = 8;
+ else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
+ fb_logo.depth = 4;
+ else
+ fb_logo.depth = 1;
+
+
+ if (fb_logo.depth > 4 && depth > 4) {
+ switch (info->fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ fb_logo.needs_truepalette = 1;
+ break;
+ case FB_VISUAL_DIRECTCOLOR:
+ fb_logo.needs_directpalette = 1;
+ fb_logo.needs_cmapreset = 1;
+ break;
+ case FB_VISUAL_PSEUDOCOLOR:
+ fb_logo.needs_cmapreset = 1;
+ break;
+ }
+ }
+
+ return fb_prepare_extra_logos(info, fb_logo.logo->height, yres);
+}
+
+int fb_show_logo(struct fb_info *info, int rotate)
+{
+ int y;
+
+ y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,
+ num_online_cpus());
+ y = fb_show_extra_logos(info, y, rotate);
+
+ return y;
+}
+#else
+int fb_prepare_logo(struct fb_info *info, int rotate) { return 0; }
+int fb_show_logo(struct fb_info *info, int rotate) { return 0; }
+#endif /* CONFIG_LOGO */
+EXPORT_SYMBOL(fb_show_logo);
+
+static void *fb_seq_start(struct seq_file *m, loff_t *pos)
+{
+ mutex_lock(&registration_lock);
+ return (*pos < FB_MAX) ? pos : NULL;
+}
+
+static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ (*pos)++;
+ return (*pos < FB_MAX) ? pos : NULL;
+}
+
+static void fb_seq_stop(struct seq_file *m, void *v)
+{
+ mutex_unlock(&registration_lock);
+}
+
+static int fb_seq_show(struct seq_file *m, void *v)
+{
+ int i = *(loff_t *)v;
+ struct fb_info *fi = registered_fb[i];
+
+ if (fi)
+ seq_printf(m, "%d %s\n", fi->node, fi->fix.id);
+ return 0;
+}
+
+static const struct seq_operations proc_fb_seq_ops = {
+ .start = fb_seq_start,
+ .next = fb_seq_next,
+ .stop = fb_seq_stop,
+ .show = fb_seq_show,
+};
+
+static int proc_fb_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &proc_fb_seq_ops);
+}
+
+static const struct file_operations fb_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = proc_fb_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+/*
+ * We hold a reference to the fb_info in file->private_data,
+ * but if the current registered fb has changed, we don't
+ * actually want to use it.
+ *
+ * So look up the fb_info using the inode minor number,
+ * and just verify it against the reference we have.
+ */
+static struct fb_info *file_fb_info(struct file *file)
+{
+ struct inode *inode = file_inode(file);
+ int fbidx = iminor(inode);
+ struct fb_info *info = registered_fb[fbidx];
+
+ if (info != file->private_data)
+ info = NULL;
+ return info;
+}
+
+static ssize_t
+fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ struct fb_info *info = file_fb_info(file);
+ u8 *buffer, *dst;
+ u8 __iomem *src;
+ int c, cnt = 0, err = 0;
+ unsigned long total_size;
+
+ if (!info || ! info->screen_base)
+ return -ENODEV;
+
+ if (info->state != FBINFO_STATE_RUNNING)
+ return -EPERM;
+
+ if (info->fbops->fb_read)
+ return info->fbops->fb_read(info, buf, count, ppos);
+
+ total_size = info->screen_size;
+
+ if (total_size == 0)
+ total_size = info->fix.smem_len;
+
+ if (p >= total_size)
+ return 0;
+
+ if (count >= total_size)
+ count = total_size;
+
+ if (count + p > total_size)
+ count = total_size - p;
+
+ buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
+ GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ src = (u8 __iomem *) (info->screen_base + p);
+
+ if (info->fbops->fb_sync)
+ info->fbops->fb_sync(info);
+
+ while (count) {
+ c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
+ dst = buffer;
+ fb_memcpy_fromfb(dst, src, c);
+ dst += c;
+ src += c;
+
+ if (copy_to_user(buf, buffer, c)) {
+ err = -EFAULT;
+ break;
+ }
+ *ppos += c;
+ buf += c;
+ cnt += c;
+ count -= c;
+ }
+
+ kfree(buffer);
+
+ return (err) ? err : cnt;
+}
+
+static ssize_t
+fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ struct fb_info *info = file_fb_info(file);
+ u8 *buffer, *src;
+ u8 __iomem *dst;
+ int c, cnt = 0, err = 0;
+ unsigned long total_size;
+
+ if (!info || !info->screen_base)
+ return -ENODEV;
+
+ if (info->state != FBINFO_STATE_RUNNING)
+ return -EPERM;
+
+ if (info->fbops->fb_write)
+ return info->fbops->fb_write(info, buf, count, ppos);
+
+ total_size = info->screen_size;
+
+ if (total_size == 0)
+ total_size = info->fix.smem_len;
+
+ if (p > total_size)
+ return -EFBIG;
+
+ if (count > total_size) {
+ err = -EFBIG;
+ count = total_size;
+ }
+
+ if (count + p > total_size) {
+ if (!err)
+ err = -ENOSPC;
+
+ count = total_size - p;
+ }
+
+ buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
+ GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ dst = (u8 __iomem *) (info->screen_base + p);
+
+ if (info->fbops->fb_sync)
+ info->fbops->fb_sync(info);
+
+ while (count) {
+ c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
+ src = buffer;
+
+ if (copy_from_user(src, buf, c)) {
+ err = -EFAULT;
+ break;
+ }
+
+ fb_memcpy_tofb(dst, src, c);
+ dst += c;
+ src += c;
+ *ppos += c;
+ buf += c;
+ cnt += c;
+ count -= c;
+ }
+
+ kfree(buffer);
+
+ return (cnt) ? cnt : err;
+}
+
+int
+fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var)
+{
+ struct fb_fix_screeninfo *fix = &info->fix;
+ unsigned int yres = info->var.yres;
+ int err = 0;
+
+ if (var->yoffset > 0) {
+ if (var->vmode & FB_VMODE_YWRAP) {
+ if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep))
+ err = -EINVAL;
+ else
+ yres = 0;
+ } else if (!fix->ypanstep || (var->yoffset % fix->ypanstep))
+ err = -EINVAL;
+ }
+
+ if (var->xoffset > 0 && (!fix->xpanstep ||
+ (var->xoffset % fix->xpanstep)))
+ err = -EINVAL;
+
+ if (err || !info->fbops->fb_pan_display ||
+ var->yoffset > info->var.yres_virtual - yres ||
+ var->xoffset > info->var.xres_virtual - info->var.xres)
+ return -EINVAL;
+
+ if ((err = info->fbops->fb_pan_display(var, info)))
+ return err;
+ info->var.xoffset = var->xoffset;
+ info->var.yoffset = var->yoffset;
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+ return 0;
+}
+EXPORT_SYMBOL(fb_pan_display);
+
+static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var,
+ u32 activate)
+{
+ struct fb_event event;
+ struct fb_blit_caps caps, fbcaps;
+ int err = 0;
+
+ memset(&caps, 0, sizeof(caps));
+ memset(&fbcaps, 0, sizeof(fbcaps));
+ caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0;
+ event.info = info;
+ event.data = &caps;
+ fb_notifier_call_chain(FB_EVENT_GET_REQ, &event);
+ info->fbops->fb_get_caps(info, &fbcaps, var);
+
+ if (((fbcaps.x ^ caps.x) & caps.x) ||
+ ((fbcaps.y ^ caps.y) & caps.y) ||
+ (fbcaps.len < caps.len))
+ err = -EINVAL;
+
+ return err;
+}
+
+int
+fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
+{
+ int flags = info->flags;
+ int ret = 0;
+
+ if (var->activate & FB_ACTIVATE_INV_MODE) {
+ struct fb_videomode mode1, mode2;
+
+ fb_var_to_videomode(&mode1, var);
+ fb_var_to_videomode(&mode2, &info->var);
+ /* make sure we don't delete the videomode of current var */
+ ret = fb_mode_is_equal(&mode1, &mode2);
+
+ if (!ret) {
+ struct fb_event event;
+
+ event.info = info;
+ event.data = &mode1;
+ ret = fb_notifier_call_chain(FB_EVENT_MODE_DELETE, &event);
+ }
+
+ if (!ret)
+ fb_delete_videomode(&mode1, &info->modelist);
+
+
+ ret = (ret) ? -EINVAL : 0;
+ goto done;
+ }
+
+ if ((var->activate & FB_ACTIVATE_FORCE) ||
+ memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
+ u32 activate = var->activate;
+
+ /* When using FOURCC mode, make sure the red, green, blue and
+ * transp fields are set to 0.
+ */
+ if ((info->fix.capabilities & FB_CAP_FOURCC) &&
+ var->grayscale > 1) {
+ if (var->red.offset || var->green.offset ||
+ var->blue.offset || var->transp.offset ||
+ var->red.length || var->green.length ||
+ var->blue.length || var->transp.length ||
+ var->red.msb_right || var->green.msb_right ||
+ var->blue.msb_right || var->transp.msb_right)
+ return -EINVAL;
+ }
+
+ if (!info->fbops->fb_check_var) {
+ *var = info->var;
+ goto done;
+ }
+
+ ret = info->fbops->fb_check_var(var, info);
+
+ if (ret)
+ goto done;
+
+ if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
+ struct fb_var_screeninfo old_var;
+ struct fb_videomode mode;
+
+ if (info->fbops->fb_get_caps) {
+ ret = fb_check_caps(info, var, activate);
+
+ if (ret)
+ goto done;
+ }
+
+ old_var = info->var;
+ info->var = *var;
+
+ if (info->fbops->fb_set_par) {
+ ret = info->fbops->fb_set_par(info);
+
+ if (ret) {
+ info->var = old_var;
+ printk(KERN_WARNING "detected "
+ "fb_set_par error, "
+ "error code: %d\n", ret);
+ goto done;
+ }
+ }
+
+ fb_pan_display(info, &info->var);
+ fb_set_cmap(&info->cmap, info);
+ fb_var_to_videomode(&mode, &info->var);
+
+ if (info->modelist.prev && info->modelist.next &&
+ !list_empty(&info->modelist))
+ ret = fb_add_videomode(&mode, &info->modelist);
+
+ if (!ret && (flags & FBINFO_MISC_USEREVENT)) {
+ struct fb_event event;
+ int evnt = (activate & FB_ACTIVATE_ALL) ?
+ FB_EVENT_MODE_CHANGE_ALL :
+ FB_EVENT_MODE_CHANGE;
+
+ info->flags &= ~FBINFO_MISC_USEREVENT;
+ event.info = info;
+ event.data = &mode;
+ fb_notifier_call_chain(evnt, &event);
+ }
+ }
+ }
+
+ done:
+ return ret;
+}
+EXPORT_SYMBOL(fb_set_var);
+
+int
+fb_blank(struct fb_info *info, int blank)
+{
+ struct fb_event event;
+ int ret = -EINVAL, early_ret;
+
+ if (blank > FB_BLANK_POWERDOWN)
+ blank = FB_BLANK_POWERDOWN;
+
+ event.info = info;
+ event.data = &blank;
+
+ early_ret = fb_notifier_call_chain(FB_EARLY_EVENT_BLANK, &event);
+
+ if (info->fbops->fb_blank)
+ ret = info->fbops->fb_blank(blank, info);
+
+ if (!ret)
+ fb_notifier_call_chain(FB_EVENT_BLANK, &event);
+ else {
+ /*
+ * if fb_blank is failed then revert effects of
+ * the early blank event.
+ */
+ if (!early_ret)
+ fb_notifier_call_chain(FB_R_EARLY_EVENT_BLANK, &event);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(fb_blank);
+
+static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fb_ops *fb;
+ struct fb_var_screeninfo var;
+ struct fb_fix_screeninfo fix;
+ struct fb_con2fbmap con2fb;
+ struct fb_cmap cmap_from;
+ struct fb_cmap_user cmap;
+ struct fb_event event;
+ void __user *argp = (void __user *)arg;
+ long ret = 0;
+
+ switch (cmd) {
+ case FBIOGET_VSCREENINFO:
+ if (!lock_fb_info(info))
+ return -ENODEV;
+ var = info->var;
+ unlock_fb_info(info);
+
+ ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
+ break;
+ case FBIOPUT_VSCREENINFO:
+ if (copy_from_user(&var, argp, sizeof(var)))
+ return -EFAULT;
+ console_lock();
+ if (!lock_fb_info(info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+ info->flags |= FBINFO_MISC_USEREVENT;
+ ret = fb_set_var(info, &var);
+ info->flags &= ~FBINFO_MISC_USEREVENT;
+ unlock_fb_info(info);
+ console_unlock();
+ if (!ret && copy_to_user(argp, &var, sizeof(var)))
+ ret = -EFAULT;
+ break;
+ case FBIOGET_FSCREENINFO:
+ if (!lock_fb_info(info))
+ return -ENODEV;
+ fix = info->fix;
+ unlock_fb_info(info);
+
+ ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
+ break;
+ case FBIOPUTCMAP:
+ if (copy_from_user(&cmap, argp, sizeof(cmap)))
+ return -EFAULT;
+ ret = fb_set_user_cmap(&cmap, info);
+ break;
+ case FBIOGETCMAP:
+ if (copy_from_user(&cmap, argp, sizeof(cmap)))
+ return -EFAULT;
+ if (!lock_fb_info(info))
+ return -ENODEV;
+ cmap_from = info->cmap;
+ unlock_fb_info(info);
+ ret = fb_cmap_to_user(&cmap_from, &cmap);
+ break;
+ case FBIOPAN_DISPLAY:
+ if (copy_from_user(&var, argp, sizeof(var)))
+ return -EFAULT;
+ console_lock();
+ if (!lock_fb_info(info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+ ret = fb_pan_display(info, &var);
+ unlock_fb_info(info);
+ console_unlock();
+ if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
+ return -EFAULT;
+ break;
+ case FBIO_CURSOR:
+ ret = -EINVAL;
+ break;
+ case FBIOGET_CON2FBMAP:
+ if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
+ return -EFAULT;
+ if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
+ return -EINVAL;
+ con2fb.framebuffer = -1;
+ event.data = &con2fb;
+ if (!lock_fb_info(info))
+ return -ENODEV;
+ event.info = info;
+ fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
+ unlock_fb_info(info);
+ ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
+ break;
+ case FBIOPUT_CON2FBMAP:
+ if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
+ return -EFAULT;
+ if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
+ return -EINVAL;
+ if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
+ return -EINVAL;
+ if (!registered_fb[con2fb.framebuffer])
+ request_module("fb%d", con2fb.framebuffer);
+ if (!registered_fb[con2fb.framebuffer]) {
+ ret = -EINVAL;
+ break;
+ }
+ event.data = &con2fb;
+ console_lock();
+ if (!lock_fb_info(info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+ event.info = info;
+ ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
+ unlock_fb_info(info);
+ console_unlock();
+ break;
+ case FBIOBLANK:
+ console_lock();
+ if (!lock_fb_info(info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+ info->flags |= FBINFO_MISC_USEREVENT;
+ ret = fb_blank(info, arg);
+ info->flags &= ~FBINFO_MISC_USEREVENT;
+ unlock_fb_info(info);
+ console_unlock();
+ break;
+ default:
+ if (!lock_fb_info(info))
+ return -ENODEV;
+ fb = info->fbops;
+ if (fb->fb_ioctl)
+ ret = fb->fb_ioctl(info, cmd, arg);
+ else
+ ret = -ENOTTY;
+ unlock_fb_info(info);
+ }
+ return ret;
+}
+
+static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct fb_info *info = file_fb_info(file);
+
+ if (!info)
+ return -ENODEV;
+ return do_fb_ioctl(info, cmd, arg);
+}
+
+#ifdef CONFIG_COMPAT
+struct fb_fix_screeninfo32 {
+ char id[16];
+ compat_caddr_t smem_start;
+ u32 smem_len;
+ u32 type;
+ u32 type_aux;
+ u32 visual;
+ u16 xpanstep;
+ u16 ypanstep;
+ u16 ywrapstep;
+ u32 line_length;
+ compat_caddr_t mmio_start;
+ u32 mmio_len;
+ u32 accel;
+ u16 reserved[3];
+};
+
+struct fb_cmap32 {
+ u32 start;
+ u32 len;
+ compat_caddr_t red;
+ compat_caddr_t green;
+ compat_caddr_t blue;
+ compat_caddr_t transp;
+};
+
+static int fb_getput_cmap(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fb_cmap_user __user *cmap;
+ struct fb_cmap32 __user *cmap32;
+ __u32 data;
+ int err;
+
+ cmap = compat_alloc_user_space(sizeof(*cmap));
+ cmap32 = compat_ptr(arg);
+
+ if (copy_in_user(&cmap->start, &cmap32->start, 2 * sizeof(__u32)))
+ return -EFAULT;
+
+ if (get_user(data, &cmap32->red) ||
+ put_user(compat_ptr(data), &cmap->red) ||
+ get_user(data, &cmap32->green) ||
+ put_user(compat_ptr(data), &cmap->green) ||
+ get_user(data, &cmap32->blue) ||
+ put_user(compat_ptr(data), &cmap->blue) ||
+ get_user(data, &cmap32->transp) ||
+ put_user(compat_ptr(data), &cmap->transp))
+ return -EFAULT;
+
+ err = do_fb_ioctl(info, cmd, (unsigned long) cmap);
+
+ if (!err) {
+ if (copy_in_user(&cmap32->start,
+ &cmap->start,
+ 2 * sizeof(__u32)))
+ err = -EFAULT;
+ }
+ return err;
+}
+
+static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix,
+ struct fb_fix_screeninfo32 __user *fix32)
+{
+ __u32 data;
+ int err;
+
+ err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id));
+
+ data = (__u32) (unsigned long) fix->smem_start;
+ err |= put_user(data, &fix32->smem_start);
+
+ err |= put_user(fix->smem_len, &fix32->smem_len);
+ err |= put_user(fix->type, &fix32->type);
+ err |= put_user(fix->type_aux, &fix32->type_aux);
+ err |= put_user(fix->visual, &fix32->visual);
+ err |= put_user(fix->xpanstep, &fix32->xpanstep);
+ err |= put_user(fix->ypanstep, &fix32->ypanstep);
+ err |= put_user(fix->ywrapstep, &fix32->ywrapstep);
+ err |= put_user(fix->line_length, &fix32->line_length);
+
+ data = (__u32) (unsigned long) fix->mmio_start;
+ err |= put_user(data, &fix32->mmio_start);
+
+ err |= put_user(fix->mmio_len, &fix32->mmio_len);
+ err |= put_user(fix->accel, &fix32->accel);
+ err |= copy_to_user(fix32->reserved, fix->reserved,
+ sizeof(fix->reserved));
+
+ if (err)
+ return -EFAULT;
+ return 0;
+}
+
+static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ mm_segment_t old_fs;
+ struct fb_fix_screeninfo fix;
+ struct fb_fix_screeninfo32 __user *fix32;
+ int err;
+
+ fix32 = compat_ptr(arg);
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ err = do_fb_ioctl(info, cmd, (unsigned long) &fix);
+ set_fs(old_fs);
+
+ if (!err)
+ err = do_fscreeninfo_to_user(&fix, fix32);
+
+ return err;
+}
+
+static long fb_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fb_info *info = file_fb_info(file);
+ struct fb_ops *fb;
+ long ret = -ENOIOCTLCMD;
+
+ if (!info)
+ return -ENODEV;
+ fb = info->fbops;
+ switch(cmd) {
+ case FBIOGET_VSCREENINFO:
+ case FBIOPUT_VSCREENINFO:
+ case FBIOPAN_DISPLAY:
+ case FBIOGET_CON2FBMAP:
+ case FBIOPUT_CON2FBMAP:
+ arg = (unsigned long) compat_ptr(arg);
+ case FBIOBLANK:
+ ret = do_fb_ioctl(info, cmd, arg);
+ break;
+
+ case FBIOGET_FSCREENINFO:
+ ret = fb_get_fscreeninfo(info, cmd, arg);
+ break;
+
+ case FBIOGETCMAP:
+ case FBIOPUTCMAP:
+ ret = fb_getput_cmap(info, cmd, arg);
+ break;
+
+ default:
+ if (fb->fb_compat_ioctl)
+ ret = fb->fb_compat_ioctl(info, cmd, arg);
+ break;
+ }
+ return ret;
+}
+#endif
+
+static int
+fb_mmap(struct file *file, struct vm_area_struct * vma)
+{
+ struct fb_info *info = file_fb_info(file);
+ struct fb_ops *fb;
+ unsigned long mmio_pgoff;
+ unsigned long start;
+ u32 len;
+
+ if (!info)
+ return -ENODEV;
+ fb = info->fbops;
+ if (!fb)
+ return -ENODEV;
+ mutex_lock(&info->mm_lock);
+ if (fb->fb_mmap) {
+ int res;
+ res = fb->fb_mmap(info, vma);
+ mutex_unlock(&info->mm_lock);
+ return res;
+ }
+
+ /*
+ * Ugh. This can be either the frame buffer mapping, or
+ * if pgoff points past it, the mmio mapping.
+ */
+ start = info->fix.smem_start;
+ len = info->fix.smem_len;
+ mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
+ if (vma->vm_pgoff >= mmio_pgoff) {
+ if (info->var.accel_flags) {
+ mutex_unlock(&info->mm_lock);
+ return -EINVAL;
+ }
+
+ vma->vm_pgoff -= mmio_pgoff;
+ start = info->fix.mmio_start;
+ len = info->fix.mmio_len;
+ }
+ mutex_unlock(&info->mm_lock);
+
+ vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+ fb_pgprotect(file, vma, start);
+
+ return vm_iomap_memory(vma, start, len);
+}
+
+static int
+fb_open(struct inode *inode, struct file *file)
+__acquires(&info->lock)
+__releases(&info->lock)
+{
+ int fbidx = iminor(inode);
+ struct fb_info *info;
+ int res = 0;
+
+ info = get_fb_info(fbidx);
+ if (!info) {
+ request_module("fb%d", fbidx);
+ info = get_fb_info(fbidx);
+ if (!info)
+ return -ENODEV;
+ }
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
+ mutex_lock(&info->lock);
+ if (!try_module_get(info->fbops->owner)) {
+ res = -ENODEV;
+ goto out;
+ }
+ file->private_data = info;
+ if (info->fbops->fb_open) {
+ res = info->fbops->fb_open(info,1);
+ if (res)
+ module_put(info->fbops->owner);
+ }
+#ifdef CONFIG_FB_DEFERRED_IO
+ if (info->fbdefio)
+ fb_deferred_io_open(info, inode, file);
+#endif
+out:
+ mutex_unlock(&info->lock);
+ if (res)
+ put_fb_info(info);
+ return res;
+}
+
+static int
+fb_release(struct inode *inode, struct file *file)
+__acquires(&info->lock)
+__releases(&info->lock)
+{
+ struct fb_info * const info = file->private_data;
+
+ mutex_lock(&info->lock);
+ if (info->fbops->fb_release)
+ info->fbops->fb_release(info,1);
+ module_put(info->fbops->owner);
+ mutex_unlock(&info->lock);
+ put_fb_info(info);
+ return 0;
+}
+
+static const struct file_operations fb_fops = {
+ .owner = THIS_MODULE,
+ .read = fb_read,
+ .write = fb_write,
+ .unlocked_ioctl = fb_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = fb_compat_ioctl,
+#endif
+ .mmap = fb_mmap,
+ .open = fb_open,
+ .release = fb_release,
+#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
+ .get_unmapped_area = get_fb_unmapped_area,
+#endif
+#ifdef CONFIG_FB_DEFERRED_IO
+ .fsync = fb_deferred_io_fsync,
+#endif
+ .llseek = default_llseek,
+};
+
+struct class *fb_class;
+EXPORT_SYMBOL(fb_class);
+
+static int fb_check_foreignness(struct fb_info *fi)
+{
+ const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN;
+
+ fi->flags &= ~FBINFO_FOREIGN_ENDIAN;
+
+#ifdef __BIG_ENDIAN
+ fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;
+#else
+ fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;
+#endif /* __BIG_ENDIAN */
+
+ if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) {
+ pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to "
+ "support this framebuffer\n", fi->fix.id);
+ return -ENOSYS;
+ } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) {
+ pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "
+ "support this framebuffer\n", fi->fix.id);
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static bool apertures_overlap(struct aperture *gen, struct aperture *hw)
+{
+ /* is the generic aperture base the same as the HW one */
+ if (gen->base == hw->base)
+ return true;
+ /* is the generic aperture base inside the hw base->hw base+size */
+ if (gen->base > hw->base && gen->base < hw->base + hw->size)
+ return true;
+ return false;
+}
+
+static bool fb_do_apertures_overlap(struct apertures_struct *gena,
+ struct apertures_struct *hwa)
+{
+ int i, j;
+ if (!hwa || !gena)
+ return false;
+
+ for (i = 0; i < hwa->count; ++i) {
+ struct aperture *h = &hwa->ranges[i];
+ for (j = 0; j < gena->count; ++j) {
+ struct aperture *g = &gena->ranges[j];
+ printk(KERN_DEBUG "checking generic (%llx %llx) vs hw (%llx %llx)\n",
+ (unsigned long long)g->base,
+ (unsigned long long)g->size,
+ (unsigned long long)h->base,
+ (unsigned long long)h->size);
+ if (apertures_overlap(g, h))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int do_unregister_framebuffer(struct fb_info *fb_info);
+
+#define VGA_FB_PHYS 0xA0000
+static int do_remove_conflicting_framebuffers(struct apertures_struct *a,
+ const char *name, bool primary)
+{
+ int i, ret;
+
+ /* check all firmware fbs and kick off if the base addr overlaps */
+ for (i = 0 ; i < FB_MAX; i++) {
+ struct apertures_struct *gen_aper;
+ if (!registered_fb[i])
+ continue;
+
+ if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE))
+ continue;
+
+ gen_aper = registered_fb[i]->apertures;
+ if (fb_do_apertures_overlap(gen_aper, a) ||
+ (primary && gen_aper && gen_aper->count &&
+ gen_aper->ranges[0].base == VGA_FB_PHYS)) {
+
+ printk(KERN_INFO "fb: switching to %s from %s\n",
+ name, registered_fb[i]->fix.id);
+ ret = do_unregister_framebuffer(registered_fb[i]);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int do_register_framebuffer(struct fb_info *fb_info)
+{
+ int i, ret;
+ struct fb_event event;
+ struct fb_videomode mode;
+
+ if (fb_check_foreignness(fb_info))
+ return -ENOSYS;
+
+ ret = do_remove_conflicting_framebuffers(fb_info->apertures,
+ fb_info->fix.id,
+ fb_is_primary_device(fb_info));
+ if (ret)
+ return ret;
+
+ if (num_registered_fb == FB_MAX)
+ return -ENXIO;
+
+ num_registered_fb++;
+ for (i = 0 ; i < FB_MAX; i++)
+ if (!registered_fb[i])
+ break;
+ fb_info->node = i;
+ atomic_set(&fb_info->count, 1);
+ mutex_init(&fb_info->lock);
+ mutex_init(&fb_info->mm_lock);
+
+ fb_info->dev = device_create(fb_class, fb_info->device,
+ MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
+ if (IS_ERR(fb_info->dev)) {
+ /* Not fatal */
+ printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
+ fb_info->dev = NULL;
+ } else
+ fb_init_device(fb_info);
+
+ if (fb_info->pixmap.addr == NULL) {
+ fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
+ if (fb_info->pixmap.addr) {
+ fb_info->pixmap.size = FBPIXMAPSIZE;
+ fb_info->pixmap.buf_align = 1;
+ fb_info->pixmap.scan_align = 1;
+ fb_info->pixmap.access_align = 32;
+ fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
+ }
+ }
+ fb_info->pixmap.offset = 0;
+
+ if (!fb_info->pixmap.blit_x)
+ fb_info->pixmap.blit_x = ~(u32)0;
+
+ if (!fb_info->pixmap.blit_y)
+ fb_info->pixmap.blit_y = ~(u32)0;
+
+ if (!fb_info->modelist.prev || !fb_info->modelist.next)
+ INIT_LIST_HEAD(&fb_info->modelist);
+
+ if (fb_info->skip_vt_switch)
+ pm_vt_switch_required(fb_info->dev, false);
+ else
+ pm_vt_switch_required(fb_info->dev, true);
+
+ fb_var_to_videomode(&mode, &fb_info->var);
+ fb_add_videomode(&mode, &fb_info->modelist);
+ registered_fb[i] = fb_info;
+
+ event.info = fb_info;
+ console_lock();
+ if (!lock_fb_info(fb_info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+
+ fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
+ unlock_fb_info(fb_info);
+ console_unlock();
+ return 0;
+}
+
+static int do_unregister_framebuffer(struct fb_info *fb_info)
+{
+ struct fb_event event;
+ int i, ret = 0;
+
+ i = fb_info->node;
+ if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)
+ return -EINVAL;
+
+ console_lock();
+ if (!lock_fb_info(fb_info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+
+ event.info = fb_info;
+ ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event);
+ unlock_fb_info(fb_info);
+ console_unlock();
+
+ if (ret)
+ return -EINVAL;
+
+ pm_vt_switch_unregister(fb_info->dev);
+
+ unlink_framebuffer(fb_info);
+ if (fb_info->pixmap.addr &&
+ (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))
+ kfree(fb_info->pixmap.addr);
+ fb_destroy_modelist(&fb_info->modelist);
+ registered_fb[i] = NULL;
+ num_registered_fb--;
+ fb_cleanup_device(fb_info);
+ event.info = fb_info;
+ console_lock();
+ fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
+ console_unlock();
+
+ /* this may free fb info */
+ put_fb_info(fb_info);
+ return 0;
+}
+
+int unlink_framebuffer(struct fb_info *fb_info)
+{
+ int i;
+
+ i = fb_info->node;
+ if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)
+ return -EINVAL;
+
+ if (fb_info->dev) {
+ device_destroy(fb_class, MKDEV(FB_MAJOR, i));
+ fb_info->dev = NULL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(unlink_framebuffer);
+
+int remove_conflicting_framebuffers(struct apertures_struct *a,
+ const char *name, bool primary)
+{
+ int ret;
+
+ mutex_lock(&registration_lock);
+ ret = do_remove_conflicting_framebuffers(a, name, primary);
+ mutex_unlock(&registration_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(remove_conflicting_framebuffers);
+
+/**
+ * register_framebuffer - registers a frame buffer device
+ * @fb_info: frame buffer info structure
+ *
+ * Registers a frame buffer device @fb_info.
+ *
+ * Returns negative errno on error, or zero for success.
+ *
+ */
+int
+register_framebuffer(struct fb_info *fb_info)
+{
+ int ret;
+
+ mutex_lock(&registration_lock);
+ ret = do_register_framebuffer(fb_info);
+ mutex_unlock(&registration_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(register_framebuffer);
+
+/**
+ * unregister_framebuffer - releases a frame buffer device
+ * @fb_info: frame buffer info structure
+ *
+ * Unregisters a frame buffer device @fb_info.
+ *
+ * Returns negative errno on error, or zero for success.
+ *
+ * This function will also notify the framebuffer console
+ * to release the driver.
+ *
+ * This is meant to be called within a driver's module_exit()
+ * function. If this is called outside module_exit(), ensure
+ * that the driver implements fb_open() and fb_release() to
+ * check that no processes are using the device.
+ */
+int
+unregister_framebuffer(struct fb_info *fb_info)
+{
+ int ret;
+
+ mutex_lock(&registration_lock);
+ ret = do_unregister_framebuffer(fb_info);
+ mutex_unlock(&registration_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(unregister_framebuffer);
+
+/**
+ * fb_set_suspend - low level driver signals suspend
+ * @info: framebuffer affected
+ * @state: 0 = resuming, !=0 = suspending
+ *
+ * This is meant to be used by low level drivers to
+ * signal suspend/resume to the core & clients.
+ * It must be called with the console semaphore held
+ */
+void fb_set_suspend(struct fb_info *info, int state)
+{
+ struct fb_event event;
+
+ event.info = info;
+ if (state) {
+ fb_notifier_call_chain(FB_EVENT_SUSPEND, &event);
+ info->state = FBINFO_STATE_SUSPENDED;
+ } else {
+ info->state = FBINFO_STATE_RUNNING;
+ fb_notifier_call_chain(FB_EVENT_RESUME, &event);
+ }
+}
+EXPORT_SYMBOL(fb_set_suspend);
+
+/**
+ * fbmem_init - init frame buffer subsystem
+ *
+ * Initialize the frame buffer subsystem.
+ *
+ * NOTE: This function is _only_ to be called by drivers/char/mem.c.
+ *
+ */
+
+static int __init
+fbmem_init(void)
+{
+ proc_create("fb", 0, NULL, &fb_proc_fops);
+
+ if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
+ printk("unable to get major %d for fb devs\n", FB_MAJOR);
+
+ fb_class = class_create(THIS_MODULE, "graphics");
+ if (IS_ERR(fb_class)) {
+ printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
+ fb_class = NULL;
+ }
+ return 0;
+}
+
+#ifdef MODULE
+module_init(fbmem_init);
+static void __exit
+fbmem_exit(void)
+{
+ remove_proc_entry("fb", NULL);
+ class_destroy(fb_class);
+ unregister_chrdev(FB_MAJOR, "fb");
+}
+
+module_exit(fbmem_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Framebuffer base");
+#else
+subsys_initcall(fbmem_init);
+#endif
+
+int fb_new_modelist(struct fb_info *info)
+{
+ struct fb_event event;
+ struct fb_var_screeninfo var = info->var;
+ struct list_head *pos, *n;
+ struct fb_modelist *modelist;
+ struct fb_videomode *m, mode;
+ int err = 1;
+
+ list_for_each_safe(pos, n, &info->modelist) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ m = &modelist->mode;
+ fb_videomode_to_var(&var, m);
+ var.activate = FB_ACTIVATE_TEST;
+ err = fb_set_var(info, &var);
+ fb_var_to_videomode(&mode, &var);
+ if (err || !fb_mode_is_equal(m, &mode)) {
+ list_del(pos);
+ kfree(pos);
+ }
+ }
+
+ err = 1;
+
+ if (!list_empty(&info->modelist)) {
+ event.info = info;
+ err = fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event);
+ }
+
+ return err;
+}
+
+static char *video_options[FB_MAX] __read_mostly;
+static int ofonly __read_mostly;
+
+/**
+ * fb_get_options - get kernel boot parameters
+ * @name: framebuffer name as it would appear in
+ * the boot parameter line
+ * (video=<name>:<options>)
+ * @option: the option will be stored here
+ *
+ * NOTE: Needed to maintain backwards compatibility
+ */
+int fb_get_options(const char *name, char **option)
+{
+ char *opt, *options = NULL;
+ int retval = 0;
+ int name_len = strlen(name), i;
+
+ if (name_len && ofonly && strncmp(name, "offb", 4))
+ retval = 1;
+
+ if (name_len && !retval) {
+ for (i = 0; i < FB_MAX; i++) {
+ if (video_options[i] == NULL)
+ continue;
+ if (!video_options[i][0])
+ continue;
+ opt = video_options[i];
+ if (!strncmp(name, opt, name_len) &&
+ opt[name_len] == ':')
+ options = opt + name_len + 1;
+ }
+ }
+ /* No match, pass global option */
+ if (!options && option && fb_mode_option)
+ options = kstrdup(fb_mode_option, GFP_KERNEL);
+ if (options && !strncmp(options, "off", 3))
+ retval = 1;
+
+ if (option)
+ *option = options;
+
+ return retval;
+}
+EXPORT_SYMBOL(fb_get_options);
+
+#ifndef MODULE
+/**
+ * video_setup - process command line options
+ * @options: string of options
+ *
+ * Process command line options for frame buffer subsystem.
+ *
+ * NOTE: This function is a __setup and __init function.
+ * It only stores the options. Drivers have to call
+ * fb_get_options() as necessary.
+ *
+ * Returns zero.
+ *
+ */
+static int __init video_setup(char *options)
+{
+ int i, global = 0;
+
+ if (!options || !*options)
+ global = 1;
+
+ if (!global && !strncmp(options, "ofonly", 6)) {
+ ofonly = 1;
+ global = 1;
+ }
+
+ if (!global && !strchr(options, ':')) {
+ fb_mode_option = options;
+ global = 1;
+ }
+
+ if (!global) {
+ for (i = 0; i < FB_MAX; i++) {
+ if (video_options[i] == NULL) {
+ video_options[i] = options;
+ break;
+ }
+
+ }
+ }
+
+ return 1;
+}
+__setup("video=", video_setup);
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/fbmon.c b/drivers/video/fbdev/core/fbmon.c
new file mode 100644
index 000000000000..c204ebe6187e
--- /dev/null
+++ b/drivers/video/fbdev/core/fbmon.c
@@ -0,0 +1,1592 @@
+/*
+ * linux/drivers/video/fbmon.c
+ *
+ * Copyright (C) 2002 James Simmons <jsimmons@users.sf.net>
+ *
+ * Credits:
+ *
+ * The EDID Parser is a conglomeration from the following sources:
+ *
+ * 1. SciTech SNAP Graphics Architecture
+ * Copyright (C) 1991-2002 SciTech Software, Inc. All rights reserved.
+ *
+ * 2. XFree86 4.3.0, interpret_edid.c
+ * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE>
+ *
+ * 3. John Fremlin <vii@users.sourceforge.net> and
+ * Ani Joshi <ajoshi@unixbox.com>
+ *
+ * Generalized Timing Formula is derived from:
+ *
+ * GTF Spreadsheet by Andy Morrish (1/5/97)
+ * available at http://www.vesa.org
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+#include <linux/fb.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <video/edid.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+#ifdef CONFIG_PPC_OF
+#include <asm/prom.h>
+#include <asm/pci-bridge.h>
+#endif
+#include "../edid.h"
+
+/*
+ * EDID parser
+ */
+
+#undef DEBUG /* define this for verbose EDID parsing output */
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(fmt,## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#define FBMON_FIX_HEADER 1
+#define FBMON_FIX_INPUT 2
+#define FBMON_FIX_TIMINGS 3
+
+#ifdef CONFIG_FB_MODE_HELPERS
+struct broken_edid {
+ u8 manufacturer[4];
+ u32 model;
+ u32 fix;
+};
+
+static const struct broken_edid brokendb[] = {
+ /* DEC FR-PCXAV-YZ */
+ {
+ .manufacturer = "DEC",
+ .model = 0x073a,
+ .fix = FBMON_FIX_HEADER,
+ },
+ /* ViewSonic PF775a */
+ {
+ .manufacturer = "VSC",
+ .model = 0x5a44,
+ .fix = FBMON_FIX_INPUT,
+ },
+ /* Sharp UXGA? */
+ {
+ .manufacturer = "SHP",
+ .model = 0x138e,
+ .fix = FBMON_FIX_TIMINGS,
+ },
+};
+
+static const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x00
+};
+
+static void copy_string(unsigned char *c, unsigned char *s)
+{
+ int i;
+ c = c + 5;
+ for (i = 0; (i < 13 && *c != 0x0A); i++)
+ *(s++) = *(c++);
+ *s = 0;
+ while (i-- && (*--s == 0x20)) *s = 0;
+}
+
+static int edid_is_serial_block(unsigned char *block)
+{
+ if ((block[0] == 0x00) && (block[1] == 0x00) &&
+ (block[2] == 0x00) && (block[3] == 0xff) &&
+ (block[4] == 0x00))
+ return 1;
+ else
+ return 0;
+}
+
+static int edid_is_ascii_block(unsigned char *block)
+{
+ if ((block[0] == 0x00) && (block[1] == 0x00) &&
+ (block[2] == 0x00) && (block[3] == 0xfe) &&
+ (block[4] == 0x00))
+ return 1;
+ else
+ return 0;
+}
+
+static int edid_is_limits_block(unsigned char *block)
+{
+ if ((block[0] == 0x00) && (block[1] == 0x00) &&
+ (block[2] == 0x00) && (block[3] == 0xfd) &&
+ (block[4] == 0x00))
+ return 1;
+ else
+ return 0;
+}
+
+static int edid_is_monitor_block(unsigned char *block)
+{
+ if ((block[0] == 0x00) && (block[1] == 0x00) &&
+ (block[2] == 0x00) && (block[3] == 0xfc) &&
+ (block[4] == 0x00))
+ return 1;
+ else
+ return 0;
+}
+
+static int edid_is_timing_block(unsigned char *block)
+{
+ if ((block[0] != 0x00) || (block[1] != 0x00) ||
+ (block[2] != 0x00) || (block[4] != 0x00))
+ return 1;
+ else
+ return 0;
+}
+
+static int check_edid(unsigned char *edid)
+{
+ unsigned char *block = edid + ID_MANUFACTURER_NAME, manufacturer[4];
+ unsigned char *b;
+ u32 model;
+ int i, fix = 0, ret = 0;
+
+ manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@';
+ manufacturer[1] = ((block[0] & 0x03) << 3) +
+ ((block[1] & 0xe0) >> 5) + '@';
+ manufacturer[2] = (block[1] & 0x1f) + '@';
+ manufacturer[3] = 0;
+ model = block[2] + (block[3] << 8);
+
+ for (i = 0; i < ARRAY_SIZE(brokendb); i++) {
+ if (!strncmp(manufacturer, brokendb[i].manufacturer, 4) &&
+ brokendb[i].model == model) {
+ fix = brokendb[i].fix;
+ break;
+ }
+ }
+
+ switch (fix) {
+ case FBMON_FIX_HEADER:
+ for (i = 0; i < 8; i++) {
+ if (edid[i] != edid_v1_header[i]) {
+ ret = fix;
+ break;
+ }
+ }
+ break;
+ case FBMON_FIX_INPUT:
+ b = edid + EDID_STRUCT_DISPLAY;
+ /* Only if display is GTF capable will
+ the input type be reset to analog */
+ if (b[4] & 0x01 && b[0] & 0x80)
+ ret = fix;
+ break;
+ case FBMON_FIX_TIMINGS:
+ b = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+ ret = fix;
+
+ for (i = 0; i < 4; i++) {
+ if (edid_is_limits_block(b)) {
+ ret = 0;
+ break;
+ }
+
+ b += DETAILED_TIMING_DESCRIPTION_SIZE;
+ }
+
+ break;
+ }
+
+ if (ret)
+ printk("fbmon: The EDID Block of "
+ "Manufacturer: %s Model: 0x%x is known to "
+ "be broken,\n", manufacturer, model);
+
+ return ret;
+}
+
+static void fix_edid(unsigned char *edid, int fix)
+{
+ int i;
+ unsigned char *b, csum = 0;
+
+ switch (fix) {
+ case FBMON_FIX_HEADER:
+ printk("fbmon: trying a header reconstruct\n");
+ memcpy(edid, edid_v1_header, 8);
+ break;
+ case FBMON_FIX_INPUT:
+ printk("fbmon: trying to fix input type\n");
+ b = edid + EDID_STRUCT_DISPLAY;
+ b[0] &= ~0x80;
+ edid[127] += 0x80;
+ break;
+ case FBMON_FIX_TIMINGS:
+ printk("fbmon: trying to fix monitor timings\n");
+ b = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+ for (i = 0; i < 4; i++) {
+ if (!(edid_is_serial_block(b) ||
+ edid_is_ascii_block(b) ||
+ edid_is_monitor_block(b) ||
+ edid_is_timing_block(b))) {
+ b[0] = 0x00;
+ b[1] = 0x00;
+ b[2] = 0x00;
+ b[3] = 0xfd;
+ b[4] = 0x00;
+ b[5] = 60; /* vfmin */
+ b[6] = 60; /* vfmax */
+ b[7] = 30; /* hfmin */
+ b[8] = 75; /* hfmax */
+ b[9] = 17; /* pixclock - 170 MHz*/
+ b[10] = 0; /* GTF */
+ break;
+ }
+
+ b += DETAILED_TIMING_DESCRIPTION_SIZE;
+ }
+
+ for (i = 0; i < EDID_LENGTH - 1; i++)
+ csum += edid[i];
+
+ edid[127] = 256 - csum;
+ break;
+ }
+}
+
+static int edid_checksum(unsigned char *edid)
+{
+ unsigned char csum = 0, all_null = 0;
+ int i, err = 0, fix = check_edid(edid);
+
+ if (fix)
+ fix_edid(edid, fix);
+
+ for (i = 0; i < EDID_LENGTH; i++) {
+ csum += edid[i];
+ all_null |= edid[i];
+ }
+
+ if (csum == 0x00 && all_null) {
+ /* checksum passed, everything's good */
+ err = 1;
+ }
+
+ return err;
+}
+
+static int edid_check_header(unsigned char *edid)
+{
+ int i, err = 1, fix = check_edid(edid);
+
+ if (fix)
+ fix_edid(edid, fix);
+
+ for (i = 0; i < 8; i++) {
+ if (edid[i] != edid_v1_header[i])
+ err = 0;
+ }
+
+ return err;
+}
+
+static void parse_vendor_block(unsigned char *block, struct fb_monspecs *specs)
+{
+ specs->manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@';
+ specs->manufacturer[1] = ((block[0] & 0x03) << 3) +
+ ((block[1] & 0xe0) >> 5) + '@';
+ specs->manufacturer[2] = (block[1] & 0x1f) + '@';
+ specs->manufacturer[3] = 0;
+ specs->model = block[2] + (block[3] << 8);
+ specs->serial = block[4] + (block[5] << 8) +
+ (block[6] << 16) + (block[7] << 24);
+ specs->year = block[9] + 1990;
+ specs->week = block[8];
+ DPRINTK(" Manufacturer: %s\n", specs->manufacturer);
+ DPRINTK(" Model: %x\n", specs->model);
+ DPRINTK(" Serial#: %u\n", specs->serial);
+ DPRINTK(" Year: %u Week %u\n", specs->year, specs->week);
+}
+
+static void get_dpms_capabilities(unsigned char flags,
+ struct fb_monspecs *specs)
+{
+ specs->dpms = 0;
+ if (flags & DPMS_ACTIVE_OFF)
+ specs->dpms |= FB_DPMS_ACTIVE_OFF;
+ if (flags & DPMS_SUSPEND)
+ specs->dpms |= FB_DPMS_SUSPEND;
+ if (flags & DPMS_STANDBY)
+ specs->dpms |= FB_DPMS_STANDBY;
+ DPRINTK(" DPMS: Active %s, Suspend %s, Standby %s\n",
+ (flags & DPMS_ACTIVE_OFF) ? "yes" : "no",
+ (flags & DPMS_SUSPEND) ? "yes" : "no",
+ (flags & DPMS_STANDBY) ? "yes" : "no");
+}
+
+static void get_chroma(unsigned char *block, struct fb_monspecs *specs)
+{
+ int tmp;
+
+ DPRINTK(" Chroma\n");
+ /* Chromaticity data */
+ tmp = ((block[5] & (3 << 6)) >> 6) | (block[0x7] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.redx = tmp/1024;
+ DPRINTK(" RedX: 0.%03d ", specs->chroma.redx);
+
+ tmp = ((block[5] & (3 << 4)) >> 4) | (block[0x8] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.redy = tmp/1024;
+ DPRINTK("RedY: 0.%03d\n", specs->chroma.redy);
+
+ tmp = ((block[5] & (3 << 2)) >> 2) | (block[0x9] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.greenx = tmp/1024;
+ DPRINTK(" GreenX: 0.%03d ", specs->chroma.greenx);
+
+ tmp = (block[5] & 3) | (block[0xa] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.greeny = tmp/1024;
+ DPRINTK("GreenY: 0.%03d\n", specs->chroma.greeny);
+
+ tmp = ((block[6] & (3 << 6)) >> 6) | (block[0xb] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.bluex = tmp/1024;
+ DPRINTK(" BlueX: 0.%03d ", specs->chroma.bluex);
+
+ tmp = ((block[6] & (3 << 4)) >> 4) | (block[0xc] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.bluey = tmp/1024;
+ DPRINTK("BlueY: 0.%03d\n", specs->chroma.bluey);
+
+ tmp = ((block[6] & (3 << 2)) >> 2) | (block[0xd] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.whitex = tmp/1024;
+ DPRINTK(" WhiteX: 0.%03d ", specs->chroma.whitex);
+
+ tmp = (block[6] & 3) | (block[0xe] << 2);
+ tmp *= 1000;
+ tmp += 512;
+ specs->chroma.whitey = tmp/1024;
+ DPRINTK("WhiteY: 0.%03d\n", specs->chroma.whitey);
+}
+
+static void calc_mode_timings(int xres, int yres, int refresh,
+ struct fb_videomode *mode)
+{
+ struct fb_var_screeninfo *var;
+
+ var = kzalloc(sizeof(struct fb_var_screeninfo), GFP_KERNEL);
+
+ if (var) {
+ var->xres = xres;
+ var->yres = yres;
+ fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON,
+ refresh, var, NULL);
+ mode->xres = xres;
+ mode->yres = yres;
+ mode->pixclock = var->pixclock;
+ mode->refresh = refresh;
+ mode->left_margin = var->left_margin;
+ mode->right_margin = var->right_margin;
+ mode->upper_margin = var->upper_margin;
+ mode->lower_margin = var->lower_margin;
+ mode->hsync_len = var->hsync_len;
+ mode->vsync_len = var->vsync_len;
+ mode->vmode = 0;
+ mode->sync = 0;
+ kfree(var);
+ }
+}
+
+static int get_est_timing(unsigned char *block, struct fb_videomode *mode)
+{
+ int num = 0;
+ unsigned char c;
+
+ c = block[0];
+ if (c&0x80) {
+ calc_mode_timings(720, 400, 70, &mode[num]);
+ mode[num++].flag = FB_MODE_IS_CALCULATED;
+ DPRINTK(" 720x400@70Hz\n");
+ }
+ if (c&0x40) {
+ calc_mode_timings(720, 400, 88, &mode[num]);
+ mode[num++].flag = FB_MODE_IS_CALCULATED;
+ DPRINTK(" 720x400@88Hz\n");
+ }
+ if (c&0x20) {
+ mode[num++] = vesa_modes[3];
+ DPRINTK(" 640x480@60Hz\n");
+ }
+ if (c&0x10) {
+ calc_mode_timings(640, 480, 67, &mode[num]);
+ mode[num++].flag = FB_MODE_IS_CALCULATED;
+ DPRINTK(" 640x480@67Hz\n");
+ }
+ if (c&0x08) {
+ mode[num++] = vesa_modes[4];
+ DPRINTK(" 640x480@72Hz\n");
+ }
+ if (c&0x04) {
+ mode[num++] = vesa_modes[5];
+ DPRINTK(" 640x480@75Hz\n");
+ }
+ if (c&0x02) {
+ mode[num++] = vesa_modes[7];
+ DPRINTK(" 800x600@56Hz\n");
+ }
+ if (c&0x01) {
+ mode[num++] = vesa_modes[8];
+ DPRINTK(" 800x600@60Hz\n");
+ }
+
+ c = block[1];
+ if (c&0x80) {
+ mode[num++] = vesa_modes[9];
+ DPRINTK(" 800x600@72Hz\n");
+ }
+ if (c&0x40) {
+ mode[num++] = vesa_modes[10];
+ DPRINTK(" 800x600@75Hz\n");
+ }
+ if (c&0x20) {
+ calc_mode_timings(832, 624, 75, &mode[num]);
+ mode[num++].flag = FB_MODE_IS_CALCULATED;
+ DPRINTK(" 832x624@75Hz\n");
+ }
+ if (c&0x10) {
+ mode[num++] = vesa_modes[12];
+ DPRINTK(" 1024x768@87Hz Interlaced\n");
+ }
+ if (c&0x08) {
+ mode[num++] = vesa_modes[13];
+ DPRINTK(" 1024x768@60Hz\n");
+ }
+ if (c&0x04) {
+ mode[num++] = vesa_modes[14];
+ DPRINTK(" 1024x768@70Hz\n");
+ }
+ if (c&0x02) {
+ mode[num++] = vesa_modes[15];
+ DPRINTK(" 1024x768@75Hz\n");
+ }
+ if (c&0x01) {
+ mode[num++] = vesa_modes[21];
+ DPRINTK(" 1280x1024@75Hz\n");
+ }
+ c = block[2];
+ if (c&0x80) {
+ mode[num++] = vesa_modes[17];
+ DPRINTK(" 1152x870@75Hz\n");
+ }
+ DPRINTK(" Manufacturer's mask: %x\n",c&0x7F);
+ return num;
+}
+
+static int get_std_timing(unsigned char *block, struct fb_videomode *mode,
+ int ver, int rev)
+{
+ int xres, yres = 0, refresh, ratio, i;
+
+ xres = (block[0] + 31) * 8;
+ if (xres <= 256)
+ return 0;
+
+ ratio = (block[1] & 0xc0) >> 6;
+ switch (ratio) {
+ case 0:
+ /* in EDID 1.3 the meaning of 0 changed to 16:10 (prior 1:1) */
+ if (ver < 1 || (ver == 1 && rev < 3))
+ yres = xres;
+ else
+ yres = (xres * 10)/16;
+ break;
+ case 1:
+ yres = (xres * 3)/4;
+ break;
+ case 2:
+ yres = (xres * 4)/5;
+ break;
+ case 3:
+ yres = (xres * 9)/16;
+ break;
+ }
+ refresh = (block[1] & 0x3f) + 60;
+
+ DPRINTK(" %dx%d@%dHz\n", xres, yres, refresh);
+ for (i = 0; i < VESA_MODEDB_SIZE; i++) {
+ if (vesa_modes[i].xres == xres &&
+ vesa_modes[i].yres == yres &&
+ vesa_modes[i].refresh == refresh) {
+ *mode = vesa_modes[i];
+ mode->flag |= FB_MODE_IS_STANDARD;
+ return 1;
+ }
+ }
+ calc_mode_timings(xres, yres, refresh, mode);
+ return 1;
+}
+
+static int get_dst_timing(unsigned char *block,
+ struct fb_videomode *mode, int ver, int rev)
+{
+ int j, num = 0;
+
+ for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE)
+ num += get_std_timing(block, &mode[num], ver, rev);
+
+ return num;
+}
+
+static void get_detailed_timing(unsigned char *block,
+ struct fb_videomode *mode)
+{
+ mode->xres = H_ACTIVE;
+ mode->yres = V_ACTIVE;
+ mode->pixclock = PIXEL_CLOCK;
+ mode->pixclock /= 1000;
+ mode->pixclock = KHZ2PICOS(mode->pixclock);
+ mode->right_margin = H_SYNC_OFFSET;
+ mode->left_margin = (H_ACTIVE + H_BLANKING) -
+ (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH);
+ mode->upper_margin = V_BLANKING - V_SYNC_OFFSET -
+ V_SYNC_WIDTH;
+ mode->lower_margin = V_SYNC_OFFSET;
+ mode->hsync_len = H_SYNC_WIDTH;
+ mode->vsync_len = V_SYNC_WIDTH;
+ if (HSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (VSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+ mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) *
+ (V_ACTIVE + V_BLANKING));
+ if (INTERLACED) {
+ mode->yres *= 2;
+ mode->upper_margin *= 2;
+ mode->lower_margin *= 2;
+ mode->vsync_len *= 2;
+ mode->vmode |= FB_VMODE_INTERLACED;
+ }
+ mode->flag = FB_MODE_IS_DETAILED;
+
+ DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000);
+ DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET,
+ H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING);
+ DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET,
+ V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING);
+ DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-",
+ (VSYNC_POSITIVE) ? "+" : "-");
+}
+
+/**
+ * fb_create_modedb - create video mode database
+ * @edid: EDID data
+ * @dbsize: database size
+ *
+ * RETURNS: struct fb_videomode, @dbsize contains length of database
+ *
+ * DESCRIPTION:
+ * This function builds a mode database using the contents of the EDID
+ * data
+ */
+static struct fb_videomode *fb_create_modedb(unsigned char *edid, int *dbsize)
+{
+ struct fb_videomode *mode, *m;
+ unsigned char *block;
+ int num = 0, i, first = 1;
+ int ver, rev;
+
+ ver = edid[EDID_STRUCT_VERSION];
+ rev = edid[EDID_STRUCT_REVISION];
+
+ mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL);
+ if (mode == NULL)
+ return NULL;
+
+ if (edid == NULL || !edid_checksum(edid) ||
+ !edid_check_header(edid)) {
+ kfree(mode);
+ return NULL;
+ }
+
+ *dbsize = 0;
+
+ DPRINTK(" Detailed Timings\n");
+ block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+ for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) {
+ if (!(block[0] == 0x00 && block[1] == 0x00)) {
+ get_detailed_timing(block, &mode[num]);
+ if (first) {
+ mode[num].flag |= FB_MODE_IS_FIRST;
+ first = 0;
+ }
+ num++;
+ }
+ }
+
+ DPRINTK(" Supported VESA Modes\n");
+ block = edid + ESTABLISHED_TIMING_1;
+ num += get_est_timing(block, &mode[num]);
+
+ DPRINTK(" Standard Timings\n");
+ block = edid + STD_TIMING_DESCRIPTIONS_START;
+ for (i = 0; i < STD_TIMING; i++, block += STD_TIMING_DESCRIPTION_SIZE)
+ num += get_std_timing(block, &mode[num], ver, rev);
+
+ block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+ for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) {
+ if (block[0] == 0x00 && block[1] == 0x00 && block[3] == 0xfa)
+ num += get_dst_timing(block + 5, &mode[num], ver, rev);
+ }
+
+ /* Yikes, EDID data is totally useless */
+ if (!num) {
+ kfree(mode);
+ return NULL;
+ }
+
+ *dbsize = num;
+ m = kmalloc(num * sizeof(struct fb_videomode), GFP_KERNEL);
+ if (!m)
+ return mode;
+ memmove(m, mode, num * sizeof(struct fb_videomode));
+ kfree(mode);
+ return m;
+}
+
+/**
+ * fb_destroy_modedb - destroys mode database
+ * @modedb: mode database to destroy
+ *
+ * DESCRIPTION:
+ * Destroy mode database created by fb_create_modedb
+ */
+void fb_destroy_modedb(struct fb_videomode *modedb)
+{
+ kfree(modedb);
+}
+
+static int fb_get_monitor_limits(unsigned char *edid, struct fb_monspecs *specs)
+{
+ int i, retval = 1;
+ unsigned char *block;
+
+ block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+
+ DPRINTK(" Monitor Operating Limits: ");
+
+ for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) {
+ if (edid_is_limits_block(block)) {
+ specs->hfmin = H_MIN_RATE * 1000;
+ specs->hfmax = H_MAX_RATE * 1000;
+ specs->vfmin = V_MIN_RATE;
+ specs->vfmax = V_MAX_RATE;
+ specs->dclkmax = MAX_PIXEL_CLOCK * 1000000;
+ specs->gtf = (GTF_SUPPORT) ? 1 : 0;
+ retval = 0;
+ DPRINTK("From EDID\n");
+ break;
+ }
+ }
+
+ /* estimate monitor limits based on modes supported */
+ if (retval) {
+ struct fb_videomode *modes, *mode;
+ int num_modes, hz, hscan, pixclock;
+ int vtotal, htotal;
+
+ modes = fb_create_modedb(edid, &num_modes);
+ if (!modes) {
+ DPRINTK("None Available\n");
+ return 1;
+ }
+
+ retval = 0;
+ for (i = 0; i < num_modes; i++) {
+ mode = &modes[i];
+ pixclock = PICOS2KHZ(modes[i].pixclock) * 1000;
+ htotal = mode->xres + mode->right_margin + mode->hsync_len
+ + mode->left_margin;
+ vtotal = mode->yres + mode->lower_margin + mode->vsync_len
+ + mode->upper_margin;
+
+ if (mode->vmode & FB_VMODE_INTERLACED)
+ vtotal /= 2;
+
+ if (mode->vmode & FB_VMODE_DOUBLE)
+ vtotal *= 2;
+
+ hscan = (pixclock + htotal / 2) / htotal;
+ hscan = (hscan + 500) / 1000 * 1000;
+ hz = (hscan + vtotal / 2) / vtotal;
+
+ if (specs->dclkmax == 0 || specs->dclkmax < pixclock)
+ specs->dclkmax = pixclock;
+
+ if (specs->dclkmin == 0 || specs->dclkmin > pixclock)
+ specs->dclkmin = pixclock;
+
+ if (specs->hfmax == 0 || specs->hfmax < hscan)
+ specs->hfmax = hscan;
+
+ if (specs->hfmin == 0 || specs->hfmin > hscan)
+ specs->hfmin = hscan;
+
+ if (specs->vfmax == 0 || specs->vfmax < hz)
+ specs->vfmax = hz;
+
+ if (specs->vfmin == 0 || specs->vfmin > hz)
+ specs->vfmin = hz;
+ }
+ DPRINTK("Extrapolated\n");
+ fb_destroy_modedb(modes);
+ }
+ DPRINTK(" H: %d-%dKHz V: %d-%dHz DCLK: %dMHz\n",
+ specs->hfmin/1000, specs->hfmax/1000, specs->vfmin,
+ specs->vfmax, specs->dclkmax/1000000);
+ return retval;
+}
+
+static void get_monspecs(unsigned char *edid, struct fb_monspecs *specs)
+{
+ unsigned char c, *block;
+
+ block = edid + EDID_STRUCT_DISPLAY;
+
+ fb_get_monitor_limits(edid, specs);
+
+ c = block[0] & 0x80;
+ specs->input = 0;
+ if (c) {
+ specs->input |= FB_DISP_DDI;
+ DPRINTK(" Digital Display Input");
+ } else {
+ DPRINTK(" Analog Display Input: Input Voltage - ");
+ switch ((block[0] & 0x60) >> 5) {
+ case 0:
+ DPRINTK("0.700V/0.300V");
+ specs->input |= FB_DISP_ANA_700_300;
+ break;
+ case 1:
+ DPRINTK("0.714V/0.286V");
+ specs->input |= FB_DISP_ANA_714_286;
+ break;
+ case 2:
+ DPRINTK("1.000V/0.400V");
+ specs->input |= FB_DISP_ANA_1000_400;
+ break;
+ case 3:
+ DPRINTK("0.700V/0.000V");
+ specs->input |= FB_DISP_ANA_700_000;
+ break;
+ }
+ }
+ DPRINTK("\n Sync: ");
+ c = block[0] & 0x10;
+ if (c)
+ DPRINTK(" Configurable signal level\n");
+ c = block[0] & 0x0f;
+ specs->signal = 0;
+ if (c & 0x10) {
+ DPRINTK("Blank to Blank ");
+ specs->signal |= FB_SIGNAL_BLANK_BLANK;
+ }
+ if (c & 0x08) {
+ DPRINTK("Separate ");
+ specs->signal |= FB_SIGNAL_SEPARATE;
+ }
+ if (c & 0x04) {
+ DPRINTK("Composite ");
+ specs->signal |= FB_SIGNAL_COMPOSITE;
+ }
+ if (c & 0x02) {
+ DPRINTK("Sync on Green ");
+ specs->signal |= FB_SIGNAL_SYNC_ON_GREEN;
+ }
+ if (c & 0x01) {
+ DPRINTK("Serration on ");
+ specs->signal |= FB_SIGNAL_SERRATION_ON;
+ }
+ DPRINTK("\n");
+ specs->max_x = block[1];
+ specs->max_y = block[2];
+ DPRINTK(" Max H-size in cm: ");
+ if (specs->max_x)
+ DPRINTK("%d\n", specs->max_x);
+ else
+ DPRINTK("variable\n");
+ DPRINTK(" Max V-size in cm: ");
+ if (specs->max_y)
+ DPRINTK("%d\n", specs->max_y);
+ else
+ DPRINTK("variable\n");
+
+ c = block[3];
+ specs->gamma = c+100;
+ DPRINTK(" Gamma: ");
+ DPRINTK("%d.%d\n", specs->gamma/100, specs->gamma % 100);
+
+ get_dpms_capabilities(block[4], specs);
+
+ switch ((block[4] & 0x18) >> 3) {
+ case 0:
+ DPRINTK(" Monochrome/Grayscale\n");
+ specs->input |= FB_DISP_MONO;
+ break;
+ case 1:
+ DPRINTK(" RGB Color Display\n");
+ specs->input |= FB_DISP_RGB;
+ break;
+ case 2:
+ DPRINTK(" Non-RGB Multicolor Display\n");
+ specs->input |= FB_DISP_MULTI;
+ break;
+ default:
+ DPRINTK(" Unknown\n");
+ specs->input |= FB_DISP_UNKNOWN;
+ break;
+ }
+
+ get_chroma(block, specs);
+
+ specs->misc = 0;
+ c = block[4] & 0x7;
+ if (c & 0x04) {
+ DPRINTK(" Default color format is primary\n");
+ specs->misc |= FB_MISC_PRIM_COLOR;
+ }
+ if (c & 0x02) {
+ DPRINTK(" First DETAILED Timing is preferred\n");
+ specs->misc |= FB_MISC_1ST_DETAIL;
+ }
+ if (c & 0x01) {
+ printk(" Display is GTF capable\n");
+ specs->gtf = 1;
+ }
+}
+
+int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var)
+{
+ int i;
+ unsigned char *block;
+
+ if (edid == NULL || var == NULL)
+ return 1;
+
+ if (!(edid_checksum(edid)))
+ return 1;
+
+ if (!(edid_check_header(edid)))
+ return 1;
+
+ block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+
+ for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) {
+ if (edid_is_timing_block(block)) {
+ var->xres = var->xres_virtual = H_ACTIVE;
+ var->yres = var->yres_virtual = V_ACTIVE;
+ var->height = var->width = 0;
+ var->right_margin = H_SYNC_OFFSET;
+ var->left_margin = (H_ACTIVE + H_BLANKING) -
+ (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH);
+ var->upper_margin = V_BLANKING - V_SYNC_OFFSET -
+ V_SYNC_WIDTH;
+ var->lower_margin = V_SYNC_OFFSET;
+ var->hsync_len = H_SYNC_WIDTH;
+ var->vsync_len = V_SYNC_WIDTH;
+ var->pixclock = PIXEL_CLOCK;
+ var->pixclock /= 1000;
+ var->pixclock = KHZ2PICOS(var->pixclock);
+
+ if (HSYNC_POSITIVE)
+ var->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (VSYNC_POSITIVE)
+ var->sync |= FB_SYNC_VERT_HIGH_ACT;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs)
+{
+ unsigned char *block;
+ int i, found = 0;
+
+ if (edid == NULL)
+ return;
+
+ if (!(edid_checksum(edid)))
+ return;
+
+ if (!(edid_check_header(edid)))
+ return;
+
+ memset(specs, 0, sizeof(struct fb_monspecs));
+
+ specs->version = edid[EDID_STRUCT_VERSION];
+ specs->revision = edid[EDID_STRUCT_REVISION];
+
+ DPRINTK("========================================\n");
+ DPRINTK("Display Information (EDID)\n");
+ DPRINTK("========================================\n");
+ DPRINTK(" EDID Version %d.%d\n", (int) specs->version,
+ (int) specs->revision);
+
+ parse_vendor_block(edid + ID_MANUFACTURER_NAME, specs);
+
+ block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
+ for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) {
+ if (edid_is_serial_block(block)) {
+ copy_string(block, specs->serial_no);
+ DPRINTK(" Serial Number: %s\n", specs->serial_no);
+ } else if (edid_is_ascii_block(block)) {
+ copy_string(block, specs->ascii);
+ DPRINTK(" ASCII Block: %s\n", specs->ascii);
+ } else if (edid_is_monitor_block(block)) {
+ copy_string(block, specs->monitor);
+ DPRINTK(" Monitor Name: %s\n", specs->monitor);
+ }
+ }
+
+ DPRINTK(" Display Characteristics:\n");
+ get_monspecs(edid, specs);
+
+ specs->modedb = fb_create_modedb(edid, &specs->modedb_len);
+
+ /*
+ * Workaround for buggy EDIDs that sets that the first
+ * detailed timing is preferred but has not detailed
+ * timing specified
+ */
+ for (i = 0; i < specs->modedb_len; i++) {
+ if (specs->modedb[i].flag & FB_MODE_IS_DETAILED) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found)
+ specs->misc &= ~FB_MISC_1ST_DETAIL;
+
+ DPRINTK("========================================\n");
+}
+
+/**
+ * fb_edid_add_monspecs() - add monitor video modes from E-EDID data
+ * @edid: 128 byte array with an E-EDID block
+ * @spacs: monitor specs to be extended
+ */
+void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs)
+{
+ unsigned char *block;
+ struct fb_videomode *m;
+ int num = 0, i;
+ u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE];
+ u8 pos = 4, svd_n = 0;
+
+ if (!edid)
+ return;
+
+ if (!edid_checksum(edid))
+ return;
+
+ if (edid[0] != 0x2 ||
+ edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE)
+ return;
+
+ DPRINTK(" Short Video Descriptors\n");
+
+ while (pos < edid[2]) {
+ u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7;
+ pr_debug("Data block %u of %u bytes\n", type, len);
+ if (type == 2)
+ for (i = pos; i < pos + len; i++) {
+ u8 idx = edid[pos + i] & 0x7f;
+ svd[svd_n++] = idx;
+ pr_debug("N%sative mode #%d\n",
+ edid[pos + i] & 0x80 ? "" : "on-n", idx);
+ }
+ pos += len + 1;
+ }
+
+ block = edid + edid[2];
+
+ DPRINTK(" Extended Detailed Timings\n");
+
+ for (i = 0; i < (128 - edid[2]) / DETAILED_TIMING_DESCRIPTION_SIZE;
+ i++, block += DETAILED_TIMING_DESCRIPTION_SIZE)
+ if (PIXEL_CLOCK)
+ edt[num++] = block - edid;
+
+ /* Yikes, EDID data is totally useless */
+ if (!(num + svd_n))
+ return;
+
+ m = kzalloc((specs->modedb_len + num + svd_n) *
+ sizeof(struct fb_videomode), GFP_KERNEL);
+
+ if (!m)
+ return;
+
+ memcpy(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode));
+
+ for (i = specs->modedb_len; i < specs->modedb_len + num; i++) {
+ get_detailed_timing(edid + edt[i - specs->modedb_len], &m[i]);
+ if (i == specs->modedb_len)
+ m[i].flag |= FB_MODE_IS_FIRST;
+ pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh);
+ }
+
+ for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) {
+ int idx = svd[i - specs->modedb_len - num];
+ if (!idx || idx > 63) {
+ pr_warning("Reserved SVD code %d\n", idx);
+ } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) {
+ pr_warning("Unimplemented SVD code %d\n", idx);
+ } else {
+ memcpy(&m[i], cea_modes + idx, sizeof(m[i]));
+ pr_debug("Adding SVD #%d: %ux%u@%u\n", idx,
+ m[i].xres, m[i].yres, m[i].refresh);
+ }
+ }
+
+ kfree(specs->modedb);
+ specs->modedb = m;
+ specs->modedb_len = specs->modedb_len + num + svd_n;
+}
+
+/*
+ * VESA Generalized Timing Formula (GTF)
+ */
+
+#define FLYBACK 550
+#define V_FRONTPORCH 1
+#define H_OFFSET 40
+#define H_SCALEFACTOR 20
+#define H_BLANKSCALE 128
+#define H_GRADIENT 600
+#define C_VAL 30
+#define M_VAL 300
+
+struct __fb_timings {
+ u32 dclk;
+ u32 hfreq;
+ u32 vfreq;
+ u32 hactive;
+ u32 vactive;
+ u32 hblank;
+ u32 vblank;
+ u32 htotal;
+ u32 vtotal;
+};
+
+/**
+ * fb_get_vblank - get vertical blank time
+ * @hfreq: horizontal freq
+ *
+ * DESCRIPTION:
+ * vblank = right_margin + vsync_len + left_margin
+ *
+ * given: right_margin = 1 (V_FRONTPORCH)
+ * vsync_len = 3
+ * flyback = 550
+ *
+ * flyback * hfreq
+ * left_margin = --------------- - vsync_len
+ * 1000000
+ */
+static u32 fb_get_vblank(u32 hfreq)
+{
+ u32 vblank;
+
+ vblank = (hfreq * FLYBACK)/1000;
+ vblank = (vblank + 500)/1000;
+ return (vblank + V_FRONTPORCH);
+}
+
+/**
+ * fb_get_hblank_by_freq - get horizontal blank time given hfreq
+ * @hfreq: horizontal freq
+ * @xres: horizontal resolution in pixels
+ *
+ * DESCRIPTION:
+ *
+ * xres * duty_cycle
+ * hblank = ------------------
+ * 100 - duty_cycle
+ *
+ * duty cycle = percent of htotal assigned to inactive display
+ * duty cycle = C - (M/Hfreq)
+ *
+ * where: C = ((offset - scale factor) * blank_scale)
+ * -------------------------------------- + scale factor
+ * 256
+ * M = blank_scale * gradient
+ *
+ */
+static u32 fb_get_hblank_by_hfreq(u32 hfreq, u32 xres)
+{
+ u32 c_val, m_val, duty_cycle, hblank;
+
+ c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 +
+ H_SCALEFACTOR) * 1000;
+ m_val = (H_BLANKSCALE * H_GRADIENT)/256;
+ m_val = (m_val * 1000000)/hfreq;
+ duty_cycle = c_val - m_val;
+ hblank = (xres * duty_cycle)/(100000 - duty_cycle);
+ return (hblank);
+}
+
+/**
+ * fb_get_hblank_by_dclk - get horizontal blank time given pixelclock
+ * @dclk: pixelclock in Hz
+ * @xres: horizontal resolution in pixels
+ *
+ * DESCRIPTION:
+ *
+ * xres * duty_cycle
+ * hblank = ------------------
+ * 100 - duty_cycle
+ *
+ * duty cycle = percent of htotal assigned to inactive display
+ * duty cycle = C - (M * h_period)
+ *
+ * where: h_period = SQRT(100 - C + (0.4 * xres * M)/dclk) + C - 100
+ * -----------------------------------------------
+ * 2 * M
+ * M = 300;
+ * C = 30;
+
+ */
+static u32 fb_get_hblank_by_dclk(u32 dclk, u32 xres)
+{
+ u32 duty_cycle, h_period, hblank;
+
+ dclk /= 1000;
+ h_period = 100 - C_VAL;
+ h_period *= h_period;
+ h_period += (M_VAL * xres * 2 * 1000)/(5 * dclk);
+ h_period *= 10000;
+
+ h_period = int_sqrt(h_period);
+ h_period -= (100 - C_VAL) * 100;
+ h_period *= 1000;
+ h_period /= 2 * M_VAL;
+
+ duty_cycle = C_VAL * 1000 - (M_VAL * h_period)/100;
+ hblank = (xres * duty_cycle)/(100000 - duty_cycle) + 8;
+ hblank &= ~15;
+ return (hblank);
+}
+
+/**
+ * fb_get_hfreq - estimate hsync
+ * @vfreq: vertical refresh rate
+ * @yres: vertical resolution
+ *
+ * DESCRIPTION:
+ *
+ * (yres + front_port) * vfreq * 1000000
+ * hfreq = -------------------------------------
+ * (1000000 - (vfreq * FLYBACK)
+ *
+ */
+
+static u32 fb_get_hfreq(u32 vfreq, u32 yres)
+{
+ u32 divisor, hfreq;
+
+ divisor = (1000000 - (vfreq * FLYBACK))/1000;
+ hfreq = (yres + V_FRONTPORCH) * vfreq * 1000;
+ return (hfreq/divisor);
+}
+
+static void fb_timings_vfreq(struct __fb_timings *timings)
+{
+ timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive);
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->dclk = timings->htotal * timings->hfreq;
+}
+
+static void fb_timings_hfreq(struct __fb_timings *timings)
+{
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->vfreq = timings->hfreq/timings->vtotal;
+ timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->dclk = timings->htotal * timings->hfreq;
+}
+
+static void fb_timings_dclk(struct __fb_timings *timings)
+{
+ timings->hblank = fb_get_hblank_by_dclk(timings->dclk,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->hfreq = timings->dclk/timings->htotal;
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->vfreq = timings->hfreq/timings->vtotal;
+}
+
+/*
+ * fb_get_mode - calculates video mode using VESA GTF
+ * @flags: if: 0 - maximize vertical refresh rate
+ * 1 - vrefresh-driven calculation;
+ * 2 - hscan-driven calculation;
+ * 3 - pixelclock-driven calculation;
+ * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock
+ * @var: pointer to fb_var_screeninfo
+ * @info: pointer to fb_info
+ *
+ * DESCRIPTION:
+ * Calculates video mode based on monitor specs using VESA GTF.
+ * The GTF is best for VESA GTF compliant monitors but is
+ * specifically formulated to work for older monitors as well.
+ *
+ * If @flag==0, the function will attempt to maximize the
+ * refresh rate. Otherwise, it will calculate timings based on
+ * the flag and accompanying value.
+ *
+ * If FB_IGNOREMON bit is set in @flags, monitor specs will be
+ * ignored and @var will be filled with the calculated timings.
+ *
+ * All calculations are based on the VESA GTF Spreadsheet
+ * available at VESA's public ftp (http://www.vesa.org).
+ *
+ * NOTES:
+ * The timings generated by the GTF will be different from VESA
+ * DMT. It might be a good idea to keep a table of standard
+ * VESA modes as well. The GTF may also not work for some displays,
+ * such as, and especially, analog TV.
+ *
+ * REQUIRES:
+ * A valid info->monspecs, otherwise 'safe numbers' will be used.
+ */
+int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct __fb_timings *timings;
+ u32 interlace = 1, dscan = 1;
+ u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax, err = 0;
+
+
+ timings = kzalloc(sizeof(struct __fb_timings), GFP_KERNEL);
+
+ if (!timings)
+ return -ENOMEM;
+
+ /*
+ * If monspecs are invalid, use values that are enough
+ * for 640x480@60
+ */
+ if (!info || !info->monspecs.hfmax || !info->monspecs.vfmax ||
+ !info->monspecs.dclkmax ||
+ info->monspecs.hfmax < info->monspecs.hfmin ||
+ info->monspecs.vfmax < info->monspecs.vfmin ||
+ info->monspecs.dclkmax < info->monspecs.dclkmin) {
+ hfmin = 29000; hfmax = 30000;
+ vfmin = 60; vfmax = 60;
+ dclkmin = 0; dclkmax = 25000000;
+ } else {
+ hfmin = info->monspecs.hfmin;
+ hfmax = info->monspecs.hfmax;
+ vfmin = info->monspecs.vfmin;
+ vfmax = info->monspecs.vfmax;
+ dclkmin = info->monspecs.dclkmin;
+ dclkmax = info->monspecs.dclkmax;
+ }
+
+ timings->hactive = var->xres;
+ timings->vactive = var->yres;
+ if (var->vmode & FB_VMODE_INTERLACED) {
+ timings->vactive /= 2;
+ interlace = 2;
+ }
+ if (var->vmode & FB_VMODE_DOUBLE) {
+ timings->vactive *= 2;
+ dscan = 2;
+ }
+
+ switch (flags & ~FB_IGNOREMON) {
+ case FB_MAXTIMINGS: /* maximize refresh rate */
+ timings->hfreq = hfmax;
+ fb_timings_hfreq(timings);
+ if (timings->vfreq > vfmax) {
+ timings->vfreq = vfmax;
+ fb_timings_vfreq(timings);
+ }
+ if (timings->dclk > dclkmax) {
+ timings->dclk = dclkmax;
+ fb_timings_dclk(timings);
+ }
+ break;
+ case FB_VSYNCTIMINGS: /* vrefresh driven */
+ timings->vfreq = val;
+ fb_timings_vfreq(timings);
+ break;
+ case FB_HSYNCTIMINGS: /* hsync driven */
+ timings->hfreq = val;
+ fb_timings_hfreq(timings);
+ break;
+ case FB_DCLKTIMINGS: /* pixelclock driven */
+ timings->dclk = PICOS2KHZ(val) * 1000;
+ fb_timings_dclk(timings);
+ break;
+ default:
+ err = -EINVAL;
+
+ }
+
+ if (err || (!(flags & FB_IGNOREMON) &&
+ (timings->vfreq < vfmin || timings->vfreq > vfmax ||
+ timings->hfreq < hfmin || timings->hfreq > hfmax ||
+ timings->dclk < dclkmin || timings->dclk > dclkmax))) {
+ err = -EINVAL;
+ } else {
+ var->pixclock = KHZ2PICOS(timings->dclk/1000);
+ var->hsync_len = (timings->htotal * 8)/100;
+ var->right_margin = (timings->hblank/2) - var->hsync_len;
+ var->left_margin = timings->hblank - var->right_margin -
+ var->hsync_len;
+ var->vsync_len = (3 * interlace)/dscan;
+ var->lower_margin = (1 * interlace)/dscan;
+ var->upper_margin = (timings->vblank * interlace)/dscan -
+ (var->vsync_len + var->lower_margin);
+ }
+
+ kfree(timings);
+ return err;
+}
+
+#ifdef CONFIG_VIDEOMODE_HELPERS
+int fb_videomode_from_videomode(const struct videomode *vm,
+ struct fb_videomode *fbmode)
+{
+ unsigned int htotal, vtotal;
+
+ fbmode->xres = vm->hactive;
+ fbmode->left_margin = vm->hback_porch;
+ fbmode->right_margin = vm->hfront_porch;
+ fbmode->hsync_len = vm->hsync_len;
+
+ fbmode->yres = vm->vactive;
+ fbmode->upper_margin = vm->vback_porch;
+ fbmode->lower_margin = vm->vfront_porch;
+ fbmode->vsync_len = vm->vsync_len;
+
+ /* prevent division by zero in KHZ2PICOS macro */
+ fbmode->pixclock = vm->pixelclock ?
+ KHZ2PICOS(vm->pixelclock / 1000) : 0;
+
+ fbmode->sync = 0;
+ fbmode->vmode = 0;
+ if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ fbmode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ fbmode->sync |= FB_SYNC_VERT_HIGH_ACT;
+ if (vm->flags & DISPLAY_FLAGS_INTERLACED)
+ fbmode->vmode |= FB_VMODE_INTERLACED;
+ if (vm->flags & DISPLAY_FLAGS_DOUBLESCAN)
+ fbmode->vmode |= FB_VMODE_DOUBLE;
+ fbmode->flag = 0;
+
+ htotal = vm->hactive + vm->hfront_porch + vm->hback_porch +
+ vm->hsync_len;
+ vtotal = vm->vactive + vm->vfront_porch + vm->vback_porch +
+ vm->vsync_len;
+ /* prevent division by zero */
+ if (htotal && vtotal) {
+ fbmode->refresh = vm->pixelclock / (htotal * vtotal);
+ /* a mode must have htotal and vtotal != 0 or it is invalid */
+ } else {
+ fbmode->refresh = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fb_videomode_from_videomode);
+
+#ifdef CONFIG_OF
+static inline void dump_fb_videomode(const struct fb_videomode *m)
+{
+ pr_debug("fb_videomode = %ux%u@%uHz (%ukHz) %u %u %u %u %u %u %u %u %u\n",
+ m->xres, m->yres, m->refresh, m->pixclock, m->left_margin,
+ m->right_margin, m->upper_margin, m->lower_margin,
+ m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag);
+}
+
+/**
+ * of_get_fb_videomode - get a fb_videomode from devicetree
+ * @np: device_node with the timing specification
+ * @fb: will be set to the return value
+ * @index: index into the list of display timings in devicetree
+ *
+ * DESCRIPTION:
+ * This function is expensive and should only be used, if only one mode is to be
+ * read from DT. To get multiple modes start with of_get_display_timings ond
+ * work with that instead.
+ */
+int of_get_fb_videomode(struct device_node *np, struct fb_videomode *fb,
+ int index)
+{
+ struct videomode vm;
+ int ret;
+
+ ret = of_get_videomode(np, &vm, index);
+ if (ret)
+ return ret;
+
+ fb_videomode_from_videomode(&vm, fb);
+
+ pr_debug("%s: got %dx%d display mode from %s\n",
+ of_node_full_name(np), vm.hactive, vm.vactive, np->name);
+ dump_fb_videomode(fb);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_get_fb_videomode);
+#endif /* CONFIG_OF */
+#endif /* CONFIG_VIDEOMODE_HELPERS */
+
+#else
+int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var)
+{
+ return 1;
+}
+void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs)
+{
+ specs = NULL;
+}
+void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs)
+{
+}
+void fb_destroy_modedb(struct fb_videomode *modedb)
+{
+}
+int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ return -EINVAL;
+}
+#endif /* CONFIG_FB_MODE_HELPERS */
+
+/*
+ * fb_validate_mode - validates var against monitor capabilities
+ * @var: pointer to fb_var_screeninfo
+ * @info: pointer to fb_info
+ *
+ * DESCRIPTION:
+ * Validates video mode against monitor capabilities specified in
+ * info->monspecs.
+ *
+ * REQUIRES:
+ * A valid info->monspecs.
+ */
+int fb_validate_mode(const struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ u32 hfreq, vfreq, htotal, vtotal, pixclock;
+ u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax;
+
+ /*
+ * If monspecs are invalid, use values that are enough
+ * for 640x480@60
+ */
+ if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
+ !info->monspecs.dclkmax ||
+ info->monspecs.hfmax < info->monspecs.hfmin ||
+ info->monspecs.vfmax < info->monspecs.vfmin ||
+ info->monspecs.dclkmax < info->monspecs.dclkmin) {
+ hfmin = 29000; hfmax = 30000;
+ vfmin = 60; vfmax = 60;
+ dclkmin = 0; dclkmax = 25000000;
+ } else {
+ hfmin = info->monspecs.hfmin;
+ hfmax = info->monspecs.hfmax;
+ vfmin = info->monspecs.vfmin;
+ vfmax = info->monspecs.vfmax;
+ dclkmin = info->monspecs.dclkmin;
+ dclkmax = info->monspecs.dclkmax;
+ }
+
+ if (!var->pixclock)
+ return -EINVAL;
+ pixclock = PICOS2KHZ(var->pixclock) * 1000;
+
+ htotal = var->xres + var->right_margin + var->hsync_len +
+ var->left_margin;
+ vtotal = var->yres + var->lower_margin + var->vsync_len +
+ var->upper_margin;
+
+ if (var->vmode & FB_VMODE_INTERLACED)
+ vtotal /= 2;
+ if (var->vmode & FB_VMODE_DOUBLE)
+ vtotal *= 2;
+
+ hfreq = pixclock/htotal;
+ hfreq = (hfreq + 500) / 1000 * 1000;
+
+ vfreq = hfreq/vtotal;
+
+ return (vfreq < vfmin || vfreq > vfmax ||
+ hfreq < hfmin || hfreq > hfmax ||
+ pixclock < dclkmin || pixclock > dclkmax) ?
+ -EINVAL : 0;
+}
+
+#if defined(CONFIG_FIRMWARE_EDID) && defined(CONFIG_X86)
+
+/*
+ * We need to ensure that the EDID block is only returned for
+ * the primary graphics adapter.
+ */
+
+const unsigned char *fb_firmware_edid(struct device *device)
+{
+ struct pci_dev *dev = NULL;
+ struct resource *res = NULL;
+ unsigned char *edid = NULL;
+
+ if (device)
+ dev = to_pci_dev(device);
+
+ if (dev)
+ res = &dev->resource[PCI_ROM_RESOURCE];
+
+ if (res && res->flags & IORESOURCE_ROM_SHADOW)
+ edid = edid_info.dummy;
+
+ return edid;
+}
+#else
+const unsigned char *fb_firmware_edid(struct device *device)
+{
+ return NULL;
+}
+#endif
+EXPORT_SYMBOL(fb_firmware_edid);
+
+EXPORT_SYMBOL(fb_parse_edid);
+EXPORT_SYMBOL(fb_edid_to_monspecs);
+EXPORT_SYMBOL(fb_edid_add_monspecs);
+EXPORT_SYMBOL(fb_get_mode);
+EXPORT_SYMBOL(fb_validate_mode);
+EXPORT_SYMBOL(fb_destroy_modedb);
diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c
new file mode 100644
index 000000000000..53444ac19fe0
--- /dev/null
+++ b/drivers/video/fbdev/core/fbsysfs.c
@@ -0,0 +1,586 @@
+/*
+ * fbsysfs.c - framebuffer device class and attributes
+ *
+ * Copyright (c) 2004 James Simmons <jsimmons@infradead.org>
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Note: currently there's only stubs for framebuffer_alloc and
+ * framebuffer_release here. The reson for that is that until all drivers
+ * are converted to use it a sysfsification will open OOPSable races.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/console.h>
+#include <linux/module.h>
+
+#define FB_SYSFS_FLAG_ATTR 1
+
+/**
+ * framebuffer_alloc - creates a new frame buffer info structure
+ *
+ * @size: size of driver private data, can be zero
+ * @dev: pointer to the device for this fb, this can be NULL
+ *
+ * Creates a new frame buffer info structure. Also reserves @size bytes
+ * for driver private data (info->par). info->par (if any) will be
+ * aligned to sizeof(long).
+ *
+ * Returns the new structure, or NULL if an error occurred.
+ *
+ */
+struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
+{
+#define BYTES_PER_LONG (BITS_PER_LONG/8)
+#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
+ int fb_info_size = sizeof(struct fb_info);
+ struct fb_info *info;
+ char *p;
+
+ if (size)
+ fb_info_size += PADDING;
+
+ p = kzalloc(fb_info_size + size, GFP_KERNEL);
+
+ if (!p)
+ return NULL;
+
+ info = (struct fb_info *) p;
+
+ if (size)
+ info->par = p + fb_info_size;
+
+ info->device = dev;
+
+#ifdef CONFIG_FB_BACKLIGHT
+ mutex_init(&info->bl_curve_mutex);
+#endif
+
+ return info;
+#undef PADDING
+#undef BYTES_PER_LONG
+}
+EXPORT_SYMBOL(framebuffer_alloc);
+
+/**
+ * framebuffer_release - marks the structure available for freeing
+ *
+ * @info: frame buffer info structure
+ *
+ * Drop the reference count of the device embedded in the
+ * framebuffer info structure.
+ *
+ */
+void framebuffer_release(struct fb_info *info)
+{
+ if (!info)
+ return;
+ kfree(info->apertures);
+ kfree(info);
+}
+EXPORT_SYMBOL(framebuffer_release);
+
+static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var)
+{
+ int err;
+
+ var->activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ fb_info->flags |= FBINFO_MISC_USEREVENT;
+ err = fb_set_var(fb_info, var);
+ fb_info->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ if (err)
+ return err;
+ return 0;
+}
+
+static int mode_string(char *buf, unsigned int offset,
+ const struct fb_videomode *mode)
+{
+ char m = 'U';
+ char v = 'p';
+
+ if (mode->flag & FB_MODE_IS_DETAILED)
+ m = 'D';
+ if (mode->flag & FB_MODE_IS_VESA)
+ m = 'V';
+ if (mode->flag & FB_MODE_IS_STANDARD)
+ m = 'S';
+
+ if (mode->vmode & FB_VMODE_INTERLACED)
+ v = 'i';
+ if (mode->vmode & FB_VMODE_DOUBLE)
+ v = 'd';
+
+ return snprintf(&buf[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n",
+ m, mode->xres, mode->yres, v, mode->refresh);
+}
+
+static ssize_t store_mode(struct device *device, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ char mstr[100];
+ struct fb_var_screeninfo var;
+ struct fb_modelist *modelist;
+ struct fb_videomode *mode;
+ struct list_head *pos;
+ size_t i;
+ int err;
+
+ memset(&var, 0, sizeof(var));
+
+ list_for_each(pos, &fb_info->modelist) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ mode = &modelist->mode;
+ i = mode_string(mstr, 0, mode);
+ if (strncmp(mstr, buf, max(count, i)) == 0) {
+
+ var = fb_info->var;
+ fb_videomode_to_var(&var, mode);
+ if ((err = activate(fb_info, &var)))
+ return err;
+ fb_info->mode = mode;
+ return count;
+ }
+ }
+ return -EINVAL;
+}
+
+static ssize_t show_mode(struct device *device, struct device_attribute *attr,
+ char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+
+ if (!fb_info->mode)
+ return 0;
+
+ return mode_string(buf, 0, fb_info->mode);
+}
+
+static ssize_t store_modes(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ LIST_HEAD(old_list);
+ int i = count / sizeof(struct fb_videomode);
+
+ if (i * sizeof(struct fb_videomode) != count)
+ return -EINVAL;
+
+ console_lock();
+ if (!lock_fb_info(fb_info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+
+ list_splice(&fb_info->modelist, &old_list);
+ fb_videomode_to_modelist((const struct fb_videomode *)buf, i,
+ &fb_info->modelist);
+ if (fb_new_modelist(fb_info)) {
+ fb_destroy_modelist(&fb_info->modelist);
+ list_splice(&old_list, &fb_info->modelist);
+ } else
+ fb_destroy_modelist(&old_list);
+
+ unlock_fb_info(fb_info);
+ console_unlock();
+
+ return 0;
+}
+
+static ssize_t show_modes(struct device *device, struct device_attribute *attr,
+ char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ unsigned int i;
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ const struct fb_videomode *mode;
+
+ i = 0;
+ list_for_each(pos, &fb_info->modelist) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ mode = &modelist->mode;
+ i += mode_string(buf, i, mode);
+ }
+ return i;
+}
+
+static ssize_t store_bpp(struct device *device, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fb_var_screeninfo var;
+ char ** last = NULL;
+ int err;
+
+ var = fb_info->var;
+ var.bits_per_pixel = simple_strtoul(buf, last, 0);
+ if ((err = activate(fb_info, &var)))
+ return err;
+ return count;
+}
+
+static ssize_t show_bpp(struct device *device, struct device_attribute *attr,
+ char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel);
+}
+
+static ssize_t store_rotate(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fb_var_screeninfo var;
+ char **last = NULL;
+ int err;
+
+ var = fb_info->var;
+ var.rotate = simple_strtoul(buf, last, 0);
+
+ if ((err = activate(fb_info, &var)))
+ return err;
+
+ return count;
+}
+
+
+static ssize_t show_rotate(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.rotate);
+}
+
+static ssize_t store_virtual(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fb_var_screeninfo var;
+ char *last = NULL;
+ int err;
+
+ var = fb_info->var;
+ var.xres_virtual = simple_strtoul(buf, &last, 0);
+ last++;
+ if (last - buf >= count)
+ return -EINVAL;
+ var.yres_virtual = simple_strtoul(last, &last, 0);
+
+ if ((err = activate(fb_info, &var)))
+ return err;
+ return count;
+}
+
+static ssize_t show_virtual(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xres_virtual,
+ fb_info->var.yres_virtual);
+}
+
+static ssize_t show_stride(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->fix.line_length);
+}
+
+static ssize_t store_blank(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ char *last = NULL;
+ int err;
+
+ console_lock();
+ fb_info->flags |= FBINFO_MISC_USEREVENT;
+ err = fb_blank(fb_info, simple_strtoul(buf, &last, 0));
+ fb_info->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t show_blank(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+// struct fb_info *fb_info = dev_get_drvdata(device);
+ return 0;
+}
+
+static ssize_t store_console(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+// struct fb_info *fb_info = dev_get_drvdata(device);
+ return 0;
+}
+
+static ssize_t show_console(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+// struct fb_info *fb_info = dev_get_drvdata(device);
+ return 0;
+}
+
+static ssize_t store_cursor(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+// struct fb_info *fb_info = dev_get_drvdata(device);
+ return 0;
+}
+
+static ssize_t show_cursor(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+// struct fb_info *fb_info = dev_get_drvdata(device);
+ return 0;
+}
+
+static ssize_t store_pan(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ struct fb_var_screeninfo var;
+ char *last = NULL;
+ int err;
+
+ var = fb_info->var;
+ var.xoffset = simple_strtoul(buf, &last, 0);
+ last++;
+ if (last - buf >= count)
+ return -EINVAL;
+ var.yoffset = simple_strtoul(last, &last, 0);
+
+ console_lock();
+ err = fb_pan_display(fb_info, &var);
+ console_unlock();
+
+ if (err < 0)
+ return err;
+ return count;
+}
+
+static ssize_t show_pan(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xoffset,
+ fb_info->var.yoffset);
+}
+
+static ssize_t show_name(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", fb_info->fix.id);
+}
+
+static ssize_t store_fbstate(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ u32 state;
+ char *last = NULL;
+
+ state = simple_strtoul(buf, &last, 0);
+
+ console_lock();
+ if (!lock_fb_info(fb_info)) {
+ console_unlock();
+ return -ENODEV;
+ }
+
+ fb_set_suspend(fb_info, (int)state);
+
+ unlock_fb_info(fb_info);
+ console_unlock();
+
+ return count;
+}
+
+static ssize_t show_fbstate(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->state);
+}
+
+#ifdef CONFIG_FB_BACKLIGHT
+static ssize_t store_bl_curve(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ u8 tmp_curve[FB_BACKLIGHT_LEVELS];
+ unsigned int i;
+
+ /* Some drivers don't use framebuffer_alloc(), but those also
+ * don't have backlights.
+ */
+ if (!fb_info || !fb_info->bl_dev)
+ return -ENODEV;
+
+ if (count != (FB_BACKLIGHT_LEVELS / 8 * 24))
+ return -EINVAL;
+
+ for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i)
+ if (sscanf(&buf[i * 24],
+ "%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n",
+ &tmp_curve[i * 8 + 0],
+ &tmp_curve[i * 8 + 1],
+ &tmp_curve[i * 8 + 2],
+ &tmp_curve[i * 8 + 3],
+ &tmp_curve[i * 8 + 4],
+ &tmp_curve[i * 8 + 5],
+ &tmp_curve[i * 8 + 6],
+ &tmp_curve[i * 8 + 7]) != 8)
+ return -EINVAL;
+
+ /* If there has been an error in the input data, we won't
+ * reach this loop.
+ */
+ mutex_lock(&fb_info->bl_curve_mutex);
+ for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i)
+ fb_info->bl_curve[i] = tmp_curve[i];
+ mutex_unlock(&fb_info->bl_curve_mutex);
+
+ return count;
+}
+
+static ssize_t show_bl_curve(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *fb_info = dev_get_drvdata(device);
+ ssize_t len = 0;
+ unsigned int i;
+
+ /* Some drivers don't use framebuffer_alloc(), but those also
+ * don't have backlights.
+ */
+ if (!fb_info || !fb_info->bl_dev)
+ return -ENODEV;
+
+ mutex_lock(&fb_info->bl_curve_mutex);
+ for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8)
+ len += snprintf(&buf[len], PAGE_SIZE,
+ "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+ fb_info->bl_curve[i + 0],
+ fb_info->bl_curve[i + 1],
+ fb_info->bl_curve[i + 2],
+ fb_info->bl_curve[i + 3],
+ fb_info->bl_curve[i + 4],
+ fb_info->bl_curve[i + 5],
+ fb_info->bl_curve[i + 6],
+ fb_info->bl_curve[i + 7]);
+ mutex_unlock(&fb_info->bl_curve_mutex);
+
+ return len;
+}
+#endif
+
+/* When cmap is added back in it should be a binary attribute
+ * not a text one. Consideration should also be given to converting
+ * fbdev to use configfs instead of sysfs */
+static struct device_attribute device_attrs[] = {
+ __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
+ __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
+ __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
+ __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
+ __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
+ __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
+ __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
+ __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
+ __ATTR(name, S_IRUGO, show_name, NULL),
+ __ATTR(stride, S_IRUGO, show_stride, NULL),
+ __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
+ __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
+#ifdef CONFIG_FB_BACKLIGHT
+ __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
+#endif
+};
+
+int fb_init_device(struct fb_info *fb_info)
+{
+ int i, error = 0;
+
+ dev_set_drvdata(fb_info->dev, fb_info);
+
+ fb_info->class_flag |= FB_SYSFS_FLAG_ATTR;
+
+ for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
+ error = device_create_file(fb_info->dev, &device_attrs[i]);
+
+ if (error)
+ break;
+ }
+
+ if (error) {
+ while (--i >= 0)
+ device_remove_file(fb_info->dev, &device_attrs[i]);
+ fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
+ }
+
+ return 0;
+}
+
+void fb_cleanup_device(struct fb_info *fb_info)
+{
+ unsigned int i;
+
+ if (fb_info->class_flag & FB_SYSFS_FLAG_ATTR) {
+ for (i = 0; i < ARRAY_SIZE(device_attrs); i++)
+ device_remove_file(fb_info->dev, &device_attrs[i]);
+
+ fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;
+ }
+}
+
+#ifdef CONFIG_FB_BACKLIGHT
+/* This function generates a linear backlight curve
+ *
+ * 0: off
+ * 1-7: min
+ * 8-127: linear from min to max
+ */
+void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max)
+{
+ unsigned int i, flat, count, range = (max - min);
+
+ mutex_lock(&fb_info->bl_curve_mutex);
+
+ fb_info->bl_curve[0] = off;
+
+ for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
+ fb_info->bl_curve[flat] = min;
+
+ count = FB_BACKLIGHT_LEVELS * 15 / 16;
+ for (i = 0; i < count; ++i)
+ fb_info->bl_curve[flat + i] = min + (range * (i + 1) / count);
+
+ mutex_unlock(&fb_info->bl_curve_mutex);
+}
+EXPORT_SYMBOL_GPL(fb_bl_default_curve);
+#endif
diff --git a/drivers/video/fbdev/core/modedb.c b/drivers/video/fbdev/core/modedb.c
new file mode 100644
index 000000000000..a9a907c440d7
--- /dev/null
+++ b/drivers/video/fbdev/core/modedb.c
@@ -0,0 +1,1137 @@
+/*
+ * linux/drivers/video/modedb.c -- Standard video mode database management
+ *
+ * Copyright (C) 1999 Geert Uytterhoeven
+ *
+ * 2001 - Documented with DocBook
+ * - Brad Douglas <brad@neruo.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+
+#undef DEBUG
+
+#define name_matches(v, s, l) \
+ ((v).name && !strncmp((s), (v).name, (l)) && strlen((v).name) == (l))
+#define res_matches(v, x, y) \
+ ((v).xres == (x) && (v).yres == (y))
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk("modedb %s: " fmt, __func__ , ## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+const char *fb_mode_option;
+EXPORT_SYMBOL_GPL(fb_mode_option);
+
+/*
+ * Standard video mode definitions (taken from XFree86)
+ */
+
+static const struct fb_videomode modedb[] = {
+
+ /* 640x400 @ 70 Hz, 31.5 kHz hsync */
+ { NULL, 70, 640, 400, 39721, 40, 24, 39, 9, 96, 2, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 640x480 @ 60 Hz, 31.5 kHz hsync */
+ { NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 800x600 @ 56 Hz, 35.15 kHz hsync */
+ { NULL, 56, 800, 600, 27777, 128, 24, 22, 1, 72, 2, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 87 Hz interlaced, 35.5 kHz hsync */
+ { NULL, 87, 1024, 768, 22271, 56, 24, 33, 8, 160, 8, 0,
+ FB_VMODE_INTERLACED },
+
+ /* 640x400 @ 85 Hz, 37.86 kHz hsync */
+ { NULL, 85, 640, 400, 31746, 96, 32, 41, 1, 64, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED },
+
+ /* 640x480 @ 72 Hz, 36.5 kHz hsync */
+ { NULL, 72, 640, 480, 31746, 144, 40, 30, 8, 40, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 640x480 @ 75 Hz, 37.50 kHz hsync */
+ { NULL, 75, 640, 480, 31746, 120, 16, 16, 1, 64, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 800x600 @ 60 Hz, 37.8 kHz hsync */
+ { NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 640x480 @ 85 Hz, 43.27 kHz hsync */
+ { NULL, 85, 640, 480, 27777, 80, 56, 25, 1, 56, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 89 Hz interlaced, 44 kHz hsync */
+ { NULL, 89, 1152, 864, 15384, 96, 16, 110, 1, 216, 10, 0,
+ FB_VMODE_INTERLACED },
+ /* 800x600 @ 72 Hz, 48.0 kHz hsync */
+ { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 60 Hz, 48.4 kHz hsync */
+ { NULL, 60, 1024, 768, 15384, 168, 8, 29, 3, 144, 6, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 640x480 @ 100 Hz, 53.01 kHz hsync */
+ { NULL, 100, 640, 480, 21834, 96, 32, 36, 8, 96, 6, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 60 Hz, 53.5 kHz hsync */
+ { NULL, 60, 1152, 864, 11123, 208, 64, 16, 4, 256, 8, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 800x600 @ 85 Hz, 55.84 kHz hsync */
+ { NULL, 85, 800, 600, 16460, 160, 64, 36, 16, 64, 5, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 70 Hz, 56.5 kHz hsync */
+ { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 87 Hz interlaced, 51 kHz hsync */
+ { NULL, 87, 1280, 1024, 12500, 56, 16, 128, 1, 216, 12, 0,
+ FB_VMODE_INTERLACED },
+
+ /* 800x600 @ 100 Hz, 64.02 kHz hsync */
+ { NULL, 100, 800, 600, 14357, 160, 64, 30, 4, 64, 6, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 76 Hz, 62.5 kHz hsync */
+ { NULL, 76, 1024, 768, 11764, 208, 8, 36, 16, 120, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 70 Hz, 62.4 kHz hsync */
+ { NULL, 70, 1152, 864, 10869, 106, 56, 20, 1, 160, 10, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 61 Hz, 64.2 kHz hsync */
+ { NULL, 61, 1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1400x1050 @ 60Hz, 63.9 kHz hsync */
+ { NULL, 60, 1400, 1050, 9259, 136, 40, 13, 1, 112, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1400x1050 @ 75,107 Hz, 82,392 kHz +hsync +vsync*/
+ { NULL, 75, 1400, 1050, 7190, 120, 56, 23, 10, 112, 13,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1400x1050 @ 60 Hz, ? kHz +hsync +vsync*/
+ { NULL, 60, 1400, 1050, 9259, 128, 40, 12, 0, 112, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 85 Hz, 70.24 kHz hsync */
+ { NULL, 85, 1024, 768, 10111, 192, 32, 34, 14, 160, 6, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 78 Hz, 70.8 kHz hsync */
+ { NULL, 78, 1152, 864, 9090, 228, 88, 32, 0, 84, 12, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 70 Hz, 74.59 kHz hsync */
+ { NULL, 70, 1280, 1024, 7905, 224, 32, 28, 8, 160, 8, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1600x1200 @ 60Hz, 75.00 kHz hsync */
+ { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 84 Hz, 76.0 kHz hsync */
+ { NULL, 84, 1152, 864, 7407, 184, 312, 32, 0, 128, 12, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 74 Hz, 78.85 kHz hsync */
+ { NULL, 74, 1280, 1024, 7407, 256, 32, 34, 3, 144, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1024x768 @ 100Hz, 80.21 kHz hsync */
+ { NULL, 100, 1024, 768, 8658, 192, 32, 21, 3, 192, 10, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 76 Hz, 81.13 kHz hsync */
+ { NULL, 76, 1280, 1024, 7407, 248, 32, 34, 3, 104, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1600x1200 @ 70 Hz, 87.50 kHz hsync */
+ { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x864 @ 100 Hz, 89.62 kHz hsync */
+ { NULL, 100, 1152, 864, 7264, 224, 32, 17, 2, 128, 19, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 85 Hz, 91.15 kHz hsync */
+ { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1600x1200 @ 75 Hz, 93.75 kHz hsync */
+ { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1680x1050 @ 60 Hz, 65.191 kHz hsync */
+ { NULL, 60, 1680, 1050, 6848, 280, 104, 30, 3, 176, 6,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1600x1200 @ 85 Hz, 105.77 kHz hsync */
+ { NULL, 85, 1600, 1200, 4545, 272, 16, 37, 4, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x1024 @ 100 Hz, 107.16 kHz hsync */
+ { NULL, 100, 1280, 1024, 5502, 256, 32, 26, 7, 128, 15, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1800x1440 @ 64Hz, 96.15 kHz hsync */
+ { NULL, 64, 1800, 1440, 4347, 304, 96, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1800x1440 @ 70Hz, 104.52 kHz hsync */
+ { NULL, 70, 1800, 1440, 4000, 304, 96, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 512x384 @ 78 Hz, 31.50 kHz hsync */
+ { NULL, 78, 512, 384, 49603, 48, 16, 16, 1, 64, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 512x384 @ 85 Hz, 34.38 kHz hsync */
+ { NULL, 85, 512, 384, 45454, 48, 16, 16, 1, 64, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 320x200 @ 70 Hz, 31.5 kHz hsync, 8:5 aspect ratio */
+ { NULL, 70, 320, 200, 79440, 16, 16, 20, 4, 48, 1, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 320x240 @ 60 Hz, 31.5 kHz hsync, 4:3 aspect ratio */
+ { NULL, 60, 320, 240, 79440, 16, 16, 16, 5, 48, 1, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 320x240 @ 72 Hz, 36.5 kHz hsync */
+ { NULL, 72, 320, 240, 63492, 16, 16, 16, 4, 48, 2, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 400x300 @ 56 Hz, 35.2 kHz hsync, 4:3 aspect ratio */
+ { NULL, 56, 400, 300, 55555, 64, 16, 10, 1, 32, 1, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 400x300 @ 60 Hz, 37.8 kHz hsync */
+ { NULL, 60, 400, 300, 50000, 48, 16, 11, 1, 64, 2, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 400x300 @ 72 Hz, 48.0 kHz hsync */
+ { NULL, 72, 400, 300, 40000, 32, 24, 11, 19, 64, 3, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 480x300 @ 56 Hz, 35.2 kHz hsync, 8:5 aspect ratio */
+ { NULL, 56, 480, 300, 46176, 80, 16, 10, 1, 40, 1, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 480x300 @ 60 Hz, 37.8 kHz hsync */
+ { NULL, 60, 480, 300, 41858, 56, 16, 11, 1, 80, 2, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 480x300 @ 63 Hz, 39.6 kHz hsync */
+ { NULL, 63, 480, 300, 40000, 56, 16, 11, 1, 80, 2, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 480x300 @ 72 Hz, 48.0 kHz hsync */
+ { NULL, 72, 480, 300, 33386, 40, 24, 11, 19, 80, 3, 0,
+ FB_VMODE_DOUBLE },
+
+ /* 1920x1200 @ 60 Hz, 74.5 Khz hsync */
+ { NULL, 60, 1920, 1200, 5177, 128, 336, 1, 38, 208, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1152x768, 60 Hz, PowerBook G4 Titanium I and II */
+ { NULL, 60, 1152, 768, 14047, 158, 26, 29, 3, 136, 6,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1366x768, 60 Hz, 47.403 kHz hsync, WXGA 16:9 aspect ratio */
+ { NULL, 60, 1366, 768, 13806, 120, 10, 14, 3, 32, 5, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 1280x800, 60 Hz, 47.403 kHz hsync, WXGA 16:10 aspect ratio */
+ { NULL, 60, 1280, 800, 12048, 200, 64, 24, 1, 136, 3, 0,
+ FB_VMODE_NONINTERLACED },
+
+ /* 720x576i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */
+ { NULL, 50, 720, 576, 74074, 64, 16, 39, 5, 64, 5, 0,
+ FB_VMODE_INTERLACED },
+
+ /* 800x520i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */
+ { NULL, 50, 800, 520, 58823, 144, 64, 72, 28, 80, 5, 0,
+ FB_VMODE_INTERLACED },
+
+ /* 864x480 @ 60 Hz, 35.15 kHz hsync */
+ { NULL, 60, 864, 480, 27777, 1, 1, 1, 1, 0, 0,
+ 0, FB_VMODE_NONINTERLACED },
+};
+
+#ifdef CONFIG_FB_MODE_HELPERS
+const struct fb_videomode cea_modes[64] = {
+ /* #1: 640x480p@59.94/60Hz */
+ [1] = {
+ NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #3: 720x480p@59.94/60Hz */
+ [3] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #5: 1920x1080i@59.94/60Hz */
+ [5] = {
+ NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #7: 720(1440)x480iH@59.94/60Hz */
+ [7] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #9: 720(1440)x240pH@59.94/60Hz */
+ [9] = {
+ NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #18: 720x576pH@50Hz */
+ [18] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #19: 1280x720p@50Hz */
+ [19] = {
+ NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #20: 1920x1080i@50Hz */
+ [20] = {
+ NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED, 0,
+ },
+ /* #32: 1920x1080p@23.98/24Hz */
+ [32] = {
+ NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+ /* #35: (2880)x480p4x@59.94/60Hz */
+ [35] = {
+ NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0,
+ FB_VMODE_NONINTERLACED, 0,
+ },
+};
+
+const struct fb_videomode vesa_modes[] = {
+ /* 0 640x350-85 VESA */
+ { NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3,
+ FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA},
+ /* 1 640x400-85 VESA */
+ { NULL, 85, 640, 400, 31746, 96, 32, 41, 01, 64, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 2 720x400-85 VESA */
+ { NULL, 85, 721, 400, 28169, 108, 36, 42, 01, 72, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 3 640x480-60 VESA */
+ { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 4 640x480-72 VESA */
+ { NULL, 72, 640, 480, 31746, 128, 24, 29, 9, 40, 2,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 5 640x480-75 VESA */
+ { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 6 640x480-85 VESA */
+ { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 7 800x600-56 VESA */
+ { NULL, 56, 800, 600, 27777, 128, 24, 22, 01, 72, 2,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 8 800x600-60 VESA */
+ { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 9 800x600-72 VESA */
+ { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 10 800x600-75 VESA */
+ { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 11 800x600-85 VESA */
+ { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 12 1024x768i-43 VESA */
+ { NULL, 43, 1024, 768, 22271, 56, 8, 41, 0, 176, 8,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED, FB_MODE_IS_VESA },
+ /* 13 1024x768-60 VESA */
+ { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 14 1024x768-70 VESA */
+ { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6,
+ 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 15 1024x768-75 VESA */
+ { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 16 1024x768-85 VESA */
+ { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 17 1152x864-75 VESA */
+ { NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 18 1280x960-60 VESA */
+ { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 19 1280x960-85 VESA */
+ { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 20 1280x1024-60 VESA */
+ { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 21 1280x1024-75 VESA */
+ { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 22 1280x1024-85 VESA */
+ { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 23 1600x1200-60 VESA */
+ { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 24 1600x1200-65 VESA */
+ { NULL, 65, 1600, 1200, 5698, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 25 1600x1200-70 VESA */
+ { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 26 1600x1200-75 VESA */
+ { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 27 1600x1200-85 VESA */
+ { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 28 1792x1344-60 VESA */
+ { NULL, 60, 1792, 1344, 4882, 328, 128, 46, 1, 200, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 29 1792x1344-75 VESA */
+ { NULL, 75, 1792, 1344, 3831, 352, 96, 69, 1, 216, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 30 1856x1392-60 VESA */
+ { NULL, 60, 1856, 1392, 4580, 352, 96, 43, 1, 224, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 31 1856x1392-75 VESA */
+ { NULL, 75, 1856, 1392, 3472, 352, 128, 104, 1, 224, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 32 1920x1440-60 VESA */
+ { NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 200, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+ /* 33 1920x1440-75 VESA */
+ { NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3,
+ FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA },
+};
+EXPORT_SYMBOL(vesa_modes);
+#endif /* CONFIG_FB_MODE_HELPERS */
+
+/**
+ * fb_try_mode - test a video mode
+ * @var: frame buffer user defined part of display
+ * @info: frame buffer info structure
+ * @mode: frame buffer video mode structure
+ * @bpp: color depth in bits per pixel
+ *
+ * Tries a video mode to test it's validity for device @info.
+ *
+ * Returns 1 on success.
+ *
+ */
+
+static int fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info,
+ const struct fb_videomode *mode, unsigned int bpp)
+{
+ int err = 0;
+
+ DPRINTK("Trying mode %s %dx%d-%d@%d\n",
+ mode->name ? mode->name : "noname",
+ mode->xres, mode->yres, bpp, mode->refresh);
+ var->xres = mode->xres;
+ var->yres = mode->yres;
+ var->xres_virtual = mode->xres;
+ var->yres_virtual = mode->yres;
+ var->xoffset = 0;
+ var->yoffset = 0;
+ var->bits_per_pixel = bpp;
+ var->activate |= FB_ACTIVATE_TEST;
+ var->pixclock = mode->pixclock;
+ var->left_margin = mode->left_margin;
+ var->right_margin = mode->right_margin;
+ var->upper_margin = mode->upper_margin;
+ var->lower_margin = mode->lower_margin;
+ var->hsync_len = mode->hsync_len;
+ var->vsync_len = mode->vsync_len;
+ var->sync = mode->sync;
+ var->vmode = mode->vmode;
+ if (info->fbops->fb_check_var)
+ err = info->fbops->fb_check_var(var, info);
+ var->activate &= ~FB_ACTIVATE_TEST;
+ return err;
+}
+
+/**
+ * fb_find_mode - finds a valid video mode
+ * @var: frame buffer user defined part of display
+ * @info: frame buffer info structure
+ * @mode_option: string video mode to find
+ * @db: video mode database
+ * @dbsize: size of @db
+ * @default_mode: default video mode to fall back to
+ * @default_bpp: default color depth in bits per pixel
+ *
+ * Finds a suitable video mode, starting with the specified mode
+ * in @mode_option with fallback to @default_mode. If
+ * @default_mode fails, all modes in the video mode database will
+ * be tried.
+ *
+ * Valid mode specifiers for @mode_option:
+ *
+ * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or
+ * <name>[-<bpp>][@<refresh>]
+ *
+ * with <xres>, <yres>, <bpp> and <refresh> decimal numbers and
+ * <name> a string.
+ *
+ * If 'M' is present after yres (and before refresh/bpp if present),
+ * the function will compute the timings using VESA(tm) Coordinated
+ * Video Timings (CVT). If 'R' is present after 'M', will compute with
+ * reduced blanking (for flatpanels). If 'i' is present, compute
+ * interlaced mode. If 'm' is present, add margins equal to 1.8%
+ * of xres rounded down to 8 pixels, and 1.8% of yres. The char
+ * 'i' and 'm' must be after 'M' and 'R'. Example:
+ *
+ * 1024x768MR-8@60m - Reduced blank with margins at 60Hz.
+ *
+ * NOTE: The passed struct @var is _not_ cleared! This allows you
+ * to supply values for e.g. the grayscale and accel_flags fields.
+ *
+ * Returns zero for failure, 1 if using specified @mode_option,
+ * 2 if using specified @mode_option with an ignored refresh rate,
+ * 3 if default mode is used, 4 if fall back to any valid mode.
+ *
+ */
+
+int fb_find_mode(struct fb_var_screeninfo *var,
+ struct fb_info *info, const char *mode_option,
+ const struct fb_videomode *db, unsigned int dbsize,
+ const struct fb_videomode *default_mode,
+ unsigned int default_bpp)
+{
+ int i;
+
+ /* Set up defaults */
+ if (!db) {
+ db = modedb;
+ dbsize = ARRAY_SIZE(modedb);
+ }
+
+ if (!default_mode)
+ default_mode = &db[0];
+
+ if (!default_bpp)
+ default_bpp = 8;
+
+ /* Did the user specify a video mode? */
+ if (!mode_option)
+ mode_option = fb_mode_option;
+ if (mode_option) {
+ const char *name = mode_option;
+ unsigned int namelen = strlen(name);
+ int res_specified = 0, bpp_specified = 0, refresh_specified = 0;
+ unsigned int xres = 0, yres = 0, bpp = default_bpp, refresh = 0;
+ int yres_specified = 0, cvt = 0, rb = 0, interlace = 0;
+ int margins = 0;
+ u32 best, diff, tdiff;
+
+ for (i = namelen-1; i >= 0; i--) {
+ switch (name[i]) {
+ case '@':
+ namelen = i;
+ if (!refresh_specified && !bpp_specified &&
+ !yres_specified) {
+ refresh = simple_strtol(&name[i+1], NULL,
+ 10);
+ refresh_specified = 1;
+ if (cvt || rb)
+ cvt = 0;
+ } else
+ goto done;
+ break;
+ case '-':
+ namelen = i;
+ if (!bpp_specified && !yres_specified) {
+ bpp = simple_strtol(&name[i+1], NULL,
+ 10);
+ bpp_specified = 1;
+ if (cvt || rb)
+ cvt = 0;
+ } else
+ goto done;
+ break;
+ case 'x':
+ if (!yres_specified) {
+ yres = simple_strtol(&name[i+1], NULL,
+ 10);
+ yres_specified = 1;
+ } else
+ goto done;
+ break;
+ case '0' ... '9':
+ break;
+ case 'M':
+ if (!yres_specified)
+ cvt = 1;
+ break;
+ case 'R':
+ if (!cvt)
+ rb = 1;
+ break;
+ case 'm':
+ if (!cvt)
+ margins = 1;
+ break;
+ case 'i':
+ if (!cvt)
+ interlace = 1;
+ break;
+ default:
+ goto done;
+ }
+ }
+ if (i < 0 && yres_specified) {
+ xres = simple_strtol(name, NULL, 10);
+ res_specified = 1;
+ }
+done:
+ if (cvt) {
+ struct fb_videomode cvt_mode;
+ int ret;
+
+ DPRINTK("CVT mode %dx%d@%dHz%s%s%s\n", xres, yres,
+ (refresh) ? refresh : 60,
+ (rb) ? " reduced blanking" : "",
+ (margins) ? " with margins" : "",
+ (interlace) ? " interlaced" : "");
+
+ memset(&cvt_mode, 0, sizeof(cvt_mode));
+ cvt_mode.xres = xres;
+ cvt_mode.yres = yres;
+ cvt_mode.refresh = (refresh) ? refresh : 60;
+
+ if (interlace)
+ cvt_mode.vmode |= FB_VMODE_INTERLACED;
+ else
+ cvt_mode.vmode &= ~FB_VMODE_INTERLACED;
+
+ ret = fb_find_mode_cvt(&cvt_mode, margins, rb);
+
+ if (!ret && !fb_try_mode(var, info, &cvt_mode, bpp)) {
+ DPRINTK("modedb CVT: CVT mode ok\n");
+ return 1;
+ }
+
+ DPRINTK("CVT mode invalid, getting mode from database\n");
+ }
+
+ DPRINTK("Trying specified video mode%s %ix%i\n",
+ refresh_specified ? "" : " (ignoring refresh rate)",
+ xres, yres);
+
+ if (!refresh_specified) {
+ /*
+ * If the caller has provided a custom mode database and
+ * a valid monspecs structure, we look for the mode with
+ * the highest refresh rate. Otherwise we play it safe
+ * it and try to find a mode with a refresh rate closest
+ * to the standard 60 Hz.
+ */
+ if (db != modedb &&
+ info->monspecs.vfmin && info->monspecs.vfmax &&
+ info->monspecs.hfmin && info->monspecs.hfmax &&
+ info->monspecs.dclkmax) {
+ refresh = 1000;
+ } else {
+ refresh = 60;
+ }
+ }
+
+ diff = -1;
+ best = -1;
+ for (i = 0; i < dbsize; i++) {
+ if ((name_matches(db[i], name, namelen) ||
+ (res_specified && res_matches(db[i], xres, yres))) &&
+ !fb_try_mode(var, info, &db[i], bpp)) {
+ if (refresh_specified && db[i].refresh == refresh)
+ return 1;
+
+ if (abs(db[i].refresh - refresh) < diff) {
+ diff = abs(db[i].refresh - refresh);
+ best = i;
+ }
+ }
+ }
+ if (best != -1) {
+ fb_try_mode(var, info, &db[best], bpp);
+ return (refresh_specified) ? 2 : 1;
+ }
+
+ diff = 2 * (xres + yres);
+ best = -1;
+ DPRINTK("Trying best-fit modes\n");
+ for (i = 0; i < dbsize; i++) {
+ DPRINTK("Trying %ix%i\n", db[i].xres, db[i].yres);
+ if (!fb_try_mode(var, info, &db[i], bpp)) {
+ tdiff = abs(db[i].xres - xres) +
+ abs(db[i].yres - yres);
+
+ /*
+ * Penalize modes with resolutions smaller
+ * than requested.
+ */
+ if (xres > db[i].xres || yres > db[i].yres)
+ tdiff += xres + yres;
+
+ if (diff > tdiff) {
+ diff = tdiff;
+ best = i;
+ }
+ }
+ }
+ if (best != -1) {
+ fb_try_mode(var, info, &db[best], bpp);
+ return 5;
+ }
+ }
+
+ DPRINTK("Trying default video mode\n");
+ if (!fb_try_mode(var, info, default_mode, default_bpp))
+ return 3;
+
+ DPRINTK("Trying all modes\n");
+ for (i = 0; i < dbsize; i++)
+ if (!fb_try_mode(var, info, &db[i], default_bpp))
+ return 4;
+
+ DPRINTK("No valid mode found\n");
+ return 0;
+}
+
+/**
+ * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode
+ * @mode: pointer to struct fb_videomode
+ * @var: pointer to struct fb_var_screeninfo
+ */
+void fb_var_to_videomode(struct fb_videomode *mode,
+ const struct fb_var_screeninfo *var)
+{
+ u32 pixclock, hfreq, htotal, vtotal;
+
+ mode->name = NULL;
+ mode->xres = var->xres;
+ mode->yres = var->yres;
+ mode->pixclock = var->pixclock;
+ mode->hsync_len = var->hsync_len;
+ mode->vsync_len = var->vsync_len;
+ mode->left_margin = var->left_margin;
+ mode->right_margin = var->right_margin;
+ mode->upper_margin = var->upper_margin;
+ mode->lower_margin = var->lower_margin;
+ mode->sync = var->sync;
+ mode->vmode = var->vmode & FB_VMODE_MASK;
+ mode->flag = FB_MODE_IS_FROM_VAR;
+ mode->refresh = 0;
+
+ if (!var->pixclock)
+ return;
+
+ pixclock = PICOS2KHZ(var->pixclock) * 1000;
+
+ htotal = var->xres + var->right_margin + var->hsync_len +
+ var->left_margin;
+ vtotal = var->yres + var->lower_margin + var->vsync_len +
+ var->upper_margin;
+
+ if (var->vmode & FB_VMODE_INTERLACED)
+ vtotal /= 2;
+ if (var->vmode & FB_VMODE_DOUBLE)
+ vtotal *= 2;
+
+ hfreq = pixclock/htotal;
+ mode->refresh = hfreq/vtotal;
+}
+
+/**
+ * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo
+ * @var: pointer to struct fb_var_screeninfo
+ * @mode: pointer to struct fb_videomode
+ */
+void fb_videomode_to_var(struct fb_var_screeninfo *var,
+ const struct fb_videomode *mode)
+{
+ var->xres = mode->xres;
+ var->yres = mode->yres;
+ var->xres_virtual = mode->xres;
+ var->yres_virtual = mode->yres;
+ var->xoffset = 0;
+ var->yoffset = 0;
+ var->pixclock = mode->pixclock;
+ var->left_margin = mode->left_margin;
+ var->right_margin = mode->right_margin;
+ var->upper_margin = mode->upper_margin;
+ var->lower_margin = mode->lower_margin;
+ var->hsync_len = mode->hsync_len;
+ var->vsync_len = mode->vsync_len;
+ var->sync = mode->sync;
+ var->vmode = mode->vmode & FB_VMODE_MASK;
+}
+
+/**
+ * fb_mode_is_equal - compare 2 videomodes
+ * @mode1: first videomode
+ * @mode2: second videomode
+ *
+ * RETURNS:
+ * 1 if equal, 0 if not
+ */
+int fb_mode_is_equal(const struct fb_videomode *mode1,
+ const struct fb_videomode *mode2)
+{
+ return (mode1->xres == mode2->xres &&
+ mode1->yres == mode2->yres &&
+ mode1->pixclock == mode2->pixclock &&
+ mode1->hsync_len == mode2->hsync_len &&
+ mode1->vsync_len == mode2->vsync_len &&
+ mode1->left_margin == mode2->left_margin &&
+ mode1->right_margin == mode2->right_margin &&
+ mode1->upper_margin == mode2->upper_margin &&
+ mode1->lower_margin == mode2->lower_margin &&
+ mode1->sync == mode2->sync &&
+ mode1->vmode == mode2->vmode);
+}
+
+/**
+ * fb_find_best_mode - find best matching videomode
+ * @var: pointer to struct fb_var_screeninfo
+ * @head: pointer to struct list_head of modelist
+ *
+ * RETURNS:
+ * struct fb_videomode, NULL if none found
+ *
+ * IMPORTANT:
+ * This function assumes that all modelist entries in
+ * info->modelist are valid.
+ *
+ * NOTES:
+ * Finds best matching videomode which has an equal or greater dimension than
+ * var->xres and var->yres. If more than 1 videomode is found, will return
+ * the videomode with the highest refresh rate
+ */
+const struct fb_videomode *fb_find_best_mode(const struct fb_var_screeninfo *var,
+ struct list_head *head)
+{
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ struct fb_videomode *mode, *best = NULL;
+ u32 diff = -1;
+
+ list_for_each(pos, head) {
+ u32 d;
+
+ modelist = list_entry(pos, struct fb_modelist, list);
+ mode = &modelist->mode;
+
+ if (mode->xres >= var->xres && mode->yres >= var->yres) {
+ d = (mode->xres - var->xres) +
+ (mode->yres - var->yres);
+ if (diff > d) {
+ diff = d;
+ best = mode;
+ } else if (diff == d && best &&
+ mode->refresh > best->refresh)
+ best = mode;
+ }
+ }
+ return best;
+}
+
+/**
+ * fb_find_nearest_mode - find closest videomode
+ *
+ * @mode: pointer to struct fb_videomode
+ * @head: pointer to modelist
+ *
+ * Finds best matching videomode, smaller or greater in dimension.
+ * If more than 1 videomode is found, will return the videomode with
+ * the closest refresh rate.
+ */
+const struct fb_videomode *fb_find_nearest_mode(const struct fb_videomode *mode,
+ struct list_head *head)
+{
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ struct fb_videomode *cmode, *best = NULL;
+ u32 diff = -1, diff_refresh = -1;
+
+ list_for_each(pos, head) {
+ u32 d;
+
+ modelist = list_entry(pos, struct fb_modelist, list);
+ cmode = &modelist->mode;
+
+ d = abs(cmode->xres - mode->xres) +
+ abs(cmode->yres - mode->yres);
+ if (diff > d) {
+ diff = d;
+ diff_refresh = abs(cmode->refresh - mode->refresh);
+ best = cmode;
+ } else if (diff == d) {
+ d = abs(cmode->refresh - mode->refresh);
+ if (diff_refresh > d) {
+ diff_refresh = d;
+ best = cmode;
+ }
+ }
+ }
+
+ return best;
+}
+
+/**
+ * fb_match_mode - find a videomode which exactly matches the timings in var
+ * @var: pointer to struct fb_var_screeninfo
+ * @head: pointer to struct list_head of modelist
+ *
+ * RETURNS:
+ * struct fb_videomode, NULL if none found
+ */
+const struct fb_videomode *fb_match_mode(const struct fb_var_screeninfo *var,
+ struct list_head *head)
+{
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ struct fb_videomode *m, mode;
+
+ fb_var_to_videomode(&mode, var);
+ list_for_each(pos, head) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ m = &modelist->mode;
+ if (fb_mode_is_equal(m, &mode))
+ return m;
+ }
+ return NULL;
+}
+
+/**
+ * fb_add_videomode - adds videomode entry to modelist
+ * @mode: videomode to add
+ * @head: struct list_head of modelist
+ *
+ * NOTES:
+ * Will only add unmatched mode entries
+ */
+int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head)
+{
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ struct fb_videomode *m;
+ int found = 0;
+
+ list_for_each(pos, head) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ m = &modelist->mode;
+ if (fb_mode_is_equal(m, mode)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ modelist = kmalloc(sizeof(struct fb_modelist),
+ GFP_KERNEL);
+
+ if (!modelist)
+ return -ENOMEM;
+ modelist->mode = *mode;
+ list_add(&modelist->list, head);
+ }
+ return 0;
+}
+
+/**
+ * fb_delete_videomode - removed videomode entry from modelist
+ * @mode: videomode to remove
+ * @head: struct list_head of modelist
+ *
+ * NOTES:
+ * Will remove all matching mode entries
+ */
+void fb_delete_videomode(const struct fb_videomode *mode,
+ struct list_head *head)
+{
+ struct list_head *pos, *n;
+ struct fb_modelist *modelist;
+ struct fb_videomode *m;
+
+ list_for_each_safe(pos, n, head) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ m = &modelist->mode;
+ if (fb_mode_is_equal(m, mode)) {
+ list_del(pos);
+ kfree(pos);
+ }
+ }
+}
+
+/**
+ * fb_destroy_modelist - destroy modelist
+ * @head: struct list_head of modelist
+ */
+void fb_destroy_modelist(struct list_head *head)
+{
+ struct list_head *pos, *n;
+
+ list_for_each_safe(pos, n, head) {
+ list_del(pos);
+ kfree(pos);
+ }
+}
+EXPORT_SYMBOL_GPL(fb_destroy_modelist);
+
+/**
+ * fb_videomode_to_modelist - convert mode array to mode list
+ * @modedb: array of struct fb_videomode
+ * @num: number of entries in array
+ * @head: struct list_head of modelist
+ */
+void fb_videomode_to_modelist(const struct fb_videomode *modedb, int num,
+ struct list_head *head)
+{
+ int i;
+
+ INIT_LIST_HEAD(head);
+
+ for (i = 0; i < num; i++) {
+ if (fb_add_videomode(&modedb[i], head))
+ return;
+ }
+}
+
+const struct fb_videomode *fb_find_best_display(const struct fb_monspecs *specs,
+ struct list_head *head)
+{
+ struct list_head *pos;
+ struct fb_modelist *modelist;
+ const struct fb_videomode *m, *m1 = NULL, *md = NULL, *best = NULL;
+ int first = 0;
+
+ if (!head->prev || !head->next || list_empty(head))
+ goto finished;
+
+ /* get the first detailed mode and the very first mode */
+ list_for_each(pos, head) {
+ modelist = list_entry(pos, struct fb_modelist, list);
+ m = &modelist->mode;
+
+ if (!first) {
+ m1 = m;
+ first = 1;
+ }
+
+ if (m->flag & FB_MODE_IS_FIRST) {
+ md = m;
+ break;
+ }
+ }
+
+ /* first detailed timing is preferred */
+ if (specs->misc & FB_MISC_1ST_DETAIL) {
+ best = md;
+ goto finished;
+ }
+
+ /* find best mode based on display width and height */
+ if (specs->max_x && specs->max_y) {
+ struct fb_var_screeninfo var;
+
+ memset(&var, 0, sizeof(struct fb_var_screeninfo));
+ var.xres = (specs->max_x * 7200)/254;
+ var.yres = (specs->max_y * 7200)/254;
+ m = fb_find_best_mode(&var, head);
+ if (m) {
+ best = m;
+ goto finished;
+ }
+ }
+
+ /* use first detailed mode */
+ if (md) {
+ best = md;
+ goto finished;
+ }
+
+ /* last resort, use the very first mode */
+ best = m1;
+finished:
+ return best;
+}
+EXPORT_SYMBOL(fb_find_best_display);
+
+EXPORT_SYMBOL(fb_videomode_to_var);
+EXPORT_SYMBOL(fb_var_to_videomode);
+EXPORT_SYMBOL(fb_mode_is_equal);
+EXPORT_SYMBOL(fb_add_videomode);
+EXPORT_SYMBOL(fb_match_mode);
+EXPORT_SYMBOL(fb_find_best_mode);
+EXPORT_SYMBOL(fb_find_nearest_mode);
+EXPORT_SYMBOL(fb_videomode_to_modelist);
+EXPORT_SYMBOL(fb_find_mode);
+EXPORT_SYMBOL(fb_find_mode_cvt);
diff --git a/drivers/video/fbdev/core/svgalib.c b/drivers/video/fbdev/core/svgalib.c
new file mode 100644
index 000000000000..9e01322fabe3
--- /dev/null
+++ b/drivers/video/fbdev/core/svgalib.c
@@ -0,0 +1,672 @@
+/*
+ * Common utility functions for VGA-based graphics cards.
+ *
+ * Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * Some parts are based on David Boucher's viafb (http://davesdomain.org.uk/viafb/)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <linux/svga.h>
+#include <asm/types.h>
+#include <asm/io.h>
+
+
+/* Write a CRT register value spread across multiple registers */
+void svga_wcrt_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value)
+{
+ u8 regval, bitval, bitnum;
+
+ while (regset->regnum != VGA_REGSET_END_VAL) {
+ regval = vga_rcrt(regbase, regset->regnum);
+ bitnum = regset->lowbit;
+ while (bitnum <= regset->highbit) {
+ bitval = 1 << bitnum;
+ regval = regval & ~bitval;
+ if (value & 1) regval = regval | bitval;
+ bitnum ++;
+ value = value >> 1;
+ }
+ vga_wcrt(regbase, regset->regnum, regval);
+ regset ++;
+ }
+}
+
+/* Write a sequencer register value spread across multiple registers */
+void svga_wseq_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value)
+{
+ u8 regval, bitval, bitnum;
+
+ while (regset->regnum != VGA_REGSET_END_VAL) {
+ regval = vga_rseq(regbase, regset->regnum);
+ bitnum = regset->lowbit;
+ while (bitnum <= regset->highbit) {
+ bitval = 1 << bitnum;
+ regval = regval & ~bitval;
+ if (value & 1) regval = regval | bitval;
+ bitnum ++;
+ value = value >> 1;
+ }
+ vga_wseq(regbase, regset->regnum, regval);
+ regset ++;
+ }
+}
+
+static unsigned int svga_regset_size(const struct vga_regset *regset)
+{
+ u8 count = 0;
+
+ while (regset->regnum != VGA_REGSET_END_VAL) {
+ count += regset->highbit - regset->lowbit + 1;
+ regset ++;
+ }
+ return 1 << count;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+
+/* Set graphics controller registers to sane values */
+void svga_set_default_gfx_regs(void __iomem *regbase)
+{
+ /* All standard GFX registers (GR00 - GR08) */
+ vga_wgfx(regbase, VGA_GFX_SR_VALUE, 0x00);
+ vga_wgfx(regbase, VGA_GFX_SR_ENABLE, 0x00);
+ vga_wgfx(regbase, VGA_GFX_COMPARE_VALUE, 0x00);
+ vga_wgfx(regbase, VGA_GFX_DATA_ROTATE, 0x00);
+ vga_wgfx(regbase, VGA_GFX_PLANE_READ, 0x00);
+ vga_wgfx(regbase, VGA_GFX_MODE, 0x00);
+/* vga_wgfx(regbase, VGA_GFX_MODE, 0x20); */
+/* vga_wgfx(regbase, VGA_GFX_MODE, 0x40); */
+ vga_wgfx(regbase, VGA_GFX_MISC, 0x05);
+/* vga_wgfx(regbase, VGA_GFX_MISC, 0x01); */
+ vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x0F);
+ vga_wgfx(regbase, VGA_GFX_BIT_MASK, 0xFF);
+}
+
+/* Set attribute controller registers to sane values */
+void svga_set_default_atc_regs(void __iomem *regbase)
+{
+ u8 count;
+
+ vga_r(regbase, 0x3DA);
+ vga_w(regbase, VGA_ATT_W, 0x00);
+
+ /* All standard ATC registers (AR00 - AR14) */
+ for (count = 0; count <= 0xF; count ++)
+ svga_wattr(regbase, count, count);
+
+ svga_wattr(regbase, VGA_ATC_MODE, 0x01);
+/* svga_wattr(regbase, VGA_ATC_MODE, 0x41); */
+ svga_wattr(regbase, VGA_ATC_OVERSCAN, 0x00);
+ svga_wattr(regbase, VGA_ATC_PLANE_ENABLE, 0x0F);
+ svga_wattr(regbase, VGA_ATC_PEL, 0x00);
+ svga_wattr(regbase, VGA_ATC_COLOR_PAGE, 0x00);
+
+ vga_r(regbase, 0x3DA);
+ vga_w(regbase, VGA_ATT_W, 0x20);
+}
+
+/* Set sequencer registers to sane values */
+void svga_set_default_seq_regs(void __iomem *regbase)
+{
+ /* Standard sequencer registers (SR01 - SR04), SR00 is not set */
+ vga_wseq(regbase, VGA_SEQ_CLOCK_MODE, VGA_SR01_CHAR_CLK_8DOTS);
+ vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, VGA_SR02_ALL_PLANES);
+ vga_wseq(regbase, VGA_SEQ_CHARACTER_MAP, 0x00);
+/* vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE | VGA_SR04_CHN_4M); */
+ vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE);
+}
+
+/* Set CRTC registers to sane values */
+void svga_set_default_crt_regs(void __iomem *regbase)
+{
+ /* Standard CRT registers CR03 CR08 CR09 CR14 CR17 */
+ svga_wcrt_mask(regbase, 0x03, 0x80, 0x80); /* Enable vertical retrace EVRA */
+ vga_wcrt(regbase, VGA_CRTC_PRESET_ROW, 0);
+ svga_wcrt_mask(regbase, VGA_CRTC_MAX_SCAN, 0, 0x1F);
+ vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0);
+ vga_wcrt(regbase, VGA_CRTC_MODE, 0xE3);
+}
+
+void svga_set_textmode_vga_regs(void __iomem *regbase)
+{
+ /* svga_wseq_mask(regbase, 0x1, 0x00, 0x01); */ /* Switch 8/9 pixel per char */
+ vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM);
+ vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, 0x03);
+
+ vga_wcrt(regbase, VGA_CRTC_MAX_SCAN, 0x0f); /* 0x4f */
+ vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0x1f);
+ svga_wcrt_mask(regbase, VGA_CRTC_MODE, 0x23, 0x7f);
+
+ vga_wcrt(regbase, VGA_CRTC_CURSOR_START, 0x0d);
+ vga_wcrt(regbase, VGA_CRTC_CURSOR_END, 0x0e);
+ vga_wcrt(regbase, VGA_CRTC_CURSOR_HI, 0x00);
+ vga_wcrt(regbase, VGA_CRTC_CURSOR_LO, 0x00);
+
+ vga_wgfx(regbase, VGA_GFX_MODE, 0x10); /* Odd/even memory mode */
+ vga_wgfx(regbase, VGA_GFX_MISC, 0x0E); /* Misc graphics register - text mode enable */
+ vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x00);
+
+ vga_r(regbase, 0x3DA);
+ vga_w(regbase, VGA_ATT_W, 0x00);
+
+ svga_wattr(regbase, 0x10, 0x0C); /* Attribute Mode Control Register - text mode, blinking and line graphics */
+ svga_wattr(regbase, 0x13, 0x08); /* Horizontal Pixel Panning Register */
+
+ vga_r(regbase, 0x3DA);
+ vga_w(regbase, VGA_ATT_W, 0x20);
+}
+
+#if 0
+void svga_dump_var(struct fb_var_screeninfo *var, int node)
+{
+ pr_debug("fb%d: var.vmode : 0x%X\n", node, var->vmode);
+ pr_debug("fb%d: var.xres : %d\n", node, var->xres);
+ pr_debug("fb%d: var.yres : %d\n", node, var->yres);
+ pr_debug("fb%d: var.bits_per_pixel: %d\n", node, var->bits_per_pixel);
+ pr_debug("fb%d: var.xres_virtual : %d\n", node, var->xres_virtual);
+ pr_debug("fb%d: var.yres_virtual : %d\n", node, var->yres_virtual);
+ pr_debug("fb%d: var.left_margin : %d\n", node, var->left_margin);
+ pr_debug("fb%d: var.right_margin : %d\n", node, var->right_margin);
+ pr_debug("fb%d: var.upper_margin : %d\n", node, var->upper_margin);
+ pr_debug("fb%d: var.lower_margin : %d\n", node, var->lower_margin);
+ pr_debug("fb%d: var.hsync_len : %d\n", node, var->hsync_len);
+ pr_debug("fb%d: var.vsync_len : %d\n", node, var->vsync_len);
+ pr_debug("fb%d: var.sync : 0x%X\n", node, var->sync);
+ pr_debug("fb%d: var.pixclock : %d\n\n", node, var->pixclock);
+}
+#endif /* 0 */
+
+
+/* ------------------------------------------------------------------------- */
+
+
+void svga_settile(struct fb_info *info, struct fb_tilemap *map)
+{
+ const u8 *font = map->data;
+ u8 __iomem *fb = (u8 __iomem *)info->screen_base;
+ int i, c;
+
+ if ((map->width != 8) || (map->height != 16) ||
+ (map->depth != 1) || (map->length != 256)) {
+ fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n",
+ map->width, map->height, map->depth, map->length);
+ return;
+ }
+
+ fb += 2;
+ for (c = 0; c < map->length; c++) {
+ for (i = 0; i < map->height; i++) {
+ fb_writeb(font[i], fb + i * 4);
+// fb[i * 4] = font[i];
+ }
+ fb += 128;
+ font += map->height;
+ }
+}
+
+/* Copy area in text (tileblit) mode */
+void svga_tilecopy(struct fb_info *info, struct fb_tilearea *area)
+{
+ int dx, dy;
+ /* colstride is halved in this function because u16 are used */
+ int colstride = 1 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
+ int rowstride = colstride * (info->var.xres_virtual / 8);
+ u16 __iomem *fb = (u16 __iomem *) info->screen_base;
+ u16 __iomem *src, *dst;
+
+ if ((area->sy > area->dy) ||
+ ((area->sy == area->dy) && (area->sx > area->dx))) {
+ src = fb + area->sx * colstride + area->sy * rowstride;
+ dst = fb + area->dx * colstride + area->dy * rowstride;
+ } else {
+ src = fb + (area->sx + area->width - 1) * colstride
+ + (area->sy + area->height - 1) * rowstride;
+ dst = fb + (area->dx + area->width - 1) * colstride
+ + (area->dy + area->height - 1) * rowstride;
+
+ colstride = -colstride;
+ rowstride = -rowstride;
+ }
+
+ for (dy = 0; dy < area->height; dy++) {
+ u16 __iomem *src2 = src;
+ u16 __iomem *dst2 = dst;
+ for (dx = 0; dx < area->width; dx++) {
+ fb_writew(fb_readw(src2), dst2);
+// *dst2 = *src2;
+ src2 += colstride;
+ dst2 += colstride;
+ }
+ src += rowstride;
+ dst += rowstride;
+ }
+}
+
+/* Fill area in text (tileblit) mode */
+void svga_tilefill(struct fb_info *info, struct fb_tilerect *rect)
+{
+ int dx, dy;
+ int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
+ int rowstride = colstride * (info->var.xres_virtual / 8);
+ int attr = (0x0F & rect->bg) << 4 | (0x0F & rect->fg);
+ u8 __iomem *fb = (u8 __iomem *)info->screen_base;
+ fb += rect->sx * colstride + rect->sy * rowstride;
+
+ for (dy = 0; dy < rect->height; dy++) {
+ u8 __iomem *fb2 = fb;
+ for (dx = 0; dx < rect->width; dx++) {
+ fb_writeb(rect->index, fb2);
+ fb_writeb(attr, fb2 + 1);
+ fb2 += colstride;
+ }
+ fb += rowstride;
+ }
+}
+
+/* Write text in text (tileblit) mode */
+void svga_tileblit(struct fb_info *info, struct fb_tileblit *blit)
+{
+ int dx, dy, i;
+ int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
+ int rowstride = colstride * (info->var.xres_virtual / 8);
+ int attr = (0x0F & blit->bg) << 4 | (0x0F & blit->fg);
+ u8 __iomem *fb = (u8 __iomem *)info->screen_base;
+ fb += blit->sx * colstride + blit->sy * rowstride;
+
+ i=0;
+ for (dy=0; dy < blit->height; dy ++) {
+ u8 __iomem *fb2 = fb;
+ for (dx = 0; dx < blit->width; dx ++) {
+ fb_writeb(blit->indices[i], fb2);
+ fb_writeb(attr, fb2 + 1);
+ fb2 += colstride;
+ i ++;
+ if (i == blit->length) return;
+ }
+ fb += rowstride;
+ }
+
+}
+
+/* Set cursor in text (tileblit) mode */
+void svga_tilecursor(void __iomem *regbase, struct fb_info *info, struct fb_tilecursor *cursor)
+{
+ u8 cs = 0x0d;
+ u8 ce = 0x0e;
+ u16 pos = cursor->sx + (info->var.xoffset / 8)
+ + (cursor->sy + (info->var.yoffset / 16))
+ * (info->var.xres_virtual / 8);
+
+ if (! cursor -> mode)
+ return;
+
+ svga_wcrt_mask(regbase, 0x0A, 0x20, 0x20); /* disable cursor */
+
+ if (cursor -> shape == FB_TILE_CURSOR_NONE)
+ return;
+
+ switch (cursor -> shape) {
+ case FB_TILE_CURSOR_UNDERLINE:
+ cs = 0x0d;
+ break;
+ case FB_TILE_CURSOR_LOWER_THIRD:
+ cs = 0x09;
+ break;
+ case FB_TILE_CURSOR_LOWER_HALF:
+ cs = 0x07;
+ break;
+ case FB_TILE_CURSOR_TWO_THIRDS:
+ cs = 0x05;
+ break;
+ case FB_TILE_CURSOR_BLOCK:
+ cs = 0x01;
+ break;
+ }
+
+ /* set cursor position */
+ vga_wcrt(regbase, 0x0E, pos >> 8);
+ vga_wcrt(regbase, 0x0F, pos & 0xFF);
+
+ vga_wcrt(regbase, 0x0B, ce); /* set cursor end */
+ vga_wcrt(regbase, 0x0A, cs); /* set cursor start and enable it */
+}
+
+int svga_get_tilemax(struct fb_info *info)
+{
+ return 256;
+}
+
+/* Get capabilities of accelerator based on the mode */
+
+void svga_get_caps(struct fb_info *info, struct fb_blit_caps *caps,
+ struct fb_var_screeninfo *var)
+{
+ if (var->bits_per_pixel == 0) {
+ /* can only support 256 8x16 bitmap */
+ caps->x = 1 << (8 - 1);
+ caps->y = 1 << (16 - 1);
+ caps->len = 256;
+ } else {
+ caps->x = (var->bits_per_pixel == 4) ? 1 << (8 - 1) : ~(u32)0;
+ caps->y = ~(u32)0;
+ caps->len = ~(u32)0;
+ }
+}
+EXPORT_SYMBOL(svga_get_caps);
+
+/* ------------------------------------------------------------------------- */
+
+
+/*
+ * Compute PLL settings (M, N, R)
+ * F_VCO = (F_BASE * M) / N
+ * F_OUT = F_VCO / (2^R)
+ */
+
+static inline u32 abs_diff(u32 a, u32 b)
+{
+ return (a > b) ? (a - b) : (b - a);
+}
+
+int svga_compute_pll(const struct svga_pll *pll, u32 f_wanted, u16 *m, u16 *n, u16 *r, int node)
+{
+ u16 am, an, ar;
+ u32 f_vco, f_current, delta_current, delta_best;
+
+ pr_debug("fb%d: ideal frequency: %d kHz\n", node, (unsigned int) f_wanted);
+
+ ar = pll->r_max;
+ f_vco = f_wanted << ar;
+
+ /* overflow check */
+ if ((f_vco >> ar) != f_wanted)
+ return -EINVAL;
+
+ /* It is usually better to have greater VCO clock
+ because of better frequency stability.
+ So first try r_max, then r smaller. */
+ while ((ar > pll->r_min) && (f_vco > pll->f_vco_max)) {
+ ar--;
+ f_vco = f_vco >> 1;
+ }
+
+ /* VCO bounds check */
+ if ((f_vco < pll->f_vco_min) || (f_vco > pll->f_vco_max))
+ return -EINVAL;
+
+ delta_best = 0xFFFFFFFF;
+ *m = 0;
+ *n = 0;
+ *r = ar;
+
+ am = pll->m_min;
+ an = pll->n_min;
+
+ while ((am <= pll->m_max) && (an <= pll->n_max)) {
+ f_current = (pll->f_base * am) / an;
+ delta_current = abs_diff (f_current, f_vco);
+
+ if (delta_current < delta_best) {
+ delta_best = delta_current;
+ *m = am;
+ *n = an;
+ }
+
+ if (f_current <= f_vco) {
+ am ++;
+ } else {
+ an ++;
+ }
+ }
+
+ f_current = (pll->f_base * *m) / *n;
+ pr_debug("fb%d: found frequency: %d kHz (VCO %d kHz)\n", node, (int) (f_current >> ar), (int) f_current);
+ pr_debug("fb%d: m = %d n = %d r = %d\n", node, (unsigned int) *m, (unsigned int) *n, (unsigned int) *r);
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+
+/* Check CRT timing values */
+int svga_check_timings(const struct svga_timing_regs *tm, struct fb_var_screeninfo *var, int node)
+{
+ u32 value;
+
+ var->xres = (var->xres+7)&~7;
+ var->left_margin = (var->left_margin+7)&~7;
+ var->right_margin = (var->right_margin+7)&~7;
+ var->hsync_len = (var->hsync_len+7)&~7;
+
+ /* Check horizontal total */
+ value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
+ if (((value / 8) - 5) >= svga_regset_size (tm->h_total_regs))
+ return -EINVAL;
+
+ /* Check horizontal display and blank start */
+ value = var->xres;
+ if (((value / 8) - 1) >= svga_regset_size (tm->h_display_regs))
+ return -EINVAL;
+ if (((value / 8) - 1) >= svga_regset_size (tm->h_blank_start_regs))
+ return -EINVAL;
+
+ /* Check horizontal sync start */
+ value = var->xres + var->right_margin;
+ if (((value / 8) - 1) >= svga_regset_size (tm->h_sync_start_regs))
+ return -EINVAL;
+
+ /* Check horizontal blank end (or length) */
+ value = var->left_margin + var->right_margin + var->hsync_len;
+ if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_blank_end_regs)))
+ return -EINVAL;
+
+ /* Check horizontal sync end (or length) */
+ value = var->hsync_len;
+ if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_sync_end_regs)))
+ return -EINVAL;
+
+ /* Check vertical total */
+ value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
+ if ((value - 1) >= svga_regset_size(tm->v_total_regs))
+ return -EINVAL;
+
+ /* Check vertical display and blank start */
+ value = var->yres;
+ if ((value - 1) >= svga_regset_size(tm->v_display_regs))
+ return -EINVAL;
+ if ((value - 1) >= svga_regset_size(tm->v_blank_start_regs))
+ return -EINVAL;
+
+ /* Check vertical sync start */
+ value = var->yres + var->lower_margin;
+ if ((value - 1) >= svga_regset_size(tm->v_sync_start_regs))
+ return -EINVAL;
+
+ /* Check vertical blank end (or length) */
+ value = var->upper_margin + var->lower_margin + var->vsync_len;
+ if ((value == 0) || (value >= svga_regset_size (tm->v_blank_end_regs)))
+ return -EINVAL;
+
+ /* Check vertical sync end (or length) */
+ value = var->vsync_len;
+ if ((value == 0) || (value >= svga_regset_size (tm->v_sync_end_regs)))
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Set CRT timing registers */
+void svga_set_timings(void __iomem *regbase, const struct svga_timing_regs *tm,
+ struct fb_var_screeninfo *var,
+ u32 hmul, u32 hdiv, u32 vmul, u32 vdiv, u32 hborder, int node)
+{
+ u8 regval;
+ u32 value;
+
+ value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal total : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_total_regs, (value / 8) - 5);
+
+ value = var->xres;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal display : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_display_regs, (value / 8) - 1);
+
+ value = var->xres;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal blank start: %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_blank_start_regs, (value / 8) - 1 + hborder);
+
+ value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal blank end : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_blank_end_regs, (value / 8) - 1 - hborder);
+
+ value = var->xres + var->right_margin;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal sync start : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_sync_start_regs, (value / 8));
+
+ value = var->xres + var->right_margin + var->hsync_len;
+ value = (value * hmul) / hdiv;
+ pr_debug("fb%d: horizontal sync end : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->h_sync_end_regs, (value / 8));
+
+ value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical total : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_total_regs, value - 2);
+
+ value = var->yres;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical display : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_display_regs, value - 1);
+
+ value = var->yres;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical blank start : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_blank_start_regs, value);
+
+ value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical blank end : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_blank_end_regs, value - 2);
+
+ value = var->yres + var->lower_margin;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical sync start : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_sync_start_regs, value);
+
+ value = var->yres + var->lower_margin + var->vsync_len;
+ value = (value * vmul) / vdiv;
+ pr_debug("fb%d: vertical sync end : %d\n", node, value);
+ svga_wcrt_multi(regbase, tm->v_sync_end_regs, value);
+
+ /* Set horizontal and vertical sync pulse polarity in misc register */
+
+ regval = vga_r(regbase, VGA_MIS_R);
+ if (var->sync & FB_SYNC_HOR_HIGH_ACT) {
+ pr_debug("fb%d: positive horizontal sync\n", node);
+ regval = regval & ~0x80;
+ } else {
+ pr_debug("fb%d: negative horizontal sync\n", node);
+ regval = regval | 0x80;
+ }
+ if (var->sync & FB_SYNC_VERT_HIGH_ACT) {
+ pr_debug("fb%d: positive vertical sync\n", node);
+ regval = regval & ~0x40;
+ } else {
+ pr_debug("fb%d: negative vertical sync\n\n", node);
+ regval = regval | 0x40;
+ }
+ vga_w(regbase, VGA_MIS_W, regval);
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+
+static inline int match_format(const struct svga_fb_format *frm,
+ struct fb_var_screeninfo *var)
+{
+ int i = 0;
+ int stored = -EINVAL;
+
+ while (frm->bits_per_pixel != SVGA_FORMAT_END_VAL)
+ {
+ if ((var->bits_per_pixel == frm->bits_per_pixel) &&
+ (var->red.length <= frm->red.length) &&
+ (var->green.length <= frm->green.length) &&
+ (var->blue.length <= frm->blue.length) &&
+ (var->transp.length <= frm->transp.length) &&
+ (var->nonstd == frm->nonstd))
+ return i;
+ if (var->bits_per_pixel == frm->bits_per_pixel)
+ stored = i;
+ i++;
+ frm++;
+ }
+ return stored;
+}
+
+int svga_match_format(const struct svga_fb_format *frm,
+ struct fb_var_screeninfo *var,
+ struct fb_fix_screeninfo *fix)
+{
+ int i = match_format(frm, var);
+
+ if (i >= 0) {
+ var->bits_per_pixel = frm[i].bits_per_pixel;
+ var->red = frm[i].red;
+ var->green = frm[i].green;
+ var->blue = frm[i].blue;
+ var->transp = frm[i].transp;
+ var->nonstd = frm[i].nonstd;
+ if (fix != NULL) {
+ fix->type = frm[i].type;
+ fix->type_aux = frm[i].type_aux;
+ fix->visual = frm[i].visual;
+ fix->xpanstep = frm[i].xpanstep;
+ }
+ }
+
+ return i;
+}
+
+
+EXPORT_SYMBOL(svga_wcrt_multi);
+EXPORT_SYMBOL(svga_wseq_multi);
+
+EXPORT_SYMBOL(svga_set_default_gfx_regs);
+EXPORT_SYMBOL(svga_set_default_atc_regs);
+EXPORT_SYMBOL(svga_set_default_seq_regs);
+EXPORT_SYMBOL(svga_set_default_crt_regs);
+EXPORT_SYMBOL(svga_set_textmode_vga_regs);
+
+EXPORT_SYMBOL(svga_settile);
+EXPORT_SYMBOL(svga_tilecopy);
+EXPORT_SYMBOL(svga_tilefill);
+EXPORT_SYMBOL(svga_tileblit);
+EXPORT_SYMBOL(svga_tilecursor);
+EXPORT_SYMBOL(svga_get_tilemax);
+
+EXPORT_SYMBOL(svga_compute_pll);
+EXPORT_SYMBOL(svga_check_timings);
+EXPORT_SYMBOL(svga_set_timings);
+EXPORT_SYMBOL(svga_match_format);
+
+MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>");
+MODULE_DESCRIPTION("Common utility functions for VGA-based graphics cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/syscopyarea.c b/drivers/video/fbdev/core/syscopyarea.c
new file mode 100644
index 000000000000..844a32fd38ed
--- /dev/null
+++ b/drivers/video/fbdev/core/syscopyarea.c
@@ -0,0 +1,377 @@
+/*
+ * Generic Bit Block Transfer for frame buffers located in system RAM with
+ * packed pixels of any depth.
+ *
+ * Based almost entirely from cfbcopyarea.c (which is based almost entirely
+ * on Geert Uytterhoeven's copyarea routine)
+ *
+ * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+#include <asm/io.h>
+#include "fb_draw.h"
+
+ /*
+ * Generic bitwise copy algorithm
+ */
+
+static void
+bitcpy(struct fb_info *p, unsigned long *dst, int dst_idx,
+ const unsigned long *src, int src_idx, int bits, unsigned n)
+{
+ unsigned long first, last;
+ int const shift = dst_idx-src_idx;
+ int left, right;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (!shift) {
+ /* Same alignment for source and dest */
+ if (dst_idx+n <= bits) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(*src, *dst, first);
+ } else {
+ /* Multiple destination words */
+ /* Leading bits */
+ if (first != ~0UL) {
+ *dst = comp(*src, *dst, first);
+ dst++;
+ src++;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 8) {
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = *src++;
+ n -= 8;
+ }
+ while (n--)
+ *dst++ = *src++;
+
+ /* Trailing bits */
+ if (last)
+ *dst = comp(*src, *dst, last);
+ }
+ } else {
+ unsigned long d0, d1;
+ int m;
+
+ /* Different alignment for source and dest */
+ right = shift & (bits - 1);
+ left = -shift & (bits - 1);
+
+ if (dst_idx+n <= bits) {
+ /* Single destination word */
+ if (last)
+ first &= last;
+ if (shift > 0) {
+ /* Single source word */
+ *dst = comp(*src >> right, *dst, first);
+ } else if (src_idx+n <= bits) {
+ /* Single source word */
+ *dst = comp(*src << left, *dst, first);
+ } else {
+ /* 2 source words */
+ d0 = *src++;
+ d1 = *src;
+ *dst = comp(d0 << left | d1 >> right, *dst,
+ first);
+ }
+ } else {
+ /* Multiple destination words */
+ /** We must always remember the last value read,
+ because in case SRC and DST overlap bitwise (e.g.
+ when moving just one pixel in 1bpp), we always
+ collect one full long for DST and that might
+ overlap with the current long from SRC. We store
+ this value in 'd0'. */
+ d0 = *src++;
+ /* Leading bits */
+ if (shift > 0) {
+ /* Single source word */
+ *dst = comp(d0 >> right, *dst, first);
+ dst++;
+ n -= bits - dst_idx;
+ } else {
+ /* 2 source words */
+ d1 = *src++;
+ *dst = comp(d0 << left | *dst >> right, *dst, first);
+ d0 = d1;
+ dst++;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ m = n % bits;
+ n /= bits;
+ while (n >= 4) {
+ d1 = *src++;
+ *dst++ = d0 << left | d1 >> right;
+ d0 = d1;
+ d1 = *src++;
+ *dst++ = d0 << left | d1 >> right;
+ d0 = d1;
+ d1 = *src++;
+ *dst++ = d0 << left | d1 >> right;
+ d0 = d1;
+ d1 = *src++;
+ *dst++ = d0 << left | d1 >> right;
+ d0 = d1;
+ n -= 4;
+ }
+ while (n--) {
+ d1 = *src++;
+ *dst++ = d0 << left | d1 >> right;
+ d0 = d1;
+ }
+
+ /* Trailing bits */
+ if (last) {
+ if (m <= right) {
+ /* Single source word */
+ *dst = comp(d0 << left, *dst, last);
+ } else {
+ /* 2 source words */
+ d1 = *src;
+ *dst = comp(d0 << left | d1 >> right,
+ *dst, last);
+ }
+ }
+ }
+ }
+}
+
+ /*
+ * Generic bitwise copy algorithm, operating backward
+ */
+
+static void
+bitcpy_rev(struct fb_info *p, unsigned long *dst, int dst_idx,
+ const unsigned long *src, int src_idx, int bits, unsigned n)
+{
+ unsigned long first, last;
+ int shift;
+
+ dst += (n-1)/bits;
+ src += (n-1)/bits;
+ if ((n-1) % bits) {
+ dst_idx += (n-1) % bits;
+ dst += dst_idx >> (ffs(bits) - 1);
+ dst_idx &= bits - 1;
+ src_idx += (n-1) % bits;
+ src += src_idx >> (ffs(bits) - 1);
+ src_idx &= bits - 1;
+ }
+
+ shift = dst_idx-src_idx;
+
+ first = FB_SHIFT_LOW(p, ~0UL, bits - 1 - dst_idx);
+ last = ~(FB_SHIFT_LOW(p, ~0UL, bits - 1 - ((dst_idx-n) % bits)));
+
+ if (!shift) {
+ /* Same alignment for source and dest */
+ if ((unsigned long)dst_idx+1 >= n) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(*src, *dst, first);
+ } else {
+ /* Multiple destination words */
+
+ /* Leading bits */
+ if (first != ~0UL) {
+ *dst = comp(*src, *dst, first);
+ dst--;
+ src--;
+ n -= dst_idx+1;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 8) {
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ *dst-- = *src--;
+ n -= 8;
+ }
+ while (n--)
+ *dst-- = *src--;
+ /* Trailing bits */
+ if (last)
+ *dst = comp(*src, *dst, last);
+ }
+ } else {
+ /* Different alignment for source and dest */
+
+ int const left = -shift & (bits-1);
+ int const right = shift & (bits-1);
+
+ if ((unsigned long)dst_idx+1 >= n) {
+ /* Single destination word */
+ if (last)
+ first &= last;
+ if (shift < 0) {
+ /* Single source word */
+ *dst = comp(*src << left, *dst, first);
+ } else if (1+(unsigned long)src_idx >= n) {
+ /* Single source word */
+ *dst = comp(*src >> right, *dst, first);
+ } else {
+ /* 2 source words */
+ *dst = comp(*src >> right | *(src-1) << left,
+ *dst, first);
+ }
+ } else {
+ /* Multiple destination words */
+ /** We must always remember the last value read,
+ because in case SRC and DST overlap bitwise (e.g.
+ when moving just one pixel in 1bpp), we always
+ collect one full long for DST and that might
+ overlap with the current long from SRC. We store
+ this value in 'd0'. */
+ unsigned long d0, d1;
+ int m;
+
+ d0 = *src--;
+ /* Leading bits */
+ if (shift < 0) {
+ /* Single source word */
+ *dst = comp(d0 << left, *dst, first);
+ } else {
+ /* 2 source words */
+ d1 = *src--;
+ *dst = comp(d0 >> right | d1 << left, *dst,
+ first);
+ d0 = d1;
+ }
+ dst--;
+ n -= dst_idx+1;
+
+ /* Main chunk */
+ m = n % bits;
+ n /= bits;
+ while (n >= 4) {
+ d1 = *src--;
+ *dst-- = d0 >> right | d1 << left;
+ d0 = d1;
+ d1 = *src--;
+ *dst-- = d0 >> right | d1 << left;
+ d0 = d1;
+ d1 = *src--;
+ *dst-- = d0 >> right | d1 << left;
+ d0 = d1;
+ d1 = *src--;
+ *dst-- = d0 >> right | d1 << left;
+ d0 = d1;
+ n -= 4;
+ }
+ while (n--) {
+ d1 = *src--;
+ *dst-- = d0 >> right | d1 << left;
+ d0 = d1;
+ }
+
+ /* Trailing bits */
+ if (last) {
+ if (m <= left) {
+ /* Single source word */
+ *dst = comp(d0 >> right, *dst, last);
+ } else {
+ /* 2 source words */
+ d1 = *src;
+ *dst = comp(d0 >> right | d1 << left,
+ *dst, last);
+ }
+ }
+ }
+ }
+}
+
+void sys_copyarea(struct fb_info *p, const struct fb_copyarea *area)
+{
+ u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy;
+ u32 height = area->height, width = area->width;
+ unsigned long const bits_per_line = p->fix.line_length*8u;
+ unsigned long *dst = NULL, *src = NULL;
+ int bits = BITS_PER_LONG, bytes = bits >> 3;
+ int dst_idx = 0, src_idx = 0, rev_copy = 0;
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ /* if the beginning of the target area might overlap with the end of
+ the source area, be have to copy the area reverse. */
+ if ((dy == sy && dx > sx) || (dy > sy)) {
+ dy += height;
+ sy += height;
+ rev_copy = 1;
+ }
+
+ /* split the base of the framebuffer into a long-aligned address and
+ the index of the first bit */
+ dst = src = (unsigned long *)((unsigned long)p->screen_base &
+ ~(bytes-1));
+ dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1));
+ /* add offset of source and target area */
+ dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel;
+ src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel;
+
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+
+ if (rev_copy) {
+ while (height--) {
+ dst_idx -= bits_per_line;
+ src_idx -= bits_per_line;
+ dst += dst_idx >> (ffs(bits) - 1);
+ dst_idx &= (bytes - 1);
+ src += src_idx >> (ffs(bits) - 1);
+ src_idx &= (bytes - 1);
+ bitcpy_rev(p, dst, dst_idx, src, src_idx, bits,
+ width*p->var.bits_per_pixel);
+ }
+ } else {
+ while (height--) {
+ dst += dst_idx >> (ffs(bits) - 1);
+ dst_idx &= (bytes - 1);
+ src += src_idx >> (ffs(bits) - 1);
+ src_idx &= (bytes - 1);
+ bitcpy(p, dst, dst_idx, src, src_idx, bits,
+ width*p->var.bits_per_pixel);
+ dst_idx += bits_per_line;
+ src_idx += bits_per_line;
+ }
+ }
+}
+
+EXPORT_SYMBOL(sys_copyarea);
+
+MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>");
+MODULE_DESCRIPTION("Generic copyarea (sys-to-sys)");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/fbdev/core/sysfillrect.c b/drivers/video/fbdev/core/sysfillrect.c
new file mode 100644
index 000000000000..33ee3d34f9d2
--- /dev/null
+++ b/drivers/video/fbdev/core/sysfillrect.c
@@ -0,0 +1,335 @@
+/*
+ * Generic fillrect for frame buffers in system RAM with packed pixels of
+ * any depth.
+ *
+ * Based almost entirely from cfbfillrect.c (which is based almost entirely
+ * on Geert Uytterhoeven's fillrect routine)
+ *
+ * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+#include "fb_draw.h"
+
+ /*
+ * Aligned pattern fill using 32/64-bit memory accesses
+ */
+
+static void
+bitfill_aligned(struct fb_info *p, unsigned long *dst, int dst_idx,
+ unsigned long pat, unsigned n, int bits)
+{
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(pat, *dst, first);
+ } else {
+ /* Multiple destination words */
+
+ /* Leading bits */
+ if (first!= ~0UL) {
+ *dst = comp(pat, *dst, first);
+ dst++;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 8) {
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ *dst++ = pat;
+ n -= 8;
+ }
+ while (n--)
+ *dst++ = pat;
+ /* Trailing bits */
+ if (last)
+ *dst = comp(pat, *dst, last);
+ }
+}
+
+
+ /*
+ * Unaligned generic pattern fill using 32/64-bit memory accesses
+ * The pattern must have been expanded to a full 32/64-bit value
+ * Left/right are the appropriate shifts to convert to the pattern to be
+ * used for the next 32/64-bit word
+ */
+
+static void
+bitfill_unaligned(struct fb_info *p, unsigned long *dst, int dst_idx,
+ unsigned long pat, int left, int right, unsigned n, int bits)
+{
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(pat, *dst, first);
+ } else {
+ /* Multiple destination words */
+ /* Leading bits */
+ if (first) {
+ *dst = comp(pat, *dst, first);
+ dst++;
+ pat = pat << left | pat >> right;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 4) {
+ *dst++ = pat;
+ pat = pat << left | pat >> right;
+ *dst++ = pat;
+ pat = pat << left | pat >> right;
+ *dst++ = pat;
+ pat = pat << left | pat >> right;
+ *dst++ = pat;
+ pat = pat << left | pat >> right;
+ n -= 4;
+ }
+ while (n--) {
+ *dst++ = pat;
+ pat = pat << left | pat >> right;
+ }
+
+ /* Trailing bits */
+ if (last)
+ *dst = comp(pat, *dst, last);
+ }
+}
+
+ /*
+ * Aligned pattern invert using 32/64-bit memory accesses
+ */
+static void
+bitfill_aligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx,
+ unsigned long pat, unsigned n, int bits)
+{
+ unsigned long val = pat;
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(*dst ^ val, *dst, first);
+ } else {
+ /* Multiple destination words */
+ /* Leading bits */
+ if (first!=0UL) {
+ *dst = comp(*dst ^ val, *dst, first);
+ dst++;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 8) {
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ *dst++ ^= val;
+ n -= 8;
+ }
+ while (n--)
+ *dst++ ^= val;
+ /* Trailing bits */
+ if (last)
+ *dst = comp(*dst ^ val, *dst, last);
+ }
+}
+
+
+ /*
+ * Unaligned generic pattern invert using 32/64-bit memory accesses
+ * The pattern must have been expanded to a full 32/64-bit value
+ * Left/right are the appropriate shifts to convert to the pattern to be
+ * used for the next 32/64-bit word
+ */
+
+static void
+bitfill_unaligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx,
+ unsigned long pat, int left, int right, unsigned n,
+ int bits)
+{
+ unsigned long first, last;
+
+ if (!n)
+ return;
+
+ first = FB_SHIFT_HIGH(p, ~0UL, dst_idx);
+ last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits));
+
+ if (dst_idx+n <= bits) {
+ /* Single word */
+ if (last)
+ first &= last;
+ *dst = comp(*dst ^ pat, *dst, first);
+ } else {
+ /* Multiple destination words */
+
+ /* Leading bits */
+ if (first != 0UL) {
+ *dst = comp(*dst ^ pat, *dst, first);
+ dst++;
+ pat = pat << left | pat >> right;
+ n -= bits - dst_idx;
+ }
+
+ /* Main chunk */
+ n /= bits;
+ while (n >= 4) {
+ *dst++ ^= pat;
+ pat = pat << left | pat >> right;
+ *dst++ ^= pat;
+ pat = pat << left | pat >> right;
+ *dst++ ^= pat;
+ pat = pat << left | pat >> right;
+ *dst++ ^= pat;
+ pat = pat << left | pat >> right;
+ n -= 4;
+ }
+ while (n--) {
+ *dst ^= pat;
+ pat = pat << left | pat >> right;
+ }
+
+ /* Trailing bits */
+ if (last)
+ *dst = comp(*dst ^ pat, *dst, last);
+ }
+}
+
+void sys_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
+{
+ unsigned long pat, pat2, fg;
+ unsigned long width = rect->width, height = rect->height;
+ int bits = BITS_PER_LONG, bytes = bits >> 3;
+ u32 bpp = p->var.bits_per_pixel;
+ unsigned long *dst;
+ int dst_idx, left;
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR )
+ fg = ((u32 *) (p->pseudo_palette))[rect->color];
+ else
+ fg = rect->color;
+
+ pat = pixel_to_pat( bpp, fg);
+
+ dst = (unsigned long *)((unsigned long)p->screen_base & ~(bytes-1));
+ dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8;
+ dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp;
+ /* FIXME For now we support 1-32 bpp only */
+ left = bits % bpp;
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+ if (!left) {
+ void (*fill_op32)(struct fb_info *p, unsigned long *dst,
+ int dst_idx, unsigned long pat, unsigned n,
+ int bits) = NULL;
+
+ switch (rect->rop) {
+ case ROP_XOR:
+ fill_op32 = bitfill_aligned_rev;
+ break;
+ case ROP_COPY:
+ fill_op32 = bitfill_aligned;
+ break;
+ default:
+ printk( KERN_ERR "cfb_fillrect(): unknown rop, "
+ "defaulting to ROP_COPY\n");
+ fill_op32 = bitfill_aligned;
+ break;
+ }
+ while (height--) {
+ dst += dst_idx >> (ffs(bits) - 1);
+ dst_idx &= (bits - 1);
+ fill_op32(p, dst, dst_idx, pat, width*bpp, bits);
+ dst_idx += p->fix.line_length*8;
+ }
+ } else {
+ int right, r;
+ void (*fill_op)(struct fb_info *p, unsigned long *dst,
+ int dst_idx, unsigned long pat, int left,
+ int right, unsigned n, int bits) = NULL;
+#ifdef __LITTLE_ENDIAN
+ right = left;
+ left = bpp - right;
+#else
+ right = bpp - left;
+#endif
+ switch (rect->rop) {
+ case ROP_XOR:
+ fill_op = bitfill_unaligned_rev;
+ break;
+ case ROP_COPY:
+ fill_op = bitfill_unaligned;
+ break;
+ default:
+ printk(KERN_ERR "sys_fillrect(): unknown rop, "
+ "defaulting to ROP_COPY\n");
+ fill_op = bitfill_unaligned;
+ break;
+ }
+ while (height--) {
+ dst += dst_idx / bits;
+ dst_idx &= (bits - 1);
+ r = dst_idx % bpp;
+ /* rotate pattern to the correct start position */
+ pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp));
+ fill_op(p, dst, dst_idx, pat2, left, right,
+ width*bpp, bits);
+ dst_idx += p->fix.line_length*8;
+ }
+ }
+}
+
+EXPORT_SYMBOL(sys_fillrect);
+
+MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>");
+MODULE_DESCRIPTION("Generic fill rectangle (sys-to-sys)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/sysimgblt.c b/drivers/video/fbdev/core/sysimgblt.c
new file mode 100644
index 000000000000..a4d05b1b17d7
--- /dev/null
+++ b/drivers/video/fbdev/core/sysimgblt.c
@@ -0,0 +1,288 @@
+/*
+ * Generic 1-bit or 8-bit source to 1-32 bit destination expansion
+ * for frame buffer located in system RAM with packed pixels of any depth.
+ *
+ * Based almost entirely on cfbimgblt.c
+ *
+ * Copyright (C) April 2007 Antonino Daplas <adaplas@pol.net>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include <asm/types.h>
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+static const u32 cfb_tab8_be[] = {
+ 0x00000000,0x000000ff,0x0000ff00,0x0000ffff,
+ 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff,
+ 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff,
+ 0xffff0000,0xffff00ff,0xffffff00,0xffffffff
+};
+
+static const u32 cfb_tab8_le[] = {
+ 0x00000000,0xff000000,0x00ff0000,0xffff0000,
+ 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00,
+ 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff,
+ 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff
+};
+
+static const u32 cfb_tab16_be[] = {
+ 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff
+};
+
+static const u32 cfb_tab16_le[] = {
+ 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff
+};
+
+static const u32 cfb_tab32[] = {
+ 0x00000000, 0xffffffff
+};
+
+static void color_imageblit(const struct fb_image *image, struct fb_info *p,
+ void *dst1, u32 start_index, u32 pitch_index)
+{
+ /* Draw the penguin */
+ u32 *dst, *dst2;
+ u32 color = 0, val, shift;
+ int i, n, bpp = p->var.bits_per_pixel;
+ u32 null_bits = 32 - bpp;
+ u32 *palette = (u32 *) p->pseudo_palette;
+ const u8 *src = image->data;
+
+ dst2 = dst1;
+ for (i = image->height; i--; ) {
+ n = image->width;
+ dst = dst1;
+ shift = 0;
+ val = 0;
+
+ if (start_index) {
+ u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0,
+ start_index));
+ val = *dst & start_mask;
+ shift = start_index;
+ }
+ while (n--) {
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR )
+ color = palette[*src];
+ else
+ color = *src;
+ color <<= FB_LEFT_POS(p, bpp);
+ val |= FB_SHIFT_HIGH(p, color, shift);
+ if (shift >= null_bits) {
+ *dst++ = val;
+
+ val = (shift == null_bits) ? 0 :
+ FB_SHIFT_LOW(p, color, 32 - shift);
+ }
+ shift += bpp;
+ shift &= (32 - 1);
+ src++;
+ }
+ if (shift) {
+ u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift);
+
+ *dst &= end_mask;
+ *dst |= val;
+ }
+ dst1 += p->fix.line_length;
+ if (pitch_index) {
+ dst2 += p->fix.line_length;
+ dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1));
+
+ start_index += pitch_index;
+ start_index &= 32 - 1;
+ }
+ }
+}
+
+static void slow_imageblit(const struct fb_image *image, struct fb_info *p,
+ void *dst1, u32 fgcolor, u32 bgcolor,
+ u32 start_index, u32 pitch_index)
+{
+ u32 shift, color = 0, bpp = p->var.bits_per_pixel;
+ u32 *dst, *dst2;
+ u32 val, pitch = p->fix.line_length;
+ u32 null_bits = 32 - bpp;
+ u32 spitch = (image->width+7)/8;
+ const u8 *src = image->data, *s;
+ u32 i, j, l;
+
+ dst2 = dst1;
+ fgcolor <<= FB_LEFT_POS(p, bpp);
+ bgcolor <<= FB_LEFT_POS(p, bpp);
+
+ for (i = image->height; i--; ) {
+ shift = val = 0;
+ l = 8;
+ j = image->width;
+ dst = dst1;
+ s = src;
+
+ /* write leading bits */
+ if (start_index) {
+ u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0,
+ start_index));
+ val = *dst & start_mask;
+ shift = start_index;
+ }
+
+ while (j--) {
+ l--;
+ color = (*s & (1 << l)) ? fgcolor : bgcolor;
+ val |= FB_SHIFT_HIGH(p, color, shift);
+
+ /* Did the bitshift spill bits to the next long? */
+ if (shift >= null_bits) {
+ *dst++ = val;
+ val = (shift == null_bits) ? 0 :
+ FB_SHIFT_LOW(p, color, 32 - shift);
+ }
+ shift += bpp;
+ shift &= (32 - 1);
+ if (!l) { l = 8; s++; }
+ }
+
+ /* write trailing bits */
+ if (shift) {
+ u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift);
+
+ *dst &= end_mask;
+ *dst |= val;
+ }
+
+ dst1 += pitch;
+ src += spitch;
+ if (pitch_index) {
+ dst2 += pitch;
+ dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1));
+ start_index += pitch_index;
+ start_index &= 32 - 1;
+ }
+
+ }
+}
+
+/*
+ * fast_imageblit - optimized monochrome color expansion
+ *
+ * Only if: bits_per_pixel == 8, 16, or 32
+ * image->width is divisible by pixel/dword (ppw);
+ * fix->line_legth is divisible by 4;
+ * beginning and end of a scanline is dword aligned
+ */
+static void fast_imageblit(const struct fb_image *image, struct fb_info *p,
+ void *dst1, u32 fgcolor, u32 bgcolor)
+{
+ u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel;
+ u32 ppw = 32/bpp, spitch = (image->width + 7)/8;
+ u32 bit_mask, end_mask, eorx, shift;
+ const char *s = image->data, *src;
+ u32 *dst;
+ const u32 *tab = NULL;
+ int i, j, k;
+
+ switch (bpp) {
+ case 8:
+ tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le;
+ break;
+ case 16:
+ tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le;
+ break;
+ case 32:
+ default:
+ tab = cfb_tab32;
+ break;
+ }
+
+ for (i = ppw-1; i--; ) {
+ fgx <<= bpp;
+ bgx <<= bpp;
+ fgx |= fgcolor;
+ bgx |= bgcolor;
+ }
+
+ bit_mask = (1 << ppw) - 1;
+ eorx = fgx ^ bgx;
+ k = image->width/ppw;
+
+ for (i = image->height; i--; ) {
+ dst = dst1;
+ shift = 8;
+ src = s;
+
+ for (j = k; j--; ) {
+ shift -= ppw;
+ end_mask = tab[(*src >> shift) & bit_mask];
+ *dst++ = (end_mask & eorx) ^ bgx;
+ if (!shift) {
+ shift = 8;
+ src++;
+ }
+ }
+ dst1 += p->fix.line_length;
+ s += spitch;
+ }
+}
+
+void sys_imageblit(struct fb_info *p, const struct fb_image *image)
+{
+ u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0;
+ u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel;
+ u32 width = image->width;
+ u32 dx = image->dx, dy = image->dy;
+ void *dst1;
+
+ if (p->state != FBINFO_STATE_RUNNING)
+ return;
+
+ bitstart = (dy * p->fix.line_length * 8) + (dx * bpp);
+ start_index = bitstart & (32 - 1);
+ pitch_index = (p->fix.line_length & (bpl - 1)) * 8;
+
+ bitstart /= 8;
+ bitstart &= ~(bpl - 1);
+ dst1 = (void __force *)p->screen_base + bitstart;
+
+ if (p->fbops->fb_sync)
+ p->fbops->fb_sync(p);
+
+ if (image->depth == 1) {
+ if (p->fix.visual == FB_VISUAL_TRUECOLOR ||
+ p->fix.visual == FB_VISUAL_DIRECTCOLOR) {
+ fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];
+ bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color];
+ } else {
+ fgcolor = image->fg_color;
+ bgcolor = image->bg_color;
+ }
+
+ if (32 % bpp == 0 && !start_index && !pitch_index &&
+ ((width & (32/bpp-1)) == 0) &&
+ bpp >= 8 && bpp <= 32)
+ fast_imageblit(image, p, dst1, fgcolor, bgcolor);
+ else
+ slow_imageblit(image, p, dst1, fgcolor, bgcolor,
+ start_index, pitch_index);
+ } else
+ color_imageblit(image, p, dst1, start_index, pitch_index);
+}
+
+EXPORT_SYMBOL(sys_imageblit);
+
+MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>");
+MODULE_DESCRIPTION("1-bit/8-bit to 1-32 bit color expansion (sys-to-sys)");
+MODULE_LICENSE("GPL");
+