162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * A framebuffer driver for VBE 2.0+ compliant video cards
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2007 Michal Januszewski <spock@gentoo.org>
662306a36Sopenharmony_ci *     Loosely based upon the vesafb driver.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/moduleparam.h>
1562306a36Sopenharmony_ci#include <linux/skbuff.h>
1662306a36Sopenharmony_ci#include <linux/timer.h>
1762306a36Sopenharmony_ci#include <linux/completion.h>
1862306a36Sopenharmony_ci#include <linux/connector.h>
1962306a36Sopenharmony_ci#include <linux/random.h>
2062306a36Sopenharmony_ci#include <linux/platform_device.h>
2162306a36Sopenharmony_ci#include <linux/limits.h>
2262306a36Sopenharmony_ci#include <linux/fb.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <linux/mutex.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci#include <video/edid.h>
2762306a36Sopenharmony_ci#include <video/uvesafb.h>
2862306a36Sopenharmony_ci#ifdef CONFIG_X86
2962306a36Sopenharmony_ci#include <video/vga.h>
3062306a36Sopenharmony_ci#endif
3162306a36Sopenharmony_ci#include "edid.h"
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct cb_id uvesafb_cn_id = {
3462306a36Sopenharmony_ci	.idx = CN_IDX_V86D,
3562306a36Sopenharmony_ci	.val = CN_VAL_V86D_UVESAFB
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_cistatic char v86d_path[PATH_MAX] = "/sbin/v86d";
3862306a36Sopenharmony_cistatic char v86d_started;	/* has v86d been started by uvesafb? */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic const struct fb_fix_screeninfo uvesafb_fix = {
4162306a36Sopenharmony_ci	.id	= "VESA VGA",
4262306a36Sopenharmony_ci	.type	= FB_TYPE_PACKED_PIXELS,
4362306a36Sopenharmony_ci	.accel	= FB_ACCEL_NONE,
4462306a36Sopenharmony_ci	.visual = FB_VISUAL_TRUECOLOR,
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int mtrr		= 3;	/* enable mtrr by default */
4862306a36Sopenharmony_cistatic bool blank	= true;	/* enable blanking by default */
4962306a36Sopenharmony_cistatic int ypan		= 1;	/* 0: scroll, 1: ypan, 2: ywrap */
5062306a36Sopenharmony_cistatic bool pmi_setpal	= true; /* use PMI for palette changes */
5162306a36Sopenharmony_cistatic bool nocrtc;		/* ignore CRTC settings */
5262306a36Sopenharmony_cistatic bool noedid;		/* don't try DDC transfers */
5362306a36Sopenharmony_cistatic int vram_remap;		/* set amt. of memory to be used */
5462306a36Sopenharmony_cistatic int vram_total;		/* set total amount of memory */
5562306a36Sopenharmony_cistatic u16 maxclk;		/* maximum pixel clock */
5662306a36Sopenharmony_cistatic u16 maxvf;		/* maximum vertical frequency */
5762306a36Sopenharmony_cistatic u16 maxhf;		/* maximum horizontal frequency */
5862306a36Sopenharmony_cistatic u16 vbemode;		/* force use of a specific VBE mode */
5962306a36Sopenharmony_cistatic char *mode_option;
6062306a36Sopenharmony_cistatic u8  dac_width	= 6;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic struct uvesafb_ktask *uvfb_tasks[UVESAFB_TASKS_MAX];
6362306a36Sopenharmony_cistatic DEFINE_MUTEX(uvfb_lock);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/*
6662306a36Sopenharmony_ci * A handler for replies from userspace.
6762306a36Sopenharmony_ci *
6862306a36Sopenharmony_ci * Make sure each message passes consistency checks and if it does,
6962306a36Sopenharmony_ci * find the kernel part of the task struct, copy the registers and
7062306a36Sopenharmony_ci * the buffer contents and then complete the task.
7162306a36Sopenharmony_ci */
7262306a36Sopenharmony_cistatic void uvesafb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct uvesafb_task *utask;
7562306a36Sopenharmony_ci	struct uvesafb_ktask *task;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
7862306a36Sopenharmony_ci		return;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (msg->seq >= UVESAFB_TASKS_MAX)
8162306a36Sopenharmony_ci		return;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	mutex_lock(&uvfb_lock);
8462306a36Sopenharmony_ci	task = uvfb_tasks[msg->seq];
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (!task || msg->ack != task->ack) {
8762306a36Sopenharmony_ci		mutex_unlock(&uvfb_lock);
8862306a36Sopenharmony_ci		return;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	utask = (struct uvesafb_task *)msg->data;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/* Sanity checks for the buffer length. */
9462306a36Sopenharmony_ci	if (task->t.buf_len < utask->buf_len ||
9562306a36Sopenharmony_ci	    utask->buf_len > msg->len - sizeof(*utask)) {
9662306a36Sopenharmony_ci		mutex_unlock(&uvfb_lock);
9762306a36Sopenharmony_ci		return;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	uvfb_tasks[msg->seq] = NULL;
10162306a36Sopenharmony_ci	mutex_unlock(&uvfb_lock);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	memcpy(&task->t, utask, sizeof(*utask));
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (task->t.buf_len && task->buf)
10662306a36Sopenharmony_ci		memcpy(task->buf, utask + 1, task->t.buf_len);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	complete(task->done);
10962306a36Sopenharmony_ci	return;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int uvesafb_helper_start(void)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	char *envp[] = {
11562306a36Sopenharmony_ci		"HOME=/",
11662306a36Sopenharmony_ci		"PATH=/sbin:/bin",
11762306a36Sopenharmony_ci		NULL,
11862306a36Sopenharmony_ci	};
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	char *argv[] = {
12162306a36Sopenharmony_ci		v86d_path,
12262306a36Sopenharmony_ci		NULL,
12362306a36Sopenharmony_ci	};
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC);
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/*
12962306a36Sopenharmony_ci * Execute a uvesafb task.
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci * Returns 0 if the task is executed successfully.
13262306a36Sopenharmony_ci *
13362306a36Sopenharmony_ci * A message sent to the userspace consists of the uvesafb_task
13462306a36Sopenharmony_ci * struct and (optionally) a buffer. The uvesafb_task struct is
13562306a36Sopenharmony_ci * a simplified version of uvesafb_ktask (its kernel counterpart)
13662306a36Sopenharmony_ci * containing only the register values, flags and the length of
13762306a36Sopenharmony_ci * the buffer.
13862306a36Sopenharmony_ci *
13962306a36Sopenharmony_ci * Each message is assigned a sequence number (increased linearly)
14062306a36Sopenharmony_ci * and a random ack number. The sequence number is used as a key
14162306a36Sopenharmony_ci * for the uvfb_tasks array which holds pointers to uvesafb_ktask
14262306a36Sopenharmony_ci * structs for all requests.
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_cistatic int uvesafb_exec(struct uvesafb_ktask *task)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	static int seq;
14762306a36Sopenharmony_ci	struct cn_msg *m;
14862306a36Sopenharmony_ci	int err;
14962306a36Sopenharmony_ci	int len = sizeof(task->t) + task->t.buf_len;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/*
15262306a36Sopenharmony_ci	 * Check whether the message isn't longer than the maximum
15362306a36Sopenharmony_ci	 * allowed by connector.
15462306a36Sopenharmony_ci	 */
15562306a36Sopenharmony_ci	if (sizeof(*m) + len > CONNECTOR_MAX_MSG_SIZE) {
15662306a36Sopenharmony_ci		pr_warn("message too long (%d), can't execute task\n",
15762306a36Sopenharmony_ci			(int)(sizeof(*m) + len));
15862306a36Sopenharmony_ci		return -E2BIG;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	m = kzalloc(sizeof(*m) + len, GFP_KERNEL);
16262306a36Sopenharmony_ci	if (!m)
16362306a36Sopenharmony_ci		return -ENOMEM;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	init_completion(task->done);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	memcpy(&m->id, &uvesafb_cn_id, sizeof(m->id));
16862306a36Sopenharmony_ci	m->seq = seq;
16962306a36Sopenharmony_ci	m->len = len;
17062306a36Sopenharmony_ci	m->ack = get_random_u32();
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* uvesafb_task structure */
17362306a36Sopenharmony_ci	memcpy(m + 1, &task->t, sizeof(task->t));
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	/* Buffer */
17662306a36Sopenharmony_ci	memcpy((u8 *)(m + 1) + sizeof(task->t), task->buf, task->t.buf_len);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	/*
17962306a36Sopenharmony_ci	 * Save the message ack number so that we can find the kernel
18062306a36Sopenharmony_ci	 * part of this task when a reply is received from userspace.
18162306a36Sopenharmony_ci	 */
18262306a36Sopenharmony_ci	task->ack = m->ack;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	mutex_lock(&uvfb_lock);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/* If all slots are taken -- bail out. */
18762306a36Sopenharmony_ci	if (uvfb_tasks[seq]) {
18862306a36Sopenharmony_ci		mutex_unlock(&uvfb_lock);
18962306a36Sopenharmony_ci		err = -EBUSY;
19062306a36Sopenharmony_ci		goto out;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* Save a pointer to the kernel part of the task struct. */
19462306a36Sopenharmony_ci	uvfb_tasks[seq] = task;
19562306a36Sopenharmony_ci	mutex_unlock(&uvfb_lock);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	err = cn_netlink_send(m, 0, 0, GFP_KERNEL);
19862306a36Sopenharmony_ci	if (err == -ESRCH) {
19962306a36Sopenharmony_ci		/*
20062306a36Sopenharmony_ci		 * Try to start the userspace helper if sending
20162306a36Sopenharmony_ci		 * the request failed the first time.
20262306a36Sopenharmony_ci		 */
20362306a36Sopenharmony_ci		err = uvesafb_helper_start();
20462306a36Sopenharmony_ci		if (err) {
20562306a36Sopenharmony_ci			pr_err("failed to execute %s\n", v86d_path);
20662306a36Sopenharmony_ci			pr_err("make sure that the v86d helper is installed and executable\n");
20762306a36Sopenharmony_ci		} else {
20862306a36Sopenharmony_ci			v86d_started = 1;
20962306a36Sopenharmony_ci			err = cn_netlink_send(m, 0, 0, gfp_any());
21062306a36Sopenharmony_ci			if (err == -ENOBUFS)
21162306a36Sopenharmony_ci				err = 0;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci	} else if (err == -ENOBUFS)
21462306a36Sopenharmony_ci		err = 0;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (!err && !(task->t.flags & TF_EXIT))
21762306a36Sopenharmony_ci		err = !wait_for_completion_timeout(task->done,
21862306a36Sopenharmony_ci				msecs_to_jiffies(UVESAFB_TIMEOUT));
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	mutex_lock(&uvfb_lock);
22162306a36Sopenharmony_ci	uvfb_tasks[seq] = NULL;
22262306a36Sopenharmony_ci	mutex_unlock(&uvfb_lock);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	seq++;
22562306a36Sopenharmony_ci	if (seq >= UVESAFB_TASKS_MAX)
22662306a36Sopenharmony_ci		seq = 0;
22762306a36Sopenharmony_ciout:
22862306a36Sopenharmony_ci	kfree(m);
22962306a36Sopenharmony_ci	return err;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci/*
23362306a36Sopenharmony_ci * Free a uvesafb_ktask struct.
23462306a36Sopenharmony_ci */
23562306a36Sopenharmony_cistatic void uvesafb_free(struct uvesafb_ktask *task)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	if (task) {
23862306a36Sopenharmony_ci		kfree(task->done);
23962306a36Sopenharmony_ci		kfree(task);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci/*
24462306a36Sopenharmony_ci * Prepare a uvesafb_ktask struct to be used again.
24562306a36Sopenharmony_ci */
24662306a36Sopenharmony_cistatic void uvesafb_reset(struct uvesafb_ktask *task)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct completion *cpl = task->done;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	memset(task, 0, sizeof(*task));
25162306a36Sopenharmony_ci	task->done = cpl;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci/*
25562306a36Sopenharmony_ci * Allocate and prepare a uvesafb_ktask struct.
25662306a36Sopenharmony_ci */
25762306a36Sopenharmony_cistatic struct uvesafb_ktask *uvesafb_prep(void)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct uvesafb_ktask *task;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	task = kzalloc(sizeof(*task), GFP_KERNEL);
26262306a36Sopenharmony_ci	if (task) {
26362306a36Sopenharmony_ci		task->done = kzalloc(sizeof(*task->done), GFP_KERNEL);
26462306a36Sopenharmony_ci		if (!task->done) {
26562306a36Sopenharmony_ci			kfree(task);
26662306a36Sopenharmony_ci			task = NULL;
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci	return task;
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic void uvesafb_setup_var(struct fb_var_screeninfo *var,
27362306a36Sopenharmony_ci		struct fb_info *info, struct vbe_mode_ib *mode)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	var->vmode = FB_VMODE_NONINTERLACED;
27862306a36Sopenharmony_ci	var->sync = FB_SYNC_VERT_HIGH_ACT;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	var->xres = mode->x_res;
28162306a36Sopenharmony_ci	var->yres = mode->y_res;
28262306a36Sopenharmony_ci	var->xres_virtual = mode->x_res;
28362306a36Sopenharmony_ci	var->yres_virtual = (par->ypan) ?
28462306a36Sopenharmony_ci			info->fix.smem_len / mode->bytes_per_scan_line :
28562306a36Sopenharmony_ci			mode->y_res;
28662306a36Sopenharmony_ci	var->xoffset = 0;
28762306a36Sopenharmony_ci	var->yoffset = 0;
28862306a36Sopenharmony_ci	var->bits_per_pixel = mode->bits_per_pixel;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (var->bits_per_pixel == 15)
29162306a36Sopenharmony_ci		var->bits_per_pixel = 16;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	if (var->bits_per_pixel > 8) {
29462306a36Sopenharmony_ci		var->red.offset    = mode->red_off;
29562306a36Sopenharmony_ci		var->red.length    = mode->red_len;
29662306a36Sopenharmony_ci		var->green.offset  = mode->green_off;
29762306a36Sopenharmony_ci		var->green.length  = mode->green_len;
29862306a36Sopenharmony_ci		var->blue.offset   = mode->blue_off;
29962306a36Sopenharmony_ci		var->blue.length   = mode->blue_len;
30062306a36Sopenharmony_ci		var->transp.offset = mode->rsvd_off;
30162306a36Sopenharmony_ci		var->transp.length = mode->rsvd_len;
30262306a36Sopenharmony_ci	} else {
30362306a36Sopenharmony_ci		var->red.offset    = 0;
30462306a36Sopenharmony_ci		var->green.offset  = 0;
30562306a36Sopenharmony_ci		var->blue.offset   = 0;
30662306a36Sopenharmony_ci		var->transp.offset = 0;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci		var->red.length    = 8;
30962306a36Sopenharmony_ci		var->green.length  = 8;
31062306a36Sopenharmony_ci		var->blue.length   = 8;
31162306a36Sopenharmony_ci		var->transp.length = 0;
31262306a36Sopenharmony_ci	}
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic int uvesafb_vbe_find_mode(struct uvesafb_par *par,
31662306a36Sopenharmony_ci		int xres, int yres, int depth, unsigned char flags)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	int i, match = -1, h = 0, d = 0x7fffffff;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	for (i = 0; i < par->vbe_modes_cnt; i++) {
32162306a36Sopenharmony_ci		h = abs(par->vbe_modes[i].x_res - xres) +
32262306a36Sopenharmony_ci		    abs(par->vbe_modes[i].y_res - yres) +
32362306a36Sopenharmony_ci		    abs(depth - par->vbe_modes[i].depth);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci		/*
32662306a36Sopenharmony_ci		 * We have an exact match in terms of resolution
32762306a36Sopenharmony_ci		 * and depth.
32862306a36Sopenharmony_ci		 */
32962306a36Sopenharmony_ci		if (h == 0)
33062306a36Sopenharmony_ci			return i;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		if (h < d || (h == d && par->vbe_modes[i].depth > depth)) {
33362306a36Sopenharmony_ci			d = h;
33462306a36Sopenharmony_ci			match = i;
33562306a36Sopenharmony_ci		}
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci	i = 1;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	if (flags & UVESAFB_EXACT_DEPTH &&
34062306a36Sopenharmony_ci			par->vbe_modes[match].depth != depth)
34162306a36Sopenharmony_ci		i = 0;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	if (flags & UVESAFB_EXACT_RES && d > 24)
34462306a36Sopenharmony_ci		i = 0;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	if (i != 0)
34762306a36Sopenharmony_ci		return match;
34862306a36Sopenharmony_ci	else
34962306a36Sopenharmony_ci		return -1;
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic u8 *uvesafb_vbe_state_save(struct uvesafb_par *par)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	struct uvesafb_ktask *task;
35562306a36Sopenharmony_ci	u8 *state;
35662306a36Sopenharmony_ci	int err;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	if (!par->vbe_state_size)
35962306a36Sopenharmony_ci		return NULL;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	state = kmalloc(par->vbe_state_size, GFP_KERNEL);
36262306a36Sopenharmony_ci	if (!state)
36362306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	task = uvesafb_prep();
36662306a36Sopenharmony_ci	if (!task) {
36762306a36Sopenharmony_ci		kfree(state);
36862306a36Sopenharmony_ci		return NULL;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	task->t.regs.eax = 0x4f04;
37262306a36Sopenharmony_ci	task->t.regs.ecx = 0x000f;
37362306a36Sopenharmony_ci	task->t.regs.edx = 0x0001;
37462306a36Sopenharmony_ci	task->t.flags = TF_BUF_RET | TF_BUF_ESBX;
37562306a36Sopenharmony_ci	task->t.buf_len = par->vbe_state_size;
37662306a36Sopenharmony_ci	task->buf = state;
37762306a36Sopenharmony_ci	err = uvesafb_exec(task);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
38062306a36Sopenharmony_ci		pr_warn("VBE get state call failed (eax=0x%x, err=%d)\n",
38162306a36Sopenharmony_ci			task->t.regs.eax, err);
38262306a36Sopenharmony_ci		kfree(state);
38362306a36Sopenharmony_ci		state = NULL;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	uvesafb_free(task);
38762306a36Sopenharmony_ci	return state;
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_cistatic void uvesafb_vbe_state_restore(struct uvesafb_par *par, u8 *state_buf)
39162306a36Sopenharmony_ci{
39262306a36Sopenharmony_ci	struct uvesafb_ktask *task;
39362306a36Sopenharmony_ci	int err;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	if (!state_buf)
39662306a36Sopenharmony_ci		return;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	task = uvesafb_prep();
39962306a36Sopenharmony_ci	if (!task)
40062306a36Sopenharmony_ci		return;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	task->t.regs.eax = 0x4f04;
40362306a36Sopenharmony_ci	task->t.regs.ecx = 0x000f;
40462306a36Sopenharmony_ci	task->t.regs.edx = 0x0002;
40562306a36Sopenharmony_ci	task->t.buf_len = par->vbe_state_size;
40662306a36Sopenharmony_ci	task->t.flags = TF_BUF_ESBX;
40762306a36Sopenharmony_ci	task->buf = state_buf;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	err = uvesafb_exec(task);
41062306a36Sopenharmony_ci	if (err || (task->t.regs.eax & 0xffff) != 0x004f)
41162306a36Sopenharmony_ci		pr_warn("VBE state restore call failed (eax=0x%x, err=%d)\n",
41262306a36Sopenharmony_ci			task->t.regs.eax, err);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	uvesafb_free(task);
41562306a36Sopenharmony_ci}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic int uvesafb_vbe_getinfo(struct uvesafb_ktask *task,
41862306a36Sopenharmony_ci			       struct uvesafb_par *par)
41962306a36Sopenharmony_ci{
42062306a36Sopenharmony_ci	int err;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	task->t.regs.eax = 0x4f00;
42362306a36Sopenharmony_ci	task->t.flags = TF_VBEIB;
42462306a36Sopenharmony_ci	task->t.buf_len = sizeof(struct vbe_ib);
42562306a36Sopenharmony_ci	task->buf = &par->vbe_ib;
42662306a36Sopenharmony_ci	memcpy(par->vbe_ib.vbe_signature, "VBE2", 4);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	err = uvesafb_exec(task);
42962306a36Sopenharmony_ci	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
43062306a36Sopenharmony_ci		pr_err("Getting VBE info block failed (eax=0x%x, err=%d)\n",
43162306a36Sopenharmony_ci		       (u32)task->t.regs.eax, err);
43262306a36Sopenharmony_ci		return -EINVAL;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (par->vbe_ib.vbe_version < 0x0200) {
43662306a36Sopenharmony_ci		pr_err("Sorry, pre-VBE 2.0 cards are not supported\n");
43762306a36Sopenharmony_ci		return -EINVAL;
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (!par->vbe_ib.mode_list_ptr) {
44162306a36Sopenharmony_ci		pr_err("Missing mode list!\n");
44262306a36Sopenharmony_ci		return -EINVAL;
44362306a36Sopenharmony_ci	}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	pr_info("");
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	/*
44862306a36Sopenharmony_ci	 * Convert string pointers and the mode list pointer into
44962306a36Sopenharmony_ci	 * usable addresses. Print informational messages about the
45062306a36Sopenharmony_ci	 * video adapter and its vendor.
45162306a36Sopenharmony_ci	 */
45262306a36Sopenharmony_ci	if (par->vbe_ib.oem_vendor_name_ptr)
45362306a36Sopenharmony_ci		pr_cont("%s, ",
45462306a36Sopenharmony_ci			((char *)task->buf) + par->vbe_ib.oem_vendor_name_ptr);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (par->vbe_ib.oem_product_name_ptr)
45762306a36Sopenharmony_ci		pr_cont("%s, ",
45862306a36Sopenharmony_ci			((char *)task->buf) + par->vbe_ib.oem_product_name_ptr);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	if (par->vbe_ib.oem_product_rev_ptr)
46162306a36Sopenharmony_ci		pr_cont("%s, ",
46262306a36Sopenharmony_ci			((char *)task->buf) + par->vbe_ib.oem_product_rev_ptr);
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (par->vbe_ib.oem_string_ptr)
46562306a36Sopenharmony_ci		pr_cont("OEM: %s, ",
46662306a36Sopenharmony_ci			((char *)task->buf) + par->vbe_ib.oem_string_ptr);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	pr_cont("VBE v%d.%d\n",
46962306a36Sopenharmony_ci		(par->vbe_ib.vbe_version & 0xff00) >> 8,
47062306a36Sopenharmony_ci		par->vbe_ib.vbe_version & 0xff);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	return 0;
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic int uvesafb_vbe_getmodes(struct uvesafb_ktask *task,
47662306a36Sopenharmony_ci				struct uvesafb_par *par)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	int off = 0, err;
47962306a36Sopenharmony_ci	u16 *mode;
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	par->vbe_modes_cnt = 0;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	/* Count available modes. */
48462306a36Sopenharmony_ci	mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);
48562306a36Sopenharmony_ci	while (*mode != 0xffff) {
48662306a36Sopenharmony_ci		par->vbe_modes_cnt++;
48762306a36Sopenharmony_ci		mode++;
48862306a36Sopenharmony_ci	}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	par->vbe_modes = kcalloc(par->vbe_modes_cnt,
49162306a36Sopenharmony_ci				 sizeof(struct vbe_mode_ib),
49262306a36Sopenharmony_ci				 GFP_KERNEL);
49362306a36Sopenharmony_ci	if (!par->vbe_modes)
49462306a36Sopenharmony_ci		return -ENOMEM;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	/* Get info about all available modes. */
49762306a36Sopenharmony_ci	mode = (u16 *) (((u8 *)&par->vbe_ib) + par->vbe_ib.mode_list_ptr);
49862306a36Sopenharmony_ci	while (*mode != 0xffff) {
49962306a36Sopenharmony_ci		struct vbe_mode_ib *mib;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		uvesafb_reset(task);
50262306a36Sopenharmony_ci		task->t.regs.eax = 0x4f01;
50362306a36Sopenharmony_ci		task->t.regs.ecx = (u32) *mode;
50462306a36Sopenharmony_ci		task->t.flags = TF_BUF_RET | TF_BUF_ESDI;
50562306a36Sopenharmony_ci		task->t.buf_len = sizeof(struct vbe_mode_ib);
50662306a36Sopenharmony_ci		task->buf = par->vbe_modes + off;
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci		err = uvesafb_exec(task);
50962306a36Sopenharmony_ci		if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
51062306a36Sopenharmony_ci			pr_warn("Getting mode info block for mode 0x%x failed (eax=0x%x, err=%d)\n",
51162306a36Sopenharmony_ci				*mode, (u32)task->t.regs.eax, err);
51262306a36Sopenharmony_ci			mode++;
51362306a36Sopenharmony_ci			par->vbe_modes_cnt--;
51462306a36Sopenharmony_ci			continue;
51562306a36Sopenharmony_ci		}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		mib = task->buf;
51862306a36Sopenharmony_ci		mib->mode_id = *mode;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci		/*
52162306a36Sopenharmony_ci		 * We only want modes that are supported with the current
52262306a36Sopenharmony_ci		 * hardware configuration, color, graphics and that have
52362306a36Sopenharmony_ci		 * support for the LFB.
52462306a36Sopenharmony_ci		 */
52562306a36Sopenharmony_ci		if ((mib->mode_attr & VBE_MODE_MASK) == VBE_MODE_MASK &&
52662306a36Sopenharmony_ci				 mib->bits_per_pixel >= 8)
52762306a36Sopenharmony_ci			off++;
52862306a36Sopenharmony_ci		else
52962306a36Sopenharmony_ci			par->vbe_modes_cnt--;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci		mode++;
53262306a36Sopenharmony_ci		mib->depth = mib->red_len + mib->green_len + mib->blue_len;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci		/*
53562306a36Sopenharmony_ci		 * Handle 8bpp modes and modes with broken color component
53662306a36Sopenharmony_ci		 * lengths.
53762306a36Sopenharmony_ci		 */
53862306a36Sopenharmony_ci		if (mib->depth == 0 || (mib->depth == 24 &&
53962306a36Sopenharmony_ci					mib->bits_per_pixel == 32))
54062306a36Sopenharmony_ci			mib->depth = mib->bits_per_pixel;
54162306a36Sopenharmony_ci	}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	if (par->vbe_modes_cnt > 0)
54462306a36Sopenharmony_ci		return 0;
54562306a36Sopenharmony_ci	else
54662306a36Sopenharmony_ci		return -EINVAL;
54762306a36Sopenharmony_ci}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci/*
55062306a36Sopenharmony_ci * The Protected Mode Interface is 32-bit x86 code, so we only run it on
55162306a36Sopenharmony_ci * x86 and not x86_64.
55262306a36Sopenharmony_ci */
55362306a36Sopenharmony_ci#ifdef CONFIG_X86_32
55462306a36Sopenharmony_cistatic int uvesafb_vbe_getpmi(struct uvesafb_ktask *task,
55562306a36Sopenharmony_ci			      struct uvesafb_par *par)
55662306a36Sopenharmony_ci{
55762306a36Sopenharmony_ci	int i, err;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	uvesafb_reset(task);
56062306a36Sopenharmony_ci	task->t.regs.eax = 0x4f0a;
56162306a36Sopenharmony_ci	task->t.regs.ebx = 0x0;
56262306a36Sopenharmony_ci	err = uvesafb_exec(task);
56362306a36Sopenharmony_ci	if (err)
56462306a36Sopenharmony_ci		return err;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	if ((task->t.regs.eax & 0xffff) != 0x4f || task->t.regs.es < 0xc000) {
56762306a36Sopenharmony_ci		par->pmi_setpal = par->ypan = 0;
56862306a36Sopenharmony_ci	} else {
56962306a36Sopenharmony_ci		par->pmi_base = (u16 *)phys_to_virt(((u32)task->t.regs.es << 4)
57062306a36Sopenharmony_ci						+ task->t.regs.edi);
57162306a36Sopenharmony_ci		par->pmi_start = (u8 *)par->pmi_base + par->pmi_base[1];
57262306a36Sopenharmony_ci		par->pmi_pal = (u8 *)par->pmi_base + par->pmi_base[2];
57362306a36Sopenharmony_ci		pr_info("protected mode interface info at %04x:%04x\n",
57462306a36Sopenharmony_ci			(u16)task->t.regs.es, (u16)task->t.regs.edi);
57562306a36Sopenharmony_ci		pr_info("pmi: set display start = %p, set palette = %p\n",
57662306a36Sopenharmony_ci			par->pmi_start, par->pmi_pal);
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci		if (par->pmi_base[3]) {
57962306a36Sopenharmony_ci			pr_info("pmi: ports =");
58062306a36Sopenharmony_ci			for (i = par->pmi_base[3]/2;
58162306a36Sopenharmony_ci					par->pmi_base[i] != 0xffff; i++)
58262306a36Sopenharmony_ci				pr_cont(" %x", par->pmi_base[i]);
58362306a36Sopenharmony_ci			pr_cont("\n");
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci			if (par->pmi_base[i] != 0xffff) {
58662306a36Sopenharmony_ci				pr_info("can't handle memory requests, pmi disabled\n");
58762306a36Sopenharmony_ci				par->ypan = par->pmi_setpal = 0;
58862306a36Sopenharmony_ci			}
58962306a36Sopenharmony_ci		}
59062306a36Sopenharmony_ci	}
59162306a36Sopenharmony_ci	return 0;
59262306a36Sopenharmony_ci}
59362306a36Sopenharmony_ci#endif /* CONFIG_X86_32 */
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci/*
59662306a36Sopenharmony_ci * Check whether a video mode is supported by the Video BIOS and is
59762306a36Sopenharmony_ci * compatible with the monitor limits.
59862306a36Sopenharmony_ci */
59962306a36Sopenharmony_cistatic int uvesafb_is_valid_mode(struct fb_videomode *mode,
60062306a36Sopenharmony_ci				 struct fb_info *info)
60162306a36Sopenharmony_ci{
60262306a36Sopenharmony_ci	if (info->monspecs.gtf) {
60362306a36Sopenharmony_ci		fb_videomode_to_var(&info->var, mode);
60462306a36Sopenharmony_ci		if (fb_validate_mode(&info->var, info))
60562306a36Sopenharmony_ci			return 0;
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	if (uvesafb_vbe_find_mode(info->par, mode->xres, mode->yres, 8,
60962306a36Sopenharmony_ci				UVESAFB_EXACT_RES) == -1)
61062306a36Sopenharmony_ci		return 0;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	return 1;
61362306a36Sopenharmony_ci}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_cistatic int uvesafb_vbe_getedid(struct uvesafb_ktask *task, struct fb_info *info)
61662306a36Sopenharmony_ci{
61762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
61862306a36Sopenharmony_ci	int err = 0;
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	if (noedid || par->vbe_ib.vbe_version < 0x0300)
62162306a36Sopenharmony_ci		return -EINVAL;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	task->t.regs.eax = 0x4f15;
62462306a36Sopenharmony_ci	task->t.regs.ebx = 0;
62562306a36Sopenharmony_ci	task->t.regs.ecx = 0;
62662306a36Sopenharmony_ci	task->t.buf_len = 0;
62762306a36Sopenharmony_ci	task->t.flags = 0;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	err = uvesafb_exec(task);
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	if ((task->t.regs.eax & 0xffff) != 0x004f || err)
63262306a36Sopenharmony_ci		return -EINVAL;
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	if ((task->t.regs.ebx & 0x3) == 3) {
63562306a36Sopenharmony_ci		pr_info("VBIOS/hardware supports both DDC1 and DDC2 transfers\n");
63662306a36Sopenharmony_ci	} else if ((task->t.regs.ebx & 0x3) == 2) {
63762306a36Sopenharmony_ci		pr_info("VBIOS/hardware supports DDC2 transfers\n");
63862306a36Sopenharmony_ci	} else if ((task->t.regs.ebx & 0x3) == 1) {
63962306a36Sopenharmony_ci		pr_info("VBIOS/hardware supports DDC1 transfers\n");
64062306a36Sopenharmony_ci	} else {
64162306a36Sopenharmony_ci		pr_info("VBIOS/hardware doesn't support DDC transfers\n");
64262306a36Sopenharmony_ci		return -EINVAL;
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	task->t.regs.eax = 0x4f15;
64662306a36Sopenharmony_ci	task->t.regs.ebx = 1;
64762306a36Sopenharmony_ci	task->t.regs.ecx = task->t.regs.edx = 0;
64862306a36Sopenharmony_ci	task->t.flags = TF_BUF_RET | TF_BUF_ESDI;
64962306a36Sopenharmony_ci	task->t.buf_len = EDID_LENGTH;
65062306a36Sopenharmony_ci	task->buf = kzalloc(EDID_LENGTH, GFP_KERNEL);
65162306a36Sopenharmony_ci	if (!task->buf)
65262306a36Sopenharmony_ci		return -ENOMEM;
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	err = uvesafb_exec(task);
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	if ((task->t.regs.eax & 0xffff) == 0x004f && !err) {
65762306a36Sopenharmony_ci		fb_edid_to_monspecs(task->buf, &info->monspecs);
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci		if (info->monspecs.vfmax && info->monspecs.hfmax) {
66062306a36Sopenharmony_ci			/*
66162306a36Sopenharmony_ci			 * If the maximum pixel clock wasn't specified in
66262306a36Sopenharmony_ci			 * the EDID block, set it to 300 MHz.
66362306a36Sopenharmony_ci			 */
66462306a36Sopenharmony_ci			if (info->monspecs.dclkmax == 0)
66562306a36Sopenharmony_ci				info->monspecs.dclkmax = 300 * 1000000;
66662306a36Sopenharmony_ci			info->monspecs.gtf = 1;
66762306a36Sopenharmony_ci		}
66862306a36Sopenharmony_ci	} else {
66962306a36Sopenharmony_ci		err = -EINVAL;
67062306a36Sopenharmony_ci	}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	kfree(task->buf);
67362306a36Sopenharmony_ci	return err;
67462306a36Sopenharmony_ci}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_cistatic void uvesafb_vbe_getmonspecs(struct uvesafb_ktask *task,
67762306a36Sopenharmony_ci				    struct fb_info *info)
67862306a36Sopenharmony_ci{
67962306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
68062306a36Sopenharmony_ci	int i;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	memset(&info->monspecs, 0, sizeof(info->monspecs));
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	/*
68562306a36Sopenharmony_ci	 * If we don't get all necessary data from the EDID block,
68662306a36Sopenharmony_ci	 * mark it as incompatible with the GTF and set nocrtc so
68762306a36Sopenharmony_ci	 * that we always use the default BIOS refresh rate.
68862306a36Sopenharmony_ci	 */
68962306a36Sopenharmony_ci	if (uvesafb_vbe_getedid(task, info)) {
69062306a36Sopenharmony_ci		info->monspecs.gtf = 0;
69162306a36Sopenharmony_ci		par->nocrtc = 1;
69262306a36Sopenharmony_ci	}
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	/* Kernel command line overrides. */
69562306a36Sopenharmony_ci	if (maxclk)
69662306a36Sopenharmony_ci		info->monspecs.dclkmax = maxclk * 1000000;
69762306a36Sopenharmony_ci	if (maxvf)
69862306a36Sopenharmony_ci		info->monspecs.vfmax = maxvf;
69962306a36Sopenharmony_ci	if (maxhf)
70062306a36Sopenharmony_ci		info->monspecs.hfmax = maxhf * 1000;
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	/*
70362306a36Sopenharmony_ci	 * In case DDC transfers are not supported, the user can provide
70462306a36Sopenharmony_ci	 * monitor limits manually. Lower limits are set to "safe" values.
70562306a36Sopenharmony_ci	 */
70662306a36Sopenharmony_ci	if (info->monspecs.gtf == 0 && maxclk && maxvf && maxhf) {
70762306a36Sopenharmony_ci		info->monspecs.dclkmin = 0;
70862306a36Sopenharmony_ci		info->monspecs.vfmin = 60;
70962306a36Sopenharmony_ci		info->monspecs.hfmin = 29000;
71062306a36Sopenharmony_ci		info->monspecs.gtf = 1;
71162306a36Sopenharmony_ci		par->nocrtc = 0;
71262306a36Sopenharmony_ci	}
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	if (info->monspecs.gtf)
71562306a36Sopenharmony_ci		pr_info("monitor limits: vf = %d Hz, hf = %d kHz, clk = %d MHz\n",
71662306a36Sopenharmony_ci			info->monspecs.vfmax,
71762306a36Sopenharmony_ci			(int)(info->monspecs.hfmax / 1000),
71862306a36Sopenharmony_ci			(int)(info->monspecs.dclkmax / 1000000));
71962306a36Sopenharmony_ci	else
72062306a36Sopenharmony_ci		pr_info("no monitor limits have been set, default refresh rate will be used\n");
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	/* Add VBE modes to the modelist. */
72362306a36Sopenharmony_ci	for (i = 0; i < par->vbe_modes_cnt; i++) {
72462306a36Sopenharmony_ci		struct fb_var_screeninfo var;
72562306a36Sopenharmony_ci		struct vbe_mode_ib *mode;
72662306a36Sopenharmony_ci		struct fb_videomode vmode;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci		mode = &par->vbe_modes[i];
72962306a36Sopenharmony_ci		memset(&var, 0, sizeof(var));
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci		var.xres = mode->x_res;
73262306a36Sopenharmony_ci		var.yres = mode->y_res;
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci		fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, &var, info);
73562306a36Sopenharmony_ci		fb_var_to_videomode(&vmode, &var);
73662306a36Sopenharmony_ci		fb_add_videomode(&vmode, &info->modelist);
73762306a36Sopenharmony_ci	}
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	/* Add valid VESA modes to our modelist. */
74062306a36Sopenharmony_ci	for (i = 0; i < VESA_MODEDB_SIZE; i++) {
74162306a36Sopenharmony_ci		if (uvesafb_is_valid_mode((struct fb_videomode *)
74262306a36Sopenharmony_ci						&vesa_modes[i], info))
74362306a36Sopenharmony_ci			fb_add_videomode(&vesa_modes[i], &info->modelist);
74462306a36Sopenharmony_ci	}
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci	for (i = 0; i < info->monspecs.modedb_len; i++) {
74762306a36Sopenharmony_ci		if (uvesafb_is_valid_mode(&info->monspecs.modedb[i], info))
74862306a36Sopenharmony_ci			fb_add_videomode(&info->monspecs.modedb[i],
74962306a36Sopenharmony_ci					&info->modelist);
75062306a36Sopenharmony_ci	}
75162306a36Sopenharmony_ci
75262306a36Sopenharmony_ci	return;
75362306a36Sopenharmony_ci}
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_cistatic void uvesafb_vbe_getstatesize(struct uvesafb_ktask *task,
75662306a36Sopenharmony_ci				     struct uvesafb_par *par)
75762306a36Sopenharmony_ci{
75862306a36Sopenharmony_ci	int err;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	uvesafb_reset(task);
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	/*
76362306a36Sopenharmony_ci	 * Get the VBE state buffer size. We want all available
76462306a36Sopenharmony_ci	 * hardware state data (CL = 0x0f).
76562306a36Sopenharmony_ci	 */
76662306a36Sopenharmony_ci	task->t.regs.eax = 0x4f04;
76762306a36Sopenharmony_ci	task->t.regs.ecx = 0x000f;
76862306a36Sopenharmony_ci	task->t.regs.edx = 0x0000;
76962306a36Sopenharmony_ci	task->t.flags = 0;
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_ci	err = uvesafb_exec(task);
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
77462306a36Sopenharmony_ci		pr_warn("VBE state buffer size cannot be determined (eax=0x%x, err=%d)\n",
77562306a36Sopenharmony_ci			task->t.regs.eax, err);
77662306a36Sopenharmony_ci		par->vbe_state_size = 0;
77762306a36Sopenharmony_ci		return;
77862306a36Sopenharmony_ci	}
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	par->vbe_state_size = 64 * (task->t.regs.ebx & 0xffff);
78162306a36Sopenharmony_ci}
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_cistatic int uvesafb_vbe_init(struct fb_info *info)
78462306a36Sopenharmony_ci{
78562306a36Sopenharmony_ci	struct uvesafb_ktask *task = NULL;
78662306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
78762306a36Sopenharmony_ci	int err;
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	task = uvesafb_prep();
79062306a36Sopenharmony_ci	if (!task)
79162306a36Sopenharmony_ci		return -ENOMEM;
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	err = uvesafb_vbe_getinfo(task, par);
79462306a36Sopenharmony_ci	if (err)
79562306a36Sopenharmony_ci		goto out;
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	err = uvesafb_vbe_getmodes(task, par);
79862306a36Sopenharmony_ci	if (err)
79962306a36Sopenharmony_ci		goto out;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	par->nocrtc = nocrtc;
80262306a36Sopenharmony_ci#ifdef CONFIG_X86_32
80362306a36Sopenharmony_ci	par->pmi_setpal = pmi_setpal;
80462306a36Sopenharmony_ci	par->ypan = ypan;
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci	if (par->pmi_setpal || par->ypan) {
80762306a36Sopenharmony_ci		if (__supported_pte_mask & _PAGE_NX) {
80862306a36Sopenharmony_ci			par->pmi_setpal = par->ypan = 0;
80962306a36Sopenharmony_ci			pr_warn("NX protection is active, better not use the PMI\n");
81062306a36Sopenharmony_ci		} else {
81162306a36Sopenharmony_ci			uvesafb_vbe_getpmi(task, par);
81262306a36Sopenharmony_ci		}
81362306a36Sopenharmony_ci	}
81462306a36Sopenharmony_ci#else
81562306a36Sopenharmony_ci	/* The protected mode interface is not available on non-x86. */
81662306a36Sopenharmony_ci	par->pmi_setpal = par->ypan = 0;
81762306a36Sopenharmony_ci#endif
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	INIT_LIST_HEAD(&info->modelist);
82062306a36Sopenharmony_ci	uvesafb_vbe_getmonspecs(task, info);
82162306a36Sopenharmony_ci	uvesafb_vbe_getstatesize(task, par);
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ciout:	uvesafb_free(task);
82462306a36Sopenharmony_ci	return err;
82562306a36Sopenharmony_ci}
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_cistatic int uvesafb_vbe_init_mode(struct fb_info *info)
82862306a36Sopenharmony_ci{
82962306a36Sopenharmony_ci	struct list_head *pos;
83062306a36Sopenharmony_ci	struct fb_modelist *modelist;
83162306a36Sopenharmony_ci	struct fb_videomode *mode;
83262306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
83362306a36Sopenharmony_ci	int i, modeid;
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	/* Has the user requested a specific VESA mode? */
83662306a36Sopenharmony_ci	if (vbemode) {
83762306a36Sopenharmony_ci		for (i = 0; i < par->vbe_modes_cnt; i++) {
83862306a36Sopenharmony_ci			if (par->vbe_modes[i].mode_id == vbemode) {
83962306a36Sopenharmony_ci				modeid = i;
84062306a36Sopenharmony_ci				uvesafb_setup_var(&info->var, info,
84162306a36Sopenharmony_ci						&par->vbe_modes[modeid]);
84262306a36Sopenharmony_ci				fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
84362306a36Sopenharmony_ci						&info->var, info);
84462306a36Sopenharmony_ci				/*
84562306a36Sopenharmony_ci				 * With pixclock set to 0, the default BIOS
84662306a36Sopenharmony_ci				 * timings will be used in set_par().
84762306a36Sopenharmony_ci				 */
84862306a36Sopenharmony_ci				info->var.pixclock = 0;
84962306a36Sopenharmony_ci				goto gotmode;
85062306a36Sopenharmony_ci			}
85162306a36Sopenharmony_ci		}
85262306a36Sopenharmony_ci		pr_info("requested VBE mode 0x%x is unavailable\n", vbemode);
85362306a36Sopenharmony_ci		vbemode = 0;
85462306a36Sopenharmony_ci	}
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	/* Count the modes in the modelist */
85762306a36Sopenharmony_ci	i = 0;
85862306a36Sopenharmony_ci	list_for_each(pos, &info->modelist)
85962306a36Sopenharmony_ci		i++;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	/*
86262306a36Sopenharmony_ci	 * Convert the modelist into a modedb so that we can use it with
86362306a36Sopenharmony_ci	 * fb_find_mode().
86462306a36Sopenharmony_ci	 */
86562306a36Sopenharmony_ci	mode = kcalloc(i, sizeof(*mode), GFP_KERNEL);
86662306a36Sopenharmony_ci	if (mode) {
86762306a36Sopenharmony_ci		i = 0;
86862306a36Sopenharmony_ci		list_for_each(pos, &info->modelist) {
86962306a36Sopenharmony_ci			modelist = list_entry(pos, struct fb_modelist, list);
87062306a36Sopenharmony_ci			mode[i] = modelist->mode;
87162306a36Sopenharmony_ci			i++;
87262306a36Sopenharmony_ci		}
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_ci		if (!mode_option)
87562306a36Sopenharmony_ci			mode_option = UVESAFB_DEFAULT_MODE;
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci		i = fb_find_mode(&info->var, info, mode_option, mode, i,
87862306a36Sopenharmony_ci			NULL, 8);
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci		kfree(mode);
88162306a36Sopenharmony_ci	}
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci	/* fb_find_mode() failed */
88462306a36Sopenharmony_ci	if (i == 0) {
88562306a36Sopenharmony_ci		info->var.xres = 640;
88662306a36Sopenharmony_ci		info->var.yres = 480;
88762306a36Sopenharmony_ci		mode = (struct fb_videomode *)
88862306a36Sopenharmony_ci				fb_find_best_mode(&info->var, &info->modelist);
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci		if (mode) {
89162306a36Sopenharmony_ci			fb_videomode_to_var(&info->var, mode);
89262306a36Sopenharmony_ci		} else {
89362306a36Sopenharmony_ci			modeid = par->vbe_modes[0].mode_id;
89462306a36Sopenharmony_ci			uvesafb_setup_var(&info->var, info,
89562306a36Sopenharmony_ci					&par->vbe_modes[modeid]);
89662306a36Sopenharmony_ci			fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
89762306a36Sopenharmony_ci					&info->var, info);
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci			goto gotmode;
90062306a36Sopenharmony_ci		}
90162306a36Sopenharmony_ci	}
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_ci	/* Look for a matching VBE mode. */
90462306a36Sopenharmony_ci	modeid = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres,
90562306a36Sopenharmony_ci			info->var.bits_per_pixel, UVESAFB_EXACT_RES);
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	if (modeid == -1)
90862306a36Sopenharmony_ci		return -EINVAL;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	uvesafb_setup_var(&info->var, info, &par->vbe_modes[modeid]);
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_cigotmode:
91362306a36Sopenharmony_ci	/*
91462306a36Sopenharmony_ci	 * If we are not VBE3.0+ compliant, we're done -- the BIOS will
91562306a36Sopenharmony_ci	 * ignore our timings anyway.
91662306a36Sopenharmony_ci	 */
91762306a36Sopenharmony_ci	if (par->vbe_ib.vbe_version < 0x0300 || par->nocrtc)
91862306a36Sopenharmony_ci		fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
91962306a36Sopenharmony_ci					&info->var, info);
92062306a36Sopenharmony_ci
92162306a36Sopenharmony_ci	return modeid;
92262306a36Sopenharmony_ci}
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_cistatic int uvesafb_setpalette(struct uvesafb_pal_entry *entries, int count,
92562306a36Sopenharmony_ci		int start, struct fb_info *info)
92662306a36Sopenharmony_ci{
92762306a36Sopenharmony_ci	struct uvesafb_ktask *task;
92862306a36Sopenharmony_ci#ifdef CONFIG_X86
92962306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
93062306a36Sopenharmony_ci	int i = par->mode_idx;
93162306a36Sopenharmony_ci#endif
93262306a36Sopenharmony_ci	int err = 0;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	/*
93562306a36Sopenharmony_ci	 * We support palette modifications for 8 bpp modes only, so
93662306a36Sopenharmony_ci	 * there can never be more than 256 entries.
93762306a36Sopenharmony_ci	 */
93862306a36Sopenharmony_ci	if (start + count > 256)
93962306a36Sopenharmony_ci		return -EINVAL;
94062306a36Sopenharmony_ci
94162306a36Sopenharmony_ci#ifdef CONFIG_X86
94262306a36Sopenharmony_ci	/* Use VGA registers if mode is VGA-compatible. */
94362306a36Sopenharmony_ci	if (i >= 0 && i < par->vbe_modes_cnt &&
94462306a36Sopenharmony_ci	    par->vbe_modes[i].mode_attr & VBE_MODE_VGACOMPAT) {
94562306a36Sopenharmony_ci		for (i = 0; i < count; i++) {
94662306a36Sopenharmony_ci			outb_p(start + i,        dac_reg);
94762306a36Sopenharmony_ci			outb_p(entries[i].red,   dac_val);
94862306a36Sopenharmony_ci			outb_p(entries[i].green, dac_val);
94962306a36Sopenharmony_ci			outb_p(entries[i].blue,  dac_val);
95062306a36Sopenharmony_ci		}
95162306a36Sopenharmony_ci	}
95262306a36Sopenharmony_ci#ifdef CONFIG_X86_32
95362306a36Sopenharmony_ci	else if (par->pmi_setpal) {
95462306a36Sopenharmony_ci		__asm__ __volatile__(
95562306a36Sopenharmony_ci		"call *(%%esi)"
95662306a36Sopenharmony_ci		: /* no return value */
95762306a36Sopenharmony_ci		: "a" (0x4f09),         /* EAX */
95862306a36Sopenharmony_ci		  "b" (0),              /* EBX */
95962306a36Sopenharmony_ci		  "c" (count),          /* ECX */
96062306a36Sopenharmony_ci		  "d" (start),          /* EDX */
96162306a36Sopenharmony_ci		  "D" (entries),        /* EDI */
96262306a36Sopenharmony_ci		  "S" (&par->pmi_pal)); /* ESI */
96362306a36Sopenharmony_ci	}
96462306a36Sopenharmony_ci#endif /* CONFIG_X86_32 */
96562306a36Sopenharmony_ci	else
96662306a36Sopenharmony_ci#endif /* CONFIG_X86 */
96762306a36Sopenharmony_ci	{
96862306a36Sopenharmony_ci		task = uvesafb_prep();
96962306a36Sopenharmony_ci		if (!task)
97062306a36Sopenharmony_ci			return -ENOMEM;
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_ci		task->t.regs.eax = 0x4f09;
97362306a36Sopenharmony_ci		task->t.regs.ebx = 0x0;
97462306a36Sopenharmony_ci		task->t.regs.ecx = count;
97562306a36Sopenharmony_ci		task->t.regs.edx = start;
97662306a36Sopenharmony_ci		task->t.flags = TF_BUF_ESDI;
97762306a36Sopenharmony_ci		task->t.buf_len = sizeof(struct uvesafb_pal_entry) * count;
97862306a36Sopenharmony_ci		task->buf = entries;
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci		err = uvesafb_exec(task);
98162306a36Sopenharmony_ci		if ((task->t.regs.eax & 0xffff) != 0x004f)
98262306a36Sopenharmony_ci			err = 1;
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci		uvesafb_free(task);
98562306a36Sopenharmony_ci	}
98662306a36Sopenharmony_ci	return err;
98762306a36Sopenharmony_ci}
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_cistatic int uvesafb_setcolreg(unsigned regno, unsigned red, unsigned green,
99062306a36Sopenharmony_ci		unsigned blue, unsigned transp,
99162306a36Sopenharmony_ci		struct fb_info *info)
99262306a36Sopenharmony_ci{
99362306a36Sopenharmony_ci	struct uvesafb_pal_entry entry;
99462306a36Sopenharmony_ci	int shift = 16 - dac_width;
99562306a36Sopenharmony_ci	int err = 0;
99662306a36Sopenharmony_ci
99762306a36Sopenharmony_ci	if (regno >= info->cmap.len)
99862306a36Sopenharmony_ci		return -EINVAL;
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci	if (info->var.bits_per_pixel == 8) {
100162306a36Sopenharmony_ci		entry.red   = red   >> shift;
100262306a36Sopenharmony_ci		entry.green = green >> shift;
100362306a36Sopenharmony_ci		entry.blue  = blue  >> shift;
100462306a36Sopenharmony_ci		entry.pad   = 0;
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci		err = uvesafb_setpalette(&entry, 1, regno, info);
100762306a36Sopenharmony_ci	} else if (regno < 16) {
100862306a36Sopenharmony_ci		switch (info->var.bits_per_pixel) {
100962306a36Sopenharmony_ci		case 16:
101062306a36Sopenharmony_ci			if (info->var.red.offset == 10) {
101162306a36Sopenharmony_ci				/* 1:5:5:5 */
101262306a36Sopenharmony_ci				((u32 *) (info->pseudo_palette))[regno] =
101362306a36Sopenharmony_ci						((red   & 0xf800) >>  1) |
101462306a36Sopenharmony_ci						((green & 0xf800) >>  6) |
101562306a36Sopenharmony_ci						((blue  & 0xf800) >> 11);
101662306a36Sopenharmony_ci			} else {
101762306a36Sopenharmony_ci				/* 0:5:6:5 */
101862306a36Sopenharmony_ci				((u32 *) (info->pseudo_palette))[regno] =
101962306a36Sopenharmony_ci						((red   & 0xf800)      ) |
102062306a36Sopenharmony_ci						((green & 0xfc00) >>  5) |
102162306a36Sopenharmony_ci						((blue  & 0xf800) >> 11);
102262306a36Sopenharmony_ci			}
102362306a36Sopenharmony_ci			break;
102462306a36Sopenharmony_ci
102562306a36Sopenharmony_ci		case 24:
102662306a36Sopenharmony_ci		case 32:
102762306a36Sopenharmony_ci			red   >>= 8;
102862306a36Sopenharmony_ci			green >>= 8;
102962306a36Sopenharmony_ci			blue  >>= 8;
103062306a36Sopenharmony_ci			((u32 *)(info->pseudo_palette))[regno] =
103162306a36Sopenharmony_ci				(red   << info->var.red.offset)   |
103262306a36Sopenharmony_ci				(green << info->var.green.offset) |
103362306a36Sopenharmony_ci				(blue  << info->var.blue.offset);
103462306a36Sopenharmony_ci			break;
103562306a36Sopenharmony_ci		}
103662306a36Sopenharmony_ci	}
103762306a36Sopenharmony_ci	return err;
103862306a36Sopenharmony_ci}
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_cistatic int uvesafb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
104162306a36Sopenharmony_ci{
104262306a36Sopenharmony_ci	struct uvesafb_pal_entry *entries;
104362306a36Sopenharmony_ci	int shift = 16 - dac_width;
104462306a36Sopenharmony_ci	int i, err = 0;
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci	if (info->var.bits_per_pixel == 8) {
104762306a36Sopenharmony_ci		if (cmap->start + cmap->len > info->cmap.start +
104862306a36Sopenharmony_ci		    info->cmap.len || cmap->start < info->cmap.start)
104962306a36Sopenharmony_ci			return -EINVAL;
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci		entries = kmalloc_array(cmap->len, sizeof(*entries),
105262306a36Sopenharmony_ci					GFP_KERNEL);
105362306a36Sopenharmony_ci		if (!entries)
105462306a36Sopenharmony_ci			return -ENOMEM;
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_ci		for (i = 0; i < cmap->len; i++) {
105762306a36Sopenharmony_ci			entries[i].red   = cmap->red[i]   >> shift;
105862306a36Sopenharmony_ci			entries[i].green = cmap->green[i] >> shift;
105962306a36Sopenharmony_ci			entries[i].blue  = cmap->blue[i]  >> shift;
106062306a36Sopenharmony_ci			entries[i].pad   = 0;
106162306a36Sopenharmony_ci		}
106262306a36Sopenharmony_ci		err = uvesafb_setpalette(entries, cmap->len, cmap->start, info);
106362306a36Sopenharmony_ci		kfree(entries);
106462306a36Sopenharmony_ci	} else {
106562306a36Sopenharmony_ci		/*
106662306a36Sopenharmony_ci		 * For modes with bpp > 8, we only set the pseudo palette in
106762306a36Sopenharmony_ci		 * the fb_info struct. We rely on uvesafb_setcolreg to do all
106862306a36Sopenharmony_ci		 * sanity checking.
106962306a36Sopenharmony_ci		 */
107062306a36Sopenharmony_ci		for (i = 0; i < cmap->len; i++) {
107162306a36Sopenharmony_ci			err |= uvesafb_setcolreg(cmap->start + i, cmap->red[i],
107262306a36Sopenharmony_ci						cmap->green[i], cmap->blue[i],
107362306a36Sopenharmony_ci						0, info);
107462306a36Sopenharmony_ci		}
107562306a36Sopenharmony_ci	}
107662306a36Sopenharmony_ci	return err;
107762306a36Sopenharmony_ci}
107862306a36Sopenharmony_ci
107962306a36Sopenharmony_cistatic int uvesafb_pan_display(struct fb_var_screeninfo *var,
108062306a36Sopenharmony_ci		struct fb_info *info)
108162306a36Sopenharmony_ci{
108262306a36Sopenharmony_ci#ifdef CONFIG_X86_32
108362306a36Sopenharmony_ci	int offset;
108462306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	offset = (var->yoffset * info->fix.line_length + var->xoffset) / 4;
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	/*
108962306a36Sopenharmony_ci	 * It turns out it's not the best idea to do panning via vm86,
109062306a36Sopenharmony_ci	 * so we only allow it if we have a PMI.
109162306a36Sopenharmony_ci	 */
109262306a36Sopenharmony_ci	if (par->pmi_start) {
109362306a36Sopenharmony_ci		__asm__ __volatile__(
109462306a36Sopenharmony_ci			"call *(%%edi)"
109562306a36Sopenharmony_ci			: /* no return value */
109662306a36Sopenharmony_ci			: "a" (0x4f07),         /* EAX */
109762306a36Sopenharmony_ci			  "b" (0),              /* EBX */
109862306a36Sopenharmony_ci			  "c" (offset),         /* ECX */
109962306a36Sopenharmony_ci			  "d" (offset >> 16),   /* EDX */
110062306a36Sopenharmony_ci			  "D" (&par->pmi_start));    /* EDI */
110162306a36Sopenharmony_ci	}
110262306a36Sopenharmony_ci#endif
110362306a36Sopenharmony_ci	return 0;
110462306a36Sopenharmony_ci}
110562306a36Sopenharmony_ci
110662306a36Sopenharmony_cistatic int uvesafb_blank(int blank, struct fb_info *info)
110762306a36Sopenharmony_ci{
110862306a36Sopenharmony_ci	struct uvesafb_ktask *task;
110962306a36Sopenharmony_ci	int err = 1;
111062306a36Sopenharmony_ci#ifdef CONFIG_X86
111162306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	if (par->vbe_ib.capabilities & VBE_CAP_VGACOMPAT) {
111462306a36Sopenharmony_ci		int loop = 10000;
111562306a36Sopenharmony_ci		u8 seq = 0, crtc17 = 0;
111662306a36Sopenharmony_ci
111762306a36Sopenharmony_ci		if (blank == FB_BLANK_POWERDOWN) {
111862306a36Sopenharmony_ci			seq = 0x20;
111962306a36Sopenharmony_ci			crtc17 = 0x00;
112062306a36Sopenharmony_ci			err = 0;
112162306a36Sopenharmony_ci		} else {
112262306a36Sopenharmony_ci			seq = 0x00;
112362306a36Sopenharmony_ci			crtc17 = 0x80;
112462306a36Sopenharmony_ci			err = (blank == FB_BLANK_UNBLANK) ? 0 : -EINVAL;
112562306a36Sopenharmony_ci		}
112662306a36Sopenharmony_ci
112762306a36Sopenharmony_ci		vga_wseq(NULL, 0x00, 0x01);
112862306a36Sopenharmony_ci		seq |= vga_rseq(NULL, 0x01) & ~0x20;
112962306a36Sopenharmony_ci		vga_wseq(NULL, 0x00, seq);
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_ci		crtc17 |= vga_rcrt(NULL, 0x17) & ~0x80;
113262306a36Sopenharmony_ci		while (loop--);
113362306a36Sopenharmony_ci		vga_wcrt(NULL, 0x17, crtc17);
113462306a36Sopenharmony_ci		vga_wseq(NULL, 0x00, 0x03);
113562306a36Sopenharmony_ci	} else
113662306a36Sopenharmony_ci#endif /* CONFIG_X86 */
113762306a36Sopenharmony_ci	{
113862306a36Sopenharmony_ci		task = uvesafb_prep();
113962306a36Sopenharmony_ci		if (!task)
114062306a36Sopenharmony_ci			return -ENOMEM;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci		task->t.regs.eax = 0x4f10;
114362306a36Sopenharmony_ci		switch (blank) {
114462306a36Sopenharmony_ci		case FB_BLANK_UNBLANK:
114562306a36Sopenharmony_ci			task->t.regs.ebx = 0x0001;
114662306a36Sopenharmony_ci			break;
114762306a36Sopenharmony_ci		case FB_BLANK_NORMAL:
114862306a36Sopenharmony_ci			task->t.regs.ebx = 0x0101;	/* standby */
114962306a36Sopenharmony_ci			break;
115062306a36Sopenharmony_ci		case FB_BLANK_POWERDOWN:
115162306a36Sopenharmony_ci			task->t.regs.ebx = 0x0401;	/* powerdown */
115262306a36Sopenharmony_ci			break;
115362306a36Sopenharmony_ci		default:
115462306a36Sopenharmony_ci			goto out;
115562306a36Sopenharmony_ci		}
115662306a36Sopenharmony_ci
115762306a36Sopenharmony_ci		err = uvesafb_exec(task);
115862306a36Sopenharmony_ci		if (err || (task->t.regs.eax & 0xffff) != 0x004f)
115962306a36Sopenharmony_ci			err = 1;
116062306a36Sopenharmony_ciout:		uvesafb_free(task);
116162306a36Sopenharmony_ci	}
116262306a36Sopenharmony_ci	return err;
116362306a36Sopenharmony_ci}
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_cistatic int uvesafb_open(struct fb_info *info, int user)
116662306a36Sopenharmony_ci{
116762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
116862306a36Sopenharmony_ci	int cnt = atomic_read(&par->ref_count);
116962306a36Sopenharmony_ci	u8 *buf = NULL;
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_ci	if (!cnt && par->vbe_state_size) {
117262306a36Sopenharmony_ci		buf =  uvesafb_vbe_state_save(par);
117362306a36Sopenharmony_ci		if (IS_ERR(buf)) {
117462306a36Sopenharmony_ci			pr_warn("save hardware state failed, error code is %ld!\n",
117562306a36Sopenharmony_ci				PTR_ERR(buf));
117662306a36Sopenharmony_ci		} else {
117762306a36Sopenharmony_ci			par->vbe_state_orig = buf;
117862306a36Sopenharmony_ci		}
117962306a36Sopenharmony_ci	}
118062306a36Sopenharmony_ci
118162306a36Sopenharmony_ci	atomic_inc(&par->ref_count);
118262306a36Sopenharmony_ci	return 0;
118362306a36Sopenharmony_ci}
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_cistatic int uvesafb_release(struct fb_info *info, int user)
118662306a36Sopenharmony_ci{
118762306a36Sopenharmony_ci	struct uvesafb_ktask *task = NULL;
118862306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
118962306a36Sopenharmony_ci	int cnt = atomic_read(&par->ref_count);
119062306a36Sopenharmony_ci
119162306a36Sopenharmony_ci	if (!cnt)
119262306a36Sopenharmony_ci		return -EINVAL;
119362306a36Sopenharmony_ci
119462306a36Sopenharmony_ci	if (cnt != 1)
119562306a36Sopenharmony_ci		goto out;
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	task = uvesafb_prep();
119862306a36Sopenharmony_ci	if (!task)
119962306a36Sopenharmony_ci		goto out;
120062306a36Sopenharmony_ci
120162306a36Sopenharmony_ci	/* First, try to set the standard 80x25 text mode. */
120262306a36Sopenharmony_ci	task->t.regs.eax = 0x0003;
120362306a36Sopenharmony_ci	uvesafb_exec(task);
120462306a36Sopenharmony_ci
120562306a36Sopenharmony_ci	/*
120662306a36Sopenharmony_ci	 * Now try to restore whatever hardware state we might have
120762306a36Sopenharmony_ci	 * saved when the fb device was first opened.
120862306a36Sopenharmony_ci	 */
120962306a36Sopenharmony_ci	uvesafb_vbe_state_restore(par, par->vbe_state_orig);
121062306a36Sopenharmony_ciout:
121162306a36Sopenharmony_ci	atomic_dec(&par->ref_count);
121262306a36Sopenharmony_ci	uvesafb_free(task);
121362306a36Sopenharmony_ci	return 0;
121462306a36Sopenharmony_ci}
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_cistatic int uvesafb_set_par(struct fb_info *info)
121762306a36Sopenharmony_ci{
121862306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
121962306a36Sopenharmony_ci	struct uvesafb_ktask *task = NULL;
122062306a36Sopenharmony_ci	struct vbe_crtc_ib *crtc = NULL;
122162306a36Sopenharmony_ci	struct vbe_mode_ib *mode = NULL;
122262306a36Sopenharmony_ci	int i, err = 0, depth = info->var.bits_per_pixel;
122362306a36Sopenharmony_ci
122462306a36Sopenharmony_ci	if (depth > 8 && depth != 32)
122562306a36Sopenharmony_ci		depth = info->var.red.length + info->var.green.length +
122662306a36Sopenharmony_ci			info->var.blue.length;
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_ci	i = uvesafb_vbe_find_mode(par, info->var.xres, info->var.yres, depth,
122962306a36Sopenharmony_ci				 UVESAFB_EXACT_RES | UVESAFB_EXACT_DEPTH);
123062306a36Sopenharmony_ci	if (i >= 0)
123162306a36Sopenharmony_ci		mode = &par->vbe_modes[i];
123262306a36Sopenharmony_ci	else
123362306a36Sopenharmony_ci		return -EINVAL;
123462306a36Sopenharmony_ci
123562306a36Sopenharmony_ci	task = uvesafb_prep();
123662306a36Sopenharmony_ci	if (!task)
123762306a36Sopenharmony_ci		return -ENOMEM;
123862306a36Sopenharmony_cisetmode:
123962306a36Sopenharmony_ci	task->t.regs.eax = 0x4f02;
124062306a36Sopenharmony_ci	task->t.regs.ebx = mode->mode_id | 0x4000;	/* use LFB */
124162306a36Sopenharmony_ci
124262306a36Sopenharmony_ci	if (par->vbe_ib.vbe_version >= 0x0300 && !par->nocrtc &&
124362306a36Sopenharmony_ci	    info->var.pixclock != 0) {
124462306a36Sopenharmony_ci		task->t.regs.ebx |= 0x0800;		/* use CRTC data */
124562306a36Sopenharmony_ci		task->t.flags = TF_BUF_ESDI;
124662306a36Sopenharmony_ci		crtc = kzalloc(sizeof(struct vbe_crtc_ib), GFP_KERNEL);
124762306a36Sopenharmony_ci		if (!crtc) {
124862306a36Sopenharmony_ci			err = -ENOMEM;
124962306a36Sopenharmony_ci			goto out;
125062306a36Sopenharmony_ci		}
125162306a36Sopenharmony_ci		crtc->horiz_start = info->var.xres + info->var.right_margin;
125262306a36Sopenharmony_ci		crtc->horiz_end	  = crtc->horiz_start + info->var.hsync_len;
125362306a36Sopenharmony_ci		crtc->horiz_total = crtc->horiz_end + info->var.left_margin;
125462306a36Sopenharmony_ci
125562306a36Sopenharmony_ci		crtc->vert_start  = info->var.yres + info->var.lower_margin;
125662306a36Sopenharmony_ci		crtc->vert_end    = crtc->vert_start + info->var.vsync_len;
125762306a36Sopenharmony_ci		crtc->vert_total  = crtc->vert_end + info->var.upper_margin;
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci		crtc->pixel_clock = PICOS2KHZ(info->var.pixclock) * 1000;
126062306a36Sopenharmony_ci		crtc->refresh_rate = (u16)(100 * (crtc->pixel_clock /
126162306a36Sopenharmony_ci				(crtc->vert_total * crtc->horiz_total)));
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci		if (info->var.vmode & FB_VMODE_DOUBLE)
126462306a36Sopenharmony_ci			crtc->flags |= 0x1;
126562306a36Sopenharmony_ci		if (info->var.vmode & FB_VMODE_INTERLACED)
126662306a36Sopenharmony_ci			crtc->flags |= 0x2;
126762306a36Sopenharmony_ci		if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
126862306a36Sopenharmony_ci			crtc->flags |= 0x4;
126962306a36Sopenharmony_ci		if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
127062306a36Sopenharmony_ci			crtc->flags |= 0x8;
127162306a36Sopenharmony_ci		memcpy(&par->crtc, crtc, sizeof(*crtc));
127262306a36Sopenharmony_ci	} else {
127362306a36Sopenharmony_ci		memset(&par->crtc, 0, sizeof(*crtc));
127462306a36Sopenharmony_ci	}
127562306a36Sopenharmony_ci
127662306a36Sopenharmony_ci	task->t.buf_len = sizeof(struct vbe_crtc_ib);
127762306a36Sopenharmony_ci	task->buf = &par->crtc;
127862306a36Sopenharmony_ci
127962306a36Sopenharmony_ci	err = uvesafb_exec(task);
128062306a36Sopenharmony_ci	if (err || (task->t.regs.eax & 0xffff) != 0x004f) {
128162306a36Sopenharmony_ci		/*
128262306a36Sopenharmony_ci		 * The mode switch might have failed because we tried to
128362306a36Sopenharmony_ci		 * use our own timings.  Try again with the default timings.
128462306a36Sopenharmony_ci		 */
128562306a36Sopenharmony_ci		if (crtc != NULL) {
128662306a36Sopenharmony_ci			pr_warn("mode switch failed (eax=0x%x, err=%d) - trying again with default timings\n",
128762306a36Sopenharmony_ci				task->t.regs.eax, err);
128862306a36Sopenharmony_ci			uvesafb_reset(task);
128962306a36Sopenharmony_ci			kfree(crtc);
129062306a36Sopenharmony_ci			crtc = NULL;
129162306a36Sopenharmony_ci			info->var.pixclock = 0;
129262306a36Sopenharmony_ci			goto setmode;
129362306a36Sopenharmony_ci		} else {
129462306a36Sopenharmony_ci			pr_err("mode switch failed (eax=0x%x, err=%d)\n",
129562306a36Sopenharmony_ci			       task->t.regs.eax, err);
129662306a36Sopenharmony_ci			err = -EINVAL;
129762306a36Sopenharmony_ci			goto out;
129862306a36Sopenharmony_ci		}
129962306a36Sopenharmony_ci	}
130062306a36Sopenharmony_ci	par->mode_idx = i;
130162306a36Sopenharmony_ci
130262306a36Sopenharmony_ci	/* For 8bpp modes, always try to set the DAC to 8 bits. */
130362306a36Sopenharmony_ci	if (par->vbe_ib.capabilities & VBE_CAP_CAN_SWITCH_DAC &&
130462306a36Sopenharmony_ci	    mode->bits_per_pixel <= 8) {
130562306a36Sopenharmony_ci		uvesafb_reset(task);
130662306a36Sopenharmony_ci		task->t.regs.eax = 0x4f08;
130762306a36Sopenharmony_ci		task->t.regs.ebx = 0x0800;
130862306a36Sopenharmony_ci
130962306a36Sopenharmony_ci		err = uvesafb_exec(task);
131062306a36Sopenharmony_ci		if (err || (task->t.regs.eax & 0xffff) != 0x004f ||
131162306a36Sopenharmony_ci		    ((task->t.regs.ebx & 0xff00) >> 8) != 8) {
131262306a36Sopenharmony_ci			dac_width = 6;
131362306a36Sopenharmony_ci		} else {
131462306a36Sopenharmony_ci			dac_width = 8;
131562306a36Sopenharmony_ci		}
131662306a36Sopenharmony_ci	}
131762306a36Sopenharmony_ci
131862306a36Sopenharmony_ci	info->fix.visual = (info->var.bits_per_pixel == 8) ?
131962306a36Sopenharmony_ci				FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
132062306a36Sopenharmony_ci	info->fix.line_length = mode->bytes_per_scan_line;
132162306a36Sopenharmony_ci
132262306a36Sopenharmony_ciout:
132362306a36Sopenharmony_ci	kfree(crtc);
132462306a36Sopenharmony_ci	uvesafb_free(task);
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_ci	return err;
132762306a36Sopenharmony_ci}
132862306a36Sopenharmony_ci
132962306a36Sopenharmony_cistatic void uvesafb_check_limits(struct fb_var_screeninfo *var,
133062306a36Sopenharmony_ci		struct fb_info *info)
133162306a36Sopenharmony_ci{
133262306a36Sopenharmony_ci	const struct fb_videomode *mode;
133362306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_ci	/*
133662306a36Sopenharmony_ci	 * If pixclock is set to 0, then we're using default BIOS timings
133762306a36Sopenharmony_ci	 * and thus don't have to perform any checks here.
133862306a36Sopenharmony_ci	 */
133962306a36Sopenharmony_ci	if (!var->pixclock)
134062306a36Sopenharmony_ci		return;
134162306a36Sopenharmony_ci
134262306a36Sopenharmony_ci	if (par->vbe_ib.vbe_version < 0x0300) {
134362306a36Sopenharmony_ci		fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, var, info);
134462306a36Sopenharmony_ci		return;
134562306a36Sopenharmony_ci	}
134662306a36Sopenharmony_ci
134762306a36Sopenharmony_ci	if (!fb_validate_mode(var, info))
134862306a36Sopenharmony_ci		return;
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_ci	mode = fb_find_best_mode(var, &info->modelist);
135162306a36Sopenharmony_ci	if (mode) {
135262306a36Sopenharmony_ci		if (mode->xres == var->xres && mode->yres == var->yres &&
135362306a36Sopenharmony_ci		    !(mode->vmode & (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE))) {
135462306a36Sopenharmony_ci			fb_videomode_to_var(var, mode);
135562306a36Sopenharmony_ci			return;
135662306a36Sopenharmony_ci		}
135762306a36Sopenharmony_ci	}
135862306a36Sopenharmony_ci
135962306a36Sopenharmony_ci	if (info->monspecs.gtf && !fb_get_mode(FB_MAXTIMINGS, 0, var, info))
136062306a36Sopenharmony_ci		return;
136162306a36Sopenharmony_ci	/* Use default refresh rate */
136262306a36Sopenharmony_ci	var->pixclock = 0;
136362306a36Sopenharmony_ci}
136462306a36Sopenharmony_ci
136562306a36Sopenharmony_cistatic int uvesafb_check_var(struct fb_var_screeninfo *var,
136662306a36Sopenharmony_ci		struct fb_info *info)
136762306a36Sopenharmony_ci{
136862306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
136962306a36Sopenharmony_ci	struct vbe_mode_ib *mode = NULL;
137062306a36Sopenharmony_ci	int match = -1;
137162306a36Sopenharmony_ci	int depth = var->red.length + var->green.length + var->blue.length;
137262306a36Sopenharmony_ci
137362306a36Sopenharmony_ci	/*
137462306a36Sopenharmony_ci	 * Various apps will use bits_per_pixel to set the color depth,
137562306a36Sopenharmony_ci	 * which is theoretically incorrect, but which we'll try to handle
137662306a36Sopenharmony_ci	 * here.
137762306a36Sopenharmony_ci	 */
137862306a36Sopenharmony_ci	if (depth == 0 || abs(depth - var->bits_per_pixel) >= 8)
137962306a36Sopenharmony_ci		depth = var->bits_per_pixel;
138062306a36Sopenharmony_ci
138162306a36Sopenharmony_ci	match = uvesafb_vbe_find_mode(par, var->xres, var->yres, depth,
138262306a36Sopenharmony_ci						UVESAFB_EXACT_RES);
138362306a36Sopenharmony_ci	if (match == -1)
138462306a36Sopenharmony_ci		return -EINVAL;
138562306a36Sopenharmony_ci
138662306a36Sopenharmony_ci	mode = &par->vbe_modes[match];
138762306a36Sopenharmony_ci	uvesafb_setup_var(var, info, mode);
138862306a36Sopenharmony_ci
138962306a36Sopenharmony_ci	/*
139062306a36Sopenharmony_ci	 * Check whether we have remapped enough memory for this mode.
139162306a36Sopenharmony_ci	 * We might be called at an early stage, when we haven't remapped
139262306a36Sopenharmony_ci	 * any memory yet, in which case we simply skip the check.
139362306a36Sopenharmony_ci	 */
139462306a36Sopenharmony_ci	if (var->yres * mode->bytes_per_scan_line > info->fix.smem_len
139562306a36Sopenharmony_ci						&& info->fix.smem_len)
139662306a36Sopenharmony_ci		return -EINVAL;
139762306a36Sopenharmony_ci
139862306a36Sopenharmony_ci	if ((var->vmode & FB_VMODE_DOUBLE) &&
139962306a36Sopenharmony_ci				!(par->vbe_modes[match].mode_attr & 0x100))
140062306a36Sopenharmony_ci		var->vmode &= ~FB_VMODE_DOUBLE;
140162306a36Sopenharmony_ci
140262306a36Sopenharmony_ci	if ((var->vmode & FB_VMODE_INTERLACED) &&
140362306a36Sopenharmony_ci				!(par->vbe_modes[match].mode_attr & 0x200))
140462306a36Sopenharmony_ci		var->vmode &= ~FB_VMODE_INTERLACED;
140562306a36Sopenharmony_ci
140662306a36Sopenharmony_ci	uvesafb_check_limits(var, info);
140762306a36Sopenharmony_ci
140862306a36Sopenharmony_ci	var->xres_virtual = var->xres;
140962306a36Sopenharmony_ci	var->yres_virtual = (par->ypan) ?
141062306a36Sopenharmony_ci				info->fix.smem_len / mode->bytes_per_scan_line :
141162306a36Sopenharmony_ci				var->yres;
141262306a36Sopenharmony_ci	return 0;
141362306a36Sopenharmony_ci}
141462306a36Sopenharmony_ci
141562306a36Sopenharmony_cistatic struct fb_ops uvesafb_ops = {
141662306a36Sopenharmony_ci	.owner		= THIS_MODULE,
141762306a36Sopenharmony_ci	.fb_open	= uvesafb_open,
141862306a36Sopenharmony_ci	.fb_release	= uvesafb_release,
141962306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
142062306a36Sopenharmony_ci	.fb_setcolreg	= uvesafb_setcolreg,
142162306a36Sopenharmony_ci	.fb_setcmap	= uvesafb_setcmap,
142262306a36Sopenharmony_ci	.fb_pan_display	= uvesafb_pan_display,
142362306a36Sopenharmony_ci	.fb_blank	= uvesafb_blank,
142462306a36Sopenharmony_ci	.fb_check_var	= uvesafb_check_var,
142562306a36Sopenharmony_ci	.fb_set_par	= uvesafb_set_par,
142662306a36Sopenharmony_ci};
142762306a36Sopenharmony_ci
142862306a36Sopenharmony_cistatic void uvesafb_init_info(struct fb_info *info, struct vbe_mode_ib *mode)
142962306a36Sopenharmony_ci{
143062306a36Sopenharmony_ci	unsigned int size_vmode;
143162306a36Sopenharmony_ci	unsigned int size_remap;
143262306a36Sopenharmony_ci	unsigned int size_total;
143362306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
143462306a36Sopenharmony_ci	int i, h;
143562306a36Sopenharmony_ci
143662306a36Sopenharmony_ci	info->pseudo_palette = ((u8 *)info->par + sizeof(struct uvesafb_par));
143762306a36Sopenharmony_ci	info->fix = uvesafb_fix;
143862306a36Sopenharmony_ci	info->fix.ypanstep = par->ypan ? 1 : 0;
143962306a36Sopenharmony_ci	info->fix.ywrapstep = (par->ypan > 1) ? 1 : 0;
144062306a36Sopenharmony_ci
144162306a36Sopenharmony_ci	/* Disable blanking if the user requested so. */
144262306a36Sopenharmony_ci	if (!blank)
144362306a36Sopenharmony_ci		uvesafb_ops.fb_blank = NULL;
144462306a36Sopenharmony_ci
144562306a36Sopenharmony_ci	/*
144662306a36Sopenharmony_ci	 * Find out how much IO memory is required for the mode with
144762306a36Sopenharmony_ci	 * the highest resolution.
144862306a36Sopenharmony_ci	 */
144962306a36Sopenharmony_ci	size_remap = 0;
145062306a36Sopenharmony_ci	for (i = 0; i < par->vbe_modes_cnt; i++) {
145162306a36Sopenharmony_ci		h = par->vbe_modes[i].bytes_per_scan_line *
145262306a36Sopenharmony_ci					par->vbe_modes[i].y_res;
145362306a36Sopenharmony_ci		if (h > size_remap)
145462306a36Sopenharmony_ci			size_remap = h;
145562306a36Sopenharmony_ci	}
145662306a36Sopenharmony_ci	size_remap *= 2;
145762306a36Sopenharmony_ci
145862306a36Sopenharmony_ci	/*
145962306a36Sopenharmony_ci	 *   size_vmode -- that is the amount of memory needed for the
146062306a36Sopenharmony_ci	 *                 used video mode, i.e. the minimum amount of
146162306a36Sopenharmony_ci	 *                 memory we need.
146262306a36Sopenharmony_ci	 */
146362306a36Sopenharmony_ci	size_vmode = info->var.yres * mode->bytes_per_scan_line;
146462306a36Sopenharmony_ci
146562306a36Sopenharmony_ci	/*
146662306a36Sopenharmony_ci	 *   size_total -- all video memory we have. Used for mtrr
146762306a36Sopenharmony_ci	 *                 entries, resource allocation and bounds
146862306a36Sopenharmony_ci	 *                 checking.
146962306a36Sopenharmony_ci	 */
147062306a36Sopenharmony_ci	size_total = par->vbe_ib.total_memory * 65536;
147162306a36Sopenharmony_ci	if (vram_total)
147262306a36Sopenharmony_ci		size_total = vram_total * 1024 * 1024;
147362306a36Sopenharmony_ci	if (size_total < size_vmode)
147462306a36Sopenharmony_ci		size_total = size_vmode;
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_ci	/*
147762306a36Sopenharmony_ci	 *   size_remap -- the amount of video memory we are going to
147862306a36Sopenharmony_ci	 *                 use for vesafb.  With modern cards it is no
147962306a36Sopenharmony_ci	 *                 option to simply use size_total as th
148062306a36Sopenharmony_ci	 *                 wastes plenty of kernel address space.
148162306a36Sopenharmony_ci	 */
148262306a36Sopenharmony_ci	if (vram_remap)
148362306a36Sopenharmony_ci		size_remap = vram_remap * 1024 * 1024;
148462306a36Sopenharmony_ci	if (size_remap < size_vmode)
148562306a36Sopenharmony_ci		size_remap = size_vmode;
148662306a36Sopenharmony_ci	if (size_remap > size_total)
148762306a36Sopenharmony_ci		size_remap = size_total;
148862306a36Sopenharmony_ci
148962306a36Sopenharmony_ci	info->fix.smem_len = size_remap;
149062306a36Sopenharmony_ci	info->fix.smem_start = mode->phys_base_ptr;
149162306a36Sopenharmony_ci
149262306a36Sopenharmony_ci	/*
149362306a36Sopenharmony_ci	 * We have to set yres_virtual here because when setup_var() was
149462306a36Sopenharmony_ci	 * called, smem_len wasn't defined yet.
149562306a36Sopenharmony_ci	 */
149662306a36Sopenharmony_ci	info->var.yres_virtual = info->fix.smem_len /
149762306a36Sopenharmony_ci				 mode->bytes_per_scan_line;
149862306a36Sopenharmony_ci
149962306a36Sopenharmony_ci	if (par->ypan && info->var.yres_virtual > info->var.yres) {
150062306a36Sopenharmony_ci		pr_info("scrolling: %s using protected mode interface, yres_virtual=%d\n",
150162306a36Sopenharmony_ci			(par->ypan > 1) ? "ywrap" : "ypan",
150262306a36Sopenharmony_ci			info->var.yres_virtual);
150362306a36Sopenharmony_ci	} else {
150462306a36Sopenharmony_ci		pr_info("scrolling: redraw\n");
150562306a36Sopenharmony_ci		info->var.yres_virtual = info->var.yres;
150662306a36Sopenharmony_ci		par->ypan = 0;
150762306a36Sopenharmony_ci	}
150862306a36Sopenharmony_ci
150962306a36Sopenharmony_ci	info->flags = (par->ypan ? FBINFO_HWACCEL_YPAN : 0);
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_ci	if (!par->ypan)
151262306a36Sopenharmony_ci		uvesafb_ops.fb_pan_display = NULL;
151362306a36Sopenharmony_ci}
151462306a36Sopenharmony_ci
151562306a36Sopenharmony_cistatic void uvesafb_init_mtrr(struct fb_info *info)
151662306a36Sopenharmony_ci{
151762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
151862306a36Sopenharmony_ci
151962306a36Sopenharmony_ci	if (mtrr && !(info->fix.smem_start & (PAGE_SIZE - 1))) {
152062306a36Sopenharmony_ci		int temp_size = info->fix.smem_len;
152162306a36Sopenharmony_ci
152262306a36Sopenharmony_ci		int rc;
152362306a36Sopenharmony_ci
152462306a36Sopenharmony_ci		/* Find the largest power-of-two */
152562306a36Sopenharmony_ci		temp_size = roundup_pow_of_two(temp_size);
152662306a36Sopenharmony_ci
152762306a36Sopenharmony_ci		/* Try and find a power of two to add */
152862306a36Sopenharmony_ci		do {
152962306a36Sopenharmony_ci			rc = arch_phys_wc_add(info->fix.smem_start, temp_size);
153062306a36Sopenharmony_ci			temp_size >>= 1;
153162306a36Sopenharmony_ci		} while (temp_size >= PAGE_SIZE && rc == -EINVAL);
153262306a36Sopenharmony_ci
153362306a36Sopenharmony_ci		if (rc >= 0)
153462306a36Sopenharmony_ci			par->mtrr_handle = rc;
153562306a36Sopenharmony_ci	}
153662306a36Sopenharmony_ci}
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_cistatic void uvesafb_ioremap(struct fb_info *info)
153962306a36Sopenharmony_ci{
154062306a36Sopenharmony_ci	info->screen_base = ioremap_wc(info->fix.smem_start, info->fix.smem_len);
154162306a36Sopenharmony_ci}
154262306a36Sopenharmony_ci
154362306a36Sopenharmony_cistatic ssize_t uvesafb_show_vbe_ver(struct device *dev,
154462306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
154562306a36Sopenharmony_ci{
154662306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
154762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
154862306a36Sopenharmony_ci
154962306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%.4x\n", par->vbe_ib.vbe_version);
155062306a36Sopenharmony_ci}
155162306a36Sopenharmony_ci
155262306a36Sopenharmony_cistatic DEVICE_ATTR(vbe_version, S_IRUGO, uvesafb_show_vbe_ver, NULL);
155362306a36Sopenharmony_ci
155462306a36Sopenharmony_cistatic ssize_t uvesafb_show_vbe_modes(struct device *dev,
155562306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
155662306a36Sopenharmony_ci{
155762306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
155862306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
155962306a36Sopenharmony_ci	int ret = 0, i;
156062306a36Sopenharmony_ci
156162306a36Sopenharmony_ci	for (i = 0; i < par->vbe_modes_cnt && ret < PAGE_SIZE; i++) {
156262306a36Sopenharmony_ci		ret += scnprintf(buf + ret, PAGE_SIZE - ret,
156362306a36Sopenharmony_ci			"%dx%d-%d, 0x%.4x\n",
156462306a36Sopenharmony_ci			par->vbe_modes[i].x_res, par->vbe_modes[i].y_res,
156562306a36Sopenharmony_ci			par->vbe_modes[i].depth, par->vbe_modes[i].mode_id);
156662306a36Sopenharmony_ci	}
156762306a36Sopenharmony_ci
156862306a36Sopenharmony_ci	return ret;
156962306a36Sopenharmony_ci}
157062306a36Sopenharmony_ci
157162306a36Sopenharmony_cistatic DEVICE_ATTR(vbe_modes, S_IRUGO, uvesafb_show_vbe_modes, NULL);
157262306a36Sopenharmony_ci
157362306a36Sopenharmony_cistatic ssize_t uvesafb_show_vendor(struct device *dev,
157462306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
157562306a36Sopenharmony_ci{
157662306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
157762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
157862306a36Sopenharmony_ci
157962306a36Sopenharmony_ci	if (par->vbe_ib.oem_vendor_name_ptr)
158062306a36Sopenharmony_ci		return sysfs_emit(buf, "%s\n", (char *)
158162306a36Sopenharmony_ci			(&par->vbe_ib) + par->vbe_ib.oem_vendor_name_ptr);
158262306a36Sopenharmony_ci	else
158362306a36Sopenharmony_ci		return 0;
158462306a36Sopenharmony_ci}
158562306a36Sopenharmony_ci
158662306a36Sopenharmony_cistatic DEVICE_ATTR(oem_vendor, S_IRUGO, uvesafb_show_vendor, NULL);
158762306a36Sopenharmony_ci
158862306a36Sopenharmony_cistatic ssize_t uvesafb_show_product_name(struct device *dev,
158962306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
159062306a36Sopenharmony_ci{
159162306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
159262306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
159362306a36Sopenharmony_ci
159462306a36Sopenharmony_ci	if (par->vbe_ib.oem_product_name_ptr)
159562306a36Sopenharmony_ci		return sysfs_emit(buf, "%s\n", (char *)
159662306a36Sopenharmony_ci			(&par->vbe_ib) + par->vbe_ib.oem_product_name_ptr);
159762306a36Sopenharmony_ci	else
159862306a36Sopenharmony_ci		return 0;
159962306a36Sopenharmony_ci}
160062306a36Sopenharmony_ci
160162306a36Sopenharmony_cistatic DEVICE_ATTR(oem_product_name, S_IRUGO, uvesafb_show_product_name, NULL);
160262306a36Sopenharmony_ci
160362306a36Sopenharmony_cistatic ssize_t uvesafb_show_product_rev(struct device *dev,
160462306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
160562306a36Sopenharmony_ci{
160662306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
160762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
160862306a36Sopenharmony_ci
160962306a36Sopenharmony_ci	if (par->vbe_ib.oem_product_rev_ptr)
161062306a36Sopenharmony_ci		return sysfs_emit(buf, "%s\n", (char *)
161162306a36Sopenharmony_ci			(&par->vbe_ib) + par->vbe_ib.oem_product_rev_ptr);
161262306a36Sopenharmony_ci	else
161362306a36Sopenharmony_ci		return 0;
161462306a36Sopenharmony_ci}
161562306a36Sopenharmony_ci
161662306a36Sopenharmony_cistatic DEVICE_ATTR(oem_product_rev, S_IRUGO, uvesafb_show_product_rev, NULL);
161762306a36Sopenharmony_ci
161862306a36Sopenharmony_cistatic ssize_t uvesafb_show_oem_string(struct device *dev,
161962306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
162062306a36Sopenharmony_ci{
162162306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
162262306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
162362306a36Sopenharmony_ci
162462306a36Sopenharmony_ci	if (par->vbe_ib.oem_string_ptr)
162562306a36Sopenharmony_ci		return sysfs_emit(buf, "%s\n",
162662306a36Sopenharmony_ci			(char *)(&par->vbe_ib) + par->vbe_ib.oem_string_ptr);
162762306a36Sopenharmony_ci	else
162862306a36Sopenharmony_ci		return 0;
162962306a36Sopenharmony_ci}
163062306a36Sopenharmony_ci
163162306a36Sopenharmony_cistatic DEVICE_ATTR(oem_string, S_IRUGO, uvesafb_show_oem_string, NULL);
163262306a36Sopenharmony_ci
163362306a36Sopenharmony_cistatic ssize_t uvesafb_show_nocrtc(struct device *dev,
163462306a36Sopenharmony_ci		struct device_attribute *attr, char *buf)
163562306a36Sopenharmony_ci{
163662306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
163762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
163862306a36Sopenharmony_ci
163962306a36Sopenharmony_ci	return sysfs_emit(buf, "%d\n", par->nocrtc);
164062306a36Sopenharmony_ci}
164162306a36Sopenharmony_ci
164262306a36Sopenharmony_cistatic ssize_t uvesafb_store_nocrtc(struct device *dev,
164362306a36Sopenharmony_ci		struct device_attribute *attr, const char *buf, size_t count)
164462306a36Sopenharmony_ci{
164562306a36Sopenharmony_ci	struct fb_info *info = dev_get_drvdata(dev);
164662306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
164762306a36Sopenharmony_ci
164862306a36Sopenharmony_ci	if (count > 0) {
164962306a36Sopenharmony_ci		if (buf[0] == '0')
165062306a36Sopenharmony_ci			par->nocrtc = 0;
165162306a36Sopenharmony_ci		else
165262306a36Sopenharmony_ci			par->nocrtc = 1;
165362306a36Sopenharmony_ci	}
165462306a36Sopenharmony_ci	return count;
165562306a36Sopenharmony_ci}
165662306a36Sopenharmony_ci
165762306a36Sopenharmony_cistatic DEVICE_ATTR(nocrtc, S_IRUGO | S_IWUSR, uvesafb_show_nocrtc,
165862306a36Sopenharmony_ci			uvesafb_store_nocrtc);
165962306a36Sopenharmony_ci
166062306a36Sopenharmony_cistatic struct attribute *uvesafb_dev_attrs[] = {
166162306a36Sopenharmony_ci	&dev_attr_vbe_version.attr,
166262306a36Sopenharmony_ci	&dev_attr_vbe_modes.attr,
166362306a36Sopenharmony_ci	&dev_attr_oem_vendor.attr,
166462306a36Sopenharmony_ci	&dev_attr_oem_product_name.attr,
166562306a36Sopenharmony_ci	&dev_attr_oem_product_rev.attr,
166662306a36Sopenharmony_ci	&dev_attr_oem_string.attr,
166762306a36Sopenharmony_ci	&dev_attr_nocrtc.attr,
166862306a36Sopenharmony_ci	NULL,
166962306a36Sopenharmony_ci};
167062306a36Sopenharmony_ci
167162306a36Sopenharmony_cistatic const struct attribute_group uvesafb_dev_attgrp = {
167262306a36Sopenharmony_ci	.name = NULL,
167362306a36Sopenharmony_ci	.attrs = uvesafb_dev_attrs,
167462306a36Sopenharmony_ci};
167562306a36Sopenharmony_ci
167662306a36Sopenharmony_cistatic int uvesafb_probe(struct platform_device *dev)
167762306a36Sopenharmony_ci{
167862306a36Sopenharmony_ci	struct fb_info *info;
167962306a36Sopenharmony_ci	struct vbe_mode_ib *mode = NULL;
168062306a36Sopenharmony_ci	struct uvesafb_par *par;
168162306a36Sopenharmony_ci	int err = 0, i;
168262306a36Sopenharmony_ci
168362306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(*par) +	sizeof(u32) * 256, &dev->dev);
168462306a36Sopenharmony_ci	if (!info)
168562306a36Sopenharmony_ci		return -ENOMEM;
168662306a36Sopenharmony_ci
168762306a36Sopenharmony_ci	par = info->par;
168862306a36Sopenharmony_ci
168962306a36Sopenharmony_ci	err = uvesafb_vbe_init(info);
169062306a36Sopenharmony_ci	if (err) {
169162306a36Sopenharmony_ci		pr_err("vbe_init() failed with %d\n", err);
169262306a36Sopenharmony_ci		goto out;
169362306a36Sopenharmony_ci	}
169462306a36Sopenharmony_ci
169562306a36Sopenharmony_ci	info->fbops = &uvesafb_ops;
169662306a36Sopenharmony_ci
169762306a36Sopenharmony_ci	i = uvesafb_vbe_init_mode(info);
169862306a36Sopenharmony_ci	if (i < 0) {
169962306a36Sopenharmony_ci		err = -EINVAL;
170062306a36Sopenharmony_ci		goto out;
170162306a36Sopenharmony_ci	} else {
170262306a36Sopenharmony_ci		mode = &par->vbe_modes[i];
170362306a36Sopenharmony_ci	}
170462306a36Sopenharmony_ci
170562306a36Sopenharmony_ci	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) {
170662306a36Sopenharmony_ci		err = -ENXIO;
170762306a36Sopenharmony_ci		goto out;
170862306a36Sopenharmony_ci	}
170962306a36Sopenharmony_ci
171062306a36Sopenharmony_ci	uvesafb_init_info(info, mode);
171162306a36Sopenharmony_ci
171262306a36Sopenharmony_ci	if (!request_region(0x3c0, 32, "uvesafb")) {
171362306a36Sopenharmony_ci		pr_err("request region 0x3c0-0x3e0 failed\n");
171462306a36Sopenharmony_ci		err = -EIO;
171562306a36Sopenharmony_ci		goto out_mode;
171662306a36Sopenharmony_ci	}
171762306a36Sopenharmony_ci
171862306a36Sopenharmony_ci	if (!request_mem_region(info->fix.smem_start, info->fix.smem_len,
171962306a36Sopenharmony_ci				"uvesafb")) {
172062306a36Sopenharmony_ci		pr_err("cannot reserve video memory at 0x%lx\n",
172162306a36Sopenharmony_ci		       info->fix.smem_start);
172262306a36Sopenharmony_ci		err = -EIO;
172362306a36Sopenharmony_ci		goto out_reg;
172462306a36Sopenharmony_ci	}
172562306a36Sopenharmony_ci
172662306a36Sopenharmony_ci	uvesafb_init_mtrr(info);
172762306a36Sopenharmony_ci	uvesafb_ioremap(info);
172862306a36Sopenharmony_ci
172962306a36Sopenharmony_ci	if (!info->screen_base) {
173062306a36Sopenharmony_ci		pr_err("abort, cannot ioremap 0x%x bytes of video memory at 0x%lx\n",
173162306a36Sopenharmony_ci		       info->fix.smem_len, info->fix.smem_start);
173262306a36Sopenharmony_ci		err = -EIO;
173362306a36Sopenharmony_ci		goto out_mem;
173462306a36Sopenharmony_ci	}
173562306a36Sopenharmony_ci
173662306a36Sopenharmony_ci	platform_set_drvdata(dev, info);
173762306a36Sopenharmony_ci
173862306a36Sopenharmony_ci	if (register_framebuffer(info) < 0) {
173962306a36Sopenharmony_ci		pr_err("failed to register framebuffer device\n");
174062306a36Sopenharmony_ci		err = -EINVAL;
174162306a36Sopenharmony_ci		goto out_unmap;
174262306a36Sopenharmony_ci	}
174362306a36Sopenharmony_ci
174462306a36Sopenharmony_ci	pr_info("framebuffer at 0x%lx, mapped to 0x%p, using %dk, total %dk\n",
174562306a36Sopenharmony_ci		info->fix.smem_start, info->screen_base,
174662306a36Sopenharmony_ci		info->fix.smem_len / 1024, par->vbe_ib.total_memory * 64);
174762306a36Sopenharmony_ci	fb_info(info, "%s frame buffer device\n", info->fix.id);
174862306a36Sopenharmony_ci
174962306a36Sopenharmony_ci	err = sysfs_create_group(&dev->dev.kobj, &uvesafb_dev_attgrp);
175062306a36Sopenharmony_ci	if (err != 0)
175162306a36Sopenharmony_ci		fb_warn(info, "failed to register attributes\n");
175262306a36Sopenharmony_ci
175362306a36Sopenharmony_ci	return 0;
175462306a36Sopenharmony_ci
175562306a36Sopenharmony_ciout_unmap:
175662306a36Sopenharmony_ci	iounmap(info->screen_base);
175762306a36Sopenharmony_ciout_mem:
175862306a36Sopenharmony_ci	arch_phys_wc_del(par->mtrr_handle);
175962306a36Sopenharmony_ci	release_mem_region(info->fix.smem_start, info->fix.smem_len);
176062306a36Sopenharmony_ciout_reg:
176162306a36Sopenharmony_ci	release_region(0x3c0, 32);
176262306a36Sopenharmony_ciout_mode:
176362306a36Sopenharmony_ci	if (!list_empty(&info->modelist))
176462306a36Sopenharmony_ci		fb_destroy_modelist(&info->modelist);
176562306a36Sopenharmony_ci	fb_destroy_modedb(info->monspecs.modedb);
176662306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
176762306a36Sopenharmony_ciout:
176862306a36Sopenharmony_ci	kfree(par->vbe_modes);
176962306a36Sopenharmony_ci
177062306a36Sopenharmony_ci	framebuffer_release(info);
177162306a36Sopenharmony_ci	return err;
177262306a36Sopenharmony_ci}
177362306a36Sopenharmony_ci
177462306a36Sopenharmony_cistatic void uvesafb_remove(struct platform_device *dev)
177562306a36Sopenharmony_ci{
177662306a36Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(dev);
177762306a36Sopenharmony_ci	struct uvesafb_par *par = info->par;
177862306a36Sopenharmony_ci
177962306a36Sopenharmony_ci	sysfs_remove_group(&dev->dev.kobj, &uvesafb_dev_attgrp);
178062306a36Sopenharmony_ci	unregister_framebuffer(info);
178162306a36Sopenharmony_ci	release_region(0x3c0, 32);
178262306a36Sopenharmony_ci	iounmap(info->screen_base);
178362306a36Sopenharmony_ci	arch_phys_wc_del(par->mtrr_handle);
178462306a36Sopenharmony_ci	release_mem_region(info->fix.smem_start, info->fix.smem_len);
178562306a36Sopenharmony_ci	fb_destroy_modedb(info->monspecs.modedb);
178662306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
178762306a36Sopenharmony_ci
178862306a36Sopenharmony_ci	kfree(par->vbe_modes);
178962306a36Sopenharmony_ci	kfree(par->vbe_state_orig);
179062306a36Sopenharmony_ci	kfree(par->vbe_state_saved);
179162306a36Sopenharmony_ci
179262306a36Sopenharmony_ci	framebuffer_release(info);
179362306a36Sopenharmony_ci}
179462306a36Sopenharmony_ci
179562306a36Sopenharmony_cistatic struct platform_driver uvesafb_driver = {
179662306a36Sopenharmony_ci	.probe  = uvesafb_probe,
179762306a36Sopenharmony_ci	.remove_new = uvesafb_remove,
179862306a36Sopenharmony_ci	.driver = {
179962306a36Sopenharmony_ci		.name = "uvesafb",
180062306a36Sopenharmony_ci	},
180162306a36Sopenharmony_ci};
180262306a36Sopenharmony_ci
180362306a36Sopenharmony_cistatic struct platform_device *uvesafb_device;
180462306a36Sopenharmony_ci
180562306a36Sopenharmony_ci#ifndef MODULE
180662306a36Sopenharmony_cistatic int uvesafb_setup(char *options)
180762306a36Sopenharmony_ci{
180862306a36Sopenharmony_ci	char *this_opt;
180962306a36Sopenharmony_ci
181062306a36Sopenharmony_ci	if (!options || !*options)
181162306a36Sopenharmony_ci		return 0;
181262306a36Sopenharmony_ci
181362306a36Sopenharmony_ci	while ((this_opt = strsep(&options, ",")) != NULL) {
181462306a36Sopenharmony_ci		if (!*this_opt) continue;
181562306a36Sopenharmony_ci
181662306a36Sopenharmony_ci		if (!strcmp(this_opt, "redraw"))
181762306a36Sopenharmony_ci			ypan = 0;
181862306a36Sopenharmony_ci		else if (!strcmp(this_opt, "ypan"))
181962306a36Sopenharmony_ci			ypan = 1;
182062306a36Sopenharmony_ci		else if (!strcmp(this_opt, "ywrap"))
182162306a36Sopenharmony_ci			ypan = 2;
182262306a36Sopenharmony_ci		else if (!strcmp(this_opt, "vgapal"))
182362306a36Sopenharmony_ci			pmi_setpal = false;
182462306a36Sopenharmony_ci		else if (!strcmp(this_opt, "pmipal"))
182562306a36Sopenharmony_ci			pmi_setpal = true;
182662306a36Sopenharmony_ci		else if (!strncmp(this_opt, "mtrr:", 5))
182762306a36Sopenharmony_ci			mtrr = simple_strtoul(this_opt+5, NULL, 0);
182862306a36Sopenharmony_ci		else if (!strcmp(this_opt, "nomtrr"))
182962306a36Sopenharmony_ci			mtrr = 0;
183062306a36Sopenharmony_ci		else if (!strcmp(this_opt, "nocrtc"))
183162306a36Sopenharmony_ci			nocrtc = true;
183262306a36Sopenharmony_ci		else if (!strcmp(this_opt, "noedid"))
183362306a36Sopenharmony_ci			noedid = true;
183462306a36Sopenharmony_ci		else if (!strcmp(this_opt, "noblank"))
183562306a36Sopenharmony_ci			blank = false;
183662306a36Sopenharmony_ci		else if (!strncmp(this_opt, "vtotal:", 7))
183762306a36Sopenharmony_ci			vram_total = simple_strtoul(this_opt + 7, NULL, 0);
183862306a36Sopenharmony_ci		else if (!strncmp(this_opt, "vremap:", 7))
183962306a36Sopenharmony_ci			vram_remap = simple_strtoul(this_opt + 7, NULL, 0);
184062306a36Sopenharmony_ci		else if (!strncmp(this_opt, "maxhf:", 6))
184162306a36Sopenharmony_ci			maxhf = simple_strtoul(this_opt + 6, NULL, 0);
184262306a36Sopenharmony_ci		else if (!strncmp(this_opt, "maxvf:", 6))
184362306a36Sopenharmony_ci			maxvf = simple_strtoul(this_opt + 6, NULL, 0);
184462306a36Sopenharmony_ci		else if (!strncmp(this_opt, "maxclk:", 7))
184562306a36Sopenharmony_ci			maxclk = simple_strtoul(this_opt + 7, NULL, 0);
184662306a36Sopenharmony_ci		else if (!strncmp(this_opt, "vbemode:", 8))
184762306a36Sopenharmony_ci			vbemode = simple_strtoul(this_opt + 8, NULL, 0);
184862306a36Sopenharmony_ci		else if (this_opt[0] >= '0' && this_opt[0] <= '9') {
184962306a36Sopenharmony_ci			mode_option = this_opt;
185062306a36Sopenharmony_ci		} else {
185162306a36Sopenharmony_ci			pr_warn("unrecognized option %s\n", this_opt);
185262306a36Sopenharmony_ci		}
185362306a36Sopenharmony_ci	}
185462306a36Sopenharmony_ci
185562306a36Sopenharmony_ci	if (mtrr != 3 && mtrr != 0)
185662306a36Sopenharmony_ci		pr_warn("uvesafb: mtrr should be set to 0 or 3; %d is unsupported", mtrr);
185762306a36Sopenharmony_ci
185862306a36Sopenharmony_ci	return 0;
185962306a36Sopenharmony_ci}
186062306a36Sopenharmony_ci#endif /* !MODULE */
186162306a36Sopenharmony_ci
186262306a36Sopenharmony_cistatic ssize_t v86d_show(struct device_driver *dev, char *buf)
186362306a36Sopenharmony_ci{
186462306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%s\n", v86d_path);
186562306a36Sopenharmony_ci}
186662306a36Sopenharmony_ci
186762306a36Sopenharmony_cistatic ssize_t v86d_store(struct device_driver *dev, const char *buf,
186862306a36Sopenharmony_ci		size_t count)
186962306a36Sopenharmony_ci{
187062306a36Sopenharmony_ci	strncpy(v86d_path, buf, PATH_MAX - 1);
187162306a36Sopenharmony_ci	return count;
187262306a36Sopenharmony_ci}
187362306a36Sopenharmony_cistatic DRIVER_ATTR_RW(v86d);
187462306a36Sopenharmony_ci
187562306a36Sopenharmony_cistatic int uvesafb_init(void)
187662306a36Sopenharmony_ci{
187762306a36Sopenharmony_ci	int err;
187862306a36Sopenharmony_ci
187962306a36Sopenharmony_ci#ifndef MODULE
188062306a36Sopenharmony_ci	char *option = NULL;
188162306a36Sopenharmony_ci
188262306a36Sopenharmony_ci	if (fb_get_options("uvesafb", &option))
188362306a36Sopenharmony_ci		return -ENODEV;
188462306a36Sopenharmony_ci	uvesafb_setup(option);
188562306a36Sopenharmony_ci#endif
188662306a36Sopenharmony_ci	err = cn_add_callback(&uvesafb_cn_id, "uvesafb", uvesafb_cn_callback);
188762306a36Sopenharmony_ci	if (err)
188862306a36Sopenharmony_ci		return err;
188962306a36Sopenharmony_ci
189062306a36Sopenharmony_ci	err = platform_driver_register(&uvesafb_driver);
189162306a36Sopenharmony_ci
189262306a36Sopenharmony_ci	if (!err) {
189362306a36Sopenharmony_ci		uvesafb_device = platform_device_alloc("uvesafb", 0);
189462306a36Sopenharmony_ci		if (uvesafb_device)
189562306a36Sopenharmony_ci			err = platform_device_add(uvesafb_device);
189662306a36Sopenharmony_ci		else
189762306a36Sopenharmony_ci			err = -ENOMEM;
189862306a36Sopenharmony_ci
189962306a36Sopenharmony_ci		if (err) {
190062306a36Sopenharmony_ci			platform_device_put(uvesafb_device);
190162306a36Sopenharmony_ci			platform_driver_unregister(&uvesafb_driver);
190262306a36Sopenharmony_ci			cn_del_callback(&uvesafb_cn_id);
190362306a36Sopenharmony_ci			return err;
190462306a36Sopenharmony_ci		}
190562306a36Sopenharmony_ci
190662306a36Sopenharmony_ci		err = driver_create_file(&uvesafb_driver.driver,
190762306a36Sopenharmony_ci				&driver_attr_v86d);
190862306a36Sopenharmony_ci		if (err) {
190962306a36Sopenharmony_ci			pr_warn("failed to register attributes\n");
191062306a36Sopenharmony_ci			err = 0;
191162306a36Sopenharmony_ci		}
191262306a36Sopenharmony_ci	}
191362306a36Sopenharmony_ci	return err;
191462306a36Sopenharmony_ci}
191562306a36Sopenharmony_ci
191662306a36Sopenharmony_cimodule_init(uvesafb_init);
191762306a36Sopenharmony_ci
191862306a36Sopenharmony_cistatic void uvesafb_exit(void)
191962306a36Sopenharmony_ci{
192062306a36Sopenharmony_ci	struct uvesafb_ktask *task;
192162306a36Sopenharmony_ci
192262306a36Sopenharmony_ci	if (v86d_started) {
192362306a36Sopenharmony_ci		task = uvesafb_prep();
192462306a36Sopenharmony_ci		if (task) {
192562306a36Sopenharmony_ci			task->t.flags = TF_EXIT;
192662306a36Sopenharmony_ci			uvesafb_exec(task);
192762306a36Sopenharmony_ci			uvesafb_free(task);
192862306a36Sopenharmony_ci		}
192962306a36Sopenharmony_ci	}
193062306a36Sopenharmony_ci
193162306a36Sopenharmony_ci	driver_remove_file(&uvesafb_driver.driver, &driver_attr_v86d);
193262306a36Sopenharmony_ci	platform_device_unregister(uvesafb_device);
193362306a36Sopenharmony_ci	platform_driver_unregister(&uvesafb_driver);
193462306a36Sopenharmony_ci	cn_del_callback(&uvesafb_cn_id);
193562306a36Sopenharmony_ci}
193662306a36Sopenharmony_ci
193762306a36Sopenharmony_cimodule_exit(uvesafb_exit);
193862306a36Sopenharmony_ci
193962306a36Sopenharmony_cistatic int param_set_scroll(const char *val, const struct kernel_param *kp)
194062306a36Sopenharmony_ci{
194162306a36Sopenharmony_ci	ypan = 0;
194262306a36Sopenharmony_ci
194362306a36Sopenharmony_ci	if (!strcmp(val, "redraw"))
194462306a36Sopenharmony_ci		ypan = 0;
194562306a36Sopenharmony_ci	else if (!strcmp(val, "ypan"))
194662306a36Sopenharmony_ci		ypan = 1;
194762306a36Sopenharmony_ci	else if (!strcmp(val, "ywrap"))
194862306a36Sopenharmony_ci		ypan = 2;
194962306a36Sopenharmony_ci	else
195062306a36Sopenharmony_ci		return -EINVAL;
195162306a36Sopenharmony_ci
195262306a36Sopenharmony_ci	return 0;
195362306a36Sopenharmony_ci}
195462306a36Sopenharmony_cistatic const struct kernel_param_ops param_ops_scroll = {
195562306a36Sopenharmony_ci	.set = param_set_scroll,
195662306a36Sopenharmony_ci};
195762306a36Sopenharmony_ci#define param_check_scroll(name, p) __param_check(name, p, void)
195862306a36Sopenharmony_ci
195962306a36Sopenharmony_cimodule_param_named(scroll, ypan, scroll, 0);
196062306a36Sopenharmony_ciMODULE_PARM_DESC(scroll,
196162306a36Sopenharmony_ci	"Scrolling mode, set to 'redraw', 'ypan', or 'ywrap'");
196262306a36Sopenharmony_cimodule_param_named(vgapal, pmi_setpal, invbool, 0);
196362306a36Sopenharmony_ciMODULE_PARM_DESC(vgapal, "Set palette using VGA registers");
196462306a36Sopenharmony_cimodule_param_named(pmipal, pmi_setpal, bool, 0);
196562306a36Sopenharmony_ciMODULE_PARM_DESC(pmipal, "Set palette using PMI calls");
196662306a36Sopenharmony_cimodule_param(mtrr, uint, 0);
196762306a36Sopenharmony_ciMODULE_PARM_DESC(mtrr,
196862306a36Sopenharmony_ci	"Memory Type Range Registers setting. Use 0 to disable.");
196962306a36Sopenharmony_cimodule_param(blank, bool, 0);
197062306a36Sopenharmony_ciMODULE_PARM_DESC(blank, "Enable hardware blanking");
197162306a36Sopenharmony_cimodule_param(nocrtc, bool, 0);
197262306a36Sopenharmony_ciMODULE_PARM_DESC(nocrtc, "Ignore CRTC timings when setting modes");
197362306a36Sopenharmony_cimodule_param(noedid, bool, 0);
197462306a36Sopenharmony_ciMODULE_PARM_DESC(noedid,
197562306a36Sopenharmony_ci	"Ignore EDID-provided monitor limits when setting modes");
197662306a36Sopenharmony_cimodule_param(vram_remap, uint, 0);
197762306a36Sopenharmony_ciMODULE_PARM_DESC(vram_remap, "Set amount of video memory to be used [MiB]");
197862306a36Sopenharmony_cimodule_param(vram_total, uint, 0);
197962306a36Sopenharmony_ciMODULE_PARM_DESC(vram_total, "Set total amount of video memory [MiB]");
198062306a36Sopenharmony_cimodule_param(maxclk, ushort, 0);
198162306a36Sopenharmony_ciMODULE_PARM_DESC(maxclk, "Maximum pixelclock [MHz], overrides EDID data");
198262306a36Sopenharmony_cimodule_param(maxhf, ushort, 0);
198362306a36Sopenharmony_ciMODULE_PARM_DESC(maxhf,
198462306a36Sopenharmony_ci	"Maximum horizontal frequency [kHz], overrides EDID data");
198562306a36Sopenharmony_cimodule_param(maxvf, ushort, 0);
198662306a36Sopenharmony_ciMODULE_PARM_DESC(maxvf,
198762306a36Sopenharmony_ci	"Maximum vertical frequency [Hz], overrides EDID data");
198862306a36Sopenharmony_cimodule_param(mode_option, charp, 0);
198962306a36Sopenharmony_ciMODULE_PARM_DESC(mode_option,
199062306a36Sopenharmony_ci	"Specify initial video mode as \"<xres>x<yres>[-<bpp>][@<refresh>]\"");
199162306a36Sopenharmony_cimodule_param(vbemode, ushort, 0);
199262306a36Sopenharmony_ciMODULE_PARM_DESC(vbemode,
199362306a36Sopenharmony_ci	"VBE mode number to set, overrides the 'mode' option");
199462306a36Sopenharmony_cimodule_param_string(v86d, v86d_path, PATH_MAX, 0660);
199562306a36Sopenharmony_ciMODULE_PARM_DESC(v86d, "Path to the v86d userspace helper.");
199662306a36Sopenharmony_ci
199762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
199862306a36Sopenharmony_ciMODULE_AUTHOR("Michal Januszewski <spock@gentoo.org>");
199962306a36Sopenharmony_ciMODULE_DESCRIPTION("Framebuffer driver for VBE2.0+ compliant graphics boards");
200062306a36Sopenharmony_ci
2001