diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/video/fbdev/core | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/video/fbdev/core')
37 files changed, 15793 insertions, 0 deletions
diff --git a/drivers/video/fbdev/core/Kconfig b/drivers/video/fbdev/core/Kconfig new file mode 100644 index 0000000000..5ac1b06375 --- /dev/null +++ b/drivers/video/fbdev/core/Kconfig @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# fbdev core configuration +# + +config FB_CORE + select VIDEO_CMDLINE + tristate + +config FB_NOTIFY + bool + +config FIRMWARE_EDID + bool "Enable firmware EDID" + depends on FB + help + This enables access to the EDID transferred from the firmware. + On the i386, this is from the Video BIOS. Enable this if DDC/I2C + transfers do not work for your driver and if you are using + nvidiafb, i810fb or savagefb. + + In general, choosing Y for this option is safe. If you + experience extremely long delays while booting before you get + something on your display, try setting this to N. Matrox cards in + combination with certain motherboards and monitors are known to + suffer from this problem. + +config FB_DEVICE + bool "Provide legacy /dev/fb* device" + depends on FB_CORE + default FB + help + Say Y here if you want the legacy /dev/fb* device file and + interfaces within sysfs anc procfs. It is only required if you + have userspace programs that depend on fbdev for graphics output. + This does not affect the framebuffer console. If unsure, say N. + +config FB_DDC + tristate + depends on FB + select I2C_ALGOBIT + select I2C + +config FB_CFB_FILLRECT + tristate + depends on FB_CORE + help + Include the cfb_fillrect function for generic software rectangle + filling. This is used by drivers that don't provide their own + (accelerated) version. + +config FB_CFB_COPYAREA + tristate + depends on FB_CORE + help + Include the cfb_copyarea function for generic software area copying. + This is used by drivers that don't provide their own (accelerated) + version. + +config FB_CFB_IMAGEBLIT + tristate + depends on FB_CORE + help + Include the cfb_imageblit function for generic software image + blitting. This is used by drivers that don't provide their own + (accelerated) version. + +config FB_CFB_REV_PIXELS_IN_BYTE + bool + depends on FB_CORE + help + Allow generic frame-buffer functions to work on displays with 1, 2 + and 4 bits per pixel depths which has opposite order of pixels in + byte order to bytes in long order. + +config FB_SYS_FILLRECT + tristate + depends on FB_CORE + help + Include the sys_fillrect function for generic software rectangle + filling. This is used by drivers that don't provide their own + (accelerated) version and the framebuffer is in system RAM. + +config FB_SYS_COPYAREA + tristate + depends on FB_CORE + help + Include the sys_copyarea function for generic software area copying. + This is used by drivers that don't provide their own (accelerated) + version and the framebuffer is in system RAM. + +config FB_SYS_IMAGEBLIT + tristate + depends on FB_CORE + help + Include the sys_imageblit function for generic software image + blitting. This is used by drivers that don't provide their own + (accelerated) version and the framebuffer is in system RAM. + +config FB_PROVIDE_GET_FB_UNMAPPED_AREA + bool + depends on FB + help + Allow generic frame-buffer to provide get_fb_unmapped_area + function to provide shareable character device support on nommu. + +menuconfig FB_FOREIGN_ENDIAN + bool "Framebuffer foreign endianness support" + depends on FB + help + This menu will let you enable support for the framebuffers with + non-native endianness (e.g. Little-Endian framebuffer on a + Big-Endian machine). Most probably you don't have such hardware, + so it's safe to say "n" here. + +choice + prompt "Choice endianness support" + depends on FB_FOREIGN_ENDIAN + +config FB_BOTH_ENDIAN + bool "Support for Big- and Little-Endian framebuffers" + +config FB_BIG_ENDIAN + bool "Support for Big-Endian framebuffers only" + +config FB_LITTLE_ENDIAN + bool "Support for Little-Endian framebuffers only" + +endchoice + +config FB_SYS_FOPS + tristate + depends on FB_CORE + +config FB_DEFERRED_IO + bool + depends on FB_CORE + +config FB_DMAMEM_HELPERS + bool + depends on FB_CORE + select FB_SYS_COPYAREA + select FB_SYS_FILLRECT + select FB_SYS_FOPS + select FB_SYS_IMAGEBLIT + +config FB_IOMEM_HELPERS + bool + depends on FB_CORE + select FB_CFB_COPYAREA + select FB_CFB_FILLRECT + select FB_CFB_IMAGEBLIT + +config FB_SYSMEM_HELPERS + bool + depends on FB_CORE + select FB_SYS_COPYAREA + select FB_SYS_FILLRECT + select FB_SYS_FOPS + select FB_SYS_IMAGEBLIT + +config FB_SYSMEM_HELPERS_DEFERRED + bool + depends on FB_CORE + select FB_DEFERRED_IO + select FB_SYSMEM_HELPERS + +config FB_BACKLIGHT + tristate + depends on FB + select BACKLIGHT_CLASS_DEVICE + +config FB_MODE_HELPERS + bool "Enable Video Mode Handling Helpers" + depends on FB + help + This enables functions for handling video modes using the + Generalized Timing Formula and the EDID parser. A few drivers rely + on this feature such as the radeonfb, rivafb, and the i810fb. If + your driver does not take advantage of this feature, choosing Y will + just increase the kernel size by about 5K. + +config FB_TILEBLITTING + bool "Enable Tile Blitting Support" + depends on FB + help + This enables tile blitting. Tile blitting is a drawing technique + where the screen is divided into rectangular sections (tiles), whereas + the standard blitting divides the screen into pixels. Because the + default drawing element is a tile, drawing functions will be passed + parameters in terms of number of tiles instead of number of pixels. + For example, to draw a single character, instead of using bitmaps, + an index to an array of bitmaps will be used. To clear or move a + rectangular section of a screen, the rectangle will be described in + terms of number of tiles in the x- and y-axis. + + This is particularly important to one driver, matroxfb. If + unsure, say N. diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile new file mode 100644 index 0000000000..edfde2948e --- /dev/null +++ b/drivers/video/fbdev/core/Makefile @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_FB_NOTIFY) += fb_notify.o +obj-$(CONFIG_FB_CORE) += fb.o +fb-y := fb_info.o \ + fbmem.o fbcmap.o \ + modedb.o fbcvt.o fb_cmdline.o fb_io_fops.o +ifdef CONFIG_FB +fb-y += fb_backlight.o fbmon.o +endif +fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o +fb-$(CONFIG_FB_DEVICE) += fb_chrdev.o \ + fb_procfs.o \ + fbsysfs.o + +ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE),y) +fb-y += fbcon.o bitblit.o softcursor.o +ifeq ($(CONFIG_FB_TILEBLITTING),y) +fb-y += tileblit.o +endif +ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE_ROTATION),y) +fb-y += fbcon_rotate.o fbcon_cw.o fbcon_ud.o \ + fbcon_ccw.o +endif +endif + +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 diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c new file mode 100644 index 0000000000..8587c9da06 --- /dev/null +++ b/drivers/video/fbdev/core/bitblit.c @@ -0,0 +1,409 @@ +/* + * linux/drivers/video/console/bitblit.c -- BitBlitting Operation + * + * Originally from the 'accel_*' routines in drivers/video/console/fbcon.c + * + * Copyright (C) 2004 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/slab.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" + +/* + * Accelerated handlers. + */ +static void update_attr(u8 *dst, u8 *src, int attribute, + struct vc_data *vc) +{ + int i, offset = (vc->vc_font.height < 10) ? 1 : 2; + int width = DIV_ROUND_UP(vc->vc_font.width, 8); + unsigned int cellsize = vc->vc_font.height * width; + u8 c; + + offset = cellsize - (offset * width); + for (i = 0; i < cellsize; i++) { + c = src[i]; + if (attribute & FBCON_ATTRIBUTE_UNDERLINE && i >= offset) + c = 0xff; + if (attribute & FBCON_ATTRIBUTE_BOLD) + c |= c >> 1; + if (attribute & FBCON_ATTRIBUTE_REVERSE) + c = ~c; + dst[i] = c; + } +} + +static void bit_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) +{ + struct fb_copyarea area; + + area.sx = sx * vc->vc_font.width; + area.sy = sy * vc->vc_font.height; + area.dx = dx * vc->vc_font.width; + area.dy = dy * vc->vc_font.height; + area.height = height * vc->vc_font.height; + area.width = width * vc->vc_font.width; + + info->fbops->fb_copyarea(info, &area); +} + +static void bit_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) +{ + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + struct fb_fillrect region; + + region.color = attr_bgcol_ec(bgshift, vc, info); + region.dx = sx * vc->vc_font.width; + region.dy = sy * vc->vc_font.height; + region.width = width * vc->vc_font.width; + region.height = height * vc->vc_font.height; + region.rop = ROP_COPY; + + info->fbops->fb_fillrect(info, ®ion); +} + +static inline void bit_putcs_aligned(struct vc_data *vc, struct fb_info *info, + const u16 *s, u32 attr, u32 cnt, + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) +{ + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = vc->vc_font.data + (scr_readw(s++)& + charmask)*cellsize; + + if (attr) { + update_attr(buf, src, attr, vc); + src = buf; + } + + if (likely(idx == 1)) + __fb_pad_aligned_buffer(dst, d_pitch, src, idx, + image->height); + else + fb_pad_aligned_buffer(dst, d_pitch, src, idx, + image->height); + + dst += s_pitch; + } + + info->fbops->fb_imageblit(info, image); +} + +static inline void bit_putcs_unaligned(struct vc_data *vc, + struct fb_info *info, const u16 *s, + u32 attr, u32 cnt, u32 d_pitch, + u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, + u8 *dst) +{ + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 shift_low = 0, mod = vc->vc_font.width % 8; + u32 shift_high = 8; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = vc->vc_font.data + (scr_readw(s++)& + charmask)*cellsize; + + if (attr) { + update_attr(buf, src, attr, vc); + src = buf; + } + + fb_pad_unaligned_buffer(dst, d_pitch, src, idx, + image->height, shift_high, + shift_low, mod); + shift_low += mod; + dst += (shift_low >= 8) ? s_pitch : s_pitch - 1; + shift_low &= 7; + shift_high = 8 - shift_low; + } + + info->fbops->fb_imageblit(info, image); + +} + +static void bit_putcs(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg) +{ + struct fb_image image; + u32 width = DIV_ROUND_UP(vc->vc_font.width, 8); + u32 cellsize = width * vc->vc_font.height; + u32 maxcnt = info->pixmap.size/cellsize; + u32 scan_align = info->pixmap.scan_align - 1; + u32 buf_align = info->pixmap.buf_align - 1; + u32 mod = vc->vc_font.width % 8, cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; + + image.fg_color = fg; + image.bg_color = bg; + image.dx = xx * vc->vc_font.width; + image.dy = yy * vc->vc_font.height; + image.height = vc->vc_font.height; + image.depth = 1; + + if (attribute) { + buf = kmalloc(cellsize, GFP_ATOMIC); + if (!buf) + return; + } + + while (count) { + if (count > maxcnt) + cnt = maxcnt; + else + cnt = count; + + image.width = vc->vc_font.width * cnt; + pitch = DIV_ROUND_UP(image.width, 8) + scan_align; + pitch &= ~scan_align; + size = pitch * image.height + buf_align; + size &= ~buf_align; + dst = fb_get_buffer_offset(info, &info->pixmap, size); + image.data = dst; + + if (!mod) + bit_putcs_aligned(vc, info, s, attribute, cnt, pitch, + width, cellsize, &image, buf, dst); + else + bit_putcs_unaligned(vc, info, s, attribute, cnt, + pitch, width, cellsize, &image, + buf, dst); + + image.dx += cnt * vc->vc_font.width; + count -= cnt; + s += cnt; + } + + /* buf is always NULL except when in monochrome mode, so in this case + it's a gain to check buf against NULL even though kfree() handles + NULL pointers just fine */ + if (unlikely(buf)) + kfree(buf); + +} + +static void bit_clear_margins(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only) +{ + unsigned int cw = vc->vc_font.width; + unsigned int ch = vc->vc_font.height; + unsigned int rw = info->var.xres - (vc->vc_cols*cw); + unsigned int bh = info->var.yres - (vc->vc_rows*ch); + unsigned int rs = info->var.xres - rw; + unsigned int bs = info->var.yres - bh; + struct fb_fillrect region; + + region.color = color; + region.rop = ROP_COPY; + + if ((int) rw > 0 && !bottom_only) { + region.dx = info->var.xoffset + rs; + region.dy = 0; + region.width = rw; + region.height = info->var.yres_virtual; + info->fbops->fb_fillrect(info, ®ion); + } + + if ((int) bh > 0) { + region.dx = info->var.xoffset; + region.dy = info->var.yoffset + bs; + region.width = rs; + region.height = bh; + info->fbops->fb_fillrect(info, ®ion); + } +} + +static void bit_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = DIV_ROUND_UP(vc->vc_font.width, 8), c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1; + char *src; + + cursor.set = 0; + + if (!vc->vc_font.data) + return; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->cursor_state.image.data = src; + cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { + u8 *dst; + + dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + if (!dst) + return; + kfree(ops->cursor_data); + ops->cursor_data = dst; + update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if ((ops->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) || + (ops->cursor_state.image.dy != (vc->vc_font.height * y)) || + ops->cursor_reset) { + ops->cursor_state.image.dx = vc->vc_font.width * vc->state.x; + ops->cursor_state.image.dy = vc->vc_font.height * y; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.image.height != vc->vc_font.height || + ops->cursor_state.image.width != vc->vc_font.width || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.height; + ops->cursor_state.image.width = vc->vc_font.width; + cursor.set |= FB_CUR_SETSIZE; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; + + if (!mask) + return; + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + size = (vc->vc_font.height - cur_height) * w; + while (size--) + mask[i++] = ~msk; + size = cur_height * w; + while (size--) + mask[i++] = msk; + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + + if (err) + soft_cursor(info, &cursor); + + ops->cursor_reset = 0; +} + +static int bit_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + int err; + + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_set_bitops(struct fbcon_ops *ops) +{ + ops->bmove = bit_bmove; + ops->clear = bit_clear; + ops->putcs = bit_putcs; + ops->clear_margins = bit_clear_margins; + ops->cursor = bit_cursor; + ops->update_start = bit_update_start; + ops->rotate_font = NULL; + + if (ops->rotate) + fbcon_set_rotate(ops); +} diff --git a/drivers/video/fbdev/core/cfbcopyarea.c b/drivers/video/fbdev/core/cfbcopyarea.c new file mode 100644 index 0000000000..5b80bf3dae --- /dev/null +++ b/drivers/video/fbdev/core/cfbcopyarea.c @@ -0,0 +1,437 @@ +/* + * 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. + */ + 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. + */ + 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); + if (!first) + FB_WRITEL(d0, dst); + else + 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 int 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 0000000000..ba9f58b2a5 --- /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 0000000000..9ebda4e0dc --- /dev/null +++ b/drivers/video/fbdev/core/cfbimgblt.c @@ -0,0 +1,366 @@ +/* + * 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, eorx, shift; + const char *s = image->data, *src; + u32 __iomem *dst; + const u32 *tab = NULL; + size_t tablen; + u32 colortab[16]; + int i, j, k; + + switch (bpp) { + case 8: + tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; + tablen = 16; + break; + case 16: + tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; + tablen = 4; + break; + case 32: + tab = cfb_tab32; + tablen = 2; + break; + default: + return; + } + + 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 = 0; i < tablen; ++i) + colortab[i] = (tab[i] & eorx) ^ bgx; + + for (i = image->height; i--; ) { + dst = (u32 __iomem *)dst1; + shift = 8; + src = s; + + /* + * Manually unroll the per-line copying loop for better + * performance. This works until we processed the last + * completely filled source byte (inclusive). + */ + switch (ppw) { + case 4: /* 8 bpp */ + for (j = k; j >= 2; j -= 2, ++src) { + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + case 2: /* 16 bpp */ + for (j = k; j >= 4; j -= 4, ++src) { + FB_WRITEL(colortab[(*src >> 6) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 2) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + case 1: /* 32 bpp */ + for (j = k; j >= 8; j -= 8, ++src) { + FB_WRITEL(colortab[(*src >> 7) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 6) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 5) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 3) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 2) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 1) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + } + + /* + * For image widths that are not a multiple of 8, there + * are trailing pixels left on the current line. Print + * them as well. + */ + for (; j--; ) { + shift -= ppw; + FB_WRITEL(colortab[(*src >> shift) & bit_mask], 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_backlight.c b/drivers/video/fbdev/core/fb_backlight.c new file mode 100644 index 0000000000..e2d3b3adc8 --- /dev/null +++ b/drivers/video/fbdev/core/fb_backlight.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/export.h> +#include <linux/fb.h> +#include <linux/mutex.h> + +#if IS_ENABLED(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/fb_chrdev.c b/drivers/video/fbdev/core/fb_chrdev.c new file mode 100644 index 0000000000..eadb81f53a --- /dev/null +++ b/drivers/video/fbdev/core/fb_chrdev.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/compat.h> +#include <linux/console.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/major.h> + +#include "fb_internal.h" + +/* + * 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) +{ + struct fb_info *info = file_fb_info(file); + + if (!info) + return -ENODEV; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + if (info->fbops->fb_read) + return info->fbops->fb_read(info, buf, count, ppos); + + return fb_io_read(info, buf, count, ppos); +} + +static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct fb_info *info = file_fb_info(file); + + if (!info) + return -ENODEV; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + if (info->fbops->fb_write) + return info->fbops->fb_write(info, buf, count, ppos); + + return fb_io_write(info, buf, count, ppos); +} + +static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + const struct fb_ops *fb; + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + struct fb_cmap cmap_from; + struct fb_cmap_user cmap; + void __user *argp = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case FBIOGET_VSCREENINFO: + lock_fb_info(info); + 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; + /* only for kernel-internal use */ + var.activate &= ~FB_ACTIVATE_KD_TEXT; + console_lock(); + lock_fb_info(info); + ret = fbcon_modechange_possible(info, &var); + if (!ret) + ret = fb_set_var(info, &var); + if (!ret) + fbcon_update_vcs(info, var.activate & FB_ACTIVATE_ALL); + unlock_fb_info(info); + console_unlock(); + if (!ret && copy_to_user(argp, &var, sizeof(var))) + ret = -EFAULT; + break; + case FBIOGET_FSCREENINFO: + lock_fb_info(info); + memcpy(&fix, &info->fix, sizeof(fix)); + if (info->flags & FBINFO_HIDE_SMEM_START) + fix.smem_start = 0; + 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; + lock_fb_info(info); + 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(); + lock_fb_info(info); + 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: + ret = fbcon_get_con2fb_map_ioctl(argp); + break; + case FBIOPUT_CON2FBMAP: + ret = fbcon_set_con2fb_map_ioctl(argp); + break; + case FBIOBLANK: + if (arg > FB_BLANK_POWERDOWN) + return -EINVAL; + console_lock(); + lock_fb_info(info); + ret = fb_blank(info, arg); + /* might again call into fb_blank */ + fbcon_fb_blanked(info, arg); + unlock_fb_info(info); + console_unlock(); + break; + default: + lock_fb_info(info); + 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_cmap32 cmap32; + struct fb_cmap cmap_from; + struct fb_cmap_user cmap; + + if (copy_from_user(&cmap32, compat_ptr(arg), sizeof(cmap32))) + return -EFAULT; + + cmap = (struct fb_cmap_user) { + .start = cmap32.start, + .len = cmap32.len, + .red = compat_ptr(cmap32.red), + .green = compat_ptr(cmap32.green), + .blue = compat_ptr(cmap32.blue), + .transp = compat_ptr(cmap32.transp), + }; + + if (cmd == FBIOPUTCMAP) + return fb_set_user_cmap(&cmap, info); + + lock_fb_info(info); + cmap_from = info->cmap; + unlock_fb_info(info); + + return fb_cmap_to_user(&cmap_from, &cmap); +} + +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) +{ + struct fb_fix_screeninfo fix; + + lock_fb_info(info); + fix = info->fix; + if (info->flags & FBINFO_HIDE_SMEM_START) + fix.smem_start = 0; + unlock_fb_info(info); + return do_fscreeninfo_to_user(&fix, compat_ptr(arg)); +} + +static long fb_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct fb_info *info = file_fb_info(file); + const 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); + fallthrough; + 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); + unsigned long mmio_pgoff; + unsigned long start; + u32 len; + + if (!info) + return -ENODEV; + mutex_lock(&info->mm_lock); + + if (info->fbops->fb_mmap) { + int res; + + /* + * The framebuffer needs to be accessed decrypted, be sure + * SME protection is removed ahead of the call + */ + vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); + res = info->fbops->fb_mmap(info, vma); + mutex_unlock(&info->mm_lock); + return res; +#if IS_ENABLED(CONFIG_FB_DEFERRED_IO) + } else if (info->fbdefio) { + /* + * FB deferred I/O wants you to handle mmap in your drivers. At a + * minimum, point struct fb_ops.fb_mmap to fb_deferred_io_mmap(). + */ + dev_warn_once(info->dev, "fbdev mmap not set up for deferred I/O.\n"); + mutex_unlock(&info->mm_lock); + return -ENODEV; +#endif + } + + /* + * 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); + + lock_fb_info(info); + 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: + unlock_fb_info(info); + 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; + + lock_fb_info(info); +#if IS_ENABLED(CONFIG_FB_DEFERRED_IO) + if (info->fbdefio) + fb_deferred_io_release(info); +#endif + if (info->fbops->fb_release) + info->fbops->fb_release(info, 1); + module_put(info->fbops->owner); + unlock_fb_info(info); + put_fb_info(info); + return 0; +} + +#if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU) +static unsigned long get_fb_unmapped_area(struct file *filp, + unsigned long addr, unsigned long len, + unsigned long pgoff, unsigned long flags) +{ + struct fb_info * const info = filp->private_data; + unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len); + + if (pgoff > fb_size || len > fb_size - pgoff) + return -EINVAL; + + return (unsigned long)info->screen_base + pgoff; +} +#endif + +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, +#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \ + (defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \ + !defined(CONFIG_MMU)) + .get_unmapped_area = get_fb_unmapped_area, +#endif +#ifdef CONFIG_FB_DEFERRED_IO + .fsync = fb_deferred_io_fsync, +#endif + .llseek = default_llseek, +}; + +int fb_register_chrdev(void) +{ + int ret; + + ret = register_chrdev(FB_MAJOR, "fb", &fb_fops); + if (ret) { + pr_err("Unable to get major %d for fb devs\n", FB_MAJOR); + return ret; + } + + return ret; +} + +void fb_unregister_chrdev(void) +{ + unregister_chrdev(FB_MAJOR, "fb"); +} diff --git a/drivers/video/fbdev/core/fb_cmdline.c b/drivers/video/fbdev/core/fb_cmdline.c new file mode 100644 index 0000000000..4d1634c492 --- /dev/null +++ b/drivers/video/fbdev/core/fb_cmdline.c @@ -0,0 +1,61 @@ +/* + * linux/drivers/video/fb_cmdline.c + * + * Copyright (C) 2014 Intel Corp + * 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. + * + * Authors: + * Daniel Vetter <daniel.vetter@ffwll.ch> + */ + +#include <linux/export.h> +#include <linux/fb.h> +#include <linux/string.h> + +#include <video/cmdline.h> + +/** + * 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 + * + * The caller owns the string returned in @option and is + * responsible for releasing the memory. + * + * NOTE: Needed to maintain backwards compatibility + */ +int fb_get_options(const char *name, char **option) +{ + const char *options = NULL; + bool is_of = false; + bool enabled; + + if (name) + is_of = strncmp(name, "offb", 4); + + enabled = __video_get_options(name, &options, is_of); + + if (options) { + if (!strncmp(options, "off", 3)) + enabled = false; + } + + if (option) { + if (options) + *option = kstrdup(options, GFP_KERNEL); + else + *option = NULL; + } + + return enabled ? 0 : 1; // 0 on success, 1 otherwise +} +EXPORT_SYMBOL(fb_get_options); diff --git a/drivers/video/fbdev/core/fb_ddc.c b/drivers/video/fbdev/core/fb_ddc.c new file mode 100644 index 0000000000..8bf5f2f54b --- /dev/null +++ b/drivers/video/fbdev/core/fb_ddc.c @@ -0,0 +1,127 @@ +/* + * 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); + if (algo_data->getscl) { + for (j = 0; j < 5; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + if (j == 5) + continue; + } else { + udelay(algo_data->udelay); + } + + 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); + if (algo_data->getscl) { + for (j = 0; j < 10; j++) { + msleep(10); + if (algo_data->getscl(algo_data->data)) + break; + } + } else { + udelay(algo_data->udelay); + } + + 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 0000000000..1ae1d35a59 --- /dev/null +++ b/drivers/video/fbdev/core/fb_defio.c @@ -0,0 +1,343 @@ +/* + * 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; +} + +static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info *info, + unsigned long offset, + struct page *page) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct list_head *pos = &fbdefio->pagereflist; + unsigned long pgoff = offset >> PAGE_SHIFT; + struct fb_deferred_io_pageref *pageref, *cur; + + if (WARN_ON_ONCE(pgoff >= info->npagerefs)) + return NULL; /* incorrect allocation size */ + + /* 1:1 mapping between pageref and page offset */ + pageref = &info->pagerefs[pgoff]; + + /* + * 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 a call to .page_mkwrite even if the original process' + * PTE is marked writable. + */ + if (!list_empty(&pageref->list)) + goto pageref_already_added; + + pageref->page = page; + pageref->offset = pgoff << PAGE_SHIFT; + + if (unlikely(fbdefio->sort_pagereflist)) { + /* + * We loop through the list of pagerefs before adding in + * order to keep the pagerefs sorted. This has significant + * overhead of O(n^2) with n being the number of written + * pages. If possible, drivers should try to work with + * unsorted page lists instead. + */ + list_for_each_entry(cur, &fbdefio->pagereflist, list) { + if (cur->offset > pageref->offset) + break; + } + pos = &cur->list; + } + + list_add_tail(&pageref->list, pos); + +pageref_already_added: + return pageref; +} + +static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref, + struct fb_info *info) +{ + list_del_init(&pageref->list); +} + +/* this is to find and return the vmalloc-ed fb pages */ +static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + struct fb_info *info = vmf->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 (vmf->vma->vm_file) + page->mapping = vmf->vma->vm_file->f_mapping; + else + printk(KERN_ERR "no mapping available\n"); + + BUG_ON(!page->mapping); + page->index = vmf->pgoff; /* for page_mkclean() */ + + 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 = file_write_and_wait_range(file, start, end); + if (err) + return err; + + /* Skip if deferred io is compiled-in but disabled on this fbdev */ + if (!info->fbdefio) + return 0; + + inode_lock(inode); + flush_delayed_work(&info->deferred_work); + inode_unlock(inode); + + return 0; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); + +/* + * Adds a page to the dirty list. Call this from struct + * vm_operations_struct.page_mkwrite. + */ +static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset, + struct page *page) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct fb_deferred_io_pageref *pageref; + vm_fault_t ret; + + /* protect against the workqueue changing the page list */ + mutex_lock(&fbdefio->lock); + + pageref = fb_deferred_io_pageref_get(info, offset, page); + if (WARN_ON_ONCE(!pageref)) { + ret = VM_FAULT_OOM; + goto err_mutex_unlock; + } + + /* + * 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(pageref->page); + + 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; + +err_mutex_unlock: + mutex_unlock(&fbdefio->lock); + return ret; +} + +/* + * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O + * @fb_info: The fbdev info structure + * @vmf: The VM fault + * + * 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. + * + * Returns: + * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise. + */ +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf) +{ + unsigned long offset = vmf->address - vmf->vma->vm_start; + struct page *page = vmf->page; + + file_update_time(vmf->vma->vm_file); + + return fb_deferred_io_track_page(info, offset, page); +} + +/* vm_ops->page_mkwrite handler */ +static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) +{ + struct fb_info *info = vmf->vma->vm_private_data; + + return fb_deferred_io_page_mkwrite(info, vmf); +} + +static const struct vm_operations_struct fb_deferred_io_vm_ops = { + .fault = fb_deferred_io_fault, + .page_mkwrite = fb_deferred_io_mkwrite, +}; + +static const struct address_space_operations fb_deferred_io_aops = { + .dirty_folio = noop_dirty_folio, +}; + +int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + vma->vm_ops = &fb_deferred_io_vm_ops; + vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP); + if (!(info->flags & FBINFO_VIRTFB)) + vm_flags_set(vma, VM_IO); + vma->vm_private_data = info; + return 0; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_mmap); + +/* 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 fb_deferred_io_pageref *pageref, *next; + 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(pageref, &fbdefio->pagereflist, list) { + struct page *cur = pageref->page; + lock_page(cur); + page_mkclean(cur); + unlock_page(cur); + } + + /* driver's callback with pagereflist */ + fbdefio->deferred_io(info, &fbdefio->pagereflist); + + /* clear the list */ + list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list) + fb_deferred_io_pageref_put(pageref, info); + + mutex_unlock(&fbdefio->lock); +} + +int fb_deferred_io_init(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct fb_deferred_io_pageref *pagerefs; + unsigned long npagerefs, i; + int ret; + + BUG_ON(!fbdefio); + + if (WARN_ON(!info->fix.smem_len)) + return -EINVAL; + + mutex_init(&fbdefio->lock); + INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); + INIT_LIST_HEAD(&fbdefio->pagereflist); + if (fbdefio->delay == 0) /* set a default of 1 s */ + fbdefio->delay = HZ; + + npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE); + + /* alloc a page ref for each page of the display memory */ + pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL); + if (!pagerefs) { + ret = -ENOMEM; + goto err; + } + for (i = 0; i < npagerefs; ++i) + INIT_LIST_HEAD(&pagerefs[i].list); + info->npagerefs = npagerefs; + info->pagerefs = pagerefs; + + return 0; + +err: + mutex_destroy(&fbdefio->lock); + return ret; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_init); + +void fb_deferred_io_open(struct fb_info *info, + struct inode *inode, + struct file *file) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + file->f_mapping->a_ops = &fb_deferred_io_aops; + fbdefio->open_count++; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_open); + +static void fb_deferred_io_lastclose(struct fb_info *info) +{ + struct page *page; + int i; + + flush_delayed_work(&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; + } +} + +void fb_deferred_io_release(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + if (!--fbdefio->open_count) + fb_deferred_io_lastclose(info); +} +EXPORT_SYMBOL_GPL(fb_deferred_io_release); + +void fb_deferred_io_cleanup(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + fb_deferred_io_lastclose(info); + + kvfree(info->pagerefs); + mutex_destroy(&fbdefio->lock); +} +EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); diff --git a/drivers/video/fbdev/core/fb_draw.h b/drivers/video/fbdev/core/fb_draw.h new file mode 100644 index 0000000000..e0d8298739 --- /dev/null +++ b/drivers/video/fbdev/core/fb_draw.h @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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_info.c b/drivers/video/fbdev/core/fb_info.c new file mode 100644 index 0000000000..4847ebe50d --- /dev/null +++ b/drivers/video/fbdev/core/fb_info.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/export.h> +#include <linux/fb.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +/** + * 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). The new instances of struct fb_info and + * the driver private data are both cleared to zero. + * + * 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; + info->fbcon_rotate_hint = -1; + +#if IS_ENABLED(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; + + if (WARN_ON(refcount_read(&info->count))) + return; + +#if IS_ENABLED(CONFIG_FB_BACKLIGHT) + mutex_destroy(&info->bl_curve_mutex); +#endif + + kfree(info); +} +EXPORT_SYMBOL(framebuffer_release); diff --git a/drivers/video/fbdev/core/fb_internal.h b/drivers/video/fbdev/core/fb_internal.h new file mode 100644 index 0000000000..4c8d509a00 --- /dev/null +++ b/drivers/video/fbdev/core/fb_internal.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FB_INTERNAL_H +#define _FB_INTERNAL_H + +#include <linux/device.h> +#include <linux/fb.h> +#include <linux/mutex.h> + +/* fb_devfs.c */ +#if defined(CONFIG_FB_DEVICE) +int fb_register_chrdev(void); +void fb_unregister_chrdev(void); +#else +static inline int fb_register_chrdev(void) +{ + return 0; +} +static inline void fb_unregister_chrdev(void) +{ } +#endif + +/* fbmem.c */ +extern struct class *fb_class; +extern struct mutex registration_lock; +extern struct fb_info *registered_fb[FB_MAX]; +extern int num_registered_fb; +struct fb_info *get_fb_info(unsigned int idx); +void put_fb_info(struct fb_info *fb_info); + +/* fb_procfs.c */ +#if defined(CONFIG_FB_DEVICE) +int fb_init_procfs(void); +void fb_cleanup_procfs(void); +#else +static inline int fb_init_procfs(void) +{ + return 0; +} +static inline void fb_cleanup_procfs(void) +{ } +#endif + +/* fbsysfs.c */ +#if defined(CONFIG_FB_DEVICE) +int fb_device_create(struct fb_info *fb_info); +void fb_device_destroy(struct fb_info *fb_info); +#else +static inline int fb_device_create(struct fb_info *fb_info) +{ + /* + * Acquire a reference on the parent device to avoid + * unplug operations behind our back. With the fbdev + * device enabled, this is performed within register_device(). + */ + get_device(fb_info->device); + + return 0; +} +static inline void fb_device_destroy(struct fb_info *fb_info) +{ + /* Undo the get_device() from fb_device_create() */ + put_device(fb_info->device); +} +#endif + +#endif diff --git a/drivers/video/fbdev/core/fb_io_fops.c b/drivers/video/fbdev/core/fb_io_fops.c new file mode 100644 index 0000000000..5985e5e1b0 --- /dev/null +++ b/drivers/video/fbdev/core/fb_io_fops.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/fb.h> +#include <linux/module.h> +#include <linux/uaccess.h> + +ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + u8 *buffer, *dst; + u8 __iomem *src; + int c, cnt = 0, err = 0; + unsigned long total_size, trailing; + + if (!info->screen_base) + return -ENODEV; + + 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_fromio(dst, src, c); + dst += c; + src += c; + + trailing = copy_to_user(buf, buffer, c); + if (trailing == c) { + err = -EFAULT; + break; + } + c -= trailing; + + *ppos += c; + buf += c; + cnt += c; + count -= c; + } + + kfree(buffer); + + return cnt ? cnt : err; +} +EXPORT_SYMBOL(fb_io_read); + +ssize_t fb_io_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + u8 *buffer, *src; + u8 __iomem *dst; + int c, cnt = 0, err = 0; + unsigned long total_size, trailing; + + if (!info->screen_base) + return -ENODEV; + + 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; + + trailing = copy_from_user(src, buf, c); + if (trailing == c) { + err = -EFAULT; + break; + } + c -= trailing; + + fb_memcpy_toio(dst, src, c); + dst += c; + src += c; + *ppos += c; + buf += c; + cnt += c; + count -= c; + } + + kfree(buffer); + + return (cnt) ? cnt : err; +} +EXPORT_SYMBOL(fb_io_write); diff --git a/drivers/video/fbdev/core/fb_notify.c b/drivers/video/fbdev/core/fb_notify.c new file mode 100644 index 0000000000..10e3b9a74a --- /dev/null +++ b/drivers/video/fbdev/core/fb_notify.c @@ -0,0 +1,54 @@ +/* + * 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 + * + * Return: 0 on success, negative error code on failure. + */ +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 + * + * Return: 0 on success, negative error code on failure. + */ +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 + * @val: value passed to callback + * @v: pointer passed to callback + * + * Return: The return value of the last notifier function + */ +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_procfs.c b/drivers/video/fbdev/core/fb_procfs.c new file mode 100644 index 0000000000..59641142f8 --- /dev/null +++ b/drivers/video/fbdev/core/fb_procfs.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/proc_fs.h> + +#include "fb_internal.h" + +static struct proc_dir_entry *fb_proc_dir_entry; + +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_stop(struct seq_file *m, void *v) +{ + mutex_unlock(®istration_lock); +} + +static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos) +{ + (*pos)++; + + return (*pos < FB_MAX) ? pos : NULL; +} + +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 __maybe_unused fb_proc_seq_ops = { + .start = fb_seq_start, + .stop = fb_seq_stop, + .next = fb_seq_next, + .show = fb_seq_show, +}; + +int fb_init_procfs(void) +{ + struct proc_dir_entry *proc; + + proc = proc_create_seq("fb", 0, NULL, &fb_proc_seq_ops); + if (!proc) + return -ENOMEM; + + fb_proc_dir_entry = proc; + + return 0; +} + +void fb_cleanup_procfs(void) +{ + proc_remove(fb_proc_dir_entry); +} 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 0000000000..0cb0989abd --- /dev/null +++ b/drivers/video/fbdev/core/fb_sys_fops.c @@ -0,0 +1,108 @@ +/* + * 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, c; + ssize_t ret; + + if (!info->screen_buffer) + return -ENODEV; + + 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 = info->screen_buffer + p; + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + c = copy_to_user(buf, src, count); + if (c) + err = -EFAULT; + ret = count - c; + + *ppos += ret; + + return ret ? ret : err; +} +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, c; + size_t ret; + + if (!info->screen_buffer) + return -ENODEV; + + 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 = info->screen_buffer + p; + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + c = copy_from_user(dst, buf, count); + if (c) + err = -EFAULT; + ret = count - c; + + *ppos += ret; + + return ret ? ret : err; +} +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 0000000000..ff09e57f3c --- /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_gfp - 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; + + flags |= __GFP_NOWARN; + + if (cmap->len != len) { + fb_dealloc_cmap(cmap); + if (!len) + return 0; + + cmap->red = kzalloc(size, flags); + if (!cmap->red) + goto fail; + cmap->green = kzalloc(size, flags); + if (!cmap->green) + goto fail; + cmap->blue = kzalloc(size, flags); + if (!cmap->blue) + goto fail; + if (transp) { + cmap->transp = kzalloc(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) +{ + unsigned int tooff = 0, fromoff = 0; + size_t size; + + if (to->start > from->start) + fromoff = to->start - from->start; + else + tooff = from->start - to->start; + if (fromoff >= from->len || tooff >= to->len) + return -EINVAL; + + size = min_t(size_t, to->len - tooff, 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) +{ + unsigned int tooff = 0, fromoff = 0; + size_t size; + + if (to->start > from->start) + fromoff = to->start - from->start; + else + tooff = from->start - to->start; + if (fromoff >= from->len || tooff >= to->len) + return -EINVAL; + + size = min_t(size_t, to->len - tooff, 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; + lock_fb_info(info); + 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/fbcon.c b/drivers/video/fbdev/core/fbcon.c new file mode 100644 index 0000000000..f157a5a1df --- /dev/null +++ b/drivers/video/fbdev/core/fbcon.c @@ -0,0 +1,3432 @@ +/* + * linux/drivers/video/fbcon.c -- Low level frame buffer based console driver + * + * Copyright (C) 1995 Geert Uytterhoeven + * + * + * This file is based on the original Amiga console driver (amicon.c): + * + * Copyright (C) 1993 Hamish Macdonald + * Greg Harp + * Copyright (C) 1994 David Carter [carter@compsci.bristol.ac.uk] + * + * with work by William Rucklidge (wjr@cs.cornell.edu) + * Geert Uytterhoeven + * Jes Sorensen (jds@kom.auc.dk) + * Martin Apel + * + * and on the original Atari console driver (atacon.c): + * + * Copyright (C) 1993 Bjoern Brauel + * Roman Hodek + * + * with work by Guenther Kelleter + * Martin Schaller + * Andreas Schwab + * + * Hardware cursor support added by Emmanuel Marty (core@ggi-project.org) + * Smart redraw scrolling, arbitrary font width support, 512char font support + * and software scrollback added by + * Jakub Jelinek (jj@ultra.linux.cz) + * + * Random hacking by Martin Mares <mj@ucw.cz> + * + * 2001 - Documented with DocBook + * - Brad Douglas <brad@neruo.com> + * + * The low level operations for the various display memory organizations are + * now in separate source files. + * + * Currently the following organizations are supported: + * + * o afb Amiga bitplanes + * o cfb{2,4,8,16,24,32} Packed pixels + * o ilbm Amiga interleaved bitplanes + * o iplan2p[248] Atari interleaved bitplanes + * o mfb Monochrome + * o vga VGA characters/attributes + * + * To do: + * + * - Implement 16 plane mode (iplan2p16) + * + * + * 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/types.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/delay.h> /* MSch: for IRQ probe */ +#include <linux/console.h> +#include <linux/string.h> +#include <linux/kd.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/vt_kern.h> +#include <linux/selection.h> +#include <linux/font.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/crc32.h> /* For counting font checksums */ +#include <linux/uaccess.h> +#include <asm/irq.h> + +#include "fbcon.h" +#include "fb_internal.h" + +/* + * FIXME: Locking + * + * - fbcon state itself is protected by the console_lock, and the code does a + * pretty good job at making sure that lock is held everywhere it's needed. + * + * - fbcon doesn't bother with fb_lock/unlock at all. This is buggy, since it + * means concurrent access to the same fbdev from both fbcon and userspace + * will blow up. To fix this all fbcon calls from fbmem.c need to be moved out + * of fb_lock/unlock protected sections, since otherwise we'll recurse and + * deadlock eventually. Aside: Due to these deadlock issues the fbdev code in + * fbmem.c cannot use locking asserts, and there's lots of callers which get + * the rules wrong, e.g. fbsysfs.c entirely missed fb_lock/unlock calls too. + */ + +enum { + FBCON_LOGO_CANSHOW = -1, /* the logo can be shown */ + FBCON_LOGO_DRAW = -2, /* draw the logo to a console */ + FBCON_LOGO_DONTSHOW = -3 /* do not show the logo */ +}; + +static struct fbcon_display fb_display[MAX_NR_CONSOLES]; + +static struct fb_info *fbcon_registered_fb[FB_MAX]; +static int fbcon_num_registered_fb; + +#define fbcon_for_each_registered_fb(i) \ + for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++) \ + if (!fbcon_registered_fb[i]) {} else + +static signed char con2fb_map[MAX_NR_CONSOLES]; +static signed char con2fb_map_boot[MAX_NR_CONSOLES]; + +static struct fb_info *fbcon_info_from_console(int console) +{ + WARN_CONSOLE_UNLOCKED(); + + return fbcon_registered_fb[con2fb_map[console]]; +} + +static int logo_lines; +/* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO + enums. */ +static int logo_shown = FBCON_LOGO_CANSHOW; +/* console mappings */ +static unsigned int first_fb_vc; +static unsigned int last_fb_vc = MAX_NR_CONSOLES - 1; +static int fbcon_is_default = 1; +static int primary_device = -1; +static int fbcon_has_console_bind; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY +static int map_override; + +static inline void fbcon_map_override(void) +{ + map_override = 1; +} +#else +static inline void fbcon_map_override(void) +{ +} +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY */ + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER +static bool deferred_takeover = true; +#else +#define deferred_takeover false +#endif + +/* font data */ +static char fontname[40]; + +/* current fb_info */ +static int info_idx = -1; + +/* console rotation */ +static int initial_rotation = -1; +static int fbcon_has_sysfs; +static int margin_color; + +static const struct consw fb_con; + +#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) + +static int fbcon_cursor_noblink; + +#define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1) + +/* + * Interface used by the world + */ + +static void fbcon_clear_margins(struct vc_data *vc, int bottom_only); +static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table); + +/* + * Internal routines + */ +static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, + int unit); +static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int dy); +static void fbcon_modechanged(struct fb_info *info); +static void fbcon_set_all_vcs(struct fb_info *info); + +static struct device *fbcon_device; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION +static inline void fbcon_set_rotation(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + if (!(info->flags & FBINFO_MISC_TILEBLITTING) && + ops->p->con_rotate < 4) + ops->rotate = ops->p->con_rotate; + else + ops->rotate = 0; +} + +static void fbcon_rotate(struct fb_info *info, u32 rotate) +{ + struct fbcon_ops *ops= info->fbcon_par; + struct fb_info *fb_info; + + if (!ops || ops->currcon == -1) + return; + + fb_info = fbcon_info_from_console(ops->currcon); + + if (info == fb_info) { + struct fbcon_display *p = &fb_display[ops->currcon]; + + if (rotate < 4) + p->con_rotate = rotate; + else + p->con_rotate = 0; + + fbcon_modechanged(info); + } +} + +static void fbcon_rotate_all(struct fb_info *info, u32 rotate) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i; + + if (!ops || ops->currcon < 0 || rotate > 3) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + p = &fb_display[vc->vc_num]; + p->con_rotate = rotate; + } + + fbcon_set_all_vcs(info); +} +#else +static inline void fbcon_set_rotation(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + ops->rotate = FB_ROTATE_UR; +} + +static void fbcon_rotate(struct fb_info *info, u32 rotate) +{ + return; +} + +static void fbcon_rotate_all(struct fb_info *info, u32 rotate) +{ + return; +} +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */ + +static int fbcon_get_rotate(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + return (ops) ? ops->rotate : 0; +} + +static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + return (info->state != FBINFO_STATE_RUNNING || + vc->vc_mode != KD_TEXT || ops->graphics); +} + +static int get_color(struct vc_data *vc, struct fb_info *info, + u16 c, int is_fg) +{ + int depth = fb_get_color_depth(&info->var, &info->fix); + int color = 0; + + if (console_blanked) { + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + + c = vc->vc_video_erase_char & charmask; + } + + if (depth != 1) + color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c) + : attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c); + + switch (depth) { + case 1: + { + int col = mono_col(info); + /* 0 or 1 */ + int fg = (info->fix.visual != FB_VISUAL_MONO01) ? col : 0; + int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : col; + + if (console_blanked) + fg = bg; + + color = (is_fg) ? fg : bg; + break; + } + case 2: + /* + * Scale down 16-colors to 4 colors. Default 4-color palette + * is grayscale. However, simply dividing the values by 4 + * will not work, as colors 1, 2 and 3 will be scaled-down + * to zero rendering them invisible. So empirically convert + * colors to a sane 4-level grayscale. + */ + switch (color) { + case 0: + color = 0; /* black */ + break; + case 1 ... 6: + color = 2; /* white */ + break; + case 7 ... 8: + color = 1; /* gray */ + break; + default: + color = 3; /* intense white */ + break; + } + break; + case 3: + /* + * Last 8 entries of default 16-color palette is a more intense + * version of the first 8 (i.e., same chrominance, different + * luminance). + */ + color &= 7; + break; + } + + + return color; +} + +static void fb_flashcursor(struct work_struct *work) +{ + struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work); + struct fb_info *info; + struct vc_data *vc = NULL; + int c; + int mode; + int ret; + + /* FIXME: we should sort out the unbind locking instead */ + /* instead we just fail to flash the cursor if we can't get + * the lock instead of blocking fbcon deinit */ + ret = console_trylock(); + if (ret == 0) + return; + + /* protected by console_lock */ + info = ops->info; + + if (ops->currcon != -1) + vc = vc_cons[ops->currcon].d; + + if (!vc || !con_is_visible(vc) || + fbcon_info_from_console(vc->vc_num) != info || + vc->vc_deccm != 1) { + console_unlock(); + return; + } + + c = scr_readw((u16 *) vc->vc_pos); + mode = (!ops->cursor_flash || ops->cursor_state.enable) ? + CM_ERASE : CM_DRAW; + ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + console_unlock(); + + queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, + ops->cur_blink_jiffies); +} + +static void fbcon_add_cursor_work(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_cursor_noblink) + queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, + ops->cur_blink_jiffies); +} + +static void fbcon_del_cursor_work(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + cancel_delayed_work_sync(&ops->cursor_work); +} + +#ifndef MODULE +static int __init fb_console_setup(char *this_opt) +{ + char *options; + int i, j; + + if (!this_opt || !*this_opt) + return 1; + + while ((options = strsep(&this_opt, ",")) != NULL) { + if (!strncmp(options, "font:", 5)) { + strscpy(fontname, options + 5, sizeof(fontname)); + continue; + } + + if (!strncmp(options, "scrollback:", 11)) { + pr_warn("Ignoring scrollback size option\n"); + continue; + } + + if (!strncmp(options, "map:", 4)) { + options += 4; + if (*options) { + for (i = 0, j = 0; i < MAX_NR_CONSOLES; i++) { + if (!options[j]) + j = 0; + con2fb_map_boot[i] = + (options[j++]-'0') % FB_MAX; + } + + fbcon_map_override(); + } + continue; + } + + if (!strncmp(options, "vc:", 3)) { + options += 3; + if (*options) + first_fb_vc = simple_strtoul(options, &options, 10) - 1; + if (first_fb_vc >= MAX_NR_CONSOLES) + first_fb_vc = 0; + if (*options++ == '-') + last_fb_vc = simple_strtoul(options, &options, 10) - 1; + if (last_fb_vc < first_fb_vc || last_fb_vc >= MAX_NR_CONSOLES) + last_fb_vc = MAX_NR_CONSOLES - 1; + fbcon_is_default = 0; + continue; + } + + if (!strncmp(options, "rotate:", 7)) { + options += 7; + if (*options) + initial_rotation = simple_strtoul(options, &options, 0); + if (initial_rotation > 3) + initial_rotation = 0; + continue; + } + + if (!strncmp(options, "margin:", 7)) { + options += 7; + if (*options) + margin_color = simple_strtoul(options, &options, 0); + continue; + } +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + if (!strcmp(options, "nodefer")) { + deferred_takeover = false; + continue; + } +#endif + + if (!strncmp(options, "logo-pos:", 9)) { + options += 9; + if (!strcmp(options, "center")) + fb_center_logo = true; + continue; + } + + if (!strncmp(options, "logo-count:", 11)) { + options += 11; + if (*options) + fb_logo_count = simple_strtol(options, &options, 0); + continue; + } + } + return 1; +} + +__setup("fbcon=", fb_console_setup); +#endif + +static int search_fb_in_map(int idx) +{ + int i, retval = 0; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + retval = 1; + } + return retval; +} + +static int search_for_mapped_con(void) +{ + int i, retval = 0; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] != -1) + retval = 1; + } + return retval; +} + +static int do_fbcon_takeover(int show_logo) +{ + int err, i; + + if (!fbcon_num_registered_fb) + return -ENODEV; + + if (!show_logo) + logo_shown = FBCON_LOGO_DONTSHOW; + + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map[i] = info_idx; + + err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc, + fbcon_is_default); + + if (err) { + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map[i] = -1; + info_idx = -1; + } else { + fbcon_has_console_bind = 1; + } + + return err; +} + +#ifdef MODULE +static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, + int cols, int rows, int new_cols, int new_rows) +{ + logo_shown = FBCON_LOGO_DONTSHOW; +} +#else +static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, + int cols, int rows, int new_cols, int new_rows) +{ + /* Need to make room for the logo */ + struct fbcon_ops *ops = info->fbcon_par; + int cnt, erase = vc->vc_video_erase_char, step; + unsigned short *save = NULL, *r, *q; + int logo_height; + + if (info->fbops->owner) { + logo_shown = FBCON_LOGO_DONTSHOW; + return; + } + + /* + * remove underline attribute from erase character + * if black and white framebuffer. + */ + if (fb_get_color_depth(&info->var, &info->fix) == 1) + erase &= ~0x400; + logo_height = fb_prepare_logo(info, ops->rotate); + logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * rows); + step = logo_lines * cols; + for (r = q - logo_lines * cols; r < q; r++) + if (scr_readw(r) != vc->vc_video_erase_char) + break; + if (r != q && new_rows >= rows + logo_lines) { + save = kmalloc(array3_size(logo_lines, new_cols, 2), + GFP_KERNEL); + if (save) { + int i = min(cols, new_cols); + scr_memsetw(save, erase, array3_size(logo_lines, new_cols, 2)); + r = q - step; + for (cnt = 0; cnt < logo_lines; cnt++, r += i) + scr_memcpyw(save + cnt * new_cols, r, 2 * i); + r = q; + } + } + if (r == q) { + /* We can scroll screen down */ + r = q - step - cols; + for (cnt = rows - logo_lines; cnt > 0; cnt--) { + scr_memcpyw(r + step, r, vc->vc_size_row); + r -= cols; + } + if (!save) { + int lines; + if (vc->state.y + logo_lines >= rows) + lines = rows - vc->state.y - 1; + else + lines = logo_lines; + vc->state.y += lines; + vc->vc_pos += lines * vc->vc_size_row; + } + } + scr_memsetw((unsigned short *) vc->vc_origin, + erase, + vc->vc_size_row * logo_lines); + + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); + update_screen(vc); + } + + if (save) { + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * + rows); + scr_memcpyw(q, save, array3_size(logo_lines, new_cols, 2)); + vc->state.y += logo_lines; + vc->vc_pos += logo_lines * vc->vc_size_row; + kfree(save); + } + + if (logo_shown == FBCON_LOGO_DONTSHOW) + return; + + if (logo_lines > vc->vc_bottom) { + logo_shown = FBCON_LOGO_CANSHOW; + printk(KERN_INFO + "fbcon_init: disable boot-logo (boot-logo bigger than screen).\n"); + } else { + logo_shown = FBCON_LOGO_DRAW; + vc->vc_top = logo_lines; + } +} +#endif /* MODULE */ + +#ifdef CONFIG_FB_TILEBLITTING +static void set_blitting_type(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + ops->p = &fb_display[vc->vc_num]; + + if ((info->flags & FBINFO_MISC_TILEBLITTING)) + fbcon_set_tileops(vc, info); + else { + fbcon_set_rotation(info); + fbcon_set_bitops(ops); + } +} + +static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +{ + int err = 0; + + if (info->flags & FBINFO_MISC_TILEBLITTING && + info->tileops->fb_get_tilemax(info) < charcount) + err = 1; + + return err; +} +#else +static void set_blitting_type(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; + ops->p = &fb_display[vc->vc_num]; + fbcon_set_rotation(info); + fbcon_set_bitops(ops); +} + +static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +{ + return 0; +} + +#endif /* CONFIG_MISC_TILEBLITTING */ + +static void fbcon_release(struct fb_info *info) +{ + lock_fb_info(info); + if (info->fbops->fb_release) + info->fbops->fb_release(info, 0); + unlock_fb_info(info); + + module_put(info->fbops->owner); + + if (info->fbcon_par) { + struct fbcon_ops *ops = info->fbcon_par; + + fbcon_del_cursor_work(info); + kfree(ops->cursor_state.mask); + kfree(ops->cursor_data); + kfree(ops->cursor_src); + kfree(ops->fontbuffer); + kfree(info->fbcon_par); + info->fbcon_par = NULL; + } +} + +static int fbcon_open(struct fb_info *info) +{ + struct fbcon_ops *ops; + + if (!try_module_get(info->fbops->owner)) + return -ENODEV; + + lock_fb_info(info); + if (info->fbops->fb_open && + info->fbops->fb_open(info, 0)) { + unlock_fb_info(info); + module_put(info->fbops->owner); + return -ENODEV; + } + unlock_fb_info(info); + + ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL); + if (!ops) { + fbcon_release(info); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor); + ops->info = info; + info->fbcon_par = ops; + ops->cur_blink_jiffies = HZ / 5; + + return 0; +} + +static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info, + int unit) +{ + int err; + + err = fbcon_open(info); + if (err) + return err; + + if (vc) + set_blitting_type(vc, info); + + return err; +} + +static void con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo, + struct fb_info *newinfo) +{ + int ret; + + fbcon_release(oldinfo); + + /* + If oldinfo and newinfo are driving the same hardware, + the fb_release() method of oldinfo may attempt to + restore the hardware state. This will leave the + newinfo in an undefined state. Thus, a call to + fb_set_par() may be needed for the newinfo. + */ + if (newinfo && newinfo->fbops->fb_set_par) { + ret = newinfo->fbops->fb_set_par(newinfo); + + if (ret) + printk(KERN_ERR "con2fb_release_oldinfo: " + "detected unhandled fb_set_par error, " + "error code %d\n", ret); + } +} + +static void con2fb_init_display(struct vc_data *vc, struct fb_info *info, + int unit, int show_logo) +{ + struct fbcon_ops *ops = info->fbcon_par; + int ret; + + ops->currcon = fg_console; + + if (info->fbops->fb_set_par && !ops->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "con2fb_init_display: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + ops->initialized = true; + ops->graphics = 0; + fbcon_set_disp(info, &info->var, unit); + + if (show_logo) { + struct vc_data *fg_vc = vc_cons[fg_console].d; + struct fb_info *fg_info = + fbcon_info_from_console(fg_console); + + fbcon_prepare_logo(fg_vc, fg_info, fg_vc->vc_cols, + fg_vc->vc_rows, fg_vc->vc_cols, + fg_vc->vc_rows); + } + + update_screen(vc_cons[fg_console].d); +} + +/** + * set_con2fb_map - map console to frame buffer device + * @unit: virtual console number to map + * @newidx: frame buffer index to map virtual console to + * @user: user request + * + * Maps a virtual console @unit to a frame buffer device + * @newidx. + * + * This should be called with the console lock held. + */ +static int set_con2fb_map(int unit, int newidx, int user) +{ + struct vc_data *vc = vc_cons[unit].d; + int oldidx = con2fb_map[unit]; + struct fb_info *info = fbcon_registered_fb[newidx]; + struct fb_info *oldinfo = NULL; + int err = 0, show_logo; + + WARN_CONSOLE_UNLOCKED(); + + if (oldidx == newidx) + return 0; + + if (!info) + return -EINVAL; + + if (!search_for_mapped_con() || !con_is_bound(&fb_con)) { + info_idx = newidx; + return do_fbcon_takeover(0); + } + + if (oldidx != -1) + oldinfo = fbcon_registered_fb[oldidx]; + + if (!search_fb_in_map(newidx)) { + err = con2fb_acquire_newinfo(vc, info, unit); + if (err) + return err; + + fbcon_add_cursor_work(info); + } + + con2fb_map[unit] = newidx; + + /* + * If old fb is not mapped to any of the consoles, + * fbcon should release it. + */ + if (oldinfo && !search_fb_in_map(oldidx)) + con2fb_release_oldinfo(vc, oldinfo, info); + + show_logo = (fg_console == 0 && !user && + logo_shown != FBCON_LOGO_DONTSHOW); + + con2fb_map_boot[unit] = newidx; + con2fb_init_display(vc, info, unit, show_logo); + + if (!search_fb_in_map(info_idx)) + info_idx = newidx; + + return err; +} + +/* + * Low Level Operations + */ +/* NOTE: fbcon cannot be __init: it may be called from do_take_over_console later */ +static int var_to_display(struct fbcon_display *disp, + struct fb_var_screeninfo *var, + struct fb_info *info) +{ + disp->xres_virtual = var->xres_virtual; + disp->yres_virtual = var->yres_virtual; + disp->bits_per_pixel = var->bits_per_pixel; + disp->grayscale = var->grayscale; + disp->nonstd = var->nonstd; + disp->accel_flags = var->accel_flags; + disp->height = var->height; + disp->width = var->width; + disp->red = var->red; + disp->green = var->green; + disp->blue = var->blue; + disp->transp = var->transp; + disp->rotate = var->rotate; + disp->mode = fb_match_mode(var, &info->modelist); + if (disp->mode == NULL) + /* This should not happen */ + return -EINVAL; + return 0; +} + +static void display_to_var(struct fb_var_screeninfo *var, + struct fbcon_display *disp) +{ + fb_videomode_to_var(var, disp->mode); + var->xres_virtual = disp->xres_virtual; + var->yres_virtual = disp->yres_virtual; + var->bits_per_pixel = disp->bits_per_pixel; + var->grayscale = disp->grayscale; + var->nonstd = disp->nonstd; + var->accel_flags = disp->accel_flags; + var->height = disp->height; + var->width = disp->width; + var->red = disp->red; + var->green = disp->green; + var->blue = disp->blue; + var->transp = disp->transp; + var->rotate = disp->rotate; +} + +static const char *fbcon_startup(void) +{ + const char *display_desc = "frame buffer device"; + struct fbcon_display *p = &fb_display[fg_console]; + struct vc_data *vc = vc_cons[fg_console].d; + const struct font_desc *font = NULL; + struct fb_info *info = NULL; + struct fbcon_ops *ops; + int rows, cols; + + /* + * If num_registered_fb is zero, this is a call for the dummy part. + * The frame buffer devices weren't initialized yet. + */ + if (!fbcon_num_registered_fb || info_idx == -1) + return display_desc; + /* + * Instead of blindly using registered_fb[0], we use info_idx, set by + * fbcon_fb_registered(); + */ + info = fbcon_registered_fb[info_idx]; + if (!info) + return NULL; + + if (fbcon_open(info)) + return NULL; + + ops = info->fbcon_par; + ops->currcon = -1; + ops->graphics = 1; + ops->cur_rotate = -1; + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) + p->con_rotate = info->fbcon_rotate_hint; + if (p->con_rotate == -1) + p->con_rotate = FB_ROTATE_UR; + + set_blitting_type(vc, info); + + /* Setup default font */ + if (!p->fontdata) { + if (!fontname[0] || !(font = find_font(fontname))) + font = get_default_font(info->var.xres, + info->var.yres, + info->pixmap.blit_x, + info->pixmap.blit_y); + vc->vc_font.width = font->width; + vc->vc_font.height = font->height; + vc->vc_font.data = (void *)(p->fontdata = font->data); + vc->vc_font.charcount = font->charcount; + } + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + + pr_debug("mode: %s\n", info->fix.id); + pr_debug("visual: %d\n", info->fix.visual); + pr_debug("res: %dx%d-%d\n", info->var.xres, + info->var.yres, + info->var.bits_per_pixel); + + fbcon_add_cursor_work(info); + return display_desc; +} + +static void fbcon_init(struct vc_data *vc, int init) +{ + struct fb_info *info; + struct fbcon_ops *ops; + struct vc_data **default_mode = vc->vc_display_fg; + struct vc_data *svc = *default_mode; + struct fbcon_display *t, *p = &fb_display[vc->vc_num]; + int logo = 1, new_rows, new_cols, rows, cols; + int ret; + + if (WARN_ON(info_idx == -1)) + return; + + if (con2fb_map[vc->vc_num] == -1) + con2fb_map[vc->vc_num] = info_idx; + + info = fbcon_info_from_console(vc->vc_num); + + if (logo_shown < 0 && console_loglevel <= CONSOLE_LOGLEVEL_QUIET) + logo_shown = FBCON_LOGO_DONTSHOW; + + if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW || + (info->fix.type == FB_TYPE_TEXT)) + logo = 0; + + if (var_to_display(p, &info->var, info)) + return; + + if (!info->fbcon_par) + con2fb_acquire_newinfo(vc, info, vc->vc_num); + + /* If we are not the first console on this + fb, copy the font from that console */ + t = &fb_display[fg_console]; + if (!p->fontdata) { + if (t->fontdata) { + struct vc_data *fvc = vc_cons[fg_console].d; + + vc->vc_font.data = (void *)(p->fontdata = + fvc->vc_font.data); + vc->vc_font.width = fvc->vc_font.width; + vc->vc_font.height = fvc->vc_font.height; + vc->vc_font.charcount = fvc->vc_font.charcount; + p->userfont = t->userfont; + + if (p->userfont) + REFCOUNT(p->fontdata)++; + } else { + const struct font_desc *font = NULL; + + if (!fontname[0] || !(font = find_font(fontname))) + font = get_default_font(info->var.xres, + info->var.yres, + info->pixmap.blit_x, + info->pixmap.blit_y); + vc->vc_font.width = font->width; + vc->vc_font.height = font->height; + vc->vc_font.data = (void *)(p->fontdata = font->data); + vc->vc_font.charcount = font->charcount; + } + } + + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + if (vc->vc_font.charcount == 256) { + vc->vc_hi_font_mask = 0; + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) + vc->vc_complement_mask <<= 1; + } + + if (!*svc->uni_pagedict_loc) + con_set_default_unimap(svc); + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + + ops = info->fbcon_par; + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) + p->con_rotate = info->fbcon_rotate_hint; + if (p->con_rotate == -1) + p->con_rotate = FB_ROTATE_UR; + + set_blitting_type(vc, info); + + cols = vc->vc_cols; + rows = vc->vc_rows; + new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + new_cols /= vc->vc_font.width; + new_rows /= vc->vc_font.height; + + /* + * We must always set the mode. The mode of the previous console + * driver could be in the same resolution but we are using different + * hardware so we have to initialize the hardware. + * + * We need to do it in fbcon_init() to prevent screen corruption. + */ + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + if (info->fbops->fb_set_par && !ops->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "fbcon_init: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + ops->initialized = true; + } + + ops->graphics = 0; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + if ((info->flags & FBINFO_HWACCEL_COPYAREA) && + !(info->flags & FBINFO_HWACCEL_DISABLED)) + p->scrollmode = SCROLL_MOVE; + else /* default to something safe */ + p->scrollmode = SCROLL_REDRAW; +#endif + + /* + * ++guenther: console.c:vc_allocate() relies on initializing + * vc_{cols,rows}, but we must not set those if we are only + * resizing the console. + */ + if (init) { + vc->vc_cols = new_cols; + vc->vc_rows = new_rows; + } else + vc_resize(vc, new_cols, new_rows); + + if (logo) + fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); + + if (ops->rotate_font && ops->rotate_font(info, vc)) { + ops->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + + ops->p = &fb_display[fg_console]; +} + +static void fbcon_free_font(struct fbcon_display *p) +{ + if (p->userfont && p->fontdata && (--REFCOUNT(p->fontdata) == 0)) + kfree(p->fontdata - FONT_EXTRA_WORDS * sizeof(int)); + p->fontdata = NULL; + p->userfont = 0; +} + +static void set_vc_hi_font(struct vc_data *vc, bool set); + +static void fbcon_release_all(void) +{ + struct fb_info *info; + int i, j, mapped; + + fbcon_for_each_registered_fb(i) { + mapped = 0; + info = fbcon_registered_fb[i]; + + for (j = first_fb_vc; j <= last_fb_vc; j++) { + if (con2fb_map[j] == i) { + mapped = 1; + con2fb_map[j] = -1; + } + } + + if (mapped) + fbcon_release(info); + } +} + +static void fbcon_deinit(struct vc_data *vc) +{ + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_info *info; + struct fbcon_ops *ops; + int idx; + + fbcon_free_font(p); + idx = con2fb_map[vc->vc_num]; + + if (idx == -1) + goto finished; + + info = fbcon_registered_fb[idx]; + + if (!info) + goto finished; + + ops = info->fbcon_par; + + if (!ops) + goto finished; + + if (con_is_visible(vc)) + fbcon_del_cursor_work(info); + + ops->initialized = false; +finished: + + fbcon_free_font(p); + vc->vc_font.data = NULL; + + if (vc->vc_hi_font_mask && vc->vc_screenbuf) + set_vc_hi_font(vc, false); + + if (!con_is_bound(&fb_con)) + fbcon_release_all(); + + if (vc->vc_num == logo_shown) + logo_shown = FBCON_LOGO_CANSHOW; + + return; +} + +/* ====================================================================== */ + +/* fbcon_XXX routines - interface used by the world + * + * This system is now divided into two levels because of complications + * caused by hardware scrolling. Top level functions: + * + * fbcon_bmove(), fbcon_clear(), fbcon_putc(), fbcon_clear_margins() + * + * handles y values in range [0, scr_height-1] that correspond to real + * screen positions. y_wrap shift means that first line of bitmap may be + * anywhere on this display. These functions convert lineoffsets to + * bitmap offsets and deal with the wrap-around case by splitting blits. + * + * fbcon_bmove_physical_8() -- These functions fast implementations + * fbcon_clear_physical_8() -- of original fbcon_XXX fns. + * fbcon_putc_physical_8() -- (font width != 8) may be added later + * + * WARNING: + * + * At the moment fbcon_putc() cannot blit across vertical wrap boundary + * Implies should only really hardware scroll in rows. Only reason for + * restriction is simplicity & efficiency at the moment. + */ + +static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height, + int width) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + struct fbcon_display *p = &fb_display[vc->vc_num]; + u_int y_break; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!height || !width) + return; + + if (sy < vc->vc_top && vc->vc_top == logo_lines) { + vc->vc_top = 0; + /* + * If the font dimensions are not an integral of the display + * dimensions then the ops->clear below won't end up clearing + * the margins. Call clear_margins here in case the logo + * bitmap stretched into the margin area. + */ + fbcon_clear_margins(vc, 0); + } + + /* Split blits that cross physical y_wrap boundary */ + + y_break = p->vrows - p->yscroll; + if (sy < y_break && sy + height - 1 >= y_break) { + u_int b = y_break - sy; + ops->clear(vc, info, real_y(p, sy), sx, b, width); + ops->clear(vc, info, real_y(p, sy + b), sx, height - b, + width); + } else + ops->clear(vc, info, real_y(p, sy), sx, height, width); +} + +static void fbcon_putcs(struct vc_data *vc, const unsigned short *s, + int count, int ypos, int xpos) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) + ops->putcs(vc, info, s, count, real_y(p, ypos), xpos, + get_color(vc, info, scr_readw(s), 1), + get_color(vc, info, scr_readw(s), 0)); +} + +static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos) +{ + unsigned short chr; + + scr_writew(c, &chr); + fbcon_putcs(vc, &chr, 1, ypos, xpos); +} + +static void fbcon_clear_margins(struct vc_data *vc, int bottom_only) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) + ops->clear_margins(vc, info, margin_color, bottom_only); +} + +static void fbcon_cursor(struct vc_data *vc, int mode) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + int c = scr_readw((u16 *) vc->vc_pos); + + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1) + return; + + if (vc->vc_cursor_type & CUR_SW) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; + + if (!ops->cursor) + return; + + ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); +} + +static int scrollback_phys_max = 0; +static int scrollback_max = 0; +static int scrollback_current = 0; + +static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, + int unit) +{ + struct fbcon_display *p, *t; + struct vc_data **default_mode, *vc; + struct vc_data *svc; + struct fbcon_ops *ops = info->fbcon_par; + int rows, cols; + + p = &fb_display[unit]; + + if (var_to_display(p, var, info)) + return; + + vc = vc_cons[unit].d; + + if (!vc) + return; + + default_mode = vc->vc_display_fg; + svc = *default_mode; + t = &fb_display[svc->vc_num]; + + if (!vc->vc_font.data) { + vc->vc_font.data = (void *)(p->fontdata = t->fontdata); + vc->vc_font.width = (*default_mode)->vc_font.width; + vc->vc_font.height = (*default_mode)->vc_font.height; + vc->vc_font.charcount = (*default_mode)->vc_font.charcount; + p->userfont = t->userfont; + if (p->userfont) + REFCOUNT(p->fontdata)++; + } + + var->activate = FB_ACTIVATE_NOW; + info->var.activate = var->activate; + var->yoffset = info->var.yoffset; + var->xoffset = info->var.xoffset; + fb_set_var(info, var); + ops->var = info->var; + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + if (vc->vc_font.charcount == 256) { + vc->vc_hi_font_mask = 0; + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) + vc->vc_complement_mask <<= 1; + } + + if (!*svc->uni_pagedict_loc) + con_set_default_unimap(svc); + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + + if (con_is_visible(vc)) { + update_screen(vc); + } +} + +static __inline__ void ywrap_up(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; + if (p->yscroll >= p->vrows) /* Deal with wrap */ + p->yscroll -= p->vrows; + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode |= FB_VMODE_YWRAP; + ops->update_start(info); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ywrap_down(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; + if (p->yscroll < 0) /* Deal with wrap */ + p->yscroll += p->vrows; + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode |= FB_VMODE_YWRAP; + ops->update_start(info); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static __inline__ void ypan_up(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + p->yscroll += count; + if (p->yscroll > p->vrows - vc->vc_rows) { + ops->bmove(vc, info, p->vrows - vc->vc_rows, + 0, 0, 0, vc->vc_rows, vc->vc_cols); + p->yscroll -= p->vrows - vc->vc_rows; + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; + + if (p->yscroll > p->vrows - vc->vc_rows) { + p->yscroll -= p->vrows - vc->vc_rows; + fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t); + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ypan_down(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + p->yscroll -= count; + if (p->yscroll < 0) { + ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, + 0, vc->vc_rows, vc->vc_cols); + p->yscroll += p->vrows - vc->vc_rows; + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; + + if (p->yscroll < 0) { + p->yscroll += p->vrows - vc->vc_rows; + fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count); + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int dy) +{ + unsigned short *s = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + unsigned short attr = 1; + + do { + c = scr_readw(s); + if (attr != (c & 0xff00)) { + attr = c & 0xff00; + if (s > start) { + fbcon_putcs(vc, start, s - start, + dy, x); + x += s - start; + start = s; + } + } + console_conditional_schedule(); + s++; + } while (s < le); + if (s > start) + fbcon_putcs(vc, start, s - start, dy, x); + console_conditional_schedule(); + dy++; + } +} + +static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info, + struct fbcon_display *p, int line, int count, int ycount) +{ + int offset = ycount * vc->vc_cols; + unsigned short *d = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + unsigned short *s = d + offset; + struct fbcon_ops *ops = info->fbcon_par; + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + + do { + c = scr_readw(s); + + if (c == scr_readw(d)) { + if (s > start) { + ops->bmove(vc, info, line + ycount, x, + line, x, 1, s-start); + x += s - start + 1; + start = s + 1; + } else { + x++; + start++; + } + } + + scr_writew(c, d); + console_conditional_schedule(); + s++; + d++; + } while (s < le); + if (s > start) + ops->bmove(vc, info, line + ycount, x, line, x, 1, + s-start); + console_conditional_schedule(); + if (ycount > 0) + line++; + else { + line--; + /* NOTE: We subtract two lines from these pointers */ + s -= vc->vc_size_row; + d -= vc->vc_size_row; + } + } +} + +static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset) +{ + unsigned short *d = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + unsigned short *s = d + offset; + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + unsigned short attr = 1; + + do { + c = scr_readw(s); + if (attr != (c & 0xff00)) { + attr = c & 0xff00; + if (s > start) { + fbcon_putcs(vc, start, s - start, + line, x); + x += s - start; + start = s; + } + } + if (c == scr_readw(d)) { + if (s > start) { + fbcon_putcs(vc, start, s - start, + line, x); + x += s - start + 1; + start = s + 1; + } else { + x++; + start++; + } + } + scr_writew(c, d); + console_conditional_schedule(); + s++; + d++; + } while (s < le); + if (s > start) + fbcon_putcs(vc, start, s - start, line, x); + console_conditional_schedule(); + if (offset > 0) + line++; + else { + line--; + /* NOTE: We subtract two lines from these pointers */ + s -= vc->vc_size_row; + d -= vc->vc_size_row; + } + } +} + +static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx, + int dy, int dx, int height, int width, u_int y_break) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + u_int b; + + if (sy < y_break && sy + height > y_break) { + b = y_break - sy; + if (dy < sy) { /* Avoid trashing self */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + } else { + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + } + return; + } + + if (dy < y_break && dy + height > y_break) { + b = y_break - dy; + if (dy < sy) { /* Avoid trashing self */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + } else { + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + } + return; + } + ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, + height, width); +} + +static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, + int height, int width) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!width || !height) + return; + + /* Split blits that cross physical y_wrap case. + * Pathological case involves 4 blits, better to use recursive + * code rather than unrolled case + * + * Recursive invocations don't need to erase the cursor over and + * over again, so we use fbcon_bmove_rec() + */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width, + p->vrows - p->yscroll); +} + +static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b, + enum con_scroll dir, unsigned int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK; + + if (fbcon_is_inactive(vc, info)) + return true; + + fbcon_cursor(vc, CM_ERASE); + + /* + * ++Geert: Only use ywrap/ypan if the console is in text mode + * ++Andrew: Only use ypan on hardware text mode when scrolling the + * whole screen (prevents flicker). + */ + + switch (dir) { + case SM_UP: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; + switch (fb_scrollmode(p)) { + case SCROLL_MOVE: + fbcon_redraw_blit(vc, info, p, t, b - t - count, + count); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + (b - count)), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + + case SCROLL_WRAP_MOVE: + if (b - t - count > 3 * vc->vc_rows >> 2) { + if (t > 0) + fbcon_bmove(vc, 0, 0, count, 0, t, + vc->vc_cols); + ywrap_up(vc, count); + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b - count, 0, b, 0, + vc->vc_rows - b, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t + count, 0, t, 0, + b - t - count, vc->vc_cols); + else + goto redraw_up; + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_REDRAW: + if ((p->yscroll + count <= + 2 * (p->vrows - vc->vc_rows)) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (t > 0) + fbcon_redraw_move(vc, p, 0, t, count); + ypan_up_redraw(vc, t, count); + if (vc->vc_rows - b > 0) + fbcon_redraw_move(vc, p, b, + vc->vc_rows - b, b); + } else + fbcon_redraw_move(vc, p, t + count, b - t - count, t); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_MOVE: + if ((p->yscroll + count <= + 2 * (p->vrows - vc->vc_rows)) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (t > 0) + fbcon_bmove(vc, 0, 0, count, 0, t, + vc->vc_cols); + ypan_up(vc, count); + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b - count, 0, b, 0, + vc->vc_rows - b, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t + count, 0, t, 0, + b - t - count, vc->vc_cols); + else + goto redraw_up; + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_REDRAW: + redraw_up: + fbcon_redraw(vc, t, b - t - count, + count * vc->vc_cols); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + (b - count)), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + } + break; + + case SM_DOWN: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; + switch (fb_scrollmode(p)) { + case SCROLL_MOVE: + fbcon_redraw_blit(vc, info, p, b - 1, b - t - count, + -count); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + t), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + + case SCROLL_WRAP_MOVE: + if (b - t - count > 3 * vc->vc_rows >> 2) { + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b, 0, b - count, 0, + vc->vc_rows - b, + vc->vc_cols); + ywrap_down(vc, count); + if (t > 0) + fbcon_bmove(vc, count, 0, 0, 0, t, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t, 0, t + count, 0, + b - t - count, vc->vc_cols); + else + goto redraw_down; + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_MOVE: + if ((count - p->yscroll <= p->vrows - vc->vc_rows) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b, 0, b - count, 0, + vc->vc_rows - b, + vc->vc_cols); + ypan_down(vc, count); + if (t > 0) + fbcon_bmove(vc, count, 0, 0, 0, t, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t, 0, t + count, 0, + b - t - count, vc->vc_cols); + else + goto redraw_down; + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_REDRAW: + if ((count - p->yscroll <= p->vrows - vc->vc_rows) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (vc->vc_rows - b > 0) + fbcon_redraw_move(vc, p, b, vc->vc_rows - b, + b - count); + ypan_down_redraw(vc, t, count); + if (t > 0) + fbcon_redraw_move(vc, p, count, t, 0); + } else + fbcon_redraw_move(vc, p, t, b - t - count, t + count); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_REDRAW: + redraw_down: + fbcon_redraw(vc, b - 1, b - t - count, + -count * vc->vc_cols); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + t), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + } + } + return false; +} + + +static void updatescrollmode_accel(struct fbcon_display *p, + struct fb_info *info, + struct vc_data *vc) +{ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + struct fbcon_ops *ops = info->fbcon_par; + int cap = info->flags; + u16 t = 0; + int ypan = FBCON_SWAP(ops->rotate, info->fix.ypanstep, + info->fix.xpanstep); + int ywrap = FBCON_SWAP(ops->rotate, info->fix.ywrapstep, t); + int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, + info->var.xres_virtual); + int good_pan = (cap & FBINFO_HWACCEL_YPAN) && + divides(ypan, vc->vc_font.height) && vyres > yres; + int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) && + divides(ywrap, vc->vc_font.height) && + divides(vc->vc_font.height, vyres) && + divides(vc->vc_font.height, yres); + int reading_fast = cap & FBINFO_READS_FAST; + int fast_copyarea = (cap & FBINFO_HWACCEL_COPYAREA) && + !(cap & FBINFO_HWACCEL_DISABLED); + int fast_imageblit = (cap & FBINFO_HWACCEL_IMAGEBLIT) && + !(cap & FBINFO_HWACCEL_DISABLED); + + if (good_wrap || good_pan) { + if (reading_fast || fast_copyarea) + p->scrollmode = good_wrap ? + SCROLL_WRAP_MOVE : SCROLL_PAN_MOVE; + else + p->scrollmode = good_wrap ? SCROLL_REDRAW : + SCROLL_PAN_REDRAW; + } else { + if (reading_fast || (fast_copyarea && !fast_imageblit)) + p->scrollmode = SCROLL_MOVE; + else + p->scrollmode = SCROLL_REDRAW; + } +#endif +} + +static void updatescrollmode(struct fbcon_display *p, + struct fb_info *info, + struct vc_data *vc) +{ + struct fbcon_ops *ops = info->fbcon_par; + int fh = vc->vc_font.height; + int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, + info->var.xres_virtual); + + p->vrows = vyres/fh; + if (yres > (fh * (vc->vc_rows + 1))) + p->vrows -= (yres - (fh * vc->vc_rows)) / fh; + if ((yres % fh) && (vyres % fh < yres % fh)) + p->vrows--; + + /* update scrollmode in case hardware acceleration is used */ + updatescrollmode_accel(p, info, vc); +} + +#define PITCH(w) (((w) + 7) >> 3) +#define CALC_FONTSZ(h, p, c) ((h) * (p) * (c)) /* size = height * pitch * charcount */ + +static int fbcon_resize(struct vc_data *vc, unsigned int width, + unsigned int height, unsigned int user) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var = info->var; + int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh; + + if (p->userfont && FNTSIZE(vc->vc_font.data)) { + int size; + int pitch = PITCH(vc->vc_font.width); + + /* + * If user font, ensure that a possible change to user font + * height or width will not allow a font data out-of-bounds access. + * NOTE: must use original charcount in calculation as font + * charcount can change and cannot be used to determine the + * font data allocated size. + */ + if (pitch <= 0) + return -EINVAL; + size = CALC_FONTSZ(vc->vc_font.height, pitch, vc->vc_font.charcount); + if (size > FNTSIZE(vc->vc_font.data)) + return -EINVAL; + } + + virt_w = FBCON_SWAP(ops->rotate, width, height); + virt_h = FBCON_SWAP(ops->rotate, height, width); + virt_fw = FBCON_SWAP(ops->rotate, vc->vc_font.width, + vc->vc_font.height); + virt_fh = FBCON_SWAP(ops->rotate, vc->vc_font.height, + vc->vc_font.width); + var.xres = virt_w * virt_fw; + var.yres = virt_h * virt_fh; + x_diff = info->var.xres - var.xres; + y_diff = info->var.yres - var.yres; + if (x_diff < 0 || x_diff > virt_fw || + y_diff < 0 || y_diff > virt_fh) { + const struct fb_videomode *mode; + + pr_debug("attempting resize %ix%i\n", var.xres, var.yres); + mode = fb_find_best_mode(&var, &info->modelist); + if (mode == NULL) + return -EINVAL; + display_to_var(&var, p); + fb_videomode_to_var(&var, mode); + + if (virt_w > var.xres/virt_fw || virt_h > var.yres/virt_fh) + return -EINVAL; + + pr_debug("resize now %ix%i\n", var.xres, var.yres); + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + var.activate = FB_ACTIVATE_NOW | + FB_ACTIVATE_FORCE; + fb_set_var(info, &var); + } + var_to_display(p, &info->var, info); + ops->var = info->var; + } + updatescrollmode(p, info, vc); + return 0; +} + +static int fbcon_switch(struct vc_data *vc) +{ + struct fb_info *info, *old_info = NULL; + struct fbcon_ops *ops; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var; + int i, ret, prev_console; + + info = fbcon_info_from_console(vc->vc_num); + ops = info->fbcon_par; + + if (logo_shown >= 0) { + struct vc_data *conp2 = vc_cons[logo_shown].d; + + if (conp2->vc_top == logo_lines + && conp2->vc_bottom == conp2->vc_rows) + conp2->vc_top = 0; + logo_shown = FBCON_LOGO_CANSHOW; + } + + prev_console = ops->currcon; + if (prev_console != -1) + old_info = fbcon_info_from_console(prev_console); + /* + * FIXME: If we have multiple fbdev's loaded, we need to + * update all info->currcon. Perhaps, we can place this + * in a centralized structure, but this might break some + * drivers. + * + * info->currcon = vc->vc_num; + */ + fbcon_for_each_registered_fb(i) { + if (fbcon_registered_fb[i]->fbcon_par) { + struct fbcon_ops *o = fbcon_registered_fb[i]->fbcon_par; + + o->currcon = vc->vc_num; + } + } + memset(&var, 0, sizeof(struct fb_var_screeninfo)); + display_to_var(&var, p); + var.activate = FB_ACTIVATE_NOW; + + /* + * make sure we don't unnecessarily trip the memcmp() + * in fb_set_var() + */ + info->var.activate = var.activate; + var.vmode |= info->var.vmode & ~FB_VMODE_MASK; + fb_set_var(info, &var); + ops->var = info->var; + + if (old_info != NULL && (old_info != info || + info->flags & FBINFO_MISC_ALWAYS_SETPAR)) { + if (info->fbops->fb_set_par) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "fbcon_switch: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + if (old_info != info) + fbcon_del_cursor_work(old_info); + } + + if (fbcon_is_inactive(vc, info) || + ops->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + set_blitting_type(vc, info); + ops->cursor_reset = 1; + + if (ops->rotate_font && ops->rotate_font(info, vc)) { + ops->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + + if (vc->vc_font.charcount > 256) + vc->vc_complement_mask <<= 1; + + updatescrollmode(p, info, vc); + + switch (fb_scrollmode(p)) { + case SCROLL_WRAP_MOVE: + scrollback_phys_max = p->vrows - vc->vc_rows; + break; + case SCROLL_PAN_MOVE: + case SCROLL_PAN_REDRAW: + scrollback_phys_max = p->vrows - 2 * vc->vc_rows; + if (scrollback_phys_max < 0) + scrollback_phys_max = 0; + break; + default: + scrollback_phys_max = 0; + break; + } + + scrollback_max = 0; + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { + ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; + ops->update_start(info); + } + + fbcon_set_palette(vc, color_table); + fbcon_clear_margins(vc, 0); + + if (logo_shown == FBCON_LOGO_DRAW) { + + logo_shown = fg_console; + fb_show_logo(info, ops->rotate); + update_region(vc, + vc->vc_origin + vc->vc_size_row * vc->vc_top, + vc->vc_size_row * (vc->vc_bottom - + vc->vc_top) / 2); + return 0; + } + return 1; +} + +static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info, + int blank) +{ + if (blank) { + unsigned short charmask = vc->vc_hi_font_mask ? + 0x1ff : 0xff; + unsigned short oldc; + + oldc = vc->vc_video_erase_char; + vc->vc_video_erase_char &= charmask; + fbcon_clear(vc, 0, 0, vc->vc_rows, vc->vc_cols); + vc->vc_video_erase_char = oldc; + } +} + +static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + if (mode_switch) { + struct fb_var_screeninfo var = info->var; + + ops->graphics = 1; + + if (!blank) { + var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE | + FB_ACTIVATE_KD_TEXT; + fb_set_var(info, &var); + ops->graphics = 0; + ops->var = info->var; + } + } + + if (!fbcon_is_inactive(vc, info)) { + if (ops->blank_state != blank) { + ops->blank_state = blank; + fbcon_cursor(vc, blank ? CM_ERASE : CM_DRAW); + ops->cursor_flash = (!blank); + + if (fb_blank(info, blank)) + fbcon_generic_blank(vc, info, blank); + } + + if (!blank) + update_screen(vc); + } + + if (mode_switch || fbcon_is_inactive(vc, info) || + ops->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + return 0; +} + +static int fbcon_debug_enter(struct vc_data *vc) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + ops->save_graphics = ops->graphics; + ops->graphics = 0; + if (info->fbops->fb_debug_enter) + info->fbops->fb_debug_enter(info); + fbcon_set_palette(vc, color_table); + return 0; +} + +static int fbcon_debug_leave(struct vc_data *vc) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + ops->graphics = ops->save_graphics; + if (info->fbops->fb_debug_leave) + info->fbops->fb_debug_leave(info); + return 0; +} + +static int fbcon_get_font(struct vc_data *vc, struct console_font *font, unsigned int vpitch) +{ + u8 *fontdata = vc->vc_font.data; + u8 *data = font->data; + int i, j; + + font->width = vc->vc_font.width; + font->height = vc->vc_font.height; + if (font->height > vpitch) + return -ENOSPC; + font->charcount = vc->vc_hi_font_mask ? 512 : 256; + if (!font->data) + return 0; + + if (font->width <= 8) { + j = vc->vc_font.height; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, vpitch - j); + data += vpitch; + fontdata += j; + } + } else if (font->width <= 16) { + j = vc->vc_font.height * 2; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, 2*vpitch - j); + data += 2*vpitch; + fontdata += j; + } + } else if (font->width <= 24) { + if (font->charcount * (vc->vc_font.height * sizeof(u32)) > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + for (j = 0; j < vc->vc_font.height; j++) { + *data++ = fontdata[0]; + *data++ = fontdata[1]; + *data++ = fontdata[2]; + fontdata += sizeof(u32); + } + memset(data, 0, 3 * (vpitch - j)); + data += 3 * (vpitch - j); + } + } else { + j = vc->vc_font.height * 4; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, 4 * vpitch - j); + data += 4 * vpitch; + fontdata += j; + } + } + return 0; +} + +/* set/clear vc_hi_font_mask and update vc attrs accordingly */ +static void set_vc_hi_font(struct vc_data *vc, bool set) +{ + if (!set) { + vc->vc_hi_font_mask = 0; + if (vc->vc_can_do_color) { + vc->vc_complement_mask >>= 1; + vc->vc_s_complement_mask >>= 1; + } + + /* ++Edmund: reorder the attribute bits */ + if (vc->vc_can_do_color) { + unsigned short *cp = + (unsigned short *) vc->vc_origin; + int count = vc->vc_screenbuf_size / 2; + unsigned short c; + for (; count > 0; count--, cp++) { + c = scr_readw(cp); + scr_writew(((c & 0xfe00) >> 1) | + (c & 0xff), cp); + } + c = vc->vc_video_erase_char; + vc->vc_video_erase_char = + ((c & 0xfe00) >> 1) | (c & 0xff); + vc->vc_attr >>= 1; + } + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) { + vc->vc_complement_mask <<= 1; + vc->vc_s_complement_mask <<= 1; + } + + /* ++Edmund: reorder the attribute bits */ + { + unsigned short *cp = + (unsigned short *) vc->vc_origin; + int count = vc->vc_screenbuf_size / 2; + unsigned short c; + for (; count > 0; count--, cp++) { + unsigned short newc; + c = scr_readw(cp); + if (vc->vc_can_do_color) + newc = + ((c & 0xff00) << 1) | (c & + 0xff); + else + newc = c & ~0x100; + scr_writew(newc, cp); + } + c = vc->vc_video_erase_char; + if (vc->vc_can_do_color) { + vc->vc_video_erase_char = + ((c & 0xff00) << 1) | (c & 0xff); + vc->vc_attr <<= 1; + } else + vc->vc_video_erase_char = c & ~0x100; + } + } +} + +static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount, + const u8 * data, int userfont) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + int resize, ret, old_userfont, old_width, old_height, old_charcount; + char *old_data = NULL; + + resize = (w != vc->vc_font.width) || (h != vc->vc_font.height); + if (p->userfont) + old_data = vc->vc_font.data; + vc->vc_font.data = (void *)(p->fontdata = data); + old_userfont = p->userfont; + if ((p->userfont = userfont)) + REFCOUNT(data)++; + + old_width = vc->vc_font.width; + old_height = vc->vc_font.height; + old_charcount = vc->vc_font.charcount; + + vc->vc_font.width = w; + vc->vc_font.height = h; + vc->vc_font.charcount = charcount; + if (vc->vc_hi_font_mask && charcount == 256) + set_vc_hi_font(vc, false); + else if (!vc->vc_hi_font_mask && charcount == 512) + set_vc_hi_font(vc, true); + + if (resize) { + int cols, rows; + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= w; + rows /= h; + ret = vc_resize(vc, cols, rows); + if (ret) + goto err_out; + } else if (con_is_visible(vc) + && vc->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); + update_screen(vc); + } + + if (old_data && (--REFCOUNT(old_data) == 0)) + kfree(old_data - FONT_EXTRA_WORDS * sizeof(int)); + return 0; + +err_out: + p->fontdata = old_data; + vc->vc_font.data = (void *)old_data; + + if (userfont) { + p->userfont = old_userfont; + if (--REFCOUNT(data) == 0) + kfree(data - FONT_EXTRA_WORDS * sizeof(int)); + } + + vc->vc_font.width = old_width; + vc->vc_font.height = old_height; + vc->vc_font.charcount = old_charcount; + + return ret; +} + +/* + * User asked to set font; we are guaranteed that charcount does not exceed 512 + * but lets not assume that, since charcount of 512 is small for unicode support. + */ + +static int fbcon_set_font(struct vc_data *vc, struct console_font *font, + unsigned int vpitch, unsigned int flags) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + unsigned charcount = font->charcount; + int w = font->width; + int h = font->height; + int size; + int i, csum; + u8 *new_data, *data = font->data; + int pitch = PITCH(font->width); + + /* Is there a reason why fbconsole couldn't handle any charcount >256? + * If not this check should be changed to charcount < 256 */ + if (charcount != 256 && charcount != 512) + return -EINVAL; + + /* font bigger than screen resolution ? */ + if (w > FBCON_SWAP(info->var.rotate, info->var.xres, info->var.yres) || + h > FBCON_SWAP(info->var.rotate, info->var.yres, info->var.xres)) + return -EINVAL; + + if (font->width > 32 || font->height > 32) + return -EINVAL; + + /* Make sure drawing engine can handle the font */ + if (!(info->pixmap.blit_x & BIT(font->width - 1)) || + !(info->pixmap.blit_y & BIT(font->height - 1))) + return -EINVAL; + + /* Make sure driver can handle the font length */ + if (fbcon_invalid_charcount(info, charcount)) + return -EINVAL; + + size = CALC_FONTSZ(h, pitch, charcount); + + new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size, GFP_USER); + + if (!new_data) + return -ENOMEM; + + memset(new_data, 0, FONT_EXTRA_WORDS * sizeof(int)); + + new_data += FONT_EXTRA_WORDS * sizeof(int); + FNTSIZE(new_data) = size; + REFCOUNT(new_data) = 0; /* usage counter */ + for (i=0; i< charcount; i++) { + memcpy(new_data + i*h*pitch, data + i*vpitch*pitch, h*pitch); + } + + /* Since linux has a nice crc32 function use it for counting font + * checksums. */ + csum = crc32(0, new_data, size); + + FNTSUM(new_data) = csum; + /* Check if the same font is on some other console already */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + struct vc_data *tmp = vc_cons[i].d; + + if (fb_display[i].userfont && + fb_display[i].fontdata && + FNTSUM(fb_display[i].fontdata) == csum && + FNTSIZE(fb_display[i].fontdata) == size && + tmp->vc_font.width == w && + !memcmp(fb_display[i].fontdata, new_data, size)) { + kfree(new_data - FONT_EXTRA_WORDS * sizeof(int)); + new_data = (u8 *)fb_display[i].fontdata; + break; + } + } + return fbcon_do_set_font(vc, font->width, font->height, charcount, new_data, 1); +} + +static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, char *name) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + const struct font_desc *f; + + if (!name) + f = get_default_font(info->var.xres, info->var.yres, + info->pixmap.blit_x, info->pixmap.blit_y); + else if (!(f = find_font(name))) + return -ENOENT; + + font->width = f->width; + font->height = f->height; + return fbcon_do_set_font(vc, f->width, f->height, f->charcount, f->data, 0); +} + +static u16 palette_red[16]; +static u16 palette_green[16]; +static u16 palette_blue[16]; + +static struct fb_cmap palette_cmap = { + 0, 16, palette_red, palette_green, palette_blue, NULL +}; + +static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + int i, j, k, depth; + u8 val; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!con_is_visible(vc)) + return; + + depth = fb_get_color_depth(&info->var, &info->fix); + if (depth > 3) { + for (i = j = 0; i < 16; i++) { + k = table[i]; + val = vc->vc_palette[j++]; + palette_red[k] = (val << 8) | val; + val = vc->vc_palette[j++]; + palette_green[k] = (val << 8) | val; + val = vc->vc_palette[j++]; + palette_blue[k] = (val << 8) | val; + } + palette_cmap.len = 16; + palette_cmap.start = 0; + /* + * If framebuffer is capable of less than 16 colors, + * use default palette of fbcon. + */ + } else + fb_copy_cmap(fb_default_cmap(1 << depth), &palette_cmap); + + fb_set_cmap(&palette_cmap, info); +} + +static u16 *fbcon_screen_pos(const struct vc_data *vc, int offset) +{ + return (u16 *) (vc->vc_origin + offset); +} + +static unsigned long fbcon_getxy(struct vc_data *vc, unsigned long pos, + int *px, int *py) +{ + unsigned long ret; + int x, y; + + if (pos >= vc->vc_origin && pos < vc->vc_scr_end) { + unsigned long offset = (pos - vc->vc_origin) / 2; + + x = offset % vc->vc_cols; + y = offset / vc->vc_cols; + ret = pos + (vc->vc_cols - x) * 2; + } else { + /* Should not happen */ + x = y = 0; + ret = vc->vc_origin; + } + if (px) + *px = x; + if (py) + *py = y; + return ret; +} + +/* As we might be inside of softback, we may work with non-contiguous buffer, + that's why we have to use a separate routine. */ +static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt) +{ + while (cnt--) { + u16 a = scr_readw(p); + if (!vc->vc_can_do_color) + a ^= 0x0800; + else if (vc->vc_hi_font_mask == 0x100) + a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | + (((a) & 0x0e00) << 4); + else + a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | + (((a) & 0x0700) << 4); + scr_writew(a, p++); + } +} + +void fbcon_suspended(struct fb_info *info) +{ + struct vc_data *vc = NULL; + struct fbcon_ops *ops = info->fbcon_par; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + + /* Clear cursor, restore saved data */ + fbcon_cursor(vc, CM_ERASE); +} + +void fbcon_resumed(struct fb_info *info) +{ + struct vc_data *vc; + struct fbcon_ops *ops = info->fbcon_par; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + + update_screen(vc); +} + +static void fbcon_modechanged(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int rows, cols; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + if (vc->vc_mode != KD_TEXT || + fbcon_info_from_console(ops->currcon) != info) + return; + + p = &fb_display[vc->vc_num]; + set_blitting_type(vc, info); + + if (con_is_visible(vc)) { + var_to_display(p, &info->var, info); + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + updatescrollmode(p, info, vc); + scrollback_max = 0; + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { + ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; + ops->update_start(info); + } + + fbcon_set_palette(vc, color_table); + update_screen(vc); + } +} + +static void fbcon_set_all_vcs(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i, rows, cols, fg = -1; + + if (!ops || ops->currcon < 0) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + if (con_is_visible(vc)) { + fg = i; + continue; + } + + p = &fb_display[vc->vc_num]; + set_blitting_type(vc, info); + var_to_display(p, &info->var, info); + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + } + + if (fg != -1) + fbcon_modechanged(info); +} + + +void fbcon_update_vcs(struct fb_info *info, bool all) +{ + if (all) + fbcon_set_all_vcs(info); + else + fbcon_modechanged(info); +} +EXPORT_SYMBOL(fbcon_update_vcs); + +/* let fbcon check if it supports a new screen resolution */ +int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + unsigned int i; + + WARN_CONSOLE_UNLOCKED(); + + if (!ops) + return 0; + + /* prevent setting a screen size which is smaller than font size */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + if (vc->vc_font.width > FBCON_SWAP(var->rotate, var->xres, var->yres) || + vc->vc_font.height > FBCON_SWAP(var->rotate, var->yres, var->xres)) + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fbcon_modechange_possible); + +int fbcon_mode_deleted(struct fb_info *info, + struct fb_videomode *mode) +{ + struct fb_info *fb_info; + struct fbcon_display *p; + int i, j, found = 0; + + /* before deletion, ensure that mode is not in use */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + j = con2fb_map[i]; + if (j == -1) + continue; + fb_info = fbcon_registered_fb[j]; + if (fb_info != info) + continue; + p = &fb_display[i]; + if (!p || !p->mode) + continue; + if (fb_mode_is_equal(p->mode, mode)) { + found = 1; + break; + } + } + return found; +} + +#ifdef CONFIG_VT_HW_CONSOLE_BINDING +static void fbcon_unbind(void) +{ + int ret; + + ret = do_unbind_con_driver(&fb_con, first_fb_vc, last_fb_vc, + fbcon_is_default); + + if (!ret) + fbcon_has_console_bind = 0; +} +#else +static inline void fbcon_unbind(void) {} +#endif /* CONFIG_VT_HW_CONSOLE_BINDING */ + +void fbcon_fb_unbind(struct fb_info *info) +{ + int i, new_idx = -1; + int idx = info->node; + + console_lock(); + + if (!fbcon_has_console_bind) { + console_unlock(); + return; + } + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] != idx && + con2fb_map[i] != -1) { + new_idx = con2fb_map[i]; + break; + } + } + + if (new_idx != -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + set_con2fb_map(i, new_idx, 0); + } + } else { + struct fb_info *info = fbcon_registered_fb[idx]; + + /* This is sort of like set_con2fb_map, except it maps + * the consoles to no device and then releases the + * oldinfo to free memory and cancel the cursor blink + * timer. I can imagine this just becoming part of + * set_con2fb_map where new_idx is -1 + */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) { + con2fb_map[i] = -1; + if (!search_fb_in_map(idx)) { + con2fb_release_oldinfo(vc_cons[i].d, + info, NULL); + } + } + } + fbcon_unbind(); + } + + console_unlock(); +} + +void fbcon_fb_unregistered(struct fb_info *info) +{ + int i, idx; + + console_lock(); + + fbcon_registered_fb[info->node] = NULL; + fbcon_num_registered_fb--; + + if (deferred_takeover) { + console_unlock(); + return; + } + + idx = info->node; + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + con2fb_map[i] = -1; + } + + if (idx == info_idx) { + info_idx = -1; + + fbcon_for_each_registered_fb(i) { + info_idx = i; + break; + } + } + + if (info_idx != -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == -1) + con2fb_map[i] = info_idx; + } + } + + if (primary_device == idx) + primary_device = -1; + + if (!fbcon_num_registered_fb) + do_unregister_con_driver(&fb_con); + console_unlock(); +} + +void fbcon_remap_all(struct fb_info *info) +{ + int i, idx = info->node; + + console_lock(); + if (deferred_takeover) { + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map_boot[i] = idx; + fbcon_map_override(); + console_unlock(); + return; + } + + for (i = first_fb_vc; i <= last_fb_vc; i++) + set_con2fb_map(i, idx, 0); + + if (con_is_bound(&fb_con)) { + printk(KERN_INFO "fbcon: Remapping primary device, " + "fb%i, to tty %i-%i\n", idx, + first_fb_vc + 1, last_fb_vc + 1); + info_idx = idx; + } + console_unlock(); +} + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY +static void fbcon_select_primary(struct fb_info *info) +{ + if (!map_override && primary_device == -1 && + fb_is_primary_device(info)) { + int i; + + printk(KERN_INFO "fbcon: %s (fb%i) is primary device\n", + info->fix.id, info->node); + primary_device = info->node; + + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map_boot[i] = primary_device; + + if (con_is_bound(&fb_con)) { + printk(KERN_INFO "fbcon: Remapping primary device, " + "fb%i, to tty %i-%i\n", info->node, + first_fb_vc + 1, last_fb_vc + 1); + info_idx = primary_device; + } + } + +} +#else +static inline void fbcon_select_primary(struct fb_info *info) +{ + return; +} +#endif /* CONFIG_FRAMEBUFFER_DETECT_PRIMARY */ + +static bool lockless_register_fb; +module_param_named_unsafe(lockless_register_fb, lockless_register_fb, bool, 0400); +MODULE_PARM_DESC(lockless_register_fb, + "Lockless framebuffer registration for debugging [default=off]"); + +/* called with console_lock held */ +static int do_fb_registered(struct fb_info *info) +{ + int ret = 0, i, idx; + + WARN_CONSOLE_UNLOCKED(); + + fbcon_registered_fb[info->node] = info; + fbcon_num_registered_fb++; + + idx = info->node; + fbcon_select_primary(info); + + if (deferred_takeover) { + pr_info("fbcon: Deferring console take-over\n"); + return 0; + } + + if (info_idx == -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map_boot[i] == idx) { + info_idx = idx; + break; + } + } + + if (info_idx != -1) + ret = do_fbcon_takeover(1); + } else { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map_boot[i] == idx) + set_con2fb_map(i, idx, 0); + } + } + + return ret; +} + +int fbcon_fb_registered(struct fb_info *info) +{ + int ret; + + if (!lockless_register_fb) + console_lock(); + else + atomic_inc(&ignore_console_lock_warning); + + ret = do_fb_registered(info); + + if (!lockless_register_fb) + console_unlock(); + else + atomic_dec(&ignore_console_lock_warning); + + return ret; +} + +void fbcon_fb_blanked(struct fb_info *info, int blank) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + + if (!ops || ops->currcon < 0) + return; + + vc = vc_cons[ops->currcon].d; + if (vc->vc_mode != KD_TEXT || + fbcon_info_from_console(ops->currcon) != info) + return; + + if (con_is_visible(vc)) { + if (blank) + do_blank_screen(0); + else + do_unblank_screen(0); + } + ops->blank_state = blank; +} + +void fbcon_new_modelist(struct fb_info *info) +{ + int i; + struct vc_data *vc; + struct fb_var_screeninfo var; + const struct fb_videomode *mode; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (fbcon_info_from_console(i) != info) + continue; + if (!fb_display[i].mode) + continue; + vc = vc_cons[i].d; + display_to_var(&var, &fb_display[i]); + mode = fb_find_nearest_mode(fb_display[i].mode, + &info->modelist); + fb_videomode_to_var(&var, mode); + fbcon_set_disp(info, &var, vc->vc_num); + } +} + +void fbcon_get_requirement(struct fb_info *info, + struct fb_blit_caps *caps) +{ + struct vc_data *vc; + + if (caps->flags) { + int i, charcnt; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (vc && vc->vc_mode == KD_TEXT && + info->node == con2fb_map[i]) { + caps->x |= 1 << (vc->vc_font.width - 1); + caps->y |= 1 << (vc->vc_font.height - 1); + charcnt = vc->vc_font.charcount; + if (caps->len < charcnt) + caps->len = charcnt; + } + } + } else { + vc = vc_cons[fg_console].d; + + if (vc && vc->vc_mode == KD_TEXT && + info->node == con2fb_map[fg_console]) { + caps->x = 1 << (vc->vc_font.width - 1); + caps->y = 1 << (vc->vc_font.height - 1); + caps->len = vc->vc_font.charcount; + } + } +} + +int fbcon_set_con2fb_map_ioctl(void __user *argp) +{ + struct fb_con2fbmap con2fb; + int ret; + + 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 (!fbcon_registered_fb[con2fb.framebuffer]) + request_module("fb%d", con2fb.framebuffer); + if (!fbcon_registered_fb[con2fb.framebuffer]) { + return -EINVAL; + } + + console_lock(); + ret = set_con2fb_map(con2fb.console - 1, + con2fb.framebuffer, 1); + console_unlock(); + + return ret; +} + +int fbcon_get_con2fb_map_ioctl(void __user *argp) +{ + struct fb_con2fbmap con2fb; + + if (copy_from_user(&con2fb, argp, sizeof(con2fb))) + return -EFAULT; + if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) + return -EINVAL; + + console_lock(); + con2fb.framebuffer = con2fb_map[con2fb.console - 1]; + console_unlock(); + + return copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0; +} + +/* + * The console `switch' structure for the frame buffer based console + */ + +static const struct consw fb_con = { + .owner = THIS_MODULE, + .con_startup = fbcon_startup, + .con_init = fbcon_init, + .con_deinit = fbcon_deinit, + .con_clear = fbcon_clear, + .con_putc = fbcon_putc, + .con_putcs = fbcon_putcs, + .con_cursor = fbcon_cursor, + .con_scroll = fbcon_scroll, + .con_switch = fbcon_switch, + .con_blank = fbcon_blank, + .con_font_set = fbcon_set_font, + .con_font_get = fbcon_get_font, + .con_font_default = fbcon_set_def_font, + .con_set_palette = fbcon_set_palette, + .con_invert_region = fbcon_invert_region, + .con_screen_pos = fbcon_screen_pos, + .con_getxy = fbcon_getxy, + .con_resize = fbcon_resize, + .con_debug_enter = fbcon_debug_enter, + .con_debug_leave = fbcon_debug_leave, +}; + +static ssize_t store_rotate(struct device *device, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct fb_info *info; + int rotate, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = simple_strtoul(buf, last, 0); + fbcon_rotate(info, rotate); +err: + console_unlock(); + return count; +} + +static ssize_t store_rotate_all(struct device *device, + struct device_attribute *attr,const char *buf, + size_t count) +{ + struct fb_info *info; + int rotate, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = simple_strtoul(buf, last, 0); + fbcon_rotate_all(info, rotate); +err: + console_unlock(); + return count; +} + +static ssize_t show_rotate(struct device *device, + struct device_attribute *attr,char *buf) +{ + struct fb_info *info; + int rotate = 0, idx; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = fbcon_get_rotate(info); +err: + console_unlock(); + return sysfs_emit(buf, "%d\n", rotate); +} + +static ssize_t show_cursor_blink(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info; + struct fbcon_ops *ops; + int idx, blink = -1; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + ops = info->fbcon_par; + + if (!ops) + goto err; + + blink = delayed_work_pending(&ops->cursor_work); +err: + console_unlock(); + return sysfs_emit(buf, "%d\n", blink); +} + +static ssize_t store_cursor_blink(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info; + int blink, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + + if (!info->fbcon_par) + goto err; + + blink = simple_strtoul(buf, last, 0); + + if (blink) { + fbcon_cursor_noblink = 0; + fbcon_add_cursor_work(info); + } else { + fbcon_cursor_noblink = 1; + fbcon_del_cursor_work(info); + } + +err: + console_unlock(); + return count; +} + +static struct device_attribute device_attrs[] = { + __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), + __ATTR(rotate_all, S_IWUSR, NULL, store_rotate_all), + __ATTR(cursor_blink, S_IRUGO|S_IWUSR, show_cursor_blink, + store_cursor_blink), +}; + +static int fbcon_init_device(void) +{ + int i, error = 0; + + fbcon_has_sysfs = 1; + + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { + error = device_create_file(fbcon_device, &device_attrs[i]); + + if (error) + break; + } + + if (error) { + while (--i >= 0) + device_remove_file(fbcon_device, &device_attrs[i]); + + fbcon_has_sysfs = 0; + } + + return 0; +} + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER +static void fbcon_register_existing_fbs(struct work_struct *work) +{ + int i; + + console_lock(); + + deferred_takeover = false; + logo_shown = FBCON_LOGO_DONTSHOW; + + fbcon_for_each_registered_fb(i) + do_fb_registered(fbcon_registered_fb[i]); + + console_unlock(); +} + +static struct notifier_block fbcon_output_nb; +static DECLARE_WORK(fbcon_deferred_takeover_work, fbcon_register_existing_fbs); + +static int fbcon_output_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + WARN_CONSOLE_UNLOCKED(); + + pr_info("fbcon: Taking over console\n"); + + dummycon_unregister_output_notifier(&fbcon_output_nb); + + /* We may get called in atomic context */ + schedule_work(&fbcon_deferred_takeover_work); + + return NOTIFY_OK; +} +#endif + +static void fbcon_start(void) +{ + WARN_CONSOLE_UNLOCKED(); + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + if (conswitchp != &dummy_con) + deferred_takeover = false; + + if (deferred_takeover) { + fbcon_output_nb.notifier_call = fbcon_output_notifier; + dummycon_register_output_notifier(&fbcon_output_nb); + return; + } +#endif +} + +void __init fb_console_init(void) +{ + int i; + + console_lock(); + fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), NULL, + "fbcon"); + + if (IS_ERR(fbcon_device)) { + printk(KERN_WARNING "Unable to create device " + "for fbcon; errno = %ld\n", + PTR_ERR(fbcon_device)); + fbcon_device = NULL; + } else + fbcon_init_device(); + + for (i = 0; i < MAX_NR_CONSOLES; i++) + con2fb_map[i] = -1; + + fbcon_start(); + console_unlock(); +} + +#ifdef MODULE + +static void __exit fbcon_deinit_device(void) +{ + int i; + + if (fbcon_has_sysfs) { + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) + device_remove_file(fbcon_device, &device_attrs[i]); + + fbcon_has_sysfs = 0; + } +} + +void __exit fb_console_exit(void) +{ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + console_lock(); + if (deferred_takeover) + dummycon_unregister_output_notifier(&fbcon_output_nb); + console_unlock(); + + cancel_work_sync(&fbcon_deferred_takeover_work); +#endif + + console_lock(); + fbcon_deinit_device(); + device_destroy(fb_class, MKDEV(0, 0)); + + do_unregister_con_driver(&fb_con); + console_unlock(); +} +#endif diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h new file mode 100644 index 0000000000..0eaf54a211 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon.h @@ -0,0 +1,269 @@ +/* + * linux/drivers/video/console/fbcon.h -- Low level frame buffer based console driver + * + * Copyright (C) 1997 Geert Uytterhoeven + * + * 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. + */ + +#ifndef _VIDEO_FBCON_H +#define _VIDEO_FBCON_H + +#include <linux/types.h> +#include <linux/vt_buffer.h> +#include <linux/vt_kern.h> +#include <linux/workqueue.h> + +#include <asm/io.h> + + /* + * This is the interface between the low-level console driver and the + * low-level frame buffer device + */ + +struct fbcon_display { + /* Filled in by the low-level console driver */ + const u_char *fontdata; + int userfont; /* != 0 if fontdata kmalloc()ed */ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + u_short scrollmode; /* Scroll Method, use fb_scrollmode() */ +#endif + u_short inverse; /* != 0 text black on white as default */ + short yscroll; /* Hardware scrolling */ + int vrows; /* number of virtual rows */ + int cursor_shape; + int con_rotate; + u32 xres_virtual; + u32 yres_virtual; + u32 height; + u32 width; + u32 bits_per_pixel; + u32 grayscale; + u32 nonstd; + u32 accel_flags; + u32 rotate; + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; + const struct fb_videomode *mode; +}; + +struct fbcon_ops { + void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width); + void (*clear)(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width); + void (*putcs)(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg); + void (*clear_margins)(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only); + void (*cursor)(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg); + int (*update_start)(struct fb_info *info); + int (*rotate_font)(struct fb_info *info, struct vc_data *vc); + struct fb_var_screeninfo var; /* copy of the current fb_var_screeninfo */ + struct delayed_work cursor_work; /* Cursor timer */ + struct fb_cursor cursor_state; + struct fbcon_display *p; + struct fb_info *info; + int currcon; /* Current VC. */ + int cur_blink_jiffies; + int cursor_flash; + int cursor_reset; + int blank_state; + int graphics; + int save_graphics; /* for debug enter/leave */ + bool initialized; + int rotate; + int cur_rotate; + char *cursor_data; + u8 *fontbuffer; + u8 *fontdata; + u8 *cursor_src; + u32 cursor_size; + u32 fd_size; +}; + /* + * Attribute Decoding + */ + +/* Color */ +#define attr_fgcol(fgshift,s) \ + (((s) >> (fgshift)) & 0x0f) +#define attr_bgcol(bgshift,s) \ + (((s) >> (bgshift)) & 0x0f) + +/* Monochrome */ +#define attr_bold(s) \ + ((s) & 0x200) +#define attr_reverse(s) \ + ((s) & 0x800) +#define attr_underline(s) \ + ((s) & 0x400) +#define attr_blink(s) \ + ((s) & 0x8000) + + +static inline int mono_col(const struct fb_info *info) +{ + __u32 max_len; + max_len = max(info->var.green.length, info->var.red.length); + max_len = max(info->var.blue.length, max_len); + return (~(0xfff << max_len)) & 0xff; +} + +static inline int attr_col_ec(int shift, struct vc_data *vc, + struct fb_info *info, int is_fg) +{ + int is_mono01; + int col; + int fg; + int bg; + + if (!vc) + return 0; + + if (vc->vc_can_do_color) + return is_fg ? attr_fgcol(shift,vc->vc_video_erase_char) + : attr_bgcol(shift,vc->vc_video_erase_char); + + if (!info) + return 0; + + col = mono_col(info); + is_mono01 = info->fix.visual == FB_VISUAL_MONO01; + + if (attr_reverse(vc->vc_video_erase_char)) { + fg = is_mono01 ? col : 0; + bg = is_mono01 ? 0 : col; + } + else { + fg = is_mono01 ? 0 : col; + bg = is_mono01 ? col : 0; + } + + return is_fg ? fg : bg; +} + +#define attr_bgcol_ec(bgshift, vc, info) attr_col_ec(bgshift, vc, info, 0) +#define attr_fgcol_ec(fgshift, vc, info) attr_col_ec(fgshift, vc, info, 1) + + /* + * Scroll Method + */ + +/* There are several methods fbcon can use to move text around the screen: + * + * Operation Pan Wrap + *--------------------------------------------- + * SCROLL_MOVE copyarea No No + * SCROLL_PAN_MOVE copyarea Yes No + * SCROLL_WRAP_MOVE copyarea No Yes + * SCROLL_REDRAW imageblit No No + * SCROLL_PAN_REDRAW imageblit Yes No + * SCROLL_WRAP_REDRAW imageblit No Yes + * + * (SCROLL_WRAP_REDRAW is not implemented yet) + * + * In general, fbcon will choose the best scrolling + * method based on the rule below: + * + * Pan/Wrap > accel imageblit > accel copyarea > + * soft imageblit > (soft copyarea) + * + * Exception to the rule: Pan + accel copyarea is + * preferred over Pan + accel imageblit. + * + * The above is typical for PCI/AGP cards. Unless + * overridden, fbcon will never use soft copyarea. + * + * If you need to override the above rule, set the + * appropriate flags in fb_info->flags. For example, + * to prefer copyarea over imageblit, set + * FBINFO_READS_FAST. + * + * Other notes: + * + use the hardware engine to move the text + * (hw-accelerated copyarea() and fillrect()) + * + use hardware-supported panning on a large virtual screen + * + amifb can not only pan, but also wrap the display by N lines + * (i.e. visible line i = physical line (i+N) % yres). + * + read what's already rendered on the screen and + * write it in a different place (this is cfb_copyarea()) + * + re-render the text to the screen + * + * Whether to use wrapping or panning can only be figured out at + * runtime (when we know whether our font height is a multiple + * of the pan/wrap step) + * + */ + +#define SCROLL_MOVE 0x001 +#define SCROLL_PAN_MOVE 0x002 +#define SCROLL_WRAP_MOVE 0x003 +#define SCROLL_REDRAW 0x004 +#define SCROLL_PAN_REDRAW 0x005 + +static inline u_short fb_scrollmode(struct fbcon_display *fb) +{ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + return fb->scrollmode; +#else + /* hardcoded to SCROLL_REDRAW if acceleration was disabled. */ + return SCROLL_REDRAW; +#endif +} + + +#ifdef CONFIG_FB_TILEBLITTING +extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info); +#endif +extern void fbcon_set_bitops(struct fbcon_ops *ops); +extern int soft_cursor(struct fb_info *info, struct fb_cursor *cursor); + +#define FBCON_ATTRIBUTE_UNDERLINE 1 +#define FBCON_ATTRIBUTE_REVERSE 2 +#define FBCON_ATTRIBUTE_BOLD 4 + +static inline int real_y(struct fbcon_display *p, int ypos) +{ + int rows = p->vrows; + + ypos += p->yscroll; + return ypos < rows ? ypos : ypos - rows; +} + + +static inline int get_attribute(struct fb_info *info, u16 c) +{ + int attribute = 0; + + if (fb_get_color_depth(&info->var, &info->fix) == 1) { + if (attr_underline(c)) + attribute |= FBCON_ATTRIBUTE_UNDERLINE; + if (attr_reverse(c)) + attribute |= FBCON_ATTRIBUTE_REVERSE; + if (attr_bold(c)) + attribute |= FBCON_ATTRIBUTE_BOLD; + } + + return attribute; +} + +#define FBCON_SWAP(i,r,v) ({ \ + typeof(r) _r = (r); \ + typeof(v) _v = (v); \ + (void) (&_r == &_v); \ + (i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; }) + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION +extern void fbcon_set_rotate(struct fbcon_ops *ops); +#else +#define fbcon_set_rotate(x) do {} while(0) +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */ + +#endif /* _VIDEO_FBCON_H */ diff --git a/drivers/video/fbdev/core/fbcon_ccw.c b/drivers/video/fbdev/core/fbcon_ccw.c new file mode 100644 index 0000000000..2789ace796 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_ccw.c @@ -0,0 +1,411 @@ +/* + * linux/drivers/video/console/fbcon_ccw.c -- Software Rotation - 270 degrees + * + * Copyright (C) 2005 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/slab.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" +#include "fbcon_rotate.h" + +/* + * Rotation 270 degrees + */ + +static void ccw_update_attr(u8 *dst, u8 *src, int attribute, + struct vc_data *vc) +{ + int i, j, offset = (vc->vc_font.height < 10) ? 1 : 2; + int width = (vc->vc_font.height + 7) >> 3; + int mod = vc->vc_font.height % 8; + u8 c, msk = ~(0xff << offset), msk1 = 0; + + if (mod) + msk <<= (8 - mod); + + if (offset > mod) + msk1 |= 0x01; + + for (i = 0; i < vc->vc_font.width; i++) { + for (j = 0; j < width; j++) { + c = *src; + + if (attribute & FBCON_ATTRIBUTE_UNDERLINE) { + if (j == width - 1) + c |= msk; + + if (msk1 && j == width - 2) + c |= msk1; + } + + if (attribute & FBCON_ATTRIBUTE_BOLD && i) + *(dst - width) |= c; + + if (attribute & FBCON_ATTRIBUTE_REVERSE) + c = ~c; + src++; + *dst++ = c; + } + } +} + + +static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vyres = GETVYRES(ops->p, info); + + area.sx = sy * vc->vc_font.height; + area.sy = vyres - ((sx + width) * vc->vc_font.width); + area.dx = dy * vc->vc_font.height; + area.dy = vyres - ((dx + width) * vc->vc_font.width); + area.width = height * vc->vc_font.height; + area.height = width * vc->vc_font.width; + + info->fbops->fb_copyarea(info, &area); +} + +static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vyres = GETVYRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dx = sy * vc->vc_font.height; + region.dy = vyres - ((sx + width) * vc->vc_font.width); + region.height = width * vc->vc_font.width; + region.width = height * vc->vc_font.height; + region.rop = ROP_COPY; + + info->fbops->fb_fillrect(info, ®ion); +} + +static inline void ccw_putcs_aligned(struct vc_data *vc, struct fb_info *info, + const u16 *s, u32 attr, u32 cnt, + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) +{ + struct fbcon_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; + + if (attr) { + ccw_update_attr(buf, src, attr, vc); + src = buf; + } + + if (likely(idx == 1)) + __fb_pad_aligned_buffer(dst, d_pitch, src, idx, + vc->vc_font.width); + else + fb_pad_aligned_buffer(dst, d_pitch, src, idx, + vc->vc_font.width); + + dst += d_pitch * vc->vc_font.width; + } + + info->fbops->fb_imageblit(info, image); +} + +static void ccw_putcs(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg) +{ + struct fb_image image; + struct fbcon_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + u32 cellsize = width * vc->vc_font.width; + u32 maxcnt = info->pixmap.size/cellsize; + u32 scan_align = info->pixmap.scan_align - 1; + u32 buf_align = info->pixmap.buf_align - 1; + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; + u32 vyres = GETVYRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + image.fg_color = fg; + image.bg_color = bg; + image.dx = yy * vc->vc_font.height; + image.dy = vyres - ((xx + count) * vc->vc_font.width); + image.width = vc->vc_font.height; + image.depth = 1; + + if (attribute) { + buf = kmalloc(cellsize, GFP_KERNEL); + if (!buf) + return; + } + + s += count - 1; + + while (count) { + if (count > maxcnt) + cnt = maxcnt; + else + cnt = count; + + image.height = vc->vc_font.width * cnt; + pitch = ((image.width + 7) >> 3) + scan_align; + pitch &= ~scan_align; + size = pitch * image.height + buf_align; + size &= ~buf_align; + dst = fb_get_buffer_offset(info, &info->pixmap, size); + image.data = dst; + ccw_putcs_aligned(vc, info, s, attribute, cnt, pitch, + width, cellsize, &image, buf, dst); + image.dy += image.height; + count -= cnt; + s -= cnt; + } + + /* buf is always NULL except when in monochrome mode, so in this case + it's a gain to check buf against NULL even though kfree() handles + NULL pointers just fine */ + if (unlikely(buf)) + kfree(buf); + +} + +static void ccw_clear_margins(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only) +{ + unsigned int cw = vc->vc_font.width; + unsigned int ch = vc->vc_font.height; + unsigned int rw = info->var.yres - (vc->vc_cols*cw); + unsigned int bh = info->var.xres - (vc->vc_rows*ch); + unsigned int bs = vc->vc_rows*ch; + struct fb_fillrect region; + + region.color = color; + region.rop = ROP_COPY; + + if ((int) rw > 0 && !bottom_only) { + region.dx = 0; + region.dy = info->var.yoffset; + region.height = rw; + region.width = info->var.xres_virtual; + info->fbops->fb_fillrect(info, ®ion); + } + + if ((int) bh > 0) { + region.dx = info->var.xoffset + bs; + region.dy = 0; + region.height = info->var.yres_virtual; + region.width = bh; + info->fbops->fb_fillrect(info, ®ion); + } +} + +static void ccw_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vyres = GETVYRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->cursor_state.image.data = src; + cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { + u8 *dst; + + dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC); + if (!dst) + return; + kfree(ops->cursor_data); + ops->cursor_data = dst; + ccw_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.width || + ops->cursor_state.image.width != vc->vc_font.height || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.width; + ops->cursor_state.image.width = vc->vc_font.height; + cursor.set |= FB_CUR_SETSIZE; + } + + dx = y * vc->vc_font.height; + dy = vyres - ((vc->state.x + 1) * vc->vc_font.width); + + if (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; + int width = (vc->vc_font.width + 7)/8; + + if (!mask) + return; + + tmp = kmalloc_array(width, vc->vc_font.height, GFP_ATOMIC); + + if (!tmp) { + kfree(mask); + return; + } + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = (vc->vc_font.height - cur_height) * width; + while (size--) + tmp[i++] = 0; + size = cur_height * width; + while (size--) + tmp[i++] = 0xff; + memset(mask, 0, w * vc->vc_font.width); + rotate_ccw(tmp, mask, vc->vc_font.width, vc->vc_font.height); + kfree(tmp); + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + + if (err) + soft_cursor(info, &cursor); + + ops->cursor_reset = 0; +} + +static int ccw_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + u32 yoffset; + u32 vyres = GETVYRES(ops->p, info); + int err; + + yoffset = (vyres - info->var.yres) - ops->var.xoffset; + ops->var.xoffset = ops->var.yoffset; + ops->var.yoffset = yoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_ccw(struct fbcon_ops *ops) +{ + ops->bmove = ccw_bmove; + ops->clear = ccw_clear; + ops->putcs = ccw_putcs; + ops->clear_margins = ccw_clear_margins; + ops->cursor = ccw_cursor; + ops->update_start = ccw_update_start; +} diff --git a/drivers/video/fbdev/core/fbcon_cw.c b/drivers/video/fbdev/core/fbcon_cw.c new file mode 100644 index 0000000000..86a254c1b2 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_cw.c @@ -0,0 +1,394 @@ +/* + * linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 90 degrees + * + * Copyright (C) 2005 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/slab.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" +#include "fbcon_rotate.h" + +/* + * Rotation 90 degrees + */ + +static void cw_update_attr(u8 *dst, u8 *src, int attribute, + struct vc_data *vc) +{ + int i, j, offset = (vc->vc_font.height < 10) ? 1 : 2; + int width = (vc->vc_font.height + 7) >> 3; + u8 c, msk = ~(0xff >> offset); + + for (i = 0; i < vc->vc_font.width; i++) { + for (j = 0; j < width; j++) { + c = *src; + if (attribute & FBCON_ATTRIBUTE_UNDERLINE && !j) + c |= msk; + if (attribute & FBCON_ATTRIBUTE_BOLD && i) + c |= *(src-width); + if (attribute & FBCON_ATTRIBUTE_REVERSE) + c = ~c; + src++; + *dst++ = c; + } + } +} + + +static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vxres = GETVXRES(ops->p, info); + + area.sx = vxres - ((sy + height) * vc->vc_font.height); + area.sy = sx * vc->vc_font.width; + area.dx = vxres - ((dy + height) * vc->vc_font.height); + area.dy = dx * vc->vc_font.width; + area.width = height * vc->vc_font.height; + area.height = width * vc->vc_font.width; + + info->fbops->fb_copyarea(info, &area); +} + +static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vxres = GETVXRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dx = vxres - ((sy + height) * vc->vc_font.height); + region.dy = sx * vc->vc_font.width; + region.height = width * vc->vc_font.width; + region.width = height * vc->vc_font.height; + region.rop = ROP_COPY; + + info->fbops->fb_fillrect(info, ®ion); +} + +static inline void cw_putcs_aligned(struct vc_data *vc, struct fb_info *info, + const u16 *s, u32 attr, u32 cnt, + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) +{ + struct fbcon_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (scr_readw(s++) & charmask)*cellsize; + + if (attr) { + cw_update_attr(buf, src, attr, vc); + src = buf; + } + + if (likely(idx == 1)) + __fb_pad_aligned_buffer(dst, d_pitch, src, idx, + vc->vc_font.width); + else + fb_pad_aligned_buffer(dst, d_pitch, src, idx, + vc->vc_font.width); + + dst += d_pitch * vc->vc_font.width; + } + + info->fbops->fb_imageblit(info, image); +} + +static void cw_putcs(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg) +{ + struct fb_image image; + struct fbcon_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + u32 cellsize = width * vc->vc_font.width; + u32 maxcnt = info->pixmap.size/cellsize; + u32 scan_align = info->pixmap.scan_align - 1; + u32 buf_align = info->pixmap.buf_align - 1; + u32 cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + image.fg_color = fg; + image.bg_color = bg; + image.dx = vxres - ((yy + 1) * vc->vc_font.height); + image.dy = xx * vc->vc_font.width; + image.width = vc->vc_font.height; + image.depth = 1; + + if (attribute) { + buf = kmalloc(cellsize, GFP_KERNEL); + if (!buf) + return; + } + + while (count) { + if (count > maxcnt) + cnt = maxcnt; + else + cnt = count; + + image.height = vc->vc_font.width * cnt; + pitch = ((image.width + 7) >> 3) + scan_align; + pitch &= ~scan_align; + size = pitch * image.height + buf_align; + size &= ~buf_align; + dst = fb_get_buffer_offset(info, &info->pixmap, size); + image.data = dst; + cw_putcs_aligned(vc, info, s, attribute, cnt, pitch, + width, cellsize, &image, buf, dst); + image.dy += image.height; + count -= cnt; + s += cnt; + } + + /* buf is always NULL except when in monochrome mode, so in this case + it's a gain to check buf against NULL even though kfree() handles + NULL pointers just fine */ + if (unlikely(buf)) + kfree(buf); + +} + +static void cw_clear_margins(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only) +{ + unsigned int cw = vc->vc_font.width; + unsigned int ch = vc->vc_font.height; + unsigned int rw = info->var.yres - (vc->vc_cols*cw); + unsigned int bh = info->var.xres - (vc->vc_rows*ch); + unsigned int rs = info->var.yres - rw; + struct fb_fillrect region; + + region.color = color; + region.rop = ROP_COPY; + + if ((int) rw > 0 && !bottom_only) { + region.dx = 0; + region.dy = info->var.yoffset + rs; + region.height = rw; + region.width = info->var.xres_virtual; + info->fbops->fb_fillrect(info, ®ion); + } + + if ((int) bh > 0) { + region.dx = info->var.xoffset; + region.dy = info->var.yoffset; + region.height = info->var.yres; + region.width = bh; + info->fbops->fb_fillrect(info, ®ion); + } +} + +static void cw_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->cursor_state.image.data = src; + cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { + u8 *dst; + + dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC); + if (!dst) + return; + kfree(ops->cursor_data); + ops->cursor_data = dst; + cw_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.width || + ops->cursor_state.image.width != vc->vc_font.height || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.width; + ops->cursor_state.image.width = vc->vc_font.height; + cursor.set |= FB_CUR_SETSIZE; + } + + dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height); + dy = vc->state.x * vc->vc_font.width; + + if (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; + int width = (vc->vc_font.width + 7)/8; + + if (!mask) + return; + + tmp = kmalloc_array(width, vc->vc_font.height, GFP_ATOMIC); + + if (!tmp) { + kfree(mask); + return; + } + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = (vc->vc_font.height - cur_height) * width; + while (size--) + tmp[i++] = 0; + size = cur_height * width; + while (size--) + tmp[i++] = 0xff; + memset(mask, 0, w * vc->vc_font.width); + rotate_cw(tmp, mask, vc->vc_font.width, vc->vc_font.height); + kfree(tmp); + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + + if (err) + soft_cursor(info, &cursor); + + ops->cursor_reset = 0; +} + +static int cw_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + u32 vxres = GETVXRES(ops->p, info); + u32 xoffset; + int err; + + xoffset = vxres - (info->var.xres + ops->var.yoffset); + ops->var.yoffset = ops->var.xoffset; + ops->var.xoffset = xoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_cw(struct fbcon_ops *ops) +{ + ops->bmove = cw_bmove; + ops->clear = cw_clear; + ops->putcs = cw_putcs; + ops->clear_margins = cw_clear_margins; + ops->cursor = cw_cursor; + ops->update_start = cw_update_start; +} diff --git a/drivers/video/fbdev/core/fbcon_rotate.c b/drivers/video/fbdev/core/fbcon_rotate.c new file mode 100644 index 0000000000..ec3c883400 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_rotate.c @@ -0,0 +1,111 @@ +/* + * linux/drivers/video/console/fbcon_rotate.c -- Software Rotation + * + * Copyright (C) 2005 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/slab.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" +#include "fbcon_rotate.h" + +static int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc) +{ + struct fbcon_ops *ops = info->fbcon_par; + int len, err = 0; + int s_cellsize, d_cellsize, i; + const u8 *src; + u8 *dst; + + if (vc->vc_font.data == ops->fontdata && + ops->p->con_rotate == ops->cur_rotate) + goto finished; + + src = ops->fontdata = vc->vc_font.data; + ops->cur_rotate = ops->p->con_rotate; + len = vc->vc_font.charcount; + s_cellsize = ((vc->vc_font.width + 7)/8) * + vc->vc_font.height; + d_cellsize = s_cellsize; + + if (ops->rotate == FB_ROTATE_CW || + ops->rotate == FB_ROTATE_CCW) + d_cellsize = ((vc->vc_font.height + 7)/8) * + vc->vc_font.width; + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (ops->fd_size < d_cellsize * len) { + dst = kmalloc_array(len, d_cellsize, GFP_KERNEL); + + if (dst == NULL) { + err = -ENOMEM; + goto finished; + } + + ops->fd_size = d_cellsize * len; + kfree(ops->fontbuffer); + ops->fontbuffer = dst; + } + + dst = ops->fontbuffer; + memset(dst, 0, ops->fd_size); + + switch (ops->rotate) { + case FB_ROTATE_UD: + for (i = len; i--; ) { + rotate_ud(src, dst, vc->vc_font.width, + vc->vc_font.height); + + src += s_cellsize; + dst += d_cellsize; + } + break; + case FB_ROTATE_CW: + for (i = len; i--; ) { + rotate_cw(src, dst, vc->vc_font.width, + vc->vc_font.height); + src += s_cellsize; + dst += d_cellsize; + } + break; + case FB_ROTATE_CCW: + for (i = len; i--; ) { + rotate_ccw(src, dst, vc->vc_font.width, + vc->vc_font.height); + src += s_cellsize; + dst += d_cellsize; + } + break; + } + +finished: + return err; +} + +void fbcon_set_rotate(struct fbcon_ops *ops) +{ + ops->rotate_font = fbcon_rotate_font; + + switch(ops->rotate) { + case FB_ROTATE_CW: + fbcon_rotate_cw(ops); + break; + case FB_ROTATE_UD: + fbcon_rotate_ud(ops); + break; + case FB_ROTATE_CCW: + fbcon_rotate_ccw(ops); + break; + } +} diff --git a/drivers/video/fbdev/core/fbcon_rotate.h b/drivers/video/fbdev/core/fbcon_rotate.h new file mode 100644 index 0000000000..01cbe303b8 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_rotate.h @@ -0,0 +1,96 @@ +/* + * linux/drivers/video/console/fbcon_rotate.h -- Software Display Rotation + * + * Copyright (C) 2005 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. + */ + +#ifndef _FBCON_ROTATE_H +#define _FBCON_ROTATE_H + +#define GETVYRES(s,i) ({ \ + (fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE) ? \ + (i)->var.yres : (i)->var.yres_virtual; }) + +#define GETVXRES(s,i) ({ \ + (fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE || !(i)->fix.xpanstep) ? \ + (i)->var.xres : (i)->var.xres_virtual; }) + + +static inline int pattern_test_bit(u32 x, u32 y, u32 pitch, const char *pat) +{ + u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8; + + pat +=index; + return (*pat) & (0x80 >> bit); +} + +static inline void pattern_set_bit(u32 x, u32 y, u32 pitch, char *pat) +{ + u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8; + + pat += index; + + (*pat) |= 0x80 >> bit; +} + +static inline void rotate_ud(const char *in, char *out, u32 width, u32 height) +{ + int i, j; + int shift = (8 - (width % 8)) & 7; + + width = (width + 7) & ~7; + + for (i = 0; i < height; i++) { + for (j = 0; j < width - shift; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(width - (1 + j + shift), + height - (1 + i), + width, out); + } + + } +} + +static inline void rotate_cw(const char *in, char *out, u32 width, u32 height) +{ + int i, j, h = height, w = width; + int shift = (8 - (height % 8)) & 7; + + width = (width + 7) & ~7; + height = (height + 7) & ~7; + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(height - 1 - i - shift, j, + height, out); + + } + } +} + +static inline void rotate_ccw(const char *in, char *out, u32 width, u32 height) +{ + int i, j, h = height, w = width; + int shift = (8 - (width % 8)) & 7; + + width = (width + 7) & ~7; + height = (height + 7) & ~7; + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(i, width - 1 - j - shift, + height, out); + } + } +} + +extern void fbcon_rotate_cw(struct fbcon_ops *ops); +extern void fbcon_rotate_ud(struct fbcon_ops *ops); +extern void fbcon_rotate_ccw(struct fbcon_ops *ops); +#endif diff --git a/drivers/video/fbdev/core/fbcon_ud.c b/drivers/video/fbdev/core/fbcon_ud.c new file mode 100644 index 0000000000..23bc045769 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_ud.c @@ -0,0 +1,438 @@ +/* + * linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 180 degrees + * + * Copyright (C) 2005 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/slab.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" +#include "fbcon_rotate.h" + +/* + * Rotation 180 degrees + */ + +static void ud_update_attr(u8 *dst, u8 *src, int attribute, + struct vc_data *vc) +{ + int i, offset = (vc->vc_font.height < 10) ? 1 : 2; + int width = (vc->vc_font.width + 7) >> 3; + unsigned int cellsize = vc->vc_font.height * width; + u8 c; + + offset = offset * width; + + for (i = 0; i < cellsize; i++) { + c = src[i]; + if (attribute & FBCON_ATTRIBUTE_UNDERLINE && i < offset) + c = 0xff; + if (attribute & FBCON_ATTRIBUTE_BOLD) + c |= c << 1; + if (attribute & FBCON_ATTRIBUTE_REVERSE) + c = ~c; + dst[i] = c; + } +} + + +static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + area.sy = vyres - ((sy + height) * vc->vc_font.height); + area.sx = vxres - ((sx + width) * vc->vc_font.width); + area.dy = vyres - ((dy + height) * vc->vc_font.height); + area.dx = vxres - ((dx + width) * vc->vc_font.width); + area.height = height * vc->vc_font.height; + area.width = width * vc->vc_font.width; + + info->fbops->fb_copyarea(info, &area); +} + +static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + region.dy = vyres - ((sy + height) * vc->vc_font.height); + region.dx = vxres - ((sx + width) * vc->vc_font.width); + region.width = width * vc->vc_font.width; + region.height = height * vc->vc_font.height; + region.rop = ROP_COPY; + + info->fbops->fb_fillrect(info, ®ion); +} + +static inline void ud_putcs_aligned(struct vc_data *vc, struct fb_info *info, + const u16 *s, u32 attr, u32 cnt, + u32 d_pitch, u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, u8 *dst) +{ + struct fbcon_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; + + if (attr) { + ud_update_attr(buf, src, attr, vc); + src = buf; + } + + if (likely(idx == 1)) + __fb_pad_aligned_buffer(dst, d_pitch, src, idx, + image->height); + else + fb_pad_aligned_buffer(dst, d_pitch, src, idx, + image->height); + + dst += s_pitch; + } + + info->fbops->fb_imageblit(info, image); +} + +static inline void ud_putcs_unaligned(struct vc_data *vc, + struct fb_info *info, const u16 *s, + u32 attr, u32 cnt, u32 d_pitch, + u32 s_pitch, u32 cellsize, + struct fb_image *image, u8 *buf, + u8 *dst) +{ + struct fbcon_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 shift_low = 0, mod = vc->vc_font.width % 8; + u32 shift_high = 8; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize; + + if (attr) { + ud_update_attr(buf, src, attr, vc); + src = buf; + } + + fb_pad_unaligned_buffer(dst, d_pitch, src, idx, + image->height, shift_high, + shift_low, mod); + shift_low += mod; + dst += (shift_low >= 8) ? s_pitch : s_pitch - 1; + shift_low &= 7; + shift_high = 8 - shift_low; + } + + info->fbops->fb_imageblit(info, image); + +} + +static void ud_putcs(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg) +{ + struct fb_image image; + struct fbcon_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.width + 7)/8; + u32 cellsize = width * vc->vc_font.height; + u32 maxcnt = info->pixmap.size/cellsize; + u32 scan_align = info->pixmap.scan_align - 1; + u32 buf_align = info->pixmap.buf_align - 1; + u32 mod = vc->vc_font.width % 8, cnt, pitch, size; + u32 attribute = get_attribute(info, scr_readw(s)); + u8 *dst, *buf = NULL; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + image.fg_color = fg; + image.bg_color = bg; + image.dy = vyres - ((yy * vc->vc_font.height) + vc->vc_font.height); + image.dx = vxres - ((xx + count) * vc->vc_font.width); + image.height = vc->vc_font.height; + image.depth = 1; + + if (attribute) { + buf = kmalloc(cellsize, GFP_KERNEL); + if (!buf) + return; + } + + s += count - 1; + + while (count) { + if (count > maxcnt) + cnt = maxcnt; + else + cnt = count; + + image.width = vc->vc_font.width * cnt; + pitch = ((image.width + 7) >> 3) + scan_align; + pitch &= ~scan_align; + size = pitch * image.height + buf_align; + size &= ~buf_align; + dst = fb_get_buffer_offset(info, &info->pixmap, size); + image.data = dst; + + if (!mod) + ud_putcs_aligned(vc, info, s, attribute, cnt, pitch, + width, cellsize, &image, buf, dst); + else + ud_putcs_unaligned(vc, info, s, attribute, cnt, pitch, + width, cellsize, &image, + buf, dst); + + image.dx += image.width; + count -= cnt; + s -= cnt; + xx += cnt; + } + + /* buf is always NULL except when in monochrome mode, so in this case + it's a gain to check buf against NULL even though kfree() handles + NULL pointers just fine */ + if (unlikely(buf)) + kfree(buf); + +} + +static void ud_clear_margins(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only) +{ + unsigned int cw = vc->vc_font.width; + unsigned int ch = vc->vc_font.height; + unsigned int rw = info->var.xres - (vc->vc_cols*cw); + unsigned int bh = info->var.yres - (vc->vc_rows*ch); + struct fb_fillrect region; + + region.color = color; + region.rop = ROP_COPY; + + if ((int) rw > 0 && !bottom_only) { + region.dy = 0; + region.dx = info->var.xoffset; + region.width = rw; + region.height = info->var.yres_virtual; + info->fbops->fb_fillrect(info, ®ion); + } + + if ((int) bh > 0) { + region.dy = info->var.yoffset; + region.dx = info->var.xoffset; + region.height = bh; + region.width = info->var.xres; + info->fbops->fb_fillrect(info, ®ion); + } +} + +static void ud_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.width + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->cursor_state.image.data = src; + cursor.set |= FB_CUR_SETIMAGE; + } + + if (attribute) { + u8 *dst; + + dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + if (!dst) + return; + kfree(ops->cursor_data); + ops->cursor_data = dst; + ud_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.height || + ops->cursor_state.image.width != vc->vc_font.width || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.height; + ops->cursor_state.image.width = vc->vc_font.width; + cursor.set |= FB_CUR_SETSIZE; + } + + dy = vyres - ((y * vc->vc_font.height) + vc->vc_font.height); + dx = vxres - ((vc->state.x * vc->vc_font.width) + vc->vc_font.width); + + if (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; + + if (!mask) + return; + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = cur_height * w; + + while (size--) + mask[i++] = msk; + + size = (vc->vc_font.height - cur_height) * w; + + while (size--) + mask[i++] = ~msk; + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->cursor_state.enable; + cursor.image.depth = 1; + cursor.rop = ROP_XOR; + + if (info->fbops->fb_cursor) + err = info->fbops->fb_cursor(info, &cursor); + + if (err) + soft_cursor(info, &cursor); + + ops->cursor_reset = 0; +} + +static int ud_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + int xoffset, yoffset; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + int err; + + xoffset = vxres - info->var.xres - ops->var.xoffset; + yoffset = vyres - info->var.yres - ops->var.yoffset; + if (yoffset < 0) + yoffset += vyres; + ops->var.xoffset = xoffset; + ops->var.yoffset = yoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_ud(struct fbcon_ops *ops) +{ + ops->bmove = ud_bmove; + ops->clear = ud_clear; + ops->putcs = ud_putcs; + ops->clear_margins = ud_clear_margins; + ops->cursor = ud_cursor; + ops->update_start = ud_update_start; +} diff --git a/drivers/video/fbdev/core/fbcvt.c b/drivers/video/fbdev/core/fbcvt.c new file mode 100644 index 0000000000..64843464c6 --- /dev/null +++ b/drivers/video/fbdev/core/fbcvt.c @@ -0,0 +1,368 @@ +/* + * 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 size = 256; + int off = 0; + u8 *buf; + + buf = kzalloc(size, 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; + + off += scnprintf(buf + off, size - off, "fbcvt: %dx%d@%d: CVT Name - ", + cvt->xres, cvt->yres, cvt->refresh); + + if (cvt->status) { + off += scnprintf(buf + off, size - off, + "Not a CVT standard - %d.%03d Mega Pixel Image\n", + pixcount, pixcount_mod); + } else { + if (pixcount) + off += scnprintf(buf + off, size - off, "%d", pixcount); + + off += scnprintf(buf + off, size - off, ".%03dM", pixcount_mod); + + if (cvt->aspect_ratio == 0) + off += scnprintf(buf + off, size - off, "3"); + else if (cvt->aspect_ratio == 3) + off += scnprintf(buf + off, size - off, "4"); + else if (cvt->aspect_ratio == 1 || cvt->aspect_ratio == 4) + off += scnprintf(buf + off, size - off, "9"); + else if (cvt->aspect_ratio == 2) + off += scnprintf(buf + off, size - off, "A"); + + if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) + off += scnprintf(buf + off, size - off, "-R"); + } + + 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_front_porch = 3 + cvt.v_margin; + cvt.v_back_porch = cvt.vtotal - cvt.yres/cvt.interlace - + cvt.v_front_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 0000000000..ee44a46a66 --- /dev/null +++ b/drivers/video/fbdev/core/fbmem.c @@ -0,0 +1,1202 @@ +/* + * 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/types.h> +#include <linux/errno.h> +#include <linux/kernel.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/platform_device.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 <linux/fbcon.h> +#include <linux/mem_encrypt.h> +#include <linux/pci.h> + +#include <video/nomodeset.h> +#include <video/vga.h> + +#include "fb_internal.h" + + /* + * Frame buffer device initialization and setup routines + */ + +#define FBPIXMAPSIZE (1024 * 8) + +struct class *fb_class; + +DEFINE_MUTEX(registration_lock); +struct fb_info *registered_fb[FB_MAX] __read_mostly; +int num_registered_fb __read_mostly; +#define for_each_registered_fb(i) \ + for (i = 0; i < FB_MAX; i++) \ + if (!registered_fb[i]) {} else + +bool fb_center_logo __read_mostly; + +int fb_logo_count __read_mostly = -1; + +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) + refcount_inc(&fb_info->count); + mutex_unlock(®istration_lock); + + return fb_info; +} + +void put_fb_info(struct fb_info *fb_info) +{ + if (!refcount_dec_and_test(&fb_info->count)) + return; + if (fb_info->fbops->fb_destroy) + fb_info->fbops->fb_destroy(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 && j < logo->width; 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); + swap(image->width, image->height); + 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); + swap(image->width, image->height); + 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 (image->width > info->var.xres || image->height > info->var.yres) + return; + + 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) { + u32 dx = image->dx; + + for (x = 0; x < num && image->dx <= dx; 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) { + u32 dy = image->dy; + + for (x = 0; x < num && image->dy <= dy; 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->fbops->owner) + 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_array(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); + } + + if (fb_center_logo) { + int xres = info->var.xres; + int yres = info->var.yres; + + if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) { + xres = info->var.yres; + yres = info->var.xres; + } + + while (n && (n * (logo->width + 8) - 8 > xres)) + --n; + image.dx = (xres - (n * (logo->width + 8) - 8)) / 2; + image.dy = y ?: (yres - logo->height) / 2; + } else { + image.dx = 0; + image.dy = y; + } + + image.width = logo->width; + image.height = logo->height; + + if (rotate) { + logo_rotate = kmalloc_array(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 image.dy + 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; + int height; + + memset(&fb_logo, 0, sizeof(struct logo_data)); + + if (info->flags & FBINFO_MISC_TILEBLITTING || + info->fbops->owner || !fb_logo_count) + 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; + } + } + + height = fb_logo.logo->height; + if (fb_center_logo) + height += (yres - fb_logo.logo->height) / 2; + + return fb_prepare_extra_logos(info, height, yres); +} + +int fb_show_logo(struct fb_info *info, int rotate) +{ + unsigned int count; + int y; + + if (!fb_logo_count) + return 0; + + count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count; + y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count); + 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); + +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_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; + fbcon_get_requirement(info, &caps); + 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 ret = 0; + u32 activate; + struct fb_var_screeninfo old_var; + struct fb_videomode mode; + struct fb_event event; + u32 unused; + + 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) { + ret = fbcon_mode_deleted(info, &mode1); + if (!ret) + fb_delete_videomode(&mode1, &info->modelist); + } + + return ret ? -EINVAL : 0; + } + + if (!(var->activate & FB_ACTIVATE_FORCE) && + !memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) + return 0; + + 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; + return 0; + } + + /* bitfill_aligned() assumes that it's at least 8x8 */ + if (var->xres < 8 || var->yres < 8) + return -EINVAL; + + /* Too huge resolution causes multiplication overflow. */ + if (check_mul_overflow(var->xres, var->yres, &unused) || + check_mul_overflow(var->xres_virtual, var->yres_virtual, &unused)) + return -EINVAL; + + ret = info->fbops->fb_check_var(var, info); + + if (ret) + return ret; + + /* verify that virtual resolution >= physical resolution */ + if (var->xres_virtual < var->xres || + var->yres_virtual < var->yres) { + pr_warn("WARNING: fbcon: Driver '%s' missed to adjust virtual screen size (%ux%u vs. %ux%u)\n", + info->fix.id, + var->xres_virtual, var->yres_virtual, + var->xres, var->yres); + return -EINVAL; + } + + if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) + return 0; + + if (info->fbops->fb_get_caps) { + ret = fb_check_caps(info, var, activate); + + if (ret) + return ret; + } + + 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); + return ret; + } + } + + 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) + return ret; + + event.info = info; + event.data = &mode; + fb_notifier_call_chain(FB_EVENT_MODE_CHANGE, &event); + + return 0; +} +EXPORT_SYMBOL(fb_set_var); + +int +fb_blank(struct fb_info *info, int blank) +{ + struct fb_event event; + int ret = -EINVAL; + + if (blank > FB_BLANK_POWERDOWN) + blank = FB_BLANK_POWERDOWN; + + event.info = info; + event.data = ␣ + + if (info->fbops->fb_blank) + ret = info->fbops->fb_blank(blank, info); + + if (!ret) + fb_notifier_call_chain(FB_EVENT_BLANK, &event); + + return ret; +} +EXPORT_SYMBOL(fb_blank); + +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 int do_register_framebuffer(struct fb_info *fb_info) +{ + int i; + struct fb_videomode mode; + + if (fb_check_foreignness(fb_info)) + return -ENOSYS; + + 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; + refcount_set(&fb_info->count, 1); + mutex_init(&fb_info->lock); + mutex_init(&fb_info->mm_lock); + + fb_device_create(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->device, false); + else + pm_vt_switch_required(fb_info->device, true); + + fb_var_to_videomode(&mode, &fb_info->var); + fb_add_videomode(&mode, &fb_info->modelist); + registered_fb[i] = fb_info; + +#ifdef CONFIG_GUMSTIX_AM200EPD + { + struct fb_event event; + event.info = fb_info; + fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); + } +#endif + + return fbcon_fb_registered(fb_info); +} + +static void unbind_console(struct fb_info *fb_info) +{ + int i = fb_info->node; + + if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)) + return; + + fbcon_fb_unbind(fb_info); +} + +static void unlink_framebuffer(struct fb_info *fb_info) +{ + int i; + + i = fb_info->node; + if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)) + return; + + fb_device_destroy(fb_info); + pm_vt_switch_unregister(fb_info->device); + unbind_console(fb_info); +} + +static void do_unregister_framebuffer(struct fb_info *fb_info) +{ + unlink_framebuffer(fb_info); + if (fb_info->pixmap.addr && + (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) { + kfree(fb_info->pixmap.addr); + fb_info->pixmap.addr = NULL; + } + + fb_destroy_modelist(&fb_info->modelist); + registered_fb[fb_info->node] = NULL; + num_registered_fb--; +#ifdef CONFIG_GUMSTIX_AM200EPD + { + struct fb_event event; + event.info = fb_info; + fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); + } +#endif + fbcon_fb_unregistered(fb_info); + + /* this may free fb info */ + put_fb_info(fb_info); +} + +/** + * 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. + */ +void +unregister_framebuffer(struct fb_info *fb_info) +{ + mutex_lock(®istration_lock); + do_unregister_framebuffer(fb_info); + mutex_unlock(®istration_lock); +} +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) +{ + WARN_CONSOLE_UNLOCKED(); + + if (state) { + fbcon_suspended(info); + info->state = FBINFO_STATE_SUSPENDED; + } else { + info->state = FBINFO_STATE_RUNNING; + fbcon_resumed(info); + } +} +EXPORT_SYMBOL(fb_set_suspend); + +static int __init fbmem_init(void) +{ + int ret; + + fb_class = class_create("graphics"); + if (IS_ERR(fb_class)) { + ret = PTR_ERR(fb_class); + pr_err("Unable to create fb class; errno = %d\n", ret); + goto err_fb_class; + } + + ret = fb_init_procfs(); + if (ret) + goto err_class_destroy; + + ret = fb_register_chrdev(); + if (ret) + goto err_fb_cleanup_procfs; + + fb_console_init(); + + return 0; + +err_fb_cleanup_procfs: + fb_cleanup_procfs(); +err_class_destroy: + class_destroy(fb_class); +err_fb_class: + fb_class = NULL; + return ret; +} + +#ifdef MODULE +static void __exit fbmem_exit(void) +{ + fb_console_exit(); + fb_unregister_chrdev(); + fb_cleanup_procfs(); + class_destroy(fb_class); +} + +module_init(fbmem_init); +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_var_screeninfo var = info->var; + struct list_head *pos, *n; + struct fb_modelist *modelist; + struct fb_videomode *m, mode; + int err; + + 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); + } + } + + if (list_empty(&info->modelist)) + return 1; + + fbcon_new_modelist(info); + + return 0; +} + +#if defined(CONFIG_VIDEO_NOMODESET) +bool fb_modesetting_disabled(const char *drvname) +{ + bool fwonly = video_firmware_drivers_only(); + + if (fwonly) + pr_warn("Driver %s not loading because of nomodeset parameter\n", + drvname); + + return fwonly; +} +EXPORT_SYMBOL(fb_modesetting_disabled); +#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 0000000000..79e5bfbdd3 --- /dev/null +++ b/drivers/video/fbdev/core/fbmon.c @@ -0,0 +1,1520 @@ +/* + * 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 https://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> +#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...) no_printk(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, const struct fb_monspecs *specs) +{ + int i; + + for (i = 0; i < DMT_SIZE; i++) { + u32 std_2byte_code = block[0] << 8 | block[1]; + if (std_2byte_code == dmt_modes[i].std_2byte_code) + break; + } + + if (i < DMT_SIZE && dmt_modes[i].mode) { + /* DMT mode found */ + *mode = *dmt_modes[i].mode; + mode->flag |= FB_MODE_IS_STANDARD; + DPRINTK(" DMT id=%d\n", dmt_modes[i].dmt_id); + + } else { + int xres, yres = 0, refresh, ratio; + + 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); + + calc_mode_timings(xres, yres, refresh, mode); + } + + /* Check the mode we got is within valid spec of the monitor */ + if (specs && specs->dclkmax + && PICOS2KHZ(mode->pixclock) * 1000 > specs->dclkmax) { + DPRINTK(" mode exceed max DCLK\n"); + return 0; + } + + return 1; +} + +static int get_dst_timing(unsigned char *block, struct fb_videomode *mode, + int ver, int rev, const struct fb_monspecs *specs) +{ + int j, num = 0; + + for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE) + num += get_std_timing(block, &mode[num], ver, rev, specs); + + 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 + * @specs: monitor specifications, may be NULL + * + * 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, + const struct fb_monspecs *specs) +{ + struct fb_videomode *mode, *m; + unsigned char *block; + int num = 0, i, first = 1; + int ver, rev; + + mode = kcalloc(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; + } + + ver = edid[EDID_STRUCT_VERSION]; + rev = edid[EDID_STRUCT_REVISION]; + + *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, specs); + + 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, specs); + } + + /* Yikes, EDID data is totally useless */ + if (!num) { + kfree(mode); + return NULL; + } + + *dbsize = num; + m = kmalloc_array(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, specs); + 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, specs); + if (!specs->modedb) + return; + + /* + * 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"); +} + +/* + * 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_hfreq - 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 (https://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; + + ret = fb_videomode_from_videomode(&vm, fb); + if (ret) + return ret; + + pr_debug("%pOF: got %dx%d display mode\n", + np, vm.hactive, vm.vactive); + 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) +{ +} +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_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 0000000000..1b3c9958ef --- /dev/null +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * fbsysfs.c - framebuffer device class and attributes + * + * Copyright (c) 2004 James Simmons <jsimmons@infradead.org> + */ + +#include <linux/console.h> +#include <linux/fb.h> +#include <linux/fbcon.h> +#include <linux/major.h> + +#include "fb_internal.h" + +#define FB_SYSFS_FLAG_ATTR 1 + +static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) +{ + int err; + + var->activate |= FB_ACTIVATE_FORCE; + console_lock(); + lock_fb_info(fb_info); + err = fb_set_var(fb_info, var); + if (!err) + fbcon_update_vcs(fb_info, var->activate & FB_ACTIVATE_ALL); + unlock_fb_info(fb_info); + 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; + size_t i; + int err; + + memset(&var, 0, sizeof(var)); + + list_for_each_entry(modelist, &fb_info->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(); + lock_fb_info(fb_info); + + 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 fb_modelist *modelist; + const struct fb_videomode *mode; + + i = 0; + list_for_each_entry(modelist, &fb_info->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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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, arg; + + arg = simple_strtoul(buf, &last, 0); + console_lock(); + err = fb_blank(fb_info, arg); + /* might again call into fb_blank */ + fbcon_fb_blanked(fb_info, arg); + 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 sysfs_emit(buf, "%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 sysfs_emit(buf, "%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(); + lock_fb_info(fb_info); + + 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 sysfs_emit(buf, "%d\n", fb_info->state); +} + +#if IS_ENABLED(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 += scnprintf(&buf[len], PAGE_SIZE - len, "%8ph\n", + fb_info->bl_curve + i); + 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), +#if IS_ENABLED(CONFIG_FB_BACKLIGHT) + __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve), +#endif +}; + +static 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; +} + +static 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; + } +} + +int fb_device_create(struct fb_info *fb_info) +{ + int node = fb_info->node; + dev_t devt = MKDEV(FB_MAJOR, node); + int ret; + + fb_info->dev = device_create(fb_class, fb_info->device, devt, NULL, "fb%d", node); + if (IS_ERR(fb_info->dev)) { + /* Not fatal */ + ret = PTR_ERR(fb_info->dev); + pr_warn("Unable to create device for framebuffer %d; error %d\n", node, ret); + fb_info->dev = NULL; + } else { + fb_init_device(fb_info); + } + + return 0; +} + +void fb_device_destroy(struct fb_info *fb_info) +{ + dev_t devt = MKDEV(FB_MAJOR, fb_info->node); + + if (!fb_info->dev) + return; + + fb_cleanup_device(fb_info); + device_destroy(fb_class, devt); + fb_info->dev = NULL; +} diff --git a/drivers/video/fbdev/core/modedb.c b/drivers/video/fbdev/core/modedb.c new file mode 100644 index 0000000000..7196b055f2 --- /dev/null +++ b/drivers/video/fbdev/core/modedb.c @@ -0,0 +1,1209 @@ +/* + * 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 + +/* + * 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 }, + + /* 1920x1080 @ 60 Hz, 67.3 kHz hsync */ + { NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5, 0, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED }, + + /* 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 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 }, + /* 34 1920x1200-60 RB VESA */ + { NULL, 60, 1920, 1200, 6493, 80, 48, 26, 3, 32, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 35 1920x1200-60 VESA */ + { NULL, 60, 1920, 1200, 5174, 336, 136, 36, 3, 200, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 36 1920x1200-75 VESA */ + { NULL, 75, 1920, 1200, 4077, 344, 136, 46, 3, 208, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 37 1920x1200-85 VESA */ + { NULL, 85, 1920, 1200, 3555, 352, 144, 53, 3, 208, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 38 2560x1600-60 RB VESA */ + { NULL, 60, 2560, 1600, 3724, 80, 48, 37, 3, 32, 6, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 39 2560x1600-60 VESA */ + { NULL, 60, 2560, 1600, 2869, 472, 192, 49, 3, 280, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 40 2560x1600-75 VESA */ + { NULL, 75, 2560, 1600, 2256, 488, 208, 63, 3, 280, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 41 2560x1600-85 VESA */ + { NULL, 85, 2560, 1600, 1979, 488, 208, 73, 3, 280, 6, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 42 2560x1600-120 RB VESA */ + { NULL, 120, 2560, 1600, 1809, 80, 48, 85, 3, 32, 6, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; +EXPORT_SYMBOL(vesa_modes); + +const struct dmt_videomode dmt_modes[DMT_SIZE] = { + { 0x01, 0x0000, 0x000000, &vesa_modes[0] }, + { 0x02, 0x3119, 0x000000, &vesa_modes[1] }, + { 0x03, 0x0000, 0x000000, &vesa_modes[2] }, + { 0x04, 0x3140, 0x000000, &vesa_modes[3] }, + { 0x05, 0x314c, 0x000000, &vesa_modes[4] }, + { 0x06, 0x314f, 0x000000, &vesa_modes[5] }, + { 0x07, 0x3159, 0x000000, &vesa_modes[6] }, + { 0x08, 0x0000, 0x000000, &vesa_modes[7] }, + { 0x09, 0x4540, 0x000000, &vesa_modes[8] }, + { 0x0a, 0x454c, 0x000000, &vesa_modes[9] }, + { 0x0b, 0x454f, 0x000000, &vesa_modes[10] }, + { 0x0c, 0x4559, 0x000000, &vesa_modes[11] }, + { 0x0d, 0x0000, 0x000000, NULL }, + { 0x0e, 0x0000, 0x000000, NULL }, + { 0x0f, 0x0000, 0x000000, &vesa_modes[12] }, + { 0x10, 0x6140, 0x000000, &vesa_modes[13] }, + { 0x11, 0x614a, 0x000000, &vesa_modes[14] }, + { 0x12, 0x614f, 0x000000, &vesa_modes[15] }, + { 0x13, 0x6159, 0x000000, &vesa_modes[16] }, + { 0x14, 0x0000, 0x000000, NULL }, + { 0x15, 0x714f, 0x000000, &vesa_modes[17] }, + { 0x16, 0x0000, 0x7f1c21, NULL }, + { 0x17, 0x0000, 0x7f1c28, NULL }, + { 0x18, 0x0000, 0x7f1c44, NULL }, + { 0x19, 0x0000, 0x7f1c62, NULL }, + { 0x1a, 0x0000, 0x000000, NULL }, + { 0x1b, 0x0000, 0x8f1821, NULL }, + { 0x1c, 0x8100, 0x8f1828, NULL }, + { 0x1d, 0x810f, 0x8f1844, NULL }, + { 0x1e, 0x8119, 0x8f1862, NULL }, + { 0x1f, 0x0000, 0x000000, NULL }, + { 0x20, 0x8140, 0x000000, &vesa_modes[18] }, + { 0x21, 0x8159, 0x000000, &vesa_modes[19] }, + { 0x22, 0x0000, 0x000000, NULL }, + { 0x23, 0x8180, 0x000000, &vesa_modes[20] }, + { 0x24, 0x818f, 0x000000, &vesa_modes[21] }, + { 0x25, 0x8199, 0x000000, &vesa_modes[22] }, + { 0x26, 0x0000, 0x000000, NULL }, + { 0x27, 0x0000, 0x000000, NULL }, + { 0x28, 0x0000, 0x000000, NULL }, + { 0x29, 0x0000, 0x0c2021, NULL }, + { 0x2a, 0x9040, 0x0c2028, NULL }, + { 0x2b, 0x904f, 0x0c2044, NULL }, + { 0x2c, 0x9059, 0x0c2062, NULL }, + { 0x2d, 0x0000, 0x000000, NULL }, + { 0x2e, 0x9500, 0xc11821, NULL }, + { 0x2f, 0x9500, 0xc11828, NULL }, + { 0x30, 0x950f, 0xc11844, NULL }, + { 0x31, 0x9519, 0xc11868, NULL }, + { 0x32, 0x0000, 0x000000, NULL }, + { 0x33, 0xa940, 0x000000, &vesa_modes[23] }, + { 0x34, 0xa945, 0x000000, &vesa_modes[24] }, + { 0x35, 0xa94a, 0x000000, &vesa_modes[25] }, + { 0x36, 0xa94f, 0x000000, &vesa_modes[26] }, + { 0x37, 0xa959, 0x000000, &vesa_modes[27] }, + { 0x38, 0x0000, 0x000000, NULL }, + { 0x39, 0x0000, 0x0c2821, NULL }, + { 0x3a, 0xb300, 0x0c2828, NULL }, + { 0x3b, 0xb30f, 0x0c2844, NULL }, + { 0x3c, 0xb319, 0x0c2868, NULL }, + { 0x3d, 0x0000, 0x000000, NULL }, + { 0x3e, 0xc140, 0x000000, &vesa_modes[28] }, + { 0x3f, 0xc14f, 0x000000, &vesa_modes[29] }, + { 0x40, 0x0000, 0x000000, NULL}, + { 0x41, 0xc940, 0x000000, &vesa_modes[30] }, + { 0x42, 0xc94f, 0x000000, &vesa_modes[31] }, + { 0x43, 0x0000, 0x000000, NULL }, + { 0x44, 0x0000, 0x572821, &vesa_modes[34] }, + { 0x45, 0xd100, 0x572828, &vesa_modes[35] }, + { 0x46, 0xd10f, 0x572844, &vesa_modes[36] }, + { 0x47, 0xd119, 0x572862, &vesa_modes[37] }, + { 0x48, 0x0000, 0x000000, NULL }, + { 0x49, 0xd140, 0x000000, &vesa_modes[32] }, + { 0x4a, 0xd14f, 0x000000, &vesa_modes[33] }, + { 0x4b, 0x0000, 0x000000, NULL }, + { 0x4c, 0x0000, 0x1f3821, &vesa_modes[38] }, + { 0x4d, 0x0000, 0x1f3828, &vesa_modes[39] }, + { 0x4e, 0x0000, 0x1f3844, &vesa_modes[40] }, + { 0x4f, 0x0000, 0x1f3862, &vesa_modes[41] }, + { 0x50, 0x0000, 0x000000, &vesa_modes[42] }, +}; +EXPORT_SYMBOL(dmt_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][p][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' or 'p' are present, compute + * interlaced or progressive 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', 'p' 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) +{ + char *mode_option_buf = NULL; + 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) { + fb_get_options(NULL, &mode_option_buf); + mode_option = mode_option_buf; + } + 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; + int interlace_specified = 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 'p': + if (!cvt) { + interlace = 0; + interlace_specified = 1; + } + break; + case 'i': + if (!cvt) { + interlace = 1; + interlace_specified = 1; + } + break; + default: + goto done; + } + } + if (i < 0 && yres_specified) { + xres = simple_strtol(name, NULL, 10); + res_specified = 1; + } +done: + kfree(mode_option_buf); + 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)) { + const int db_interlace = (db[i].vmode & + FB_VMODE_INTERLACED ? 1 : 0); + int score = abs(db[i].refresh - refresh); + + if (interlace_specified) + score += abs(db_interlace - interlace); + + if (!interlace_specified || + db_interlace == interlace) + if (refresh_specified && + db[i].refresh == refresh) + return 1; + + if (score < diff) { + diff = score; + 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; + + if (!htotal || !vtotal) + return; + + 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 fb_modelist *modelist; + struct fb_videomode *mode, *best = NULL; + u32 diff = -1; + + list_for_each_entry(modelist, head, list) { + u32 d; + 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 fb_modelist *modelist; + struct fb_videomode *cmode, *best = NULL; + u32 diff = -1, diff_refresh = -1; + + list_for_each_entry(modelist, head, list) { + u32 d; + 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 fb_modelist *modelist; + struct fb_videomode *m, mode; + + fb_var_to_videomode(&mode, var); + list_for_each_entry(modelist, head, 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 fb_modelist *modelist; + struct fb_videomode *m; + int found = 0; + + list_for_each_entry(modelist, head, 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 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_entry(modelist, head, 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/softcursor.c b/drivers/video/fbdev/core/softcursor.c new file mode 100644 index 0000000000..29e5b21cf3 --- /dev/null +++ b/drivers/video/fbdev/core/softcursor.c @@ -0,0 +1,76 @@ +/* + * linux/drivers/video/console/softcursor.c + * + * Generic software cursor for frame buffer devices + * + * Created 14 Nov 2002 by 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. + */ + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/slab.h> + +#include <asm/io.h> + +#include "fbcon.h" + +int soft_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct fbcon_ops *ops = info->fbcon_par; + unsigned int scan_align = info->pixmap.scan_align - 1; + unsigned int buf_align = info->pixmap.buf_align - 1; + unsigned int i, size, dsize, s_pitch, d_pitch; + struct fb_image *image; + u8 *src, *dst; + + if (info->state != FBINFO_STATE_RUNNING) + return 0; + + s_pitch = (cursor->image.width + 7) >> 3; + dsize = s_pitch * cursor->image.height; + + if (dsize + sizeof(struct fb_image) != ops->cursor_size) { + kfree(ops->cursor_src); + ops->cursor_size = dsize + sizeof(struct fb_image); + + ops->cursor_src = kmalloc(ops->cursor_size, GFP_ATOMIC); + if (!ops->cursor_src) { + ops->cursor_size = 0; + return -ENOMEM; + } + } + + src = ops->cursor_src + sizeof(struct fb_image); + image = (struct fb_image *)ops->cursor_src; + *image = cursor->image; + d_pitch = (s_pitch + scan_align) & ~scan_align; + + size = d_pitch * image->height + buf_align; + size &= ~buf_align; + dst = fb_get_buffer_offset(info, &info->pixmap, size); + + if (cursor->enable) { + switch (cursor->rop) { + case ROP_XOR: + for (i = 0; i < dsize; i++) + src[i] = image->data[i] ^ cursor->mask[i]; + break; + case ROP_COPY: + default: + for (i = 0; i < dsize; i++) + src[i] = image->data[i] & cursor->mask[i]; + break; + } + } else + memcpy(src, image->data, dsize); + + fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, image->height); + image->data = dst; + info->fbops->fb_imageblit(info, image); + return 0; +} diff --git a/drivers/video/fbdev/core/svgalib.c b/drivers/video/fbdev/core/svgalib.c new file mode 100644 index 0000000000..2cba158888 --- /dev/null +++ b/drivers/video/fbdev/core/svgalib.c @@ -0,0 +1,667 @@ +/* + * 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/math.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) + */ +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 0000000000..7b8bd3a2be --- /dev/null +++ b/drivers/video/fbdev/core/syscopyarea.c @@ -0,0 +1,370 @@ +/* + * 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, unsigned dst_idx, + const unsigned long *src, unsigned 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 << left, *dst, first); + } else if (src_idx+n <= bits) { + /* Single source word */ + *dst = comp(*src >> right, *dst, first); + } else { + /* 2 source words */ + d0 = *src++; + d1 = *src; + *dst = comp(d0 >> right | d1 << 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'. */ + d0 = *src++; + /* Leading bits */ + if (shift > 0) { + /* Single source word */ + *dst = comp(d0 << left, *dst, first); + dst++; + n -= bits - dst_idx; + } else { + /* 2 source words */ + d1 = *src++; + *dst = comp(d0 >> right | d1 << left, *dst, + first); + d0 = d1; + dst++; + n -= bits - dst_idx; + } + + /* 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 (m) { + if (m <= bits - right) { + /* Single source word */ + d0 >>= right; + } else { + /* 2 source words */ + d1 = *src; + d0 = d0 >> right | d1 << left; + } + *dst = comp(d0, *dst, last); + } + } + } +} + + /* + * Generic bitwise copy algorithm, operating backward + */ + +static void +bitcpy_rev(struct fb_info *p, unsigned long *dst, unsigned dst_idx, + const unsigned long *src, unsigned src_idx, unsigned bits, + unsigned n) +{ + unsigned long first, last; + int shift; + + 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_SHIFT_HIGH(p, ~0UL, (dst_idx + 1) % bits); + last = FB_SHIFT_HIGH(p, ~0UL, (bits + dst_idx + 1 - n) % bits); + + if (!shift) { + /* Same alignment for source and dest */ + if ((unsigned long)dst_idx+1 >= n) { + /* Single word */ + if (first) + last &= first; + *dst = comp(*src, *dst, last); + } else { + /* Multiple destination words */ + + /* Leading bits */ + if (first) { + *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 != -1UL) + *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 (first) + last &= first; + if (shift < 0) { + /* Single source word */ + *dst = comp(*src >> right, *dst, last); + } else if (1+(unsigned long)src_idx >= n) { + /* Single source word */ + *dst = comp(*src << left, *dst, last); + } else { + /* 2 source words */ + *dst = comp(*src << left | *(src-1) >> right, + *dst, last); + } + } 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 */ + d1 = d0; + d0 >>= right; + } else { + /* 2 source words */ + d1 = *src--; + d0 = d0 << left | d1 >> right; + } + if (!first) + *dst = d0; + else + *dst = comp(d0, *dst, first); + d0 = d1; + dst--; + n -= dst_idx+1; + + /* 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 (m) { + if (m <= bits - left) { + /* Single source word */ + d0 <<= left; + } else { + /* 2 source words */ + d1 = *src; + d0 = d0 << left | d1 >> right; + } + *dst = comp(d0, *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 int const bits_per_line = p->fix.line_length * 8u; + unsigned long *base = NULL; + int bits = BITS_PER_LONG, bytes = bits >> 3; + unsigned 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 */ + base = (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; + bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits, + base + (src_idx / bits), src_idx % bits, bits, + width*p->var.bits_per_pixel); + } + } 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); + 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 0000000000..bcdcaeae65 --- /dev/null +++ b/drivers/video/fbdev/core/sysfillrect.c @@ -0,0 +1,325 @@ +/* + * 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; + memset_l(dst, pat, n); + dst += n; + + /* 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 0000000000..665ef7a0a2 --- /dev/null +++ b/drivers/video/fbdev/core/sysimgblt.c @@ -0,0 +1,336 @@ +/* + * 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, eorx, shift; + const u8 *s = image->data, *src; + u32 *dst; + const u32 *tab; + size_t tablen; + u32 colortab[16]; + int i, j, k; + + switch (bpp) { + case 8: + tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; + tablen = 16; + break; + case 16: + tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; + tablen = 4; + break; + case 32: + tab = cfb_tab32; + tablen = 2; + break; + default: + return; + } + + 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 = 0; i < tablen; ++i) + colortab[i] = (tab[i] & eorx) ^ bgx; + + for (i = image->height; i--; ) { + dst = dst1; + shift = 8; + src = s; + + /* + * Manually unroll the per-line copying loop for better + * performance. This works until we processed the last + * completely filled source byte (inclusive). + */ + switch (ppw) { + case 4: /* 8 bpp */ + for (j = k; j >= 2; j -= 2, ++src) { + *dst++ = colortab[(*src >> 4) & bit_mask]; + *dst++ = colortab[(*src >> 0) & bit_mask]; + } + break; + case 2: /* 16 bpp */ + for (j = k; j >= 4; j -= 4, ++src) { + *dst++ = colortab[(*src >> 6) & bit_mask]; + *dst++ = colortab[(*src >> 4) & bit_mask]; + *dst++ = colortab[(*src >> 2) & bit_mask]; + *dst++ = colortab[(*src >> 0) & bit_mask]; + } + break; + case 1: /* 32 bpp */ + for (j = k; j >= 8; j -= 8, ++src) { + *dst++ = colortab[(*src >> 7) & bit_mask]; + *dst++ = colortab[(*src >> 6) & bit_mask]; + *dst++ = colortab[(*src >> 5) & bit_mask]; + *dst++ = colortab[(*src >> 4) & bit_mask]; + *dst++ = colortab[(*src >> 3) & bit_mask]; + *dst++ = colortab[(*src >> 2) & bit_mask]; + *dst++ = colortab[(*src >> 1) & bit_mask]; + *dst++ = colortab[(*src >> 0) & bit_mask]; + } + break; + } + + /* + * For image widths that are not a multiple of 8, there + * are trailing pixels left on the current line. Print + * them as well. + */ + for (; j--; ) { + shift -= ppw; + *dst++ = colortab[(*src >> shift) & bit_mask]; + 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"); + diff --git a/drivers/video/fbdev/core/tileblit.c b/drivers/video/fbdev/core/tileblit.c new file mode 100644 index 0000000000..2768eff247 --- /dev/null +++ b/drivers/video/fbdev/core/tileblit.c @@ -0,0 +1,151 @@ +/* + * linux/drivers/video/console/tileblit.c -- Tile Blitting Operation + * + * Copyright (C) 2004 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 <linux/vt_kern.h> +#include <linux/console.h> +#include <asm/types.h> +#include "fbcon.h" + +static void tile_bmove(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int dy, int dx, int height, int width) +{ + struct fb_tilearea area; + + area.sx = sx; + area.sy = sy; + area.dx = dx; + area.dy = dy; + area.height = height; + area.width = width; + + info->tileops->fb_tilecopy(info, &area); +} + +static void tile_clear(struct vc_data *vc, struct fb_info *info, int sy, + int sx, int height, int width) +{ + struct fb_tilerect rect; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + int fgshift = (vc->vc_hi_font_mask) ? 9 : 8; + + rect.index = vc->vc_video_erase_char & + ((vc->vc_hi_font_mask) ? 0x1ff : 0xff); + rect.fg = attr_fgcol_ec(fgshift, vc, info); + rect.bg = attr_bgcol_ec(bgshift, vc, info); + rect.sx = sx; + rect.sy = sy; + rect.width = width; + rect.height = height; + rect.rop = ROP_COPY; + + info->tileops->fb_tilefill(info, &rect); +} + +static void tile_putcs(struct vc_data *vc, struct fb_info *info, + const unsigned short *s, int count, int yy, int xx, + int fg, int bg) +{ + struct fb_tileblit blit; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int size = sizeof(u32) * count, i; + + blit.sx = xx; + blit.sy = yy; + blit.width = count; + blit.height = 1; + blit.fg = fg; + blit.bg = bg; + blit.length = count; + blit.indices = (u32 *) fb_get_buffer_offset(info, &info->pixmap, size); + for (i = 0; i < count; i++) + blit.indices[i] = (u32)(scr_readw(s++) & charmask); + + info->tileops->fb_tileblit(info, &blit); +} + +static void tile_clear_margins(struct vc_data *vc, struct fb_info *info, + int color, int bottom_only) +{ + return; +} + +static void tile_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_tilecursor cursor; + int use_sw = vc->vc_cursor_type & CUR_SW; + + cursor.sx = vc->state.x; + cursor.sy = vc->state.y; + cursor.mode = (mode == CM_ERASE || use_sw) ? 0 : 1; + cursor.fg = fg; + cursor.bg = bg; + + switch (vc->vc_cursor_type & 0x0f) { + case CUR_NONE: + cursor.shape = FB_TILE_CURSOR_NONE; + break; + case CUR_UNDERLINE: + cursor.shape = FB_TILE_CURSOR_UNDERLINE; + break; + case CUR_LOWER_THIRD: + cursor.shape = FB_TILE_CURSOR_LOWER_THIRD; + break; + case CUR_LOWER_HALF: + cursor.shape = FB_TILE_CURSOR_LOWER_HALF; + break; + case CUR_TWO_THIRDS: + cursor.shape = FB_TILE_CURSOR_TWO_THIRDS; + break; + case CUR_BLOCK: + default: + cursor.shape = FB_TILE_CURSOR_BLOCK; + break; + } + + info->tileops->fb_tilecursor(info, &cursor); +} + +static int tile_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + int err; + + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info) +{ + struct fb_tilemap map; + struct fbcon_ops *ops = info->fbcon_par; + + ops->bmove = tile_bmove; + ops->clear = tile_clear; + ops->putcs = tile_putcs; + ops->clear_margins = tile_clear_margins; + ops->cursor = tile_cursor; + ops->update_start = tile_update_start; + + if (ops->p) { + map.width = vc->vc_font.width; + map.height = vc->vc_font.height; + map.depth = 1; + map.length = vc->vc_font.charcount; + map.data = ops->p->fontdata; + info->tileops->fb_settile(info, &map); + } +} |