/*
 * linux/drivers/video/acornfb.c
 *
 * Copyright (C) 1998,1999 Russell King
 *
 * Frame buffer code for Acorn platforms
 *
 * NOTE: Most of the modes with X!=640 will disappear shortly.
 * NOTE: Startup setting of HS & VS polarity not supported.
 *       (do we need to support it if we're coming up in 640x480?)
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/malloc.h>
#include <linux/init.h>
#include <linux/fb.h>

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>

#include <video/fbcon.h>
#include <video/fbcon-mfb.h>
#include <video/fbcon-cfb2.h>
#include <video/fbcon-cfb4.h>
#include <video/fbcon-cfb8.h>
#include <video/fbcon-cfb16.h>
#include <video/fbcon-cfb32.h>

#include "acornfb.h"

/*
 * Default resolution.
 * NOTE that it has to be supported in the table towards
 * the end of this file.
 */
#define DEFAULT_XRES	640
#define DEFAULT_YRES	480
/*
 * The order here defines which BPP we
 * pick depending on which resolutions
 * we have configured.
 */
#if   defined(FBCON_HAS_CFB4)
# define DEFAULT_BPP	4
#elif defined(FBCON_HAS_CFB8)
# define DEFAULT_BPP	8
#elif defined(FBCON_HAS_CFB16)
# define DEFAULT_BPP	16
#elif defined(FBCON_HAS_CFB2)
# define DEFAULT_BPP	2
#elif defined(FBCON_HAS_MFB)
# define DEFAULT_BPP	1
#else
#error No suitable framebuffers configured
#endif


/*
 * define this to debug the video mode selection
 */
#undef DEBUG_MODE_SELECTION

/*
 * Translation from RISC OS monitor types to actual
 * HSYNC and VSYNC frequency ranges.  These are
 * probably not right, but they're the best info I
 * have.  Allow 1% either way on the nominal for TVs.
 */
#define NR_MONTYPES	6
static struct fb_monspecs monspecs[NR_MONTYPES] __initdata = {
	{ 15469, 15781, 49, 51, 0 },	/* TV		*/
	{     0, 99999,  0, 99, 0 },	/* Multi Freq	*/
	{ 58608, 58608, 64, 64, 0 },	/* Hi-res mono	*/
	{ 30000, 70000, 60, 60, 0 },	/* VGA		*/
	{ 30000, 70000, 56, 75, 0 },	/* SVGA		*/
	{ 30000, 70000, 60, 60, 0 }
};

static struct display global_disp;
static struct fb_info fb_info;
static struct acornfb_par current_par;
static struct vidc_timing current_vidc;
static struct fb_var_screeninfo __initdata init_var = {};

extern int acornfb_depth;	/* set by setup.c */
extern unsigned int vram_size;	/* set by setup.c */

#ifdef HAS_VIDC

/* CTL     VIDC	Actual
 * 24.000  0	 8.000
 * 25.175  0	 8.392
 * 36.000  0	12.000
 * 24.000  1	12.000
 * 25.175  1	12.588
 * 24.000  2	16.000
 * 25.175  2	16.783
 * 36.000  1	18.000
 * 24.000  3	24.000
 * 36.000  2	24.000
 * 25.175  3	25.175
 * 36.000  3	36.000
 */
static struct pixclock {
	u_long	min_clock;
	u_long	max_clock;
	u_int	vidc_ctl;
	u_int	vid_ctl;
} pixclocks[] = {
	/* we allow +/-1% on these */
	{ 123750, 126250, VIDC_CTRL_DIV3,   VID_CTL_24MHz },	/*  8.000MHz */
	{  82500,  84167, VIDC_CTRL_DIV2,   VID_CTL_24MHz },	/* 12.000MHz */
	{  61875,  63125, VIDC_CTRL_DIV1_5, VID_CTL_24MHz },	/* 16.000MHz */
	{  41250,  42083, VIDC_CTRL_DIV1,   VID_CTL_24MHz },	/* 24.000MHz */
#ifdef CONFIG_ARCH_A5K
	{ 117974, 120357, VIDC_CTRL_DIV3,   VID_CTL_25MHz },	/*  8.392MHz */
	{  78649,  80238, VIDC_CTRL_DIV2,   VID_CTL_25MHz },	/* 12.588MHz */
	{  58987,  60178, VIDC_CTRL_DIV1_5, VID_CTL_25MHz },	/* 16.588MHz */
	{  55000,  56111, VIDC_CTRL_DIV2,   VID_CTL_36MHz },	/* 18.000MHz */
	{  39325,  40119, VIDC_CTRL_DIV1,   VID_CTL_25MHz },	/* 25.175MHz */
	{  27500,  28055, VIDC_CTRL_DIV1,   VID_CTL_36MHz },	/* 36.000MHz */
#endif
	{ 0, }
};

static struct pixclock *
acornfb_valid_pixrate(u_long pixclock)
{
	u_int i;

	for (i = 0; pixclocks[i].min_clock; i++)
		if (pixclock > pixclocks[i].min_clock &&
		    pixclock < pixclocks[i].max_clock)
			return pixclocks + i;

	return NULL;
}

/* VIDC Rules:
 * hcr  : must be even (interlace, hcr/2 must be even)
 * hswr : must be even
 * hdsr : must be odd
 * hder : must be odd
 *
 * vcr  : must be odd
 * vswr : >= 1
 * vdsr : >= 1
 * vder : >= vdsr
 * if interlaced, then hcr/2 must be even
 */
static void
acornfb_set_timing(struct fb_var_screeninfo *var)
{
	struct pixclock *pclk;
	struct vidc_timing vidc;
	u_int horiz_correction;
	u_int sync_len, display_start, display_end, cycle;
	u_int is_interlaced;
	u_int vid_ctl, vidc_ctl;
	u_int bandwidth;

	memset(&vidc, 0, sizeof(vidc));

	pclk = acornfb_valid_pixrate(var->pixclock);
	vidc_ctl = pclk->vidc_ctl;
	vid_ctl  = pclk->vid_ctl;

	bandwidth = var->pixclock * 8 / var->bits_per_pixel;
	/* 25.175, 4bpp = 79.444ns per byte, 317.776ns per word: fifo = 2,6 */
	if (bandwidth > 143500)
		vidc_ctl |= VIDC_CTRL_FIFO_3_7;
	else if (bandwidth > 71750)
		vidc_ctl |= VIDC_CTRL_FIFO_2_6;
	else if (bandwidth > 35875)
		vidc_ctl |= VIDC_CTRL_FIFO_1_5;
	else
		vidc_ctl |= VIDC_CTRL_FIFO_0_4;

	switch (var->bits_per_pixel) {
	case 1:
		horiz_correction = 19;
		vidc_ctl |= VIDC_CTRL_1BPP;
		break;

	case 2:
		horiz_correction = 11;
		vidc_ctl |= VIDC_CTRL_2BPP;
		break;

	case 4:
		horiz_correction = 7;
		vidc_ctl |= VIDC_CTRL_4BPP;
		break;

	default:
	case 8:
		horiz_correction = 5;
		vidc_ctl |= VIDC_CTRL_8BPP;
		break;
	}

	if (!(var->sync & FB_SYNC_HOR_HIGH_ACT))
		vid_ctl |= VID_CTL_HS_NHSYNC;

	if (!(var->sync & FB_SYNC_VERT_HIGH_ACT))
		vid_ctl |= VID_CTL_VS_NVSYNC;

	sync_len	= var->hsync_len;
	display_start	= sync_len + var->left_margin;
	display_end	= display_start + var->xres;
	cycle		= display_end + var->right_margin;

	/* if interlaced, then hcr/2 must be even */
	is_interlaced = (var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED;

	if (is_interlaced) {
		vidc_ctl |= VIDC_CTRL_INTERLACE;
		if (cycle & 2) {
			cycle += 2;
			var->right_margin += 2;
		}
	}

	vidc.h_cycle		= (cycle - 2) / 2;
	vidc.h_sync_width	= (sync_len - 2) / 2;
	vidc.h_border_start	= (display_start - 1) / 2;
	vidc.h_display_start	= (display_start - horiz_correction) / 2;
	vidc.h_display_end	= (display_end - horiz_correction) / 2;
	vidc.h_border_end	= (display_end - 1) / 2;
	vidc.h_interlace	= (vidc.h_cycle + 1) / 2;

	sync_len	= var->vsync_len;
	display_start	= sync_len + var->upper_margin;
	display_end	= display_start + var->yres;
	cycle		= display_end + var->lower_margin;

	if (is_interlaced)
		cycle = (cycle - 3) / 2;
	else
		cycle = cycle - 1;

	vidc.v_cycle		= cycle;
	vidc.v_sync_width	= sync_len - 1;
	vidc.v_border_start	= display_start - 1;
	vidc.v_display_start	= vidc.v_border_start;
	vidc.v_display_end	= display_end - 1;
	vidc.v_border_end	= vidc.v_display_end;

#ifdef CONFIG_ARCH_A5K
	outb(vid_ctl, IOEB_VID_CTL);
#endif
	if (memcmp(&current_vidc, &vidc, sizeof(vidc))) {
		current_vidc = vidc;

		outl(0xe0000000 | vidc_ctl,			IO_VIDC_BASE);
		outl(0x80000000 | (vidc.h_cycle << 14),		IO_VIDC_BASE);
		outl(0x84000000 | (vidc.h_sync_width << 14),	IO_VIDC_BASE);
		outl(0x88000000 | (vidc.h_border_start << 14),	IO_VIDC_BASE);
		outl(0x8c000000 | (vidc.h_display_start << 14),	IO_VIDC_BASE);
		outl(0x90000000 | (vidc.h_display_end << 14),	IO_VIDC_BASE);
		outl(0x94000000 | (vidc.h_border_end << 14),	IO_VIDC_BASE);
		outl(0x98000000,				IO_VIDC_BASE);
		outl(0x9c000000 | (vidc.h_interlace << 14),	IO_VIDC_BASE);
		outl(0xa0000000 | (vidc.v_cycle << 14),		IO_VIDC_BASE);
		outl(0xa4000000 | (vidc.v_sync_width << 14),	IO_VIDC_BASE);
		outl(0xa8000000 | (vidc.v_border_start << 14),	IO_VIDC_BASE);
		outl(0xac000000 | (vidc.v_display_start << 14),	IO_VIDC_BASE);
		outl(0xb0000000 | (vidc.v_display_end << 14),	IO_VIDC_BASE);
		outl(0xb4000000 | (vidc.v_border_end << 14),	IO_VIDC_BASE);
		outl(0xb8000000,				IO_VIDC_BASE);
		outl(0xbc000000,				IO_VIDC_BASE);
	}
#ifdef DEBUG_MODE_SELECTION
	printk(KERN_DEBUG "VIDC registers for %dx%dx%d:\n", var->xres,
	       var->yres, var->bits_per_pixel);
	printk(KERN_DEBUG " H-cycle          : %d\n", vidc.h_cycle);
	printk(KERN_DEBUG " H-sync-width     : %d\n", vidc.h_sync_width);
	printk(KERN_DEBUG " H-border-start   : %d\n", vidc.h_border_start);
	printk(KERN_DEBUG " H-display-start  : %d\n", vidc.h_display_start);
	printk(KERN_DEBUG " H-display-end    : %d\n", vidc.h_display_end);
	printk(KERN_DEBUG " H-border-end     : %d\n", vidc.h_border_end);
	printk(KERN_DEBUG " H-interlace      : %d\n", vidc.h_interlace);
	printk(KERN_DEBUG " V-cycle          : %d\n", vidc.v_cycle);
	printk(KERN_DEBUG " V-sync-width     : %d\n", vidc.v_sync_width);
	printk(KERN_DEBUG " V-border-start   : %d\n", vidc.v_border_start);
	printk(KERN_DEBUG " V-display-start  : %d\n", vidc.v_display_start);
	printk(KERN_DEBUG " V-display-end    : %d\n", vidc.v_display_end);
	printk(KERN_DEBUG " V-border-end     : %d\n", vidc.v_border_end);
	printk(KERN_DEBUG " VIDC Ctrl (E)    : 0x%08X\n", vidc_ctl);
	printk(KERN_DEBUG " IOEB Ctrl        : 0x%08X\n", vid_ctl);
#endif
}

static inline void
acornfb_palette_write(u_int regno, union palette pal)
{
	outl(pal.p, IO_VIDC_BASE);
}

static inline union palette
acornfb_palette_encode(u_int regno, u_int red, u_int green, u_int blue,
		       u_int trans)
{
	union palette pal;

	pal.p = 0;
	pal.vidc.reg   = regno;
	pal.vidc.red   = red >> 12;
	pal.vidc.green = green >> 12;
	pal.vidc.blue  = blue >> 12;
	return pal;
}

static void
acornfb_palette_decode(u_int regno, u_int *red, u_int *green, u_int *blue,
		       u_int *trans)
{
	*red   = EXTEND4(current_par.palette[regno].vidc.red);
	*green = EXTEND4(current_par.palette[regno].vidc.green);
	*blue  = EXTEND4(current_par.palette[regno].vidc.blue);
	*trans = current_par.palette[regno].vidc.trans ? -1 : 0;
}
#endif

#ifdef HAS_VIDC20
#include <asm/arch/acornfb.h>

/* VIDC20 has a different set of rules from the VIDC:
 *  hcr  : must be multiple of 4
 *  hswr : must be even
 *  hdsr : must be even
 *  hder : must be even
 *  vcr  : >= 2, (interlace, must be odd)
 *  vswr : >= 1
 *  vdsr : >= 1
 *  vder : >= vdsr
 */
static void
acornfb_set_timing(struct fb_var_screeninfo *var)
{
	struct vidc_timing vidc;
	u_int vcr, fsize;
	u_int ext_ctl, dat_ctl;
	u_int words_per_line;

	memset(&vidc, 0, sizeof(vidc));

	vidc.h_sync_width	= var->hsync_len - 8;
	vidc.h_border_start	= vidc.h_sync_width + var->left_margin + 8 - 12;
	vidc.h_display_start	= vidc.h_border_start + 12 - 18;
	vidc.h_display_end	= vidc.h_display_start + var->xres;
	vidc.h_border_end	= vidc.h_display_end + 18 - 12;
	vidc.h_cycle		= vidc.h_border_end + var->right_margin + 12 - 8;
	vidc.h_interlace	= vidc.h_cycle / 2;
	vidc.v_sync_width	= var->vsync_len - 1;
	vidc.v_border_start	= vidc.v_sync_width + var->upper_margin;
	vidc.v_display_start	= vidc.v_border_start;
	vidc.v_display_end	= vidc.v_display_start + var->yres;
	vidc.v_border_end	= vidc.v_display_end;
	vidc.control		= acornfb_default_control();

	vcr = var->vsync_len + var->upper_margin + var->yres +
	      var->lower_margin;

	if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
		vidc.v_cycle = (vcr - 3) / 2;
		vidc.control |= VIDC20_CTRL_INT;
	} else
		vidc.v_cycle = vcr - 2;

	switch (var->bits_per_pixel) {
	case  1: vidc.control |= VIDC20_CTRL_1BPP;	break;
	case  2: vidc.control |= VIDC20_CTRL_2BPP;	break;
	case  4: vidc.control |= VIDC20_CTRL_4BPP;	break;
	default:
	case  8: vidc.control |= VIDC20_CTRL_8BPP;	break;
	case 16: vidc.control |= VIDC20_CTRL_16BPP;	break;
	case 32: vidc.control |= VIDC20_CTRL_32BPP;	break;
	}

	acornfb_vidc20_find_rates(&vidc, var);
	fsize = var->vsync_len + var->upper_margin + var->lower_margin - 1;

	if (memcmp(&current_vidc, &vidc, sizeof(vidc))) {
		current_vidc = vidc;

		outl(VIDC20_CTRL| vidc.control,		IO_VIDC_BASE);
		outl(0xd0000000 | vidc.pll_ctl,		IO_VIDC_BASE);
		outl(0x80000000 | vidc.h_cycle,		IO_VIDC_BASE);
		outl(0x81000000 | vidc.h_sync_width,	IO_VIDC_BASE);
		outl(0x82000000 | vidc.h_border_start,	IO_VIDC_BASE);
		outl(0x83000000 | vidc.h_display_start,	IO_VIDC_BASE);
		outl(0x84000000 | vidc.h_display_end,	IO_VIDC_BASE);
		outl(0x85000000 | vidc.h_border_end,	IO_VIDC_BASE);
		outl(0x86000000,			IO_VIDC_BASE);
		outl(0x87000000 | vidc.h_interlace,	IO_VIDC_BASE);
		outl(0x90000000 | vidc.v_cycle,		IO_VIDC_BASE);
		outl(0x91000000 | vidc.v_sync_width,	IO_VIDC_BASE);
		outl(0x92000000 | vidc.v_border_start,	IO_VIDC_BASE);
		outl(0x93000000 | vidc.v_display_start,	IO_VIDC_BASE);
		outl(0x94000000 | vidc.v_display_end,	IO_VIDC_BASE);
		outl(0x95000000 | vidc.v_border_end,	IO_VIDC_BASE);
		outl(0x96000000,			IO_VIDC_BASE);
		outl(0x97000000,			IO_VIDC_BASE);
	}

	outl(fsize, IOMD_FSIZE);

	ext_ctl = acornfb_default_econtrol();

	if (var->sync & FB_SYNC_COMP_HIGH_ACT) /* should be FB_SYNC_COMP */
		ext_ctl |= VIDC20_ECTL_HS_NCSYNC | VIDC20_ECTL_VS_NCSYNC;
	else {
		if (var->sync & FB_SYNC_HOR_HIGH_ACT)
			ext_ctl |= VIDC20_ECTL_HS_HSYNC;
		else
			ext_ctl |= VIDC20_ECTL_HS_NHSYNC;

		if (var->sync & FB_SYNC_VERT_HIGH_ACT)
			ext_ctl |= VIDC20_ECTL_VS_VSYNC;
		else
			ext_ctl |= VIDC20_ECTL_VS_NVSYNC;
	}

	outl(VIDC20_ECTL | ext_ctl, IO_VIDC_BASE);

	words_per_line = var->xres * var->bits_per_pixel / 32;

	if (current_par.using_vram && current_par.screen_size == 2048*1024)
		words_per_line /= 2;

	/* RiscPC doesn't use the VIDC's VRAM control. */
	dat_ctl = VIDC20_DCTL_VRAM_DIS | VIDC20_DCTL_SNA | words_per_line;

	/* The data bus width is dependent on both the type
	 * and amount of video memory.
	 *     DRAM	32bit low
	 * 1MB VRAM	32bit
	 * 2MB VRAM	64bit
	 */
	if (current_par.using_vram && current_par.vram_half_sam == 2048) {
		dat_ctl |= VIDC20_DCTL_BUS_D63_0;
	} else 
		dat_ctl |= VIDC20_DCTL_BUS_D31_0;

	outl(VIDC20_DCTL | dat_ctl, IO_VIDC_BASE);

#ifdef DEBUG_MODE_SELECTION
	printk(KERN_DEBUG "VIDC registers for %dx%dx%d:\n", var->xres,
	       var->yres, var->bits_per_pixel);
	printk(KERN_DEBUG " H-cycle          : %d\n", vidc.h_cycle);
	printk(KERN_DEBUG " H-sync-width     : %d\n", vidc.h_sync_width);
	printk(KERN_DEBUG " H-border-start   : %d\n", vidc.h_border_start);
	printk(KERN_DEBUG " H-display-start  : %d\n", vidc.h_display_start);
	printk(KERN_DEBUG " H-display-end    : %d\n", vidc.h_display_end);
	printk(KERN_DEBUG " H-border-end     : %d\n", vidc.h_border_end);
	printk(KERN_DEBUG " H-interlace      : %d\n", vidc.h_interlace);
	printk(KERN_DEBUG " V-cycle          : %d\n", vidc.v_cycle);
	printk(KERN_DEBUG " V-sync-width     : %d\n", vidc.v_sync_width);
	printk(KERN_DEBUG " V-border-start   : %d\n", vidc.v_border_start);
	printk(KERN_DEBUG " V-display-start  : %d\n", vidc.v_display_start);
	printk(KERN_DEBUG " V-display-end    : %d\n", vidc.v_display_end);
	printk(KERN_DEBUG " V-border-end     : %d\n", vidc.v_border_end);
	printk(KERN_DEBUG " Ext Ctrl  (C)    : 0x%08X\n", ext_ctl);
	printk(KERN_DEBUG " PLL Ctrl  (D)    : 0x%08X\n", vidc.pll_ctl);
	printk(KERN_DEBUG " Ctrl      (E)    : 0x%08X\n", vidc.control);
	printk(KERN_DEBUG " Data Ctrl (F)    : 0x%08X\n", dat_ctl);
	printk(KERN_DEBUG " Fsize            : 0x%08X\n", fsize);
#endif
}

static inline void
acornfb_palette_write(u_int regno, union palette pal)
{
	outl(0x10000000 | regno, IO_VIDC_BASE);
	outl(pal.p, IO_VIDC_BASE);
}

static inline union palette
acornfb_palette_encode(u_int regno, u_int red, u_int green, u_int blue,
		       u_int trans)
{
	union palette pal;

	pal.p = 0;
	pal.vidc20.red   = red >> 8;
	pal.vidc20.green = green >> 8;
	pal.vidc20.blue  = blue >> 8;
	return pal;
}

static void
acornfb_palette_decode(u_int regno, u_int *red, u_int *green, u_int *blue,
		       u_int *trans)
{
	*red   = EXTEND8(current_par.palette[regno].vidc20.red);
	*green = EXTEND8(current_par.palette[regno].vidc20.green);
	*blue  = EXTEND8(current_par.palette[regno].vidc20.blue);
	*trans = EXTEND4(current_par.palette[regno].vidc20.ext);
}
#endif

/*
 * Before selecting the timing parameters, adjust
 * the resolution to fit the rules.
 */
static int
acornfb_adjust_timing(struct fb_var_screeninfo *var, int con)
{
	u_int font_line_len;
	u_int fontht;
	u_int sam_size, min_size, size;
	u_int nr_y;

	/* xres must be even */
	var->xres = (var->xres + 1) & ~1;

	/*
	 * We don't allow xres_virtual to differ from xres
	 */
	var->xres_virtual = var->xres;
	var->xoffset = 0;

	/*
	 * Find the font height
	 */
	if (con == -1)
		fontht = fontheight(&global_disp);
	else
		fontht = fontheight(fb_display + con);

	if (fontht == 0)
		fontht = 8;

	if (current_par.using_vram)
		sam_size = current_par.vram_half_sam * 2;
	else
		sam_size = 16;

	/*
	 * Now, find a value for yres_virtual which allows
	 * us to do ywrap scrolling.  The value of
	 * yres_virtual must be such that the end of the
	 * displayable frame buffer must be aligned with
	 * the start of a font line.
	 */
	font_line_len = var->xres * var->bits_per_pixel * fontht / 8;
	min_size = var->xres * var->yres * var->bits_per_pixel / 8;

	/*
	 * If minimum screen size is greater than that we have
	 * available, reject it.
	 */
	if (min_size > current_par.screen_size)
		return -EINVAL;

	/* Find int 'y', such that y * fll == s * sam < maxsize
	 * y = s * sam / fll; s = maxsize / sam
	 */
	for (size = current_par.screen_size; min_size <= size;
	     size -= sam_size) {
		nr_y = size / font_line_len;

		if (nr_y * font_line_len == size)
			break;
	}

	if (var->accel_flags & FB_ACCELF_TEXT) {
		if (min_size > size) {
			/*
			 * failed, use ypan
			 */
			size = current_par.screen_size;
			var->yres_virtual = size / (font_line_len / fontht);
		} else
			var->yres_virtual = nr_y * fontht;
	}

	current_par.screen_end = current_par.screen_base_p + size;

	/*
	 * Fix yres & yoffset if needed.
	 */
	if (var->yres > var->yres_virtual)
		var->yres = var->yres_virtual;

	if (var->vmode & FB_VMODE_YWRAP) {
		if (var->yoffset > var->yres_virtual)
			var->yoffset = var->yres_virtual;
	} else {
		if (var->yoffset + var->yres > var->yres_virtual)
			var->yoffset = var->yres_virtual - var->yres;
	}

	/* hsync_len must be even */
	var->hsync_len = (var->hsync_len + 1) & ~1;

#ifdef HAS_VIDC
	/* left_margin must be odd */
	if ((var->left_margin & 1) == 0) {
		var->left_margin -= 1;
		var->right_margin += 1;
	}

	/* right_margin must be odd */
	var->right_margin |= 1;
#elif defined(HAS_VIDC20)
	/* left_margin must be even */
	if (var->left_margin & 1) {
		var->left_margin += 1;
		var->right_margin -= 1;
	}

	/* right_margin must be even */
	if (var->right_margin & 1)
		var->right_margin += 1;
#endif

	if (var->vsync_len < 1)
		var->vsync_len = 1;

	return 0;
}

static int
acornfb_validate_timing(struct fb_var_screeninfo *var,
			struct fb_monspecs *monspecs)
{
	unsigned long hs, vs;

	/*
	 * hs(Hz) = 10^12 / (pixclock * xtotal)
	 * vs(Hz) = hs(Hz) / ytotal
	 *
	 * No need to do long long divisions or anything
	 * like that if you factor it correctly
	 */
	hs = 1953125000 / var->pixclock;
	hs = hs * 512 /
	     (var->xres + var->left_margin + var->right_margin + var->hsync_len);
	vs = hs /
	     (var->yres + var->upper_margin + var->lower_margin + var->vsync_len);

	return (vs >= monspecs->vfmin && vs <= monspecs->vfmax &&
		hs >= monspecs->hfmin && hs <= monspecs->hfmax) ? 0 : -EINVAL;
}

static inline void
acornfb_update_dma(struct fb_var_screeninfo *var)
{
	int off = (var->yoffset * var->xres_virtual *
		   var->bits_per_pixel) >> 3;

#if defined(HAS_MEMC)
	memc_write(VDMA_INIT, off >> 2);
#elif defined(HAS_IOMD)
	outl(current_par.screen_base_p + off, IOMD_VIDINIT);
#endif
}

static int
acornfb_open(struct fb_info *info, int user)
{
	MOD_INC_USE_COUNT;
	return 0;
}

static int
acornfb_release(struct fb_info *info, int user)
{
	MOD_DEC_USE_COUNT;
	return 0;
}

static int
acornfb_getcolreg(u_int regno, u_int *red, u_int *green, u_int *blue,
		  u_int *trans, struct fb_info *info)
{
	if (regno >= current_par.palette_size)
		return 1;

	acornfb_palette_decode(regno, red, green, blue, trans);

	return 0;
}

/*
 * We have to take note of the VIDC20's 16-bit palette here.
 * The VIDC20 looks up a 16 bit pixel as follows:
 *
 *   bits   111111
 *          5432109876543210
 *   red            ++++++++  (8 bits,  7 to 0)
 *  green       ++++++++      (8 bits, 11 to 4)
 *   blue   ++++++++          (8 bits, 15 to 8)
 *
 * We use a pixel which looks like:
 *
 *   bits   111111
 *          5432109876543210
 *   red               +++++  (5 bits,  4 to  0)
 *  green         +++++       (5 bits,  9 to  5)
 *   blue    +++++            (5 bits, 14 to 10)
 */
static int
acornfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
		  u_int trans, struct fb_info *info)
{
	union palette pal;
	int bpp = fb_display[current_par.currcon].var.bits_per_pixel;

	if (regno >= current_par.palette_size)
		return 1;

	pal = acornfb_palette_encode(regno, red, green, blue, trans);
	current_par.palette[regno] = pal;

#ifdef HAS_VIDC20
	if (regno < 16) {
		switch (bpp) {
#ifdef FBCON_HAS_CFB16
		case 16:
			current_par.cmap.cfb16[regno] =
				regno | regno << 5 | regno << 10;
			break;
#endif
#ifdef FBCON_HAS_CFB32
		case 32:
			current_par.cmap.cfb32[regno] =
				regno | regno << 8 | regno << 16;
			break;
#endif
		default:
			break;
		}
	}

#ifdef FBCON_HAS_CFB16
	if (bpp == 16) {
		int i;

		pal.p = 0;
		outl(0x10000000, IO_VIDC_BASE);
		for (i = 0; i < 256; i += 1) {
			pal.vidc20.red   = current_par.palette[ i       & 31].vidc20.red;
			pal.vidc20.green = current_par.palette[(i >> 1) & 31].vidc20.green;
			pal.vidc20.blue  = current_par.palette[(i >> 2) & 31].vidc20.blue;
			outl(pal.p, IO_VIDC_BASE);
			/* Palette register pointer auto-increments */
		}
	} else
#endif
#endif
		acornfb_palette_write(regno, pal);

	return 0;
}

static int
acornfb_get_cmap(struct fb_cmap *cmap, int kspc, int con,
		 struct fb_info *info)
{
	int err = 0;

	if (con == current_par.currcon)
		err = fb_get_cmap(cmap, kspc, acornfb_getcolreg, info);
	else if (fb_display[con].cmap.len)
		fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2);
	else
		fb_copy_cmap(fb_default_cmap(current_par.palette_size),
			     cmap, kspc ? 0 : 2);
	return err;
}

static int
acornfb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
		 struct fb_info *info)
{
	int err = 0;

	if (!fb_display[con].cmap.len)
		err = fb_alloc_cmap(&fb_display[con].cmap,
				    current_par.palette_size, 0);
	if (!err) {
		if (con == current_par.currcon)
			err = fb_set_cmap(cmap, kspc, acornfb_setcolreg,
					  info);
		else
			fb_copy_cmap(cmap, &fb_display[con].cmap,
				     kspc ? 0 : 1);
	}
	return err;
}

static int
acornfb_decode_var(struct fb_var_screeninfo *var, int con, int *visual)
{
	int err;

	switch (var->bits_per_pixel) {
#ifdef FBCON_HAS_MFB
	case 1:
		*visual = FB_VISUAL_MONO10;
		break;
#endif
#ifdef FBCON_HAS_CFB8
	case 8:
#ifdef HAS_VIDC
		*visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
#else
		*visual = FB_VISUAL_PSEUDOCOLOR;
#endif
		break;
#endif
#ifdef FBCON_HAS_CFB4
	case 4:
		*visual = FB_VISUAL_PSEUDOCOLOR;
		break;
#endif
#ifdef FBCON_HAS_CFB2
	case 2:
		*visual = FB_VISUAL_PSEUDOCOLOR;
		break;
#endif
#ifdef HAS_VIDC20
#ifdef FBCON_HAS_CFB16
	case 16:
		*visual = FB_VISUAL_DIRECTCOLOR;
		break;
#endif
#ifdef FBCON_HAS_CFB32
	case 32:
		*visual = FB_VISUAL_TRUECOLOR;
		break;
#endif
#endif
	default:
		return -EINVAL;
	}

	if (!acornfb_valid_pixrate(var->pixclock))
		return -EINVAL;

	/*
	 * Validate and adjust the resolution to
	 * match the video generator hardware.
	 */
	err = acornfb_adjust_timing(var, con);
	if (err)
		return err;

	/*
	 * Validate the timing against the
	 * monitor hardware.
	 */
	err = acornfb_validate_timing(var, &fb_info.monspecs);
	if (err)
		return err;

#if defined(HAS_VIDC20)
	switch (var->bits_per_pixel) {
	case 1: case 2: case 4: case 8:
		var->red.offset    = 0;
		var->red.length    = 8;
		var->green         = var->red;
		var->blue          = var->red;
		var->transp.offset = 0;
		var->transp.length = 4;
		break;

	case 16:
		var->red.offset    = 0;
		var->red.length    = 5;
		var->green.offset  = 5;
		var->green.length  = 5;
		var->blue.offset   = 10;
		var->blue.length   = 5;
		var->transp.offset = 15;
		var->transp.length = 1;
		break;

	case 32:
		var->red.offset    = 0;
		var->red.length    = 8;
		var->green.offset  = 8;
		var->green.length  = 8;
		var->blue.offset   = 16;
		var->blue.length   = 8;
		var->transp.offset = 24;
		var->transp.length = 4;
		break;
	}
#elif defined(HAS_VIDC)
	var->red.length	   = 4;
	var->green         = var->red;
	var->blue          = var->red;
	var->transp.length = 1;
#endif
	return 0;
}

static int
acornfb_get_fix(struct fb_fix_screeninfo *fix, int con, struct fb_info *info)
{
	struct display *display;

	memset(fix, 0, sizeof(struct fb_fix_screeninfo));
	strcpy(fix->id, "Acorn");

	if (con >= 0)
		display = fb_display + con;
	else
		display = &global_disp;

	fix->smem_start	 = current_par.screen_base_p;
	fix->smem_len	 = current_par.screen_size;
	fix->type	 = display->type;
	fix->type_aux	 = display->type_aux;
	fix->xpanstep	 = 0;
	fix->ypanstep	 = display->ypanstep;
	fix->ywrapstep	 = display->ywrapstep;
	fix->visual	 = display->visual;
	fix->line_length = display->line_length;
	fix->accel	 = FB_ACCEL_NONE;

	return 0;
}

static int
acornfb_get_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
{
	if (con == -1) {
		*var = global_disp.var;
	} else
		*var = fb_display[con].var;

	return 0;
}

static int
acornfb_set_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
{
	struct display *display;
	int err, chgvar = 0, visual;

	if (con >= 0)
		display = fb_display + con;
	else
		display = &global_disp;

	if (!current_par.allow_modeset && con != -1)
		return -EINVAL;

	err = acornfb_decode_var(var, con, &visual);
	if (err)
		return err;

	switch (var->activate & FB_ACTIVATE_MASK) {
	case FB_ACTIVATE_TEST:
		return 0;

	case FB_ACTIVATE_NXTOPEN:
	case FB_ACTIVATE_NOW:
		break;

	default:
		return -EINVAL;
	}

	if (con >= 0) {
		if (display->var.xres != var->xres)
			chgvar = 1;
		if (display->var.yres != var->yres)
			chgvar = 1;
		if (display->var.xres_virtual != var->xres_virtual)
			chgvar = 1;
		if (display->var.yres_virtual != var->yres_virtual)
			chgvar = 1;
		if (memcmp(&display->var.red, &var->red, sizeof(var->red)))
			chgvar = 1;
		if (memcmp(&display->var.green, &var->green, sizeof(var->green)))
			chgvar = 1;
		if (memcmp(&display->var.blue, &var->blue, sizeof(var->blue)))
			chgvar = 1;
	}

	display->var = *var;
	display->var.activate &= ~FB_ACTIVATE_ALL;

	if (var->activate & FB_ACTIVATE_ALL)
		global_disp.var = display->var;

	display->screen_base	= (char *)current_par.screen_base;
	display->visual		= visual;
	display->type		= FB_TYPE_PACKED_PIXELS;
	display->type_aux	= 0;
	display->ypanstep	= 1;
	display->ywrapstep	= 1;
	display->line_length	=
	display->next_line      = (var->xres * var->bits_per_pixel) / 8;
	display->can_soft_blank	= visual == FB_VISUAL_PSEUDOCOLOR ? 1 : 0;
	display->inverse	= 0;

	switch (display->var.bits_per_pixel) {
#ifdef FBCON_HAS_MFB
	case 1:
		current_par.palette_size = 2;
		display->dispsw = &fbcon_mfb;
		break;
#endif
#ifdef FBCON_HAS_CFB2
	case 2:
		current_par.palette_size = 4;
		display->dispsw = &fbcon_cfb2;
		break;
#endif
#ifdef FBCON_HAS_CFB4
	case 4:
		current_par.palette_size = 16;
		display->dispsw = &fbcon_cfb4;
		break;
#endif
#ifdef FBCON_HAS_CFB8
	case 8:
		current_par.palette_size = VIDC_PALETTE_SIZE;
		display->dispsw = &fbcon_cfb8;
		break;
#endif
#ifdef FBCON_HAS_CFB16
	case 16:
		current_par.palette_size = 32;
		display->dispsw = &fbcon_cfb16;
		display->dispsw_data = current_par.cmap.cfb16;
		break;
#endif
#ifdef FBCON_HAS_CFB32
	case 32:
		current_par.palette_size = VIDC_PALETTE_SIZE;
		display->dispsw = &fbcon_cfb32;
		display->dispsw_data = current_par.cmap.cfb32;
		break;
#endif
	default:
		display->dispsw = &fbcon_dummy;
		break;
	}

	if (chgvar && info && info->changevar)
		info->changevar(con);

	if (con == current_par.currcon) {
		struct fb_cmap *cmap;
		unsigned long start, size;
		int control;

#if defined(HAS_MEMC)
		start   = 0;
		size    = current_par.screen_size - VDMA_XFERSIZE;
		control = 0;

		memc_write(VDMA_START, start);
		memc_write(VDMA_END, size >> 2);
#elif defined(HAS_IOMD)

		start = current_par.screen_base_p;
		size  = current_par.screen_end;

		if (current_par.using_vram) {
			size -= current_par.vram_half_sam;
			control = DMA_CR_E | (current_par.vram_half_sam / 256);
		} else {
			size -= 16;
			control = DMA_CR_E | DMA_CR_D | 16;
		}

		outl(start,   IOMD_VIDSTART);
		outl(size,    IOMD_VIDEND);
		outl(control, IOMD_VIDCR);
#endif
		acornfb_update_dma(var);

		if (current_par.allow_modeset)
			acornfb_set_timing(var);

		if (display->cmap.len)
			cmap = &display->cmap;
		else
			cmap = fb_default_cmap(current_par.palette_size);

		fb_set_cmap(cmap, 1, acornfb_setcolreg, info);
	}
	return 0;
}

static int
acornfb_pan_display(struct fb_var_screeninfo *var, int con,
		    struct fb_info *info)
{
	u_int y_bottom;

	if (var->xoffset)
		return -EINVAL;

	y_bottom = var->yoffset;

	if (!(var->vmode & FB_VMODE_YWRAP))
		y_bottom += var->yres;

	if (y_bottom > fb_display[con].var.yres_virtual)
		return -EINVAL;

	acornfb_update_dma(var);

	fb_display[con].var.yoffset = var->yoffset;
	if (var->vmode & FB_VMODE_YWRAP)
		fb_display[con].var.vmode |= FB_VMODE_YWRAP;
	else
		fb_display[con].var.vmode &= ~FB_VMODE_YWRAP;

	return 0;
}

static int
acornfb_ioctl(struct inode *ino, struct file *file, unsigned int cmd,
	      unsigned long arg, int con, struct fb_info *info)
{
	return -ENOIOCTLCMD;
}

static struct fb_ops acornfb_ops = {
	acornfb_open,
	acornfb_release,
	acornfb_get_fix,
	acornfb_get_var,
	acornfb_set_var,
	acornfb_get_cmap,
	acornfb_set_cmap,
	acornfb_pan_display,
	acornfb_ioctl
};

static int
acornfb_updatevar(int con, struct fb_info *info)
{
	if (con == current_par.currcon)
		acornfb_update_dma(&fb_display[con].var);

	return 0;
}

static int
acornfb_switch(int con, struct fb_info *info)
{
	struct fb_cmap *cmap;

	if (current_par.currcon >= 0) {
		cmap = &fb_display[current_par.currcon].cmap;

		if (cmap->len)
			fb_get_cmap(cmap, 1, acornfb_getcolreg, info);
	}

	current_par.currcon = con;

	fb_display[con].var.activate = FB_ACTIVATE_NOW;

	acornfb_set_var(&fb_display[con].var, con, info);

	return 0;
}

static void
acornfb_blank(int blank, struct fb_info *info)
{
	union palette p;
	int i, bpp = fb_display[current_par.currcon].var.bits_per_pixel;

	if (bpp != 16) {
		for (i = 0; i < current_par.palette_size; i++) {
			if (blank)
				p = acornfb_palette_encode(i, 0, 0, 0, 0);
			else
				p = current_par.palette[i];

			acornfb_palette_write(i, p);
		}
	}
#ifdef FBCON_HAS_CFB16
	else {
		p.p = 0;

		for (i = 0; i < 256; i++) {
			if (blank)
				p = acornfb_palette_encode(i, 0, 0, 0, 0);
			else {
				p.vidc20.red   = current_par.palette[ i       & 31].vidc20.red;
				p.vidc20.green = current_par.palette[(i >> 1) & 31].vidc20.green;
				p.vidc20.blue  = current_par.palette[(i >> 2) & 31].vidc20.blue;
			}
			acornfb_palette_write(i, current_par.palette[i]);
		}
	}
#endif
}

/*
 * Everything after here is initialisation!!!
 */
static struct fb_videomode modedb[] __initdata = {
	{	/* 320x256 @ 50Hz */
		NULL, 50,  320,  256, 125000,  92,  62,  35, 19,  38, 2,
		FB_SYNC_COMP_HIGH_ACT,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x250 @ 50Hz, 15.6 kHz hsync */
		NULL, 50,  640,  250,  62500, 185, 123,  38, 21,  76, 3,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x256 @ 50Hz, 15.6 kHz hsync */
		NULL, 50,  640,  256,  62500, 185, 123,  35, 18,  76, 3,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x512 @ 50Hz, 26.8 kHz hsync */
		NULL, 50,  640,  512,  41667, 113,  87,  18,  1,  56, 3,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x250 @ 70Hz, 31.5 kHz hsync */
		NULL, 70,  640,  250,  39722,  48,  16, 109, 88,  96, 2,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x256 @ 70Hz, 31.5 kHz hsync */
		NULL, 70,  640,  256,  39722,  48,  16, 106, 85,  96, 2,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x352 @ 70Hz, 31.5 kHz hsync */
		NULL, 70,  640,  352,  39722,  48,  16,  58, 37,  96, 2,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 640x480 @ 60Hz, 31.5 kHz hsync */
		NULL, 60,  640,  480,  39722,  48,  16,  32, 11,  96, 2,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 800x600 @ 56Hz, 35.2 kHz hsync */
		NULL, 56,  800,  600,  27778, 101,  23,  22,  1, 100, 2,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 896x352 @ 60Hz, 21.8 kHz hsync */
		NULL, 60,  896,  352,  41667,  59,  27,   9,  0, 118, 3,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 1024x 768 @ 60Hz, 48.4 kHz hsync */
		NULL, 60, 1024,  768,  15385, 160,  24,  29,  3, 136, 6,
		0,
		FB_VMODE_NONINTERLACED
	}, {	/* 1280x1024 @ 60Hz, 63.8 kHz hsync */
		NULL, 60, 1280, 1024,   9090, 186,  96,  38,  1, 160, 3,
		0,
		FB_VMODE_NONINTERLACED
	}
};

static struct fb_videomode __initdata
acornfb_default_mode = {
	name:		NULL,
	refresh:	60,
	xres:		640,
	yres:		480,
	pixclock:	39722,
	left_margin:	56,
	right_margin:	16,
	upper_margin:	34,
	lower_margin:	9,
	hsync_len:	88,
	vsync_len:	2,
	sync:		0,
	vmode:		FB_VMODE_NONINTERLACED
};

static void __init
acornfb_init_fbinfo(void)
{
	static int first = 1;

	if (!first)
		return;
	first = 0;

	strcpy(fb_info.modename, "Acorn");
	strcpy(fb_info.fontname, "Acorn8x8");

	fb_info.node		   = -1;
	fb_info.fbops		   = &acornfb_ops;
	fb_info.disp		   = &global_disp;
	fb_info.changevar	   = NULL;
	fb_info.switch_con	   = acornfb_switch;
	fb_info.updatevar	   = acornfb_updatevar;
	fb_info.blank		   = acornfb_blank;
	fb_info.flags		   = FBINFO_FLAG_DEFAULT;

	global_disp.dispsw	   = &fbcon_dummy;

	/*
	 * setup initial parameters
	 */
	memset(&init_var, 0, sizeof(init_var));

#if defined(HAS_VIDC20)
	init_var.red.length	   = 8;
	init_var.transp.length	   = 4;
#elif defined(HAS_VIDC)
	init_var.red.length	   = 4;
	init_var.transp.length	   = 1;
#endif
	init_var.green		   = init_var.red;
	init_var.blue		   = init_var.red;
	init_var.nonstd		   = 0;
	init_var.activate	   = FB_ACTIVATE_NOW;
	init_var.height		   = -1;
	init_var.width		   = -1;
	init_var.vmode		   = FB_VMODE_NONINTERLACED;
	init_var.accel_flags	   = FB_ACCELF_TEXT;

	current_par.dram_size	   = 0;
	current_par.montype	   = -1;
	current_par.dpms	   = 0;
}

/*
 * setup acornfb options:
 *
 *  font:fontname
 *	Set fontname
 *
 *  mon:hmin-hmax:vmin-vmax:dpms:width:height
 *	Set monitor parameters:
 *		hmin   = horizontal minimum frequency (Hz)
 *		hmax   = horizontal maximum frequency (Hz)	(optional)
 *		vmin   = vertical minimum frequency (Hz)
 *		vmax   = vertical maximum frequency (Hz)	(optional)
 *		dpms   = DPMS supported?			(optional)
 *		width  = width of picture in mm.		(optional)
 *		height = height of picture in mm.		(optional)
 *
 * montype:type
 *	Set RISC-OS style monitor type:
 *		0 (or tv)	- TV frequency
 *		1 (or multi)	- Multi frequency
 *		2 (or hires)	- Hi-res monochrome
 *		3 (or vga)	- VGA
 *		4 (or svga)	- SVGA
 *		auto, or option missing
 *				- try hardware detect
 *
 * dram:size
 *	Set the amount of DRAM to use for the frame buffer
 *	(even if you have VRAM).
 *	size can optionally be followed by 'M' or 'K' for
 *	MB or KB respectively.
 */
static void __init
acornfb_parse_font(char *opt)
{
	strcpy(fb_info.fontname, opt);
}

static void __init
acornfb_parse_mon(char *opt)
{
	current_par.montype = -2;

	fb_info.monspecs.hfmin = simple_strtoul(opt, &opt, 0);
	if (*opt == '-')
		fb_info.monspecs.hfmax = simple_strtoul(opt + 1, &opt, 0);
	else
		fb_info.monspecs.hfmax = fb_info.monspecs.hfmin;

	if (*opt != ':')
		return;

	fb_info.monspecs.vfmin = simple_strtoul(opt + 1, &opt, 0);
	if (*opt == '-')
		fb_info.monspecs.vfmax = simple_strtoul(opt + 1, &opt, 0);
	else
		fb_info.monspecs.vfmax = fb_info.monspecs.vfmin;

	if (*opt != ':')
		return;

	fb_info.monspecs.dpms = simple_strtoul(opt + 1, &opt, 0);

	if (*opt != ':')
		return;

	init_var.width = simple_strtoul(opt + 1, &opt, 0);

	if (*opt != ':')
		return;

	init_var.height = simple_strtoul(opt + 1, NULL, 0);
}

static void __init
acornfb_parse_montype(char *opt)
{
	current_par.montype = -2;

	if (strncmp(opt, "tv", 2) == 0) {
		opt += 2;
		current_par.montype = 0;
	} else if (strncmp(opt, "multi", 5) == 0) {
		opt += 5;
		current_par.montype = 1;
	} else if (strncmp(opt, "hires", 5) == 0) {
		opt += 5;
		current_par.montype = 2;
	} else if (strncmp(opt, "vga", 3) == 0) {
		opt += 3;
		current_par.montype = 3;
	} else if (strncmp(opt, "svga", 4) == 0) {
		opt += 4;
		current_par.montype = 4;
	} else if (strncmp(opt, "auto", 4) == 0) {
		opt += 4;
		current_par.montype = -1;
	} else if (isdigit(*opt))
		current_par.montype = simple_strtoul(opt, &opt, 0);

	if (current_par.montype == -2 ||
	    current_par.montype > NR_MONTYPES) {
		printk(KERN_ERR "acornfb: unknown monitor type: %s\n",
			opt);
		current_par.montype = -1;
	} else
	if (opt && *opt) {
		if (strcmp(opt, ",dpms") == 0)
			current_par.dpms = 1;
		else
			printk(KERN_ERR
			       "acornfb: unknown monitor option: %s\n",
			       opt);
	}
}

static void __init
acornfb_parse_dram(char *opt)
{
	unsigned int size;

	size = simple_strtoul(opt, &opt, 0);

	if (opt) {
		switch (*opt) {
		case 'M':
		case 'm':
			size *= 1024;
		case 'K':
		case 'k':
			size *= 1024;
		default:
			break;
		}
	}

	current_par.dram_size = size;
}

static struct options {
	char *name;
	void (*parse)(char *opt);
} opt_table[] __initdata = {
	{ "font",    acornfb_parse_font    },
	{ "mon",     acornfb_parse_mon     },
	{ "montype", acornfb_parse_montype },
	{ "dram",    acornfb_parse_dram    },
	{ NULL, NULL }
};

int __init
acornfb_setup(char *options)
{
	struct options *optp;
	char *opt;

	if (!options || !*options)
		return 0;

	acornfb_init_fbinfo();

	for (opt = strtok(options, ","); opt; opt = strtok(NULL, ",")) {
		if (!*opt)
			continue;

		for (optp = opt_table; optp->name; optp++) {
			int optlen;

			optlen = strlen(optp->name);

			if (strncmp(opt, optp->name, optlen) == 0 &&
			    opt[optlen] == ':') {
				optp->parse(opt + optlen + 1);
				break;
			}
		}

		if (!optp->name)
			printk(KERN_ERR "acornfb: unknown parameter: %s\n",
			       opt);
	}
	return 0;
}

/*
 * Detect type of monitor connected
 *  For now, we just assume SVGA
 */
static int __init
acornfb_detect_monitortype(void)
{
	return 4;
}

/*
 * This enables the unused memory to be freed on older Acorn machines.
 */
static inline void
free_unused_pages(unsigned int virtual_start, unsigned int virtual_end)
{
	int mb_freed = 0;

	/*
	 * Align addresses
	 */
	virtual_start = PAGE_ALIGN(virtual_start);
	virtual_end = PAGE_ALIGN(virtual_end);

	while (virtual_start < virtual_end) {
		/*
		 * Clear page reserved bit,
		 * set count to 1, and free
		 * the page.
		 */
		clear_bit(PG_reserved, &mem_map[MAP_NR(virtual_start)].flags);
		atomic_set(&mem_map[MAP_NR(virtual_start)].count, 1);
		free_page(virtual_start);

		virtual_start += PAGE_SIZE;
		mb_freed += PAGE_SIZE / 1024;
	}

	printk("acornfb: freed %dK memory\n", mb_freed);
}

int __init
acornfb_init(void)
{
	unsigned long size;
	u_int h_sync, v_sync;
	int rc, i;

	acornfb_init_fbinfo();

	if (current_par.montype == -1)
		current_par.montype = acornfb_detect_monitortype();

	if (current_par.montype == -1 || current_par.montype > NR_MONTYPES)
		current_par.montype = 4;

	if (current_par.montype > 0)
		fb_info.monspecs = monspecs[current_par.montype];
	fb_info.monspecs.dpms = current_par.dpms;

	/*
	 * Try to select a suitable default mode
	 */
	for (i = 0; i < sizeof(modedb) / sizeof(*modedb); i++) {
		unsigned long hs;

		hs = modedb[i].refresh *
		     (modedb[i].yres + modedb[i].upper_margin +
		      modedb[i].lower_margin + modedb[i].vsync_len);
		if (modedb[i].xres == DEFAULT_XRES &&
		    modedb[i].yres == DEFAULT_YRES &&
		    modedb[i].refresh >= fb_info.monspecs.vfmin &&
		    modedb[i].refresh <= fb_info.monspecs.vfmax &&
		    hs                >= fb_info.monspecs.hfmin &&
		    hs                <= fb_info.monspecs.hfmax) {
			acornfb_default_mode = modedb[i];
			break;
		}
	}

	current_par.currcon	   = -1;
	current_par.screen_base	   = SCREEN2_BASE;
	current_par.screen_base_p  = SCREEN_START;
	current_par.using_vram     = 0;

	/*
	 * If vram_size is set, we are using VRAM in
	 * a Risc PC.  However, if the user has specified
	 * an amount of DRAM then use that instead.
	 */
	if (vram_size && !current_par.dram_size) {
		size = vram_size;
		current_par.vram_half_sam = vram_size / 1024;
		current_par.using_vram = 1;
	} else if (current_par.dram_size)
		size = current_par.dram_size;
	else
		size = (init_var.xres * init_var.yres *
			init_var.bits_per_pixel) / 8;

	size = PAGE_ALIGN(size);

#if defined(HAS_VIDC20)
	if (!current_par.using_vram) {
		/*
		 * RiscPC needs to allocate the DRAM memory
		 * for the framebuffer if we are not using
		 * VRAM.  Archimedes/A5000 machines use a
		 * fixed address for their framebuffers.
		 */
		int order = 0;
		unsigned long page, top;
		while (size > (PAGE_SIZE * (1 << order)))
			order++;
		current_par.screen_base = __get_free_pages(GFP_KERNEL, order);
		if (current_par.screen_base == 0) {
			printk(KERN_ERR "acornfb: unable to allocate screen "
			       "memory\n");
			return -ENOMEM;
		}
		top = current_par.screen_base + (PAGE_SIZE * (1 << order));
		/* Mark the framebuffer pages as reserved so mmap will work. */
		for (page = current_par.screen_base; 
		     page < PAGE_ALIGN(current_par.screen_base + size);
		     page += PAGE_SIZE)
			mem_map[MAP_NR(page)].flags |= (1 << PG_reserved);
		/* Hand back any excess pages that we allocated. */
		for (page = current_par.screen_base + size; page < top; page += PAGE_SIZE)
			free_page(page);
		current_par.screen_base_p =
			virt_to_phys((void *)current_par.screen_base);
	}
#endif
#if defined(HAS_VIDC)
#define MAX_SIZE	480*1024
	/*
	 * Limit maximum screen size.
	 */
	if (size > MAX_SIZE)
		size = MAX_SIZE;

	/*
	 * Free unused pages
	 */
	free_unused_pages(PAGE_OFFSET + size, PAGE_OFFSET + MAX_SIZE);
#endif
	
	current_par.screen_size	   = size;
	current_par.palette_size   = VIDC_PALETTE_SIZE;
	current_par.allow_modeset  = 1;

	/*
	 * Lookup the timing for this resolution.  If we can't
	 * find it, then we can't restore it if we change
	 * the resolution, so we disable this feature.
	 */
	rc = fb_find_mode(&init_var, &fb_info, NULL, modedb,
			 sizeof(modedb) / sizeof(*modedb),
			 &acornfb_default_mode, DEFAULT_BPP);

	if (!rc && fb_find_mode(&init_var, &fb_info, NULL, NULL, 0,
				&acornfb_default_mode, DEFAULT_BPP)) {
		printk("Acornfb: no valid mode found\n");
	}

	/*
	 * Again, if this does not succeed, then we disallow
	 * changes to the resolution parameters.
	 */
	if (acornfb_set_var(&init_var, -1, &fb_info))
		current_par.allow_modeset = 0;

	h_sync = 1953125000 / init_var.pixclock;
	h_sync = h_sync * 512 / (init_var.xres + init_var.left_margin +
		 init_var.right_margin + init_var.hsync_len);
	v_sync = h_sync / (init_var.yres + init_var.upper_margin +
		 init_var.lower_margin + init_var.vsync_len);

	printk("Acornfb: %ldkB %cRAM, %s, using %dx%d, %d.%03dkHz, %dHz\n",
		current_par.screen_size / 1024,
		current_par.using_vram ? 'V' : 'D',
		VIDC_NAME, init_var.xres, init_var.yres,
		h_sync / 1000, h_sync % 1000, v_sync);

	if (register_framebuffer(&fb_info) < 0)
		return -EINVAL;
	return 0;
}
