fbcon: use struct device instead of struct class_device
[safe/jmp/linux-2.6] / drivers / video / console / fbcon.c
index 3cf1b61..7f4ed79 100644 (file)
 
 #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>
@@ -106,10 +103,13 @@ enum {
        FBCON_LOGO_DONTSHOW     = -3    /* do not show the logo */
 };
 
-struct display fb_display[MAX_NR_CONSOLES];
+static struct display fb_display[MAX_NR_CONSOLES];
+
 static signed char con2fb_map[MAX_NR_CONSOLES];
 static signed char con2fb_map_boot[MAX_NR_CONSOLES];
+#ifndef MODULE
 static int logo_height;
+#endif
 static int logo_lines;
 /* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO
    enums.  */
@@ -124,19 +124,24 @@ static int softback_lines;
 static int first_fb_vc;
 static int last_fb_vc = MAX_NR_CONSOLES - 1;
 static int fbcon_is_default = 1; 
+static int fbcon_has_exited;
+
 /* font data */
 static char fontname[40];
 
 /* current fb_info */
 static int info_idx = -1;
 
+/* console rotation */
+static int rotate;
+static int fbcon_has_sysfs;
+
 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)
@@ -147,6 +152,7 @@ static int fbcon_set_origin(struct vc_data *);
 #define DEFAULT_CURSOR_BLINK_RATE      (20)
 
 static int vbl_cursor_cnt;
+static int fbcon_cursor_noblink;
 
 #define divides(a, b)  ((!(a) || (b)%(a)) ? 0 : 1)
 
@@ -176,7 +182,6 @@ 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);
@@ -189,6 +194,11 @@ static void fbcon_preset_disp(struct fb_info *info, struct fb_var_screeninfo *va
                              int unit);
 static void fbcon_redraw_move(struct vc_data *vc, struct 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 void fbcon_start(void);
+static void fbcon_exit(void);
+static struct device *fbcon_device;
 
 #ifdef CONFIG_MAC
 /*
@@ -196,13 +206,95 @@ static void fbcon_redraw_move(struct vc_data *vc, struct display *p,
  */
 static int vbl_detected;
 
-static irqreturn_t fb_vbl_detect(int irq, void *dummy, struct pt_regs *fp)
+static irqreturn_t fb_vbl_detect(int irq, void *dummy)
 {
        vbl_detected++;
        return IRQ_HANDLED;
 }
 #endif
 
+#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 = registered_fb[con2fb_map[ops->currcon]];
+
+       if (info == fb_info) {
+               struct 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 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 ||
+                   registered_fb[con2fb_map[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;
@@ -293,36 +385,38 @@ static void fbcon_update_softback(struct vc_data *vc)
                softback_top = 0;
 }
 
-static void fb_flashcursor(void *private)
+static void fb_flashcursor(struct work_struct *work)
 {
-       struct fb_info *info = private;
+       struct fb_info *info = container_of(work, struct fb_info, queue);
        struct fbcon_ops *ops = info->fbcon_par;
        struct display *p;
        struct vc_data *vc = NULL;
        int c;
        int mode;
 
-       if (ops->currcon != -1)
+       acquire_console_sem();
+       if (ops && 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 ||
-           vc_cons[ops->currcon].d->vc_deccm != 1)
+           vc->vc_deccm != 1) {
+               release_console_sem();
                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),
+       ops->cursor(vc, info, 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)
+static irqreturn_t fb_vbl_handler(int irq, void *dev_id)
 {
        struct fb_info *info = dev_id;
 
@@ -348,9 +442,10 @@ static void fbcon_add_cursor_timer(struct fb_info *info)
        struct fbcon_ops *ops = info->fbcon_par;
 
        if ((!info->queue.func || info->queue.func == fb_flashcursor) &&
-           !(ops->flags & FBCON_FLAGS_CURSOR_TIMER)) {
+           !(ops->flags & FBCON_FLAGS_CURSOR_TIMER) &&
+           !fbcon_cursor_noblink) {
                if (!info->queue.func)
-                       INIT_WORK(&info->queue, fb_flashcursor, info);
+                       INIT_WORK(&info->queue, fb_flashcursor);
 
                init_timer(&ops->cursor_timer);
                ops->cursor_timer.function = cursor_timer_handler;
@@ -379,7 +474,7 @@ static int __init fb_console_setup(char *this_opt)
        int i, j;
 
        if (!this_opt || !*this_opt)
-               return 0;
+               return 1;
 
        while ((options = strsep(&this_opt, ",")) != NULL) {
                if (!strncmp(options, "font:", 5))
@@ -394,10 +489,10 @@ static int __init fb_console_setup(char *this_opt)
                                        options++;
                                }
                                if (*options != ',')
-                                       return 0;
+                                       return 1;
                                options++;
                        } else
-                               return 0;
+                               return 1;
                }
                
                if (!strncmp(options, "map:", 4)) {
@@ -409,7 +504,7 @@ static int __init fb_console_setup(char *this_opt)
                                        con2fb_map_boot[i] =
                                                (options[j++]-'0') % FB_MAX;
                                }
-                       return 0;
+                       return 1;
                }
 
                if (!strncmp(options, "vc:", 3)) {
@@ -422,8 +517,16 @@ static int __init fb_console_setup(char *this_opt)
                                last_fb_vc = simple_strtoul(options, &options, 10) - 1;
                        fbcon_is_default = 0; 
                }       
+
+               if (!strncmp(options, "rotate:", 7)) {
+                       options += 7;
+                       if (*options)
+                               rotate = simple_strtoul(options, &options, 0);
+                       if (rotate > 3)
+                               rotate = 0;
+               }
        }
-       return 0;
+       return 1;
 }
 
 __setup("fbcon=", fb_console_setup);
@@ -433,7 +536,7 @@ static int search_fb_in_map(int idx)
 {
        int i, retval = 0;
 
-       for (i = 0; i < MAX_NR_CONSOLES; i++) {
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
                if (con2fb_map[i] == idx)
                        retval = 1;
        }
@@ -444,7 +547,7 @@ static int search_for_mapped_con(void)
 {
        int i, retval = 0;
 
-       for (i = 0; i < MAX_NR_CONSOLES; i++) {
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
                if (con2fb_map[i] != -1)
                        retval = 1;
        }
@@ -466,6 +569,7 @@ static int fbcon_takeover(int show_logo)
 
        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;
@@ -476,20 +580,33 @@ static int fbcon_takeover(int show_logo)
        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;
 
+       if (info->flags & FBINFO_MODULE) {
+               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);
+       logo_height = fb_prepare_logo(info, ops->rotate);
        logo_lines = (logo_height + vc->vc_font.height - 1) /
                vc->vc_font.height;
        q = (unsigned short *) (vc->vc_origin +
@@ -517,8 +634,13 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
                        r -= cols;
                }
                if (!save) {
-                       vc->vc_y += logo_lines;
-                       vc->vc_pos += logo_lines * vc->vc_size_row;
+                       int lines;
+                       if (vc->vc_y + logo_lines >= rows)
+                               lines = rows - vc->vc_y - 1;
+                       else
+                               lines = logo_lines;
+                       vc->vc_y += lines;
+                       vc->vc_pos += lines * vc->vc_size_row;
                }
        }
        scr_memsetw((unsigned short *) vc->vc_origin,
@@ -549,27 +671,49 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
                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 display *p)
+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, p, ops);
-       else
+               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 display *p)
+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 */
 
 
@@ -587,15 +731,14 @@ static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info,
                err = -ENODEV;
 
        if (!err) {
-               ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
+               ops = kzalloc(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);
+               set_blitting_type(vc, info);
        }
 
        if (err) {
@@ -627,6 +770,7 @@ static int con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo,
                fbcon_del_cursor_timer(oldinfo);
                kfree(ops->cursor_state.mask);
                kfree(ops->cursor_data);
+               kfree(ops->fontbuffer);
                kfree(oldinfo->fbcon_par);
                oldinfo->fbcon_par = NULL;
                module_put(oldinfo->fbops->owner);
@@ -695,8 +839,8 @@ static int set_con2fb_map(int unit, int newidx, int user)
        if (oldidx == newidx)
                return 0;
 
-       if (!info)
-               err =  -EINVAL;
+       if (!info || fbcon_has_exited)
+               return -EINVAL;
 
        if (!err && !search_for_mapped_con()) {
                info_idx = newidx;
@@ -732,6 +876,9 @@ static int set_con2fb_map(int unit, int newidx, int user)
                con2fb_init_display(vc, info, unit, show_logo);
        }
 
+       if (!search_fb_in_map(info_idx))
+               info_idx = newidx;
+
        release_console_sem();
        return err;
 }
@@ -818,17 +965,18 @@ static const char *fbcon_startup(void)
                return NULL;
        }
 
-       ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
+       ops = kzalloc(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;
+       ops->cur_rotate = -1;
        info->fbcon_par = ops;
-       set_blitting_type(vc, info, NULL);
+       p->con_rotate = rotate;
+       set_blitting_type(vc, info);
 
        if (info->fix.type != FB_TYPE_TEXT) {
                if (fbcon_softback_size) {
@@ -859,15 +1007,19 @@ static const char *fbcon_startup(void)
        if (!p->fontdata) {
                if (!fontname[0] || !(font = find_font(fontname)))
                        font = get_default_font(info->var.xres,
-                                               info->var.yres);
+                                               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 = 256; /* FIXME  Need to support more fonts */
        }
 
-       cols = info->var.xres / vc->vc_font.width;
-       rows = info->var.yres / vc->vc_font.height;
+       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);
 
        DPRINTK("mode:   %s\n", info->fix.id);
@@ -931,6 +1083,7 @@ static const char *fbcon_startup(void)
 #endif                         /* CONFIG_MAC */
 
        fbcon_add_cursor_timer(info);
+       fbcon_has_exited = 0;
        return display_desc;
 }
 
@@ -953,24 +1106,43 @@ static void fbcon_init(struct vc_data *vc, int init)
            (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 = (void *)(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)++;
+       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;
+                       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 = 256; /* FIXME  Need to
+                                                       support more fonts */
+               }
        }
+
        if (p->userfont)
                charcnt = FNTCHARCNT(p->fontdata);
+
        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 (charcnt == 256) {
@@ -986,13 +1158,18 @@ static void fbcon_init(struct vc_data *vc, int init)
        if (!*vc->vc_uni_pagedir_loc)
                con_copy_unimap(vc, svc);
 
+       ops = info->fbcon_par;
+       p->con_rotate = rotate;
+       set_blitting_type(vc, info);
+
        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;
+       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;
        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
@@ -1000,7 +1177,7 @@ static void fbcon_init(struct vc_data *vc, int init)
         *
         * We need to do it in fbcon_init() to prevent screen corruption.
         */
-       if (CON_IS_VISIBLE(vc)) {
+       if (CON_IS_VISIBLE(vc) && vc->vc_mode == KD_TEXT) {
                if (info->fbops->fb_set_par &&
                    !(ops->flags & FBCON_FLAGS_INIT))
                        info->fbops->fb_set_par(info);
@@ -1030,15 +1207,56 @@ static void fbcon_init(struct vc_data *vc, int init)
 
        if (vc == svc && softback_buf)
                fbcon_update_softback(vc);
+
+       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 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 fbcon_deinit(struct vc_data *vc)
 {
        struct display *p = &fb_display[vc->vc_num];
+       struct fb_info *info;
+       struct fbcon_ops *ops;
+       int idx;
 
-       if (info_idx != -1)
-           return;
        fbcon_free_font(p);
+       idx = con2fb_map[vc->vc_num];
+
+       if (idx == -1)
+               goto finished;
+
+       info = registered_fb[idx];
+
+       if (!info)
+               goto finished;
+
+       ops = info->fbcon_par;
+
+       if (!ops)
+               goto finished;
+
+       if (CON_IS_VISIBLE(vc))
+               fbcon_del_cursor_timer(info);
+
+       ops->flags &= ~FBCON_FLAGS_INIT;
+finished:
+
+       if (!con_is_bound(&fb_con))
+               fbcon_exit();
+
+       return;
 }
 
 /* ====================================================================== */
@@ -1066,15 +1284,6 @@ static void fbcon_deinit(struct vc_data *vc)
  *  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)
 {
@@ -1136,13 +1345,17 @@ 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))
+       if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1)
                return;
 
+       if (vc->vc_cursor_type & 0x10)
+               fbcon_del_cursor_timer(info);
+       else
+               fbcon_add_cursor_timer(info);
+
        ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1;
        if (mode & CM_SOFTBACK) {
                mode &= ~CM_SOFTBACK;
@@ -1153,7 +1366,7 @@ static void fbcon_cursor(struct vc_data *vc, int mode)
                y = 0;
        }
 
-       ops->cursor(vc, info, p, mode, y, get_color(vc, info, c, 1),
+       ops->cursor(vc, info, mode, y, get_color(vc, info, c, 1),
                    get_color(vc, info, c, 0));
        vbl_cursor_cnt = CURSOR_DRAW_DELAY;
 }
@@ -1162,13 +1375,6 @@ 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
  */
@@ -1178,7 +1384,6 @@ static void fbcon_preset_disp(struct fb_info *info, struct fb_var_screeninfo *va
        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;
 
@@ -1194,9 +1399,9 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
        struct display *p = &fb_display[vc->vc_num], *t;
        struct vc_data **default_mode = vc->vc_display_fg;
        struct vc_data *svc = *default_mode;
+       struct fbcon_ops *ops = info->fbcon_par;
        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];
@@ -1213,9 +1418,10 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
 
        var->activate = FB_ACTIVATE_NOW;
        info->var.activate = var->activate;
-       info->var.yoffset = info->var.xoffset = 0;
+       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 (charcnt == 256) {
@@ -1231,9 +1437,12 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
        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;
+       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);
                if (softback_buf)
@@ -1244,15 +1453,16 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
 static __inline__ void ywrap_up(struct vc_data *vc, int count)
 {
        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];
        
        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);
+       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;
@@ -1262,15 +1472,16 @@ static __inline__ void ywrap_up(struct vc_data *vc, int count)
 static __inline__ void ywrap_down(struct vc_data *vc, int count)
 {
        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];
        
        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);
+       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;
@@ -1289,10 +1500,11 @@ static __inline__ void ypan_up(struct vc_data *vc, int count)
                            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);
+
+       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)
@@ -1303,21 +1515,20 @@ static __inline__ void ypan_up(struct vc_data *vc, int count)
 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 fbcon_ops *ops = info->fbcon_par;
        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;
+               fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t);
        }
 
-       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);
+       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)
@@ -1337,10 +1548,11 @@ static __inline__ void ypan_down(struct vc_data *vc, int count)
                            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);
+
+       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)
@@ -1351,20 +1563,20 @@ static __inline__ void ypan_down(struct vc_data *vc, int count)
 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 fbcon_ops *ops = info->fbcon_par;
        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);
+       }
+
+       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)
@@ -1499,6 +1711,56 @@ static void fbcon_redraw_move(struct vc_data *vc, struct display *p,
        }
 }
 
+static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
+                       struct 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, struct display *p,
                         int line, int count, int offset)
 {
@@ -1584,7 +1846,6 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
 {
        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))
@@ -1608,10 +1869,15 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
                        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);
+                       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 1;
                        break;
 
                case SCROLL_WRAP_MOVE:
@@ -1643,7 +1909,7 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
                                        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,
+                                       fbcon_redraw_move(vc, p, b,
                                                          vc->vc_rows - b, b);
                        } else
                                fbcon_redraw_move(vc, p, t + count, b - t - count, t);
@@ -1694,9 +1960,15 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir,
                        goto redraw_down;
                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);
+                       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 1;
                        break;
 
                case SCROLL_WRAP_MOVE:
@@ -1838,31 +2110,42 @@ static void fbcon_bmove_rec(struct vc_data *vc, struct display *p, int sy, int s
                   height, width);
 }
 
-static __inline__ void updatescrollmode(struct display *p, struct fb_info *info,
+static __inline__ void updatescrollmode(struct display *p,
+                                       struct fb_info *info,
                                        struct vc_data *vc)
 {
+       struct fbcon_ops *ops = info->fbcon_par;
        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);
+       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);
-
-       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))
+       int fast_copyarea = (cap & FBINFO_HWACCEL_COPYAREA) &&
+               !(cap & FBINFO_HWACCEL_DISABLED);
+       int fast_imageblit = (cap & FBINFO_HWACCEL_IMAGEBLIT) &&
+               !(cap & FBINFO_HWACCEL_DISABLED);
+
+       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--;
 
        if (good_wrap || good_pan) {
                if (reading_fast || fast_copyarea)
-                       p->scrollmode = good_wrap ? SCROLL_WRAP_MOVE : SCROLL_PAN_MOVE;
+                       p->scrollmode = good_wrap ?
+                               SCROLL_WRAP_MOVE : SCROLL_PAN_MOVE;
                else
                        p->scrollmode = good_wrap ? SCROLL_REDRAW :
                                SCROLL_PAN_REDRAW;
@@ -1878,18 +2161,24 @@ 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 fbcon_ops *ops = info->fbcon_par;
        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;
+       int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh;
+
+       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 > fw || (y_diff < 0 || y_diff > fh)) {
-               struct fb_videomode *mode;
+       if (x_diff < 0 || x_diff > virt_fw ||
+           y_diff < 0 || y_diff > virt_fh) {
+               const struct fb_videomode *mode;
 
                DPRINTK("attempting resize %ix%i\n", var.xres, var.yres);
                mode = fb_find_best_mode(&var, &info->modelist);
@@ -1898,7 +2187,7 @@ static int fbcon_resize(struct vc_data *vc, unsigned int width,
                display_to_var(&var, p);
                fb_videomode_to_var(&var, mode);
 
-               if (width > var.xres/fw || height > var.yres/fh)
+               if (virt_w > var.xres/virt_fw || virt_h > var.yres/virt_fh)
                        return -EINVAL;
 
                DPRINTK("resize now %ix%i\n", var.xres, var.yres);
@@ -1908,6 +2197,7 @@ static int fbcon_resize(struct vc_data *vc, unsigned int width,
                        fb_set_var(info, &var);
                }
                var_to_display(p, &info->var, info);
+               ops->var = info->var;
        }
        updatescrollmode(p, info, vc);
        return 0;
@@ -1916,11 +2206,13 @@ static int fbcon_resize(struct vc_data *vc, unsigned int width,
 static int fbcon_switch(struct vc_data *vc)
 {
        struct fb_info *info, *old_info = NULL;
+       struct fbcon_ops *ops;
        struct display *p = &fb_display[vc->vc_num];
        struct fb_var_screeninfo var;
-       int i, prev_console;
+       int i, prev_console, charcnt = 256;
 
        info = registered_fb[con2fb_map[vc->vc_num]];
+       ops = info->fbcon_par;
 
        if (softback_top) {
                if (softback_lines)
@@ -1939,7 +2231,7 @@ static int fbcon_switch(struct vc_data *vc)
                logo_shown = FBCON_LOGO_CANSHOW;
        }
 
-       prev_console = ((struct fbcon_ops *)info->fbcon_par)->currcon;
+       prev_console = ops->currcon;
        if (prev_console != -1)
                old_info = registered_fb[con2fb_map[prev_console]];
        /*
@@ -1952,9 +2244,9 @@ static int fbcon_switch(struct vc_data *vc)
         */
        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;
+                       struct fbcon_ops *o = registered_fb[i]->fbcon_par;
 
-                       ops->currcon = vc->vc_num;
+                       o->currcon = vc->vc_num;
                }
        }
        memset(&var, 0, sizeof(struct fb_var_screeninfo));
@@ -1966,21 +2258,44 @@ static int fbcon_switch(struct vc_data *vc)
         * in fb_set_var()
         */
        info->var.activate = var.activate;
-       info->var.yoffset = info->var.xoffset = p->yscroll = 0;
+       var.yoffset = info->var.yoffset;
+       var.xoffset = info->var.xoffset;
+       var.vmode = info->var.vmode;
        fb_set_var(info, &var);
+       ops->var = info->var;
 
-       if (old_info != NULL && old_info != info) {
+       if (old_info != NULL && (old_info != info ||
+                                info->flags & FBINFO_MISC_ALWAYS_SETPAR)) {
                if (info->fbops->fb_set_par)
                        info->fbops->fb_set_par(info);
-               fbcon_del_cursor_timer(old_info);
-               fbcon_add_cursor_timer(info);
+
+               if (old_info != info)
+                       fbcon_del_cursor_timer(old_info);
        }
 
-       set_blitting_type(vc, info, p);
-       ((struct fbcon_ops *)info->fbcon_par)->cursor_reset = 1;
+       if (fbcon_is_inactive(vc, info) ||
+           ops->blank_state != FB_BLANK_UNBLANK)
+               fbcon_del_cursor_timer(info);
+       else
+               fbcon_add_cursor_timer(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 (p->userfont)
+               charcnt = FNTCHARCNT(vc->vc_font.data);
+
+       if (charcnt > 256)
+               vc->vc_complement_mask <<= 1;
+
        updatescrollmode(p, info, vc);
 
        switch (p->scrollmode) {
@@ -1997,10 +2312,15 @@ static int fbcon_switch(struct vc_data *vc)
                scrollback_phys_max = 0;
                break;
        }
+
        scrollback_max = 0;
        scrollback_current = 0;
 
-       update_var(vc->vc_num, info);
+       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);
 
@@ -2008,7 +2328,7 @@ static int fbcon_switch(struct vc_data *vc)
 
                logo_shown = fg_console;
                /* This is protected above by initmem_freed */
-               fb_show_logo(info);
+               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 -
@@ -2021,6 +2341,8 @@ static int fbcon_switch(struct vc_data *vc)
 static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
                                int blank)
 {
+       struct fb_event event;
+
        if (blank) {
                unsigned short charmask = vc->vc_hi_font_mask ?
                        0x1ff : 0xff;
@@ -2031,6 +2353,11 @@ static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
                fbcon_clear(vc, 0, 0, vc->vc_rows, vc->vc_cols);
                vc->vc_video_erase_char = oldc;
        }
+
+
+       event.info = info;
+       event.data = &blank;
+       fb_notifier_call_chain(FB_EVENT_CONBLANK, &event);
 }
 
 static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
@@ -2044,10 +2371,14 @@ static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
                ops->graphics = 1;
 
                if (!blank) {
+                       if (info->fbops->fb_save_state)
+                               info->fbops->fb_save_state(info);
                        var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
                        fb_set_var(info, &var);
                        ops->graphics = 0;
-               }
+                       ops->var = info->var;
+               } else if (info->fbops->fb_restore_state)
+                       info->fbops->fb_restore_state(info);
        }
 
        if (!fbcon_is_inactive(vc, info)) {
@@ -2064,22 +2395,15 @@ static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
                        update_screen(vc);
        }
 
-       if (!blank)
-               fbcon_add_cursor_timer(info);
-       else
+       if (fbcon_is_inactive(vc, info) ||
+           ops->blank_state != FB_BLANK_UNBLANK)
                fbcon_del_cursor_timer(info);
+       else
+               fbcon_add_cursor_timer(info);
 
        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;
@@ -2135,6 +2459,7 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h,
                             const u8 * data, int userfont)
 {
        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 resize;
        int cnt;
@@ -2214,9 +2539,13 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h,
        }
 
        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);
+               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;
+               vc_resize(vc, cols, rows);
                if (CON_IS_VISIBLE(vc) && softback_buf)
                        fbcon_update_softback(vc);
        } else if (CON_IS_VISIBLE(vc)
@@ -2254,6 +2583,7 @@ static int fbcon_copy_font(struct vc_data *vc, int con)
 
 static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigned flags)
 {
+       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
        unsigned charcount = font->charcount;
        int w = font->width;
        int h = font->height;
@@ -2267,6 +2597,15 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigne
        if (charcount != 256 && charcount != 512)
                return -EINVAL;
 
+       /* Make sure drawing engine can handle the font */
+       if (!(info->pixmap.blit_x & (1 << (font->width - 1))) ||
+           !(info->pixmap.blit_y & (1 << (font->height - 1))))
+               return -EINVAL;
+
+       /* Make sure driver can handle the font length */
+       if (fbcon_invalid_charcount(info, charcount))
+               return -EINVAL;
+
        size = h * pitch * charcount;
 
        new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size, GFP_USER);
@@ -2288,7 +2627,7 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigne
 
        FNTSUM(new_data) = csum;
        /* Check if the same font is on some other console already */
-       for (i = 0; i < MAX_NR_CONSOLES; i++) {
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
                struct vc_data *tmp = vc_cons[i].d;
                
                if (fb_display[i].userfont &&
@@ -2311,7 +2650,8 @@ static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, cha
        const struct font_desc *f;
 
        if (!name)
-               f = get_default_font(info->var.xres, info->var.yres);
+               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;
 
@@ -2444,6 +2784,7 @@ static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt)
 static int fbcon_scrolldelta(struct vc_data *vc, int lines)
 {
        struct fb_info *info = registered_fb[con2fb_map[fg_console]];
+       struct fbcon_ops *ops = info->fbcon_par;
        struct display *p = &fb_display[fg_console];
        int offset, limit, scrollback_old;
 
@@ -2475,7 +2816,7 @@ static int fbcon_scrolldelta(struct vc_data *vc, int lines)
                                        scr_memcpyw((u16 *) q, (u16 *) p,
                                                    vc->vc_size_row);
                                }
-                               softback_in = p;
+                               softback_in = softback_curr = p;
                                update_region(vc, vc->vc_origin,
                                              logo_lines * vc->vc_cols);
                        }
@@ -2520,9 +2861,11 @@ static int fbcon_scrolldelta(struct vc_data *vc, int lines)
                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);
+
+       ops->var.xoffset = 0;
+       ops->var.yoffset = offset * vc->vc_font.height;
+       ops->update_start(info);
+
        if (!scrollback_current)
                fbcon_cursor(vc, CM_DRAW);
        return 0;
@@ -2570,22 +2913,29 @@ static void fbcon_modechanged(struct fb_info *info)
        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)
+       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;
+       set_blitting_type(vc, info);
 
        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;
+               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;
-               update_var(vc->vc_num, info);
+
+               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);
                if (softback_buf)
@@ -2598,36 +2948,34 @@ static void fbcon_set_all_vcs(struct fb_info *info)
        struct fbcon_ops *ops = info->fbcon_par;
        struct vc_data *vc;
        struct display *p;
-       int i, rows, cols;
+       int i, rows, cols, fg = -1;
 
        if (!ops || ops->currcon < 0)
                return;
 
-       for (i = 0; i < MAX_NR_CONSOLES; i++) {
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
                vc = vc_cons[i].d;
                if (!vc || vc->vc_mode != KD_TEXT ||
                    registered_fb[con2fb_map[i]] != info)
                        continue;
 
-               p = &fb_display[vc->vc_num];
+               if (CON_IS_VISIBLE(vc)) {
+                       fg = i;
+                       continue;
+               }
 
-               info->var.xoffset = info->var.yoffset = p->yscroll = 0;
+               p = &fb_display[vc->vc_num];
+               set_blitting_type(vc, info);
                var_to_display(p, &info->var, info);
-               cols = info->var.xres / vc->vc_font.width;
-               rows = info->var.yres / vc->vc_font.height;
+               cols = FBCON_SWAP(p->rotate, info->var.xres, info->var.yres);
+               rows = FBCON_SWAP(p->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)) {
-                       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)
-                               fbcon_update_softback(vc);
-               }
        }
+
+       if (fg != -1)
+               fbcon_modechanged(info);
 }
 
 static int fbcon_mode_deleted(struct fb_info *info,
@@ -2656,22 +3004,57 @@ static int fbcon_mode_deleted(struct fb_info *info,
        return found;
 }
 
+static int fbcon_fb_unregistered(int idx)
+{
+       int i;
+
+       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;
+
+               for (i = 0; i < FB_MAX; i++) {
+                       if (registered_fb[i] != NULL) {
+                               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 (!num_registered_fb)
+               unregister_con_driver(&fb_con);
+
+       return 0;
+}
+
 static int fbcon_fb_registered(int idx)
 {
        int ret = 0, i;
 
        if (info_idx == -1) {
-               for (i = 0; i < MAX_NR_CONSOLES; i++) {
+               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 = fbcon_takeover(1);
        } else {
-               for (i = 0; i < MAX_NR_CONSOLES; i++) {
-                       if (con2fb_map_boot[i] == idx)
+               for (i = first_fb_vc; i <= last_fb_vc; i++) {
+                       if (con2fb_map_boot[i] == idx &&
+                           con2fb_map[i] == -1)
                                set_con2fb_map(i, idx, 0);
                }
        }
@@ -2706,9 +3089,9 @@ static void fbcon_new_modelist(struct fb_info *info)
        int i;
        struct vc_data *vc;
        struct fb_var_screeninfo var;
-       struct fb_videomode *mode;
+       const struct fb_videomode *mode;
 
-       for (i = 0; i < MAX_NR_CONSOLES; i++) {
+       for (i = first_fb_vc; i <= last_fb_vc; i++) {
                if (registered_fb[con2fb_map[i]] != info)
                        continue;
                if (!fb_display[i].mode)
@@ -2727,6 +3110,42 @@ static void fbcon_new_modelist(struct fb_info *info)
        }
 }
 
+static void fbcon_get_requirement(struct fb_info *info,
+                                 struct fb_blit_caps *caps)
+{
+       struct vc_data *vc;
+       struct display *p;
+
+       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]) {
+                               p = &fb_display[i];
+                               caps->x |= 1 << (vc->vc_font.width - 1);
+                               caps->y |= 1 << (vc->vc_font.height - 1);
+                               charcnt = (p->userfont) ?
+                                       FNTCHARCNT(p->fontdata) : 256;
+                               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]) {
+                       p = &fb_display[fg_console];
+                       caps->x = 1 << (vc->vc_font.width - 1);
+                       caps->y = 1 << (vc->vc_font.height - 1);
+                       caps->len = (p->userfont) ?
+                               FNTCHARCNT(p->fontdata) : 256;
+               }
+       }
+}
+
 static int fbcon_event_notify(struct notifier_block *self, 
                              unsigned long action, void *data)
 {
@@ -2734,8 +3153,17 @@ static int fbcon_event_notify(struct notifier_block *self,
        struct fb_info *info = event->info;
        struct fb_videomode *mode;
        struct fb_con2fbmap *con2fb;
+       struct fb_blit_caps *caps;
        int ret = 0;
 
+       /*
+        * ignore all events except driver registration and deregistration
+        * if fbcon is not active
+        */
+       if (fbcon_has_exited && !(action == FB_EVENT_FB_REGISTERED ||
+                                 action == FB_EVENT_FB_UNREGISTERED))
+               goto done;
+
        switch(action) {
        case FB_EVENT_SUSPEND:
                fbcon_suspended(info);
@@ -2756,6 +3184,9 @@ static int fbcon_event_notify(struct notifier_block *self,
        case FB_EVENT_FB_REGISTERED:
                ret = fbcon_fb_registered(info->node);
                break;
+       case FB_EVENT_FB_UNREGISTERED:
+               ret = fbcon_fb_unregistered(info->node);
+               break;
        case FB_EVENT_SET_CONSOLE_MAP:
                con2fb = event->data;
                ret = set_con2fb_map(con2fb->console - 1,
@@ -2771,8 +3202,13 @@ static int fbcon_event_notify(struct notifier_block *self,
        case FB_EVENT_NEW_MODELIST:
                fbcon_new_modelist(info);
                break;
+       case FB_EVENT_GET_REQ:
+               caps = event->data;
+               fbcon_get_requirement(info, caps);
+               break;
        }
 
+done:
        return ret;
 }
 
@@ -2810,27 +3246,266 @@ static struct notifier_block fbcon_event_notifier = {
        .notifier_call  = fbcon_event_notify,
 };
 
-static int __init fb_console_init(void)
+static ssize_t store_rotate(struct device *device,
+                           struct device_attribute *attr, const char *buf,
+                           size_t count)
 {
-       int i;
+       struct fb_info *info;
+       int rotate, idx;
+       char **last = NULL;
+
+       if (fbcon_has_exited)
+               return count;
 
        acquire_console_sem();
-       fb_register_client(&fbcon_event_notifier);
+       idx = con2fb_map[fg_console];
+
+       if (idx == -1 || registered_fb[idx] == NULL)
+               goto err;
+
+       info = registered_fb[idx];
+       rotate = simple_strtoul(buf, last, 0);
+       fbcon_rotate(info, rotate);
+err:
        release_console_sem();
+       return count;
+}
 
-       for (i = 0; i < MAX_NR_CONSOLES; i++)
-               con2fb_map[i] = -1;
+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;
+
+       if (fbcon_has_exited)
+               return count;
+
+       acquire_console_sem();
+       idx = con2fb_map[fg_console];
+
+       if (idx == -1 || registered_fb[idx] == NULL)
+               goto err;
+
+       info = registered_fb[idx];
+       rotate = simple_strtoul(buf, last, 0);
+       fbcon_rotate_all(info, rotate);
+err:
+       release_console_sem();
+       return count;
+}
+
+static ssize_t show_rotate(struct device *device,
+                          struct device_attribute *attr,char *buf)
+{
+       struct fb_info *info;
+       int rotate = 0, idx;
+
+       if (fbcon_has_exited)
+               return 0;
+
+       acquire_console_sem();
+       idx = con2fb_map[fg_console];
+
+       if (idx == -1 || registered_fb[idx] == NULL)
+               goto err;
+
+       info = registered_fb[idx];
+       rotate = fbcon_get_rotate(info);
+err:
+       release_console_sem();
+       return snprintf(buf, PAGE_SIZE, "%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;
+
+       if (fbcon_has_exited)
+               return 0;
+
+       acquire_console_sem();
+       idx = con2fb_map[fg_console];
+
+       if (idx == -1 || registered_fb[idx] == NULL)
+               goto err;
+
+       info = registered_fb[idx];
+       ops = info->fbcon_par;
+
+       if (!ops)
+               goto err;
+
+       blink = (ops->flags & FBCON_FLAGS_CURSOR_TIMER) ? 1 : 0;
+err:
+       release_console_sem();
+       return snprintf(buf, PAGE_SIZE, "%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;
+
+       if (fbcon_has_exited)
+               return count;
+
+       acquire_console_sem();
+       idx = con2fb_map[fg_console];
+
+       if (idx == -1 || registered_fb[idx] == NULL)
+               goto err;
+
+       info = registered_fb[idx];
+
+       if (!info->fbcon_par)
+               goto err;
+
+       blink = simple_strtoul(buf, last, 0);
+
+       if (blink) {
+               fbcon_cursor_noblink = 0;
+               fbcon_add_cursor_timer(info);
+       } else {
+               fbcon_cursor_noblink = 1;
+               fbcon_del_cursor_timer(info);
+       }
+
+err:
+       release_console_sem();
+       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;
+}
+
+static void fbcon_start(void)
+{
        if (num_registered_fb) {
+               int i;
+
+               acquire_console_sem();
+
                for (i = 0; i < FB_MAX; i++) {
                        if (registered_fb[i] != NULL) {
                                info_idx = i;
                                break;
                        }
                }
+
+               release_console_sem();
                fbcon_takeover(0);
        }
+}
+
+static void fbcon_exit(void)
+{
+       struct fb_info *info;
+       int i, j, mapped;
+
+       if (fbcon_has_exited)
+               return;
+
+#ifdef CONFIG_ATARI
+       free_irq(IRQ_AUTO_4, fb_vbl_handler);
+#endif
+#ifdef CONFIG_MAC
+       if (MACH_IS_MAC && vbl_detected)
+               free_irq(IRQ_MAC_VBL, fb_vbl_handler);
+#endif
+
+       kfree((void *)softback_buf);
+       softback_buf = 0UL;
+
+       for (i = 0; i < FB_MAX; i++) {
+               mapped = 0;
+               info = registered_fb[i];
+
+               if (info == NULL)
+                       continue;
+
+               for (j = first_fb_vc; j <= last_fb_vc; j++) {
+                       if (con2fb_map[j] == i)
+                               mapped = 1;
+               }
 
+               if (mapped) {
+                       if (info->fbops->fb_release)
+                               info->fbops->fb_release(info, 0);
+                       module_put(info->fbops->owner);
+
+                       if (info->fbcon_par) {
+                               struct fbcon_ops *ops = info->fbcon_par;
+
+                               fbcon_del_cursor_timer(info);
+                               kfree(ops->cursor_src);
+                               kfree(info->fbcon_par);
+                               info->fbcon_par = NULL;
+                       }
+
+                       if (info->queue.func == fb_flashcursor)
+                               info->queue.func = NULL;
+               }
+       }
+
+       fbcon_has_exited = 1;
+}
+
+static int __init fb_console_init(void)
+{
+       int i;
+
+       acquire_console_sem();
+       fb_register_client(&fbcon_event_notifier);
+       fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), "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;
+
+       release_console_sem();
+       fbcon_start();
        return 0;
 }
 
@@ -2838,12 +3513,27 @@ module_init(fb_console_init);
 
 #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;
+       }
+}
+
 static void __exit fb_console_exit(void)
 {
        acquire_console_sem();
        fb_unregister_client(&fbcon_event_notifier);
+       fbcon_deinit_device();
+       device_destroy(fb_class, MKDEV(0, 0));
+       fbcon_exit();
        release_console_sem();
-       give_up_console(&fb_con);
+       unregister_con_driver(&fb_con);
 }      
 
 module_exit(fb_console_exit);