kernel_optimize_test/drivers/video/console/fbcon.c
Russell King 41359dca94 [PATCH] ARM: Acornfb: Don't claim IRQ fbcon for cursor
The generic fbcon code tries to register and use the vsync IRQ for
ARM platforms with acornfb, but forgets to disable its own cursor
timer.  The result is a flickering flashing cursor.

Remove the code from the fbcon core to register this platform
private interrupt.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2005-06-30 16:30:07 +01:00

2812 lines
70 KiB
C

/*
* 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.
*/
#undef FBCONDEBUG
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h> /* MSch: for IRQ probe */
#include <linux/tty.h>
#include <linux/console.h>
#include <linux/string.h>
#include <linux/kd.h>
#include <linux/slab.h>
#include <linux/fb.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 <asm/irq.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#ifdef CONFIG_ATARI
#include <asm/atariints.h>
#endif
#ifdef CONFIG_MAC
#include <asm/macints.h>
#endif
#if defined(__mc68000__) || defined(CONFIG_APUS)
#include <asm/machdep.h>
#include <asm/setup.h>
#endif
#include "fbcon.h"
#ifdef FBCONDEBUG
# define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
# define DPRINTK(fmt, args...)
#endif
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 */
};
struct display fb_display[MAX_NR_CONSOLES];
static signed char con2fb_map[MAX_NR_CONSOLES];
static signed char con2fb_map_boot[MAX_NR_CONSOLES];
static int logo_height;
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;
/* Software scrollback */
static int fbcon_softback_size = 32768;
static unsigned long softback_buf, softback_curr;
static unsigned long softback_in;
static unsigned long softback_top, softback_end;
static int softback_lines;
/* console mappings */
static int first_fb_vc;
static int last_fb_vc = MAX_NR_CONSOLES - 1;
static int fbcon_is_default = 1;
/* font data */
static char fontname[40];
/* current fb_info */
static int info_idx = -1;
static const struct consw fb_con;
#define CM_SOFTBACK (8)
#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
static void fbcon_free_font(struct display *);
static int fbcon_set_origin(struct vc_data *);
#define CURSOR_DRAW_DELAY (1)
/* # VBL ints between cursor state changes */
#define ATARI_CURSOR_BLINK_RATE (42)
#define MAC_CURSOR_BLINK_RATE (32)
#define DEFAULT_CURSOR_BLINK_RATE (20)
static int vbl_cursor_cnt;
#define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1)
/*
* Interface used by the world
*/
static const char *fbcon_startup(void);
static void fbcon_init(struct vc_data *vc, int init);
static void fbcon_deinit(struct vc_data *vc);
static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
int width);
static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos);
static void fbcon_putcs(struct vc_data *vc, const unsigned short *s,
int count, int ypos, int xpos);
static void fbcon_clear_margins(struct vc_data *vc, int bottom_only);
static void fbcon_cursor(struct vc_data *vc, int mode);
static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
int count);
static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
int height, int width);
static int fbcon_switch(struct vc_data *vc);
static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch);
static int fbcon_set_palette(struct vc_data *vc, unsigned char *table);
static int fbcon_scrolldelta(struct vc_data *vc, int lines);
/*
* Internal routines
*/
static __inline__ int real_y(struct display *p, int ypos);
static __inline__ void ywrap_up(struct vc_data *vc, int count);
static __inline__ void ywrap_down(struct vc_data *vc, int count);
static __inline__ void ypan_up(struct vc_data *vc, int count);
static __inline__ void ypan_down(struct vc_data *vc, int count);
static void fbcon_bmove_rec(struct vc_data *vc, struct display *p, int sy, int sx,
int dy, int dx, int height, int width, u_int y_break);
static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
struct vc_data *vc);
static void fbcon_preset_disp(struct fb_info *info, struct fb_var_screeninfo *var,
int unit);
static void fbcon_redraw_move(struct vc_data *vc, struct display *p,
int line, int count, int dy);
#ifdef CONFIG_MAC
/*
* On the Macintoy, there may or may not be a working VBL int. We need to probe
*/
static int vbl_detected;
static irqreturn_t fb_vbl_detect(int irq, void *dummy, struct pt_regs *fp)
{
vbl_detected++;
return IRQ_HANDLED;
}
#endif
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 inline int get_color(struct vc_data *vc, struct fb_info *info,
u16 c, int is_fg)
{
int depth = fb_get_color_depth(&info->var);
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:
{
/* 0 or 1 */
int fg = (info->fix.visual != FB_VISUAL_MONO01) ? 1 : 0;
int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : 1;
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.
*/
color /= 4;
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(void *private)
{
struct fb_info *info = private;
struct fbcon_ops *ops = info->fbcon_par;
struct display *p;
struct vc_data *vc = NULL;
int c;
int mode;
if (ops->currcon != -1)
vc = vc_cons[ops->currcon].d;
if (!vc || !CON_IS_VISIBLE(vc) ||
fbcon_is_inactive(vc, info) ||
registered_fb[con2fb_map[vc->vc_num]] != info)
return;
acquire_console_sem();
p = &fb_display[vc->vc_num];
c = scr_readw((u16 *) vc->vc_pos);
mode = (!ops->cursor_flash || ops->cursor_state.enable) ?
CM_ERASE : CM_DRAW;
ops->cursor(vc, info, p, mode, softback_lines, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
release_console_sem();
}
#if defined(CONFIG_ATARI) || defined(CONFIG_MAC)
static int cursor_blink_rate;
static irqreturn_t fb_vbl_handler(int irq, void *dev_id, struct pt_regs *fp)
{
struct fb_info *info = dev_id;
if (vbl_cursor_cnt && --vbl_cursor_cnt == 0) {
schedule_work(&info->queue);
vbl_cursor_cnt = cursor_blink_rate;
}
return IRQ_HANDLED;
}
#endif
static void cursor_timer_handler(unsigned long dev_addr)
{
struct fb_info *info = (struct fb_info *) dev_addr;
struct fbcon_ops *ops = info->fbcon_par;
schedule_work(&info->queue);
mod_timer(&ops->cursor_timer, jiffies + HZ/5);
}
#ifndef MODULE
static int __init fb_console_setup(char *this_opt)
{
char *options;
int i, j;
if (!this_opt || !*this_opt)
return 0;
while ((options = strsep(&this_opt, ",")) != NULL) {
if (!strncmp(options, "font:", 5))
strcpy(fontname, options + 5);
if (!strncmp(options, "scrollback:", 11)) {
options += 11;
if (*options) {
fbcon_softback_size = simple_strtoul(options, &options, 0);
if (*options == 'k' || *options == 'K') {
fbcon_softback_size *= 1024;
options++;
}
if (*options != ',')
return 0;
options++;
} else
return 0;
}
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;
}
return 0;
}
if (!strncmp(options, "vc:", 3)) {
options += 3;
if (*options)
first_fb_vc = simple_strtoul(options, &options, 10) - 1;
if (first_fb_vc < 0)
first_fb_vc = 0;
if (*options++ == '-')
last_fb_vc = simple_strtoul(options, &options, 10) - 1;
fbcon_is_default = 0;
}
}
return 0;
}
__setup("fbcon=", fb_console_setup);
#endif
static int search_fb_in_map(int idx)
{
int i, retval = 0;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (con2fb_map[i] == idx)
retval = 1;
}
return retval;
}
static int search_for_mapped_con(void)
{
int i, retval = 0;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (con2fb_map[i] != -1)
retval = 1;
}
return retval;
}
static int fbcon_takeover(int show_logo)
{
int err, i;
if (!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 = 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;
}
return err;
}
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 */
int cnt, erase = vc->vc_video_erase_char, step;
unsigned short *save = NULL, *r, *q;
/*
* remove underline attribute from erase character
* if black and white framebuffer.
*/
if (fb_get_color_depth(&info->var) == 1)
erase &= ~0x400;
logo_height = fb_prepare_logo(info);
logo_lines = (logo_height + vc->vc_font.height - 1) /
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(logo_lines * new_cols * 2, GFP_KERNEL);
if (save) {
int i = cols < new_cols ? cols : new_cols;
scr_memsetw(save, erase, 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) {
vc->vc_y += logo_lines;
vc->vc_pos += logo_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, logo_lines * new_cols * 2);
vc->vc_y += logo_lines;
vc->vc_pos += logo_lines * vc->vc_size_row;
kfree(save);
}
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 if (logo_shown != FBCON_LOGO_DONTSHOW) {
logo_shown = FBCON_LOGO_DRAW;
vc->vc_top = logo_lines;
}
}
#ifdef CONFIG_FB_TILEBLITTING
static void set_blitting_type(struct vc_data *vc, struct fb_info *info,
struct display *p)
{
struct fbcon_ops *ops = info->fbcon_par;
if ((info->flags & FBINFO_MISC_TILEBLITTING))
fbcon_set_tileops(vc, info, p, ops);
else
fbcon_set_bitops(ops);
}
#else
static void set_blitting_type(struct vc_data *vc, struct fb_info *info,
struct display *p)
{
struct fbcon_ops *ops = info->fbcon_par;
info->flags &= ~FBINFO_MISC_TILEBLITTING;
fbcon_set_bitops(ops);
}
#endif /* CONFIG_MISC_TILEBLITTING */
static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info,
int unit, int oldidx)
{
struct fbcon_ops *ops = NULL;
int err = 0;
if (!try_module_get(info->fbops->owner))
err = -ENODEV;
if (!err && info->fbops->fb_open &&
info->fbops->fb_open(info, 0))
err = -ENODEV;
if (!err) {
ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
if (!ops)
err = -ENOMEM;
}
if (!err) {
memset(ops, 0, sizeof(struct fbcon_ops));
info->fbcon_par = ops;
set_blitting_type(vc, info, NULL);
}
if (err) {
con2fb_map[unit] = oldidx;
module_put(info->fbops->owner);
}
return err;
}
static int con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo,
struct fb_info *newinfo, int unit,
int oldidx, int found)
{
struct fbcon_ops *ops = oldinfo->fbcon_par;
int err = 0;
if (oldinfo->fbops->fb_release &&
oldinfo->fbops->fb_release(oldinfo, 0)) {
con2fb_map[unit] = oldidx;
if (!found && newinfo->fbops->fb_release)
newinfo->fbops->fb_release(newinfo, 0);
if (!found)
module_put(newinfo->fbops->owner);
err = -ENODEV;
}
if (!err) {
if (oldinfo->queue.func == fb_flashcursor)
del_timer_sync(&ops->cursor_timer);
kfree(ops->cursor_state.mask);
kfree(ops->cursor_data);
kfree(oldinfo->fbcon_par);
oldinfo->fbcon_par = NULL;
module_put(oldinfo->fbops->owner);
}
return err;
}
static void con2fb_init_newinfo(struct fb_info *info)
{
if (!info->queue.func || info->queue.func == fb_flashcursor) {
struct fbcon_ops *ops = info->fbcon_par;
if (!info->queue.func)
INIT_WORK(&info->queue, fb_flashcursor, info);
init_timer(&ops->cursor_timer);
ops->cursor_timer.function = cursor_timer_handler;
ops->cursor_timer.expires = jiffies + HZ / 5;
ops->cursor_timer.data = (unsigned long ) info;
add_timer(&ops->cursor_timer);
}
}
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;
ops->currcon = fg_console;
if (info->fbops->fb_set_par && !(ops->flags & FBCON_FLAGS_INIT))
info->fbops->fb_set_par(info);
ops->flags |= FBCON_FLAGS_INIT;
ops->graphics = 0;
if (vc)
fbcon_set_disp(info, &info->var, vc);
else
fbcon_preset_disp(info, &info->var, unit);
if (show_logo) {
struct vc_data *fg_vc = vc_cons[fg_console].d;
struct fb_info *fg_info =
registered_fb[con2fb_map[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.
*/
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 = registered_fb[newidx];
struct fb_info *oldinfo = NULL;
int found, err = 0;
if (oldidx == newidx)
return 0;
if (!info)
err = -EINVAL;
if (!err && !search_for_mapped_con()) {
info_idx = newidx;
return fbcon_takeover(0);
}
if (oldidx != -1)
oldinfo = registered_fb[oldidx];
found = search_fb_in_map(newidx);
acquire_console_sem();
con2fb_map[unit] = newidx;
if (!err && !found)
err = con2fb_acquire_newinfo(vc, info, unit, oldidx);
/*
* If old fb is not mapped to any of the consoles,
* fbcon should release it.
*/
if (!err && oldinfo && !search_fb_in_map(oldidx))
err = con2fb_release_oldinfo(vc, oldinfo, info, unit, oldidx,
found);
if (!err) {
int show_logo = (fg_console == 0 && !user &&
logo_shown != FBCON_LOGO_DONTSHOW);
if (!found)
con2fb_init_newinfo(info);
con2fb_map_boot[unit] = newidx;
con2fb_init_display(vc, info, unit, show_logo);
}
release_console_sem();
return err;
}
/*
* Low Level Operations
*/
/* NOTE: fbcon cannot be __init: it may be called from take_over_console later */
static int var_to_display(struct 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 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 display *p = &fb_display[fg_console];
struct vc_data *vc = vc_cons[fg_console].d;
struct font_desc *font = NULL;
struct module *owner;
struct fb_info *info = NULL;
struct fbcon_ops *ops;
int rows, cols;
int irqres;
irqres = 1;
/*
* If num_registered_fb is zero, this is a call for the dummy part.
* The frame buffer devices weren't initialized yet.
*/
if (!num_registered_fb || info_idx == -1)
return display_desc;
/*
* Instead of blindly using registered_fb[0], we use info_idx, set by
* fb_console_init();
*/
info = registered_fb[info_idx];
if (!info)
return NULL;
owner = info->fbops->owner;
if (!try_module_get(owner))
return NULL;
if (info->fbops->fb_open && info->fbops->fb_open(info, 0)) {
module_put(owner);
return NULL;
}
ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
if (!ops) {
module_put(owner);
return NULL;
}
memset(ops, 0, sizeof(struct fbcon_ops));
ops->currcon = -1;
ops->graphics = 1;
info->fbcon_par = ops;
set_blitting_type(vc, info, NULL);
if (info->fix.type != FB_TYPE_TEXT) {
if (fbcon_softback_size) {
if (!softback_buf) {
softback_buf =
(unsigned long)
kmalloc(fbcon_softback_size,
GFP_KERNEL);
if (!softback_buf) {
fbcon_softback_size = 0;
softback_top = 0;
}
}
} else {
if (softback_buf) {
kfree((void *) softback_buf);
softback_buf = 0;
softback_top = 0;
}
}
if (softback_buf)
softback_in = softback_top = softback_curr =
softback_buf;
softback_lines = 0;
}
/* Setup default font */
if (!p->fontdata) {
if (!fontname[0] || !(font = find_font(fontname)))
font = get_default_font(info->var.xres,
info->var.yres);
vc->vc_font.width = font->width;
vc->vc_font.height = font->height;
vc->vc_font.data = p->fontdata = font->data;
vc->vc_font.charcount = 256; /* FIXME Need to support more fonts */
}
cols = info->var.xres / vc->vc_font.width;
rows = info->var.yres / vc->vc_font.height;
vc_resize(vc, cols, rows);
DPRINTK("mode: %s\n", info->fix.id);
DPRINTK("visual: %d\n", info->fix.visual);
DPRINTK("res: %dx%d-%d\n", info->var.xres,
info->var.yres,
info->var.bits_per_pixel);
#ifdef CONFIG_ATARI
if (MACH_IS_ATARI) {
cursor_blink_rate = ATARI_CURSOR_BLINK_RATE;
irqres =
request_irq(IRQ_AUTO_4, fb_vbl_handler,
IRQ_TYPE_PRIO, "framebuffer vbl",
info);
}
#endif /* CONFIG_ATARI */
#ifdef CONFIG_MAC
/*
* On a Macintoy, the VBL interrupt may or may not be active.
* As interrupt based cursor is more reliable and race free, we
* probe for VBL interrupts.
*/
if (MACH_IS_MAC) {
int ct = 0;
/*
* Probe for VBL: set temp. handler ...
*/
irqres = request_irq(IRQ_MAC_VBL, fb_vbl_detect, 0,
"framebuffer vbl", info);
vbl_detected = 0;
/*
* ... and spin for 20 ms ...
*/
while (!vbl_detected && ++ct < 1000)
udelay(20);
if (ct == 1000)
printk
("fbcon_startup: No VBL detected, using timer based cursor.\n");
free_irq(IRQ_MAC_VBL, fb_vbl_detect);
if (vbl_detected) {
/*
* interrupt based cursor ok
*/
cursor_blink_rate = MAC_CURSOR_BLINK_RATE;
irqres =
request_irq(IRQ_MAC_VBL, fb_vbl_handler, 0,
"framebuffer vbl", info);
} else {
/*
* VBL not detected: fall through, use timer based cursor
*/
irqres = 1;
}
}
#endif /* CONFIG_MAC */
/* Initialize the work queue. If the driver provides its
* own work queue this means it will use something besides
* default timer to flash the cursor. */
if (!info->queue.func) {
INIT_WORK(&info->queue, fb_flashcursor, info);
init_timer(&ops->cursor_timer);
ops->cursor_timer.function = cursor_timer_handler;
ops->cursor_timer.expires = jiffies + HZ / 5;
ops->cursor_timer.data = (unsigned long ) info;
add_timer(&ops->cursor_timer);
}
return display_desc;
}
static void fbcon_init(struct vc_data *vc, int init)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops;
struct vc_data **default_mode = vc->vc_display_fg;
struct vc_data *svc = *default_mode;
struct display *t, *p = &fb_display[vc->vc_num];
int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256;
int cap;
if (info_idx == -1 || info == NULL)
return;
cap = info->flags;
if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||
(info->fix.type == FB_TYPE_TEXT))
logo = 0;
info->var.xoffset = info->var.yoffset = p->yscroll = 0; /* reset wrap/pan */
if (var_to_display(p, &info->var, info))
return;
/* If we are not the first console on this
fb, copy the font from that console */
t = &fb_display[svc->vc_num];
if (!vc->vc_font.data) {
vc->vc_font.data = p->fontdata = t->fontdata;
vc->vc_font.width = (*default_mode)->vc_font.width;
vc->vc_font.height = (*default_mode)->vc_font.height;
p->userfont = t->userfont;
if (p->userfont)
REFCOUNT(p->fontdata)++;
}
if (p->userfont)
charcnt = FNTCHARCNT(p->fontdata);
vc->vc_can_do_color = (fb_get_color_depth(&info->var) != 1);
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
if (charcnt == 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->vc_uni_pagedir_loc)
con_set_default_unimap(svc);
if (!*vc->vc_uni_pagedir_loc)
con_copy_unimap(vc, svc);
cols = vc->vc_cols;
rows = vc->vc_rows;
new_cols = info->var.xres / vc->vc_font.width;
new_rows = info->var.yres / vc->vc_font.height;
vc_resize(vc, new_cols, new_rows);
ops = info->fbcon_par;
/*
* 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)) {
if (info->fbops->fb_set_par &&
!(ops->flags & FBCON_FLAGS_INIT))
info->fbops->fb_set_par(info);
ops->flags |= FBCON_FLAGS_INIT;
}
ops->graphics = 0;
if ((cap & FBINFO_HWACCEL_COPYAREA) &&
!(cap & FBINFO_HWACCEL_DISABLED))
p->scrollmode = SCROLL_MOVE;
else /* default to something safe */
p->scrollmode = SCROLL_REDRAW;
/*
* ++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;
}
if (logo)
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
if (vc == svc && softback_buf) {
int l = fbcon_softback_size / vc->vc_size_row;
if (l > 5)
softback_end = softback_buf + l * vc->vc_size_row;
else {
/* Smaller scrollback makes no sense, and 0 would screw
the operation totally */
softback_top = 0;
}
}
}
static void fbcon_deinit(struct vc_data *vc)
{
struct display *p = &fb_display[vc->vc_num];
if (info_idx != -1)
return;
fbcon_free_font(p);
}
/* ====================================================================== */
/* 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 __inline__ int real_y(struct display *p, int ypos)
{
int rows = p->vrows;
ypos += p->yscroll;
return ypos < rows ? ypos : ypos - rows;
}
static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
int width)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops = info->fbcon_par;
struct display *p = &fb_display[vc->vc_num];
u_int y_break;
if (fbcon_is_inactive(vc, info))
return;
if (!height || !width)
return;
/* 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 = registered_fb[con2fb_map[vc->vc_num]];
struct 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 = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops = info->fbcon_par;
if (!fbcon_is_inactive(vc, info))
ops->clear_margins(vc, info, bottom_only);
}
static void fbcon_cursor(struct vc_data *vc, int mode)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops = info->fbcon_par;
struct display *p = &fb_display[vc->vc_num];
int y;
int c = scr_readw((u16 *) vc->vc_pos);
if (fbcon_is_inactive(vc, info))
return;
ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1;
if (mode & CM_SOFTBACK) {
mode &= ~CM_SOFTBACK;
y = softback_lines;
} else {
if (softback_lines)
fbcon_set_origin(vc);
y = 0;
}
ops->cursor(vc, info, p, mode, y, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
vbl_cursor_cnt = CURSOR_DRAW_DELAY;
}
static int scrollback_phys_max = 0;
static int scrollback_max = 0;
static int scrollback_current = 0;
static int update_var(int con, struct fb_info *info)
{
if (con == ((struct fbcon_ops *)info->fbcon_par)->currcon)
return fb_pan_display(info, &info->var);
return 0;
}
/*
* If no vc is existent yet, just set struct display
*/
static void fbcon_preset_disp(struct fb_info *info, struct fb_var_screeninfo *var,
int unit)
{
struct display *p = &fb_display[unit];
struct display *t = &fb_display[fg_console];
var->xoffset = var->yoffset = p->yscroll = 0;
if (var_to_display(p, var, info))
return;
p->fontdata = t->fontdata;
p->userfont = t->userfont;
if (p->userfont)
REFCOUNT(p->fontdata)++;
}
static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
struct vc_data *vc)
{
struct display *p = &fb_display[vc->vc_num], *t;
struct vc_data **default_mode = vc->vc_display_fg;
struct vc_data *svc = *default_mode;
int rows, cols, charcnt = 256;
var->xoffset = var->yoffset = p->yscroll = 0;
if (var_to_display(p, var, info))
return;
t = &fb_display[svc->vc_num];
if (!vc->vc_font.data) {
vc->vc_font.data = p->fontdata = t->fontdata;
vc->vc_font.width = (*default_mode)->vc_font.width;
vc->vc_font.height = (*default_mode)->vc_font.height;
p->userfont = t->userfont;
if (p->userfont)
REFCOUNT(p->fontdata)++;
}
if (p->userfont)
charcnt = FNTCHARCNT(p->fontdata);
vc->vc_can_do_color = (fb_get_color_depth(var) != 1);
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
if (charcnt == 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->vc_uni_pagedir_loc)
con_set_default_unimap(svc);
if (!*vc->vc_uni_pagedir_loc)
con_copy_unimap(vc, svc);
cols = var->xres / vc->vc_font.width;
rows = var->yres / vc->vc_font.height;
vc_resize(vc, cols, rows);
if (CON_IS_VISIBLE(vc)) {
update_screen(vc);
if (softback_buf) {
int l = fbcon_softback_size / vc->vc_size_row;
if (l > 5)
softback_end = softback_buf + l *
vc->vc_size_row;
else {
/* Smaller scrollback makes no sense, and 0
would screw the operation totally */
softback_top = 0;
}
}
}
}
static __inline__ void ywrap_up(struct vc_data *vc, int count)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
p->yscroll += count;
if (p->yscroll >= p->vrows) /* Deal with wrap */
p->yscroll -= p->vrows;
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode |= FB_VMODE_YWRAP;
update_var(vc->vc_num, 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 = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
p->yscroll -= count;
if (p->yscroll < 0) /* Deal with wrap */
p->yscroll += p->vrows;
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode |= FB_VMODE_YWRAP;
update_var(vc->vc_num, 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 = registered_fb[con2fb_map[vc->vc_num]];
struct 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;
}
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode &= ~FB_VMODE_YWRAP;
update_var(vc->vc_num, 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 = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
int redraw = 0;
p->yscroll += count;
if (p->yscroll > p->vrows - vc->vc_rows) {
p->yscroll -= p->vrows - vc->vc_rows;
redraw = 1;
}
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode &= ~FB_VMODE_YWRAP;
if (redraw)
fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t);
update_var(vc->vc_num, 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 = registered_fb[con2fb_map[vc->vc_num]];
struct 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;
}
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode &= ~FB_VMODE_YWRAP;
update_var(vc->vc_num, 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 = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
int redraw = 0;
p->yscroll -= count;
if (p->yscroll < 0) {
p->yscroll += p->vrows - vc->vc_rows;
redraw = 1;
}
info->var.xoffset = 0;
info->var.yoffset = p->yscroll * vc->vc_font.height;
info->var.vmode &= ~FB_VMODE_YWRAP;
if (redraw)
fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count);
update_var(vc->vc_num, info);
fbcon_clear_margins(vc, 1);
scrollback_max -= count;
if (scrollback_max < 0)
scrollback_max = 0;
scrollback_current = 0;
}
static void fbcon_redraw_softback(struct vc_data *vc, struct display *p,
long delta)
{
int count = vc->vc_rows;
unsigned short *d, *s;
unsigned long n;
int line = 0;
d = (u16 *) softback_curr;
if (d == (u16 *) softback_in)
d = (u16 *) vc->vc_origin;
n = softback_curr + delta * vc->vc_size_row;
softback_lines -= delta;
if (delta < 0) {
if (softback_curr < softback_top && n < softback_buf) {
n += softback_end - softback_buf;
if (n < softback_top) {
softback_lines -=
(softback_top - n) / vc->vc_size_row;
n = softback_top;
}
} else if (softback_curr >= softback_top
&& n < softback_top) {
softback_lines -=
(softback_top - n) / vc->vc_size_row;
n = softback_top;
}
} else {
if (softback_curr > softback_in && n >= softback_end) {
n += softback_buf - softback_end;
if (n > softback_in) {
n = softback_in;
softback_lines = 0;
}
} else if (softback_curr <= softback_in && n > softback_in) {
n = softback_in;
softback_lines = 0;
}
}
if (n == softback_curr)
return;
softback_curr = n;
s = (u16 *) softback_curr;
if (s == (u16 *) softback_in)
s = (u16 *) vc->vc_origin;
while (count--) {
unsigned short *start;
unsigned short *le;
unsigned short c;
int x = 0;
unsigned short attr = 1;
start = s;
le = advance_row(s, 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++;
}
}
s++;
d++;
} while (s < le);
if (s > start)
fbcon_putcs(vc, start, s - start, line, x);
line++;
if (d == (u16 *) softback_end)
d = (u16 *) softback_buf;
if (d == (u16 *) softback_in)
d = (u16 *) vc->vc_origin;
if (s == (u16 *) softback_end)
s = (u16 *) softback_buf;
if (s == (u16 *) softback_in)
s = (u16 *) vc->vc_origin;
}
}
static void fbcon_redraw_move(struct vc_data *vc, struct 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(struct vc_data *vc, struct display *p,
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 inline void fbcon_softback_note(struct vc_data *vc, int t,
int count)
{
unsigned short *p;
if (vc->vc_num != fg_console)
return;
p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
while (count) {
scr_memcpyw((u16 *) softback_in, p, vc->vc_size_row);
count--;
p = advance_row(p, 1);
softback_in += vc->vc_size_row;
if (softback_in == softback_end)
softback_in = softback_buf;
if (softback_in == softback_top) {
softback_top += vc->vc_size_row;
if (softback_top == softback_end)
softback_top = softback_buf;
}
}
softback_curr = softback_in;
}
static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
int count)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
struct fbcon_ops *ops = info->fbcon_par;
int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK;
if (fbcon_is_inactive(vc, info))
return -EINVAL;
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;
if (softback_top)
fbcon_softback_note(vc, t, count);
if (logo_shown >= 0)
goto redraw_up;
switch (p->scrollmode) {
case SCROLL_MOVE:
ops->bmove(vc, info, t + count, 0, t, 0,
b - t - count, vc->vc_cols);
ops->clear(vc, info, b - count, 0, count,
vc->vc_cols);
break;
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 - count,
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, p, 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 1;
}
break;
case SM_DOWN:
if (count > vc->vc_rows) /* Maximum realistic size */
count = vc->vc_rows;
switch (p->scrollmode) {
case SCROLL_MOVE:
ops->bmove(vc, info, t, 0, t + count, 0,
b - t - count, vc->vc_cols);
ops->clear(vc, info, t, 0, count, vc->vc_cols);
break;
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, p, 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 1;
}
}
return 0;
}
static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
int height, int width)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct 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 void fbcon_bmove_rec(struct vc_data *vc, struct display *p, int sy, int sx,
int dy, int dx, int height, int width, u_int y_break)
{
struct fb_info *info = registered_fb[con2fb_map[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 __inline__ void updatescrollmode(struct display *p, struct fb_info *info,
struct vc_data *vc)
{
int fh = vc->vc_font.height;
int cap = info->flags;
int good_pan = (cap & FBINFO_HWACCEL_YPAN)
&& divides(info->fix.ypanstep, vc->vc_font.height)
&& info->var.yres_virtual > info->var.yres;
int good_wrap = (cap & FBINFO_HWACCEL_YWRAP)
&& divides(info->fix.ywrapstep, vc->vc_font.height)
&& divides(vc->vc_font.height, info->var.yres_virtual);
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);
p->vrows = info->var.yres_virtual/fh;
if (info->var.yres > (fh * (vc->vc_rows + 1)))
p->vrows -= (info->var.yres - (fh * vc->vc_rows)) / fh;
if ((info->var.yres % fh) && (info->var.yres_virtual % fh <
info->var.yres % fh))
p->vrows--;
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;
}
}
static int fbcon_resize(struct vc_data *vc, unsigned int width,
unsigned int height)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
struct fb_var_screeninfo var = info->var;
int x_diff, y_diff;
int fw = vc->vc_font.width;
int fh = vc->vc_font.height;
var.xres = width * fw;
var.yres = height * fh;
x_diff = info->var.xres - var.xres;
y_diff = info->var.yres - var.yres;
if (x_diff < 0 || x_diff > fw || (y_diff < 0 || y_diff > fh)) {
struct fb_videomode *mode;
DPRINTK("attempting resize %ix%i\n", var.xres, var.yres);
mode = fb_find_best_mode(&var, &info->modelist);
if (mode == NULL)
return -EINVAL;
fb_videomode_to_var(&var, mode);
if (width > var.xres/fw || height > var.yres/fh)
return -EINVAL;
/*
* The following can probably have any value... Do we need to
* set all of them?
*/
var.bits_per_pixel = p->bits_per_pixel;
var.xres_virtual = p->xres_virtual;
var.yres_virtual = p->yres_virtual;
var.accel_flags = p->accel_flags;
var.width = p->width;
var.height = p->height;
var.red = p->red;
var.green = p->green;
var.blue = p->blue;
var.transp = p->transp;
var.nonstd = p->nonstd;
DPRINTK("resize now %ix%i\n", var.xres, var.yres);
if (CON_IS_VISIBLE(vc)) {
var.activate = FB_ACTIVATE_NOW |
FB_ACTIVATE_FORCE;
fb_set_var(info, &var);
}
var_to_display(p, &info->var, info);
}
updatescrollmode(p, info, vc);
return 0;
}
static int fbcon_switch(struct vc_data *vc)
{
struct fb_info *info;
struct display *p = &fb_display[vc->vc_num];
struct fb_var_screeninfo var;
int i, prev_console;
info = registered_fb[con2fb_map[vc->vc_num]];
if (softback_top) {
int l = fbcon_softback_size / vc->vc_size_row;
if (softback_lines)
fbcon_set_origin(vc);
softback_top = softback_curr = softback_in = softback_buf;
softback_lines = 0;
if (l > 5)
softback_end = softback_buf + l * vc->vc_size_row;
else {
/* Smaller scrollback makes no sense, and 0 would screw
the operation totally */
softback_top = 0;
}
}
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 = ((struct fbcon_ops *)info->fbcon_par)->currcon;
/*
* 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;
*/
for (i = 0; i < FB_MAX; i++) {
if (registered_fb[i] != NULL && registered_fb[i]->fbcon_par) {
struct fbcon_ops *ops = registered_fb[i]->fbcon_par;
ops->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;
info->var.yoffset = info->var.xoffset = p->yscroll = 0;
fb_set_var(info, &var);
if (prev_console != -1 &&
registered_fb[con2fb_map[prev_console]] != info &&
info->fbops->fb_set_par)
info->fbops->fb_set_par(info);
set_blitting_type(vc, info, p);
((struct fbcon_ops *)info->fbcon_par)->cursor_reset = 1;
vc->vc_can_do_color = (fb_get_color_depth(&info->var) != 1);
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
updatescrollmode(p, info, vc);
switch (p->scrollmode) {
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;
update_var(vc->vc_num, info);
fbcon_set_palette(vc, color_table);
fbcon_clear_margins(vc, 0);
if (logo_shown == FBCON_LOGO_DRAW) {
logo_shown = fg_console;
/* This is protected above by initmem_freed */
fb_show_logo(info);
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 = registered_fb[con2fb_map[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_set_var(info, &var);
ops->graphics = 0;
}
}
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);
}
return 0;
}
static void fbcon_free_font(struct 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 int fbcon_get_font(struct vc_data *vc, struct console_font *font)
{
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;
font->charcount = vc->vc_hi_font_mask ? 512 : 256;
if (!font->data)
return 0;
if (font->width <= 8) {
j = vc->vc_font.height;
for (i = 0; i < font->charcount; i++) {
memcpy(data, fontdata, j);
memset(data + j, 0, 32 - j);
data += 32;
fontdata += j;
}
} else if (font->width <= 16) {
j = vc->vc_font.height * 2;
for (i = 0; i < font->charcount; i++) {
memcpy(data, fontdata, j);
memset(data + j, 0, 64 - j);
data += 64;
fontdata += j;
}
} else if (font->width <= 24) {
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 * (32 - j));
data += 3 * (32 - j);
}
} else {
j = vc->vc_font.height * 4;
for (i = 0; i < font->charcount; i++) {
memcpy(data, fontdata, j);
memset(data + j, 0, 128 - j);
data += 128;
fontdata += j;
}
}
return 0;
}
static int fbcon_do_set_font(struct vc_data *vc, int w, int h,
u8 * data, int userfont)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct display *p = &fb_display[vc->vc_num];
int resize;
int cnt;
char *old_data = NULL;
if (CON_IS_VISIBLE(vc) && softback_lines)
fbcon_set_origin(vc);
resize = (w != vc->vc_font.width) || (h != vc->vc_font.height);
if (p->userfont)
old_data = vc->vc_font.data;
if (userfont)
cnt = FNTCHARCNT(data);
else
cnt = 256;
vc->vc_font.data = p->fontdata = data;
if ((p->userfont = userfont))
REFCOUNT(data)++;
vc->vc_font.width = w;
vc->vc_font.height = h;
if (vc->vc_hi_font_mask && cnt == 256) {
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 if (!vc->vc_hi_font_mask && cnt == 512) {
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;
}
}
if (resize) {
/* reset wrap/pan */
info->var.xoffset = info->var.yoffset = p->yscroll = 0;
vc_resize(vc, info->var.xres / w, info->var.yres / h);
if (CON_IS_VISIBLE(vc) && softback_buf) {
int l = fbcon_softback_size / vc->vc_size_row;
if (l > 5)
softback_end =
softback_buf + l * vc->vc_size_row;
else {
/* Smaller scrollback makes no sense, and 0 would screw
the operation totally */
softback_top = 0;
}
}
} 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;
}
static int fbcon_copy_font(struct vc_data *vc, int con)
{
struct display *od = &fb_display[con];
struct console_font *f = &vc->vc_font;
if (od->fontdata == f->data)
return 0; /* already the same font... */
return fbcon_do_set_font(vc, f->width, f->height, od->fontdata, od->userfont);
}
/*
* User asked to set font; we are guaranteed that
* a) width and height are in range 1..32
* b) charcount does not exceed 512
* but lets not assume that, since someone might someday want to use larger
* fonts. And charcount of 512 is small for unicode support.
*
* However, user space gives the font in 32 rows , regardless of
* actual font height. So a new API is needed if support for larger fonts
* is ever implemented.
*/
static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigned flags)
{
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 = (font->width+7) >> 3;
/* 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;
size = h * pitch * charcount;
new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size, GFP_USER);
if (!new_data)
return -ENOMEM;
new_data += FONT_EXTRA_WORDS * sizeof(int);
FNTSIZE(new_data) = size;
FNTCHARCNT(new_data) = charcount;
REFCOUNT(new_data) = 0; /* usage counter */
for (i=0; i< charcount; i++) {
memcpy(new_data + i*h*pitch, data + i*32*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 = 0; i < MAX_NR_CONSOLES; 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 = fb_display[i].fontdata;
break;
}
}
return fbcon_do_set_font(vc, font->width, font->height, new_data, 1);
}
static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, char *name)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct font_desc *f;
if (!name)
f = get_default_font(info->var.xres, info->var.yres);
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->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 int fbcon_set_palette(struct vc_data *vc, unsigned char *table)
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
int i, j, k, depth;
u8 val;
if (fbcon_is_inactive(vc, info))
return -EINVAL;
if (!CON_IS_VISIBLE(vc))
return 0;
depth = fb_get_color_depth(&info->var);
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);
return fb_set_cmap(&palette_cmap, info);
}
static u16 *fbcon_screen_pos(struct vc_data *vc, int offset)
{
unsigned long p;
int line;
if (vc->vc_num != fg_console || !softback_lines)
return (u16 *) (vc->vc_origin + offset);
line = offset / vc->vc_size_row;
if (line >= softback_lines)
return (u16 *) (vc->vc_origin + offset -
softback_lines * vc->vc_size_row);
p = softback_curr + offset;
if (p >= softback_end)
p += softback_buf - softback_end;
return (u16 *) p;
}
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;
if (vc->vc_num == fg_console)
y += softback_lines;
ret = pos + (vc->vc_cols - x) * 2;
} else if (vc->vc_num == fg_console && softback_lines) {
unsigned long offset = pos - softback_curr;
if (pos < softback_curr)
offset += softback_end - softback_buf;
offset /= 2;
x = offset % vc->vc_cols;
y = offset / vc->vc_cols;
ret = pos + (vc->vc_cols - x) * 2;
if (ret == softback_end)
ret = softback_buf;
if (ret == softback_in)
ret = vc->vc_origin;
} 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++);
if (p == (u16 *) softback_end)
p = (u16 *) softback_buf;
if (p == (u16 *) softback_in)
p = (u16 *) vc->vc_origin;
}
}
static int fbcon_scrolldelta(struct vc_data *vc, int lines)
{
struct fb_info *info = registered_fb[con2fb_map[fg_console]];
struct display *p = &fb_display[fg_console];
int offset, limit, scrollback_old;
if (softback_top) {
if (vc->vc_num != fg_console)
return 0;
if (vc->vc_mode != KD_TEXT || !lines)
return 0;
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;
if (logo_shown == vc->vc_num) {
unsigned long p, q;
int i;
p = softback_in;
q = vc->vc_origin +
logo_lines * vc->vc_size_row;
for (i = 0; i < logo_lines; i++) {
if (p == softback_top)
break;
if (p == softback_buf)
p = softback_end;
p -= vc->vc_size_row;
q -= vc->vc_size_row;
scr_memcpyw((u16 *) q, (u16 *) p,
vc->vc_size_row);
}
softback_in = p;
update_region(vc, vc->vc_origin,
logo_lines * vc->vc_cols);
}
logo_shown = FBCON_LOGO_CANSHOW;
}
fbcon_cursor(vc, CM_ERASE | CM_SOFTBACK);
fbcon_redraw_softback(vc, p, lines);
fbcon_cursor(vc, CM_DRAW | CM_SOFTBACK);
return 0;
}
if (!scrollback_phys_max)
return -ENOSYS;
scrollback_old = scrollback_current;
scrollback_current -= lines;
if (scrollback_current < 0)
scrollback_current = 0;
else if (scrollback_current > scrollback_max)
scrollback_current = scrollback_max;
if (scrollback_current == scrollback_old)
return 0;
if (fbcon_is_inactive(vc, info))
return 0;
fbcon_cursor(vc, CM_ERASE);
offset = p->yscroll - scrollback_current;
limit = p->vrows;
switch (p->scrollmode) {
case SCROLL_WRAP_MOVE:
info->var.vmode |= FB_VMODE_YWRAP;
break;
case SCROLL_PAN_MOVE:
case SCROLL_PAN_REDRAW:
limit -= vc->vc_rows;
info->var.vmode &= ~FB_VMODE_YWRAP;
break;
}
if (offset < 0)
offset += limit;
else if (offset >= limit)
offset -= limit;
info->var.xoffset = 0;
info->var.yoffset = offset * vc->vc_font.height;
update_var(vc->vc_num, info);
if (!scrollback_current)
fbcon_cursor(vc, CM_DRAW);
return 0;
}
static int fbcon_set_origin(struct vc_data *vc)
{
if (softback_lines)
fbcon_scrolldelta(vc, softback_lines);
return 0;
}
static 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);
}
static 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 display *p;
int rows, cols;
if (!ops || ops->currcon < 0)
return;
vc = vc_cons[ops->currcon].d;
if (vc->vc_mode != KD_TEXT || registered_fb[con2fb_map[ops->currcon]] != info)
return;
p = &fb_display[vc->vc_num];
info->var.xoffset = info->var.yoffset = p->yscroll = 0;
if (CON_IS_VISIBLE(vc)) {
var_to_display(p, &info->var, info);
cols = info->var.xres / vc->vc_font.width;
rows = info->var.yres / vc->vc_font.height;
vc_resize(vc, cols, rows);
updatescrollmode(p, info, vc);
scrollback_max = 0;
scrollback_current = 0;
update_var(vc->vc_num, info);
fbcon_set_palette(vc, color_table);
update_screen(vc);
if (softback_buf) {
int l = fbcon_softback_size / vc->vc_size_row;
if (l > 5)
softback_end = softback_buf + l * vc->vc_size_row;
else {
/* Smaller scrollback makes no sense, and 0
would screw the operation totally */
softback_top = 0;
}
}
}
}
static int fbcon_mode_deleted(struct fb_info *info,
struct fb_videomode *mode)
{
struct fb_info *fb_info;
struct 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 = 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;
}
static int fbcon_fb_registered(int idx)
{
int ret = 0, i;
if (info_idx == -1) {
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (con2fb_map_boot[i] == idx) {
info_idx = idx;
break;
}
}
if (info_idx != -1)
ret = fbcon_takeover(1);
} else {
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (con2fb_map_boot[i] == idx)
set_con2fb_map(i, idx, 0);
}
}
return ret;
}
static 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 ||
registered_fb[con2fb_map[ops->currcon]] != info)
return;
if (CON_IS_VISIBLE(vc)) {
if (blank)
do_blank_screen(0);
else
do_unblank_screen(0);
}
ops->blank_state = blank;
}
static void fbcon_new_modelist(struct fb_info *info)
{
int i;
struct vc_data *vc;
struct fb_var_screeninfo var;
struct fb_videomode *mode;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (registered_fb[con2fb_map[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(&var, &info->modelist);
fb_videomode_to_var(&var, mode);
if (vc)
fbcon_set_disp(info, &var, vc);
else
fbcon_preset_disp(info, &var, i);
}
}
static int fbcon_event_notify(struct notifier_block *self,
unsigned long action, void *data)
{
struct fb_event *event = data;
struct fb_info *info = event->info;
struct fb_videomode *mode;
struct fb_con2fbmap *con2fb;
int ret = 0;
switch(action) {
case FB_EVENT_SUSPEND:
fbcon_suspended(info);
break;
case FB_EVENT_RESUME:
fbcon_resumed(info);
break;
case FB_EVENT_MODE_CHANGE:
fbcon_modechanged(info);
break;
case FB_EVENT_MODE_DELETE:
mode = event->data;
ret = fbcon_mode_deleted(info, mode);
break;
case FB_EVENT_FB_REGISTERED:
ret = fbcon_fb_registered(info->node);
break;
case FB_EVENT_SET_CONSOLE_MAP:
con2fb = event->data;
ret = set_con2fb_map(con2fb->console - 1,
con2fb->framebuffer, 1);
break;
case FB_EVENT_GET_CONSOLE_MAP:
con2fb = event->data;
con2fb->framebuffer = con2fb_map[con2fb->console - 1];
break;
case FB_EVENT_BLANK:
fbcon_fb_blanked(info, *(int *)event->data);
break;
case FB_EVENT_NEW_MODELIST:
fbcon_new_modelist(info);
break;
}
return ret;
}
/*
* 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_bmove = fbcon_bmove,
.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_font_copy = fbcon_copy_font,
.con_set_palette = fbcon_set_palette,
.con_scrolldelta = fbcon_scrolldelta,
.con_set_origin = fbcon_set_origin,
.con_invert_region = fbcon_invert_region,
.con_screen_pos = fbcon_screen_pos,
.con_getxy = fbcon_getxy,
.con_resize = fbcon_resize,
};
static struct notifier_block fbcon_event_notifier = {
.notifier_call = fbcon_event_notify,
};
static int __init fb_console_init(void)
{
int i;
acquire_console_sem();
fb_register_client(&fbcon_event_notifier);
release_console_sem();
for (i = 0; i < MAX_NR_CONSOLES; i++)
con2fb_map[i] = -1;
if (num_registered_fb) {
for (i = 0; i < FB_MAX; i++) {
if (registered_fb[i] != NULL) {
info_idx = i;
break;
}
}
fbcon_takeover(0);
}
return 0;
}
module_init(fb_console_init);
#ifdef MODULE
static void __exit fb_console_exit(void)
{
acquire_console_sem();
fb_unregister_client(&fbcon_event_notifier);
release_console_sem();
give_up_console(&fb_con);
}
module_exit(fb_console_exit);
#endif
MODULE_LICENSE("GPL");