diff options
Diffstat (limited to 'drivers/video/fbdev/core')
| -rw-r--r-- | drivers/video/fbdev/core/Makefile | 16 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbcopyarea.c | 434 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbfillrect.c | 371 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbimgblt.c | 313 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_ddc.c | 119 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_defio.c | 245 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_draw.h | 186 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_notify.c | 47 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_sys_fops.c | 104 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbcmap.c | 362 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbcvt.c | 379 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbmem.c | 2003 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbmon.c | 1599 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbsysfs.c | 586 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/modedb.c | 1137 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/svgalib.c | 672 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/syscopyarea.c | 377 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/sysfillrect.c | 335 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/sysimgblt.c | 288 | 
19 files changed, 9573 insertions, 0 deletions
diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile new file mode 100644 index 00000000000..fa306538dac --- /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 00000000000..bcb57235fcc --- /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 00000000000..ba9f58b2a5e --- /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 00000000000..a2bb276a8b2 --- /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 00000000000..94322ccfedd --- /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 00000000000..900aa4ecd61 --- /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 00000000000..624ee115f12 --- /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 00000000000..74c2da52888 --- /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 00000000000..ff275d7f3ea --- /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 00000000000..f89245b8ba8 --- /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 00000000000..7cb715dfc0e --- /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 00000000000..b5e85f6c1c2 --- /dev/null +++ b/drivers/video/fbdev/core/fbmem.c @@ -0,0 +1,2003 @@ +/* + *  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(®istration_lock); +	fb_info = registered_fb[idx]; +	if (fb_info) +		atomic_inc(&fb_info->count); +	mutex_unlock(®istration_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; 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; 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_prepare_logo); +EXPORT_SYMBOL(fb_show_logo); + +static void *fb_seq_start(struct seq_file *m, loff_t *pos) +{ +	mutex_lock(®istration_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(®istration_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 = ∩︀ +	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 = ␣ + +	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 >= 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(®istration_lock); +	ret = do_remove_conflicting_framebuffers(a, name, primary); +	mutex_unlock(®istration_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(®istration_lock); +	ret = do_register_framebuffer(fb_info); +	mutex_unlock(®istration_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(®istration_lock); +	ret = do_unregister_framebuffer(fb_info); +	mutex_unlock(®istration_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 00000000000..5b0e313849b --- /dev/null +++ b/drivers/video/fbdev/core/fbmon.c @@ -0,0 +1,1599 @@ +/* + * 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); +			} +		} else if (type == 3 && len >= 3) { +			/* Check Vendor Specific Data Block.  For HDMI, +			   it is always 00-0C-03 for HDMI Licensing, LLC. */ +			if (edid[pos + 1] == 3 && edid[pos + 2] == 0xc && +			    edid[pos + 3] == 0) +				specs->misc |= FB_MISC_HDMI; +		} +		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 00000000000..53444ac19fe --- /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 00000000000..a9a907c440d --- /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 00000000000..9e01322fabe --- /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 00000000000..844a32fd38e --- /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 00000000000..33ee3d34f9d --- /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 00000000000..a4d05b1b17d --- /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"); +  | 
