patch-2.2.15 linux/arch/i386/kernel/apm.c

Next file: linux/arch/i386/kernel/bios32.c
Previous file: linux/arch/i386/defconfig
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from ../../exclude v2.2.14/arch/i386/kernel/apm.c linux/arch/i386/kernel/apm.c
@@ -1,6 +1,6 @@
 /* -*- linux-c -*-
  * APM BIOS driver for Linux
- * Copyright 1994-1999 Stephen Rothwell (sfr@linuxcare.com)
+ * Copyright 1994-2000 Stephen Rothwell (sfr@linuxcare.com)
  *
  * Initial development of this driver was funded by NEC Australia P/L
  *	and NEC Corporation
@@ -33,6 +33,9 @@
  * Nov 1998, Version 1.7
  * Jan 1999, Version 1.8
  * Jan 1999, Version 1.9
+ * Oct 1999, Version 1.10
+ * Nov 1999, Version 1.11
+ * Jan 2000, Version 1.12
  *
  * History:
  *    0.6b: first version in official kernel, Linux 1.3.46
@@ -90,6 +93,39 @@
  *         Use CONFIG_SMP instead of __SMP__
  *         Ignore BOUNCES for three seconds.
  *         Stephen Rothwell
+ *   1.10: Fix for Thinkpad return code.
+ *         Merge 2.2 and 2.3 drivers.
+ *         Remove APM dependencies in arch/i386/kernel/process.c
+ *         Remove APM dependencies in drivers/char/sysrq.c
+ *         Reset time across standby.
+ *         Allow more inititialisation on SMP.
+ *         Remove CONFIG_APM_POWER_OFF and make it boot time
+ *         configurable (default on).
+ *         Make debug only a boot time parameter (remove APM_DEBUG).
+ *         Try to blank all devices on any error.
+ *   1.11: Remove APM dependencies in drivers/char/console.c
+ *         Fix for bioses that don't zero the top part of the
+ *         entrypoint offset (Mario Sitta <sitta@al.unipmn.it>)
+ *         (reported by Panos Katsaloulis <teras@writeme.com>).
+ *         Real mode power off patch (Walter Hofmann
+ *         <Walter.Hofmann@physik.stud.uni-erlangen.de>).
+ *   1.12: Remove CONFIG_SMP as the compiler will optimize
+ *         the code away anyway (smp_num_cpus == 1 in UP)
+ *         noted by Artur Skawina <skawina@geocities.com>.
+ *         Make power off under SMP work again.
+ *         Fix thinko with initial engaging of BIOS.
+ *         Make sure power off only happens on CPU 0
+ *         (Paul "Rusty" Russell <rusty@linuxcare.com>).
+ *         Do error notification to user mode if BIOS calls fail.
+ *         Move entrypoint offset fix to ...boot/setup.S
+ *         where it belongs (Cosmos <gis88564@cis.nctu.edu.tw>).
+ *         Remove smp-power-off. SMP users must now specify
+ *         "apm=power-off" on the kernel command line. Suggested
+ *         by Jim Avera <jima@hal.com>, modified by Alan Cox
+ *         <alan@lxorguk.ukuu.org.uk>.
+ *         Register the /proc/apm entry even on SMP so that
+ *         scripts that check for it before doing power off
+ *         work (Jim Avera <jima@hal.com>).
  *
  * APM 1.1 Reference:
  *
@@ -106,11 +142,8 @@
  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
  *
- * [This document is available from Intel at:
- *    http://www.intel.com/IAL/powermgm
- *  or Microsoft at
- *    http://www.microsoft.com/windows/thirdparty/hardware/pcfuture.htm
- * ]
+ * [This document is available from Microsoft at:
+ *    http://www.microsoft.com/hwdev/busbios/amp_12.htm]
  */
 
 #include <linux/config.h>
@@ -122,21 +155,44 @@
 #include <linux/timer.h>
 #include <linux/fcntl.h>
 #include <linux/malloc.h>
-#include <linux/linkage.h>
 #include <linux/stat.h>
 #include <linux/proc_fs.h>
 #include <linux/miscdevice.h>
 #include <linux/apm_bios.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <asm/desc.h>
+#include <asm/softirq.h>
+
+/* 2.2-2.3 Compatability defines */
+#define DECLARE_WAIT_QUEUE_HEAD(w) struct wait_queue *w = NULL
+#define DECLARE_WAITQUEUE(w,c) struct wait_queue w = {(c), NULL}
+#define __setup(x, y)
+#define module_init(x)
+#define set_current_state(a) current->state = (a)
+#define create_proc_info_entry(n, m, b, g) \
+	{ \
+		struct proc_dir_entry *r = create_proc_entry(n, m, b); \
+		if (r) r->get_info = g; \
+	}
 
 EXPORT_SYMBOL(apm_register_callback);
 EXPORT_SYMBOL(apm_unregister_callback);
 
 extern unsigned long get_cmos_time(void);
+extern void machine_real_restart(unsigned char *, int);
+
+extern void (*acpi_idle)(void);
+extern void (*acpi_power_off)(void);
+#ifdef CONFIG_MAGIC_SYSRQ
+extern void (*sysrq_power_off)(void);
+#endif
+#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
+extern int (*console_blank_hook)(int);
+#endif
 
 /*
  * The apm_bios device is one of the misc char devices.
@@ -144,51 +200,13 @@
  */
 #define	APM_MINOR_DEV	134
 
-/* Configurable options:
- *
- * CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
- * This is necessary on the NEC Versa M series, which generates these when
- * resuming from SYSTEM SUSPEND.  However, enabling this on other laptops
- * will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
- * USER SUSPEND is ignored -- this may prevent the APM driver from updating
- * the system time on a RESUME.
- *
- * CONFIG_APM_DO_ENABLE: enable APM features at boot time.  From page 36 of
- * the specification: "When disabled, the APM BIOS does not automatically
- * power manage devices, enter the Standby State, enter the Suspend State,
- * or take power saving steps in response to CPU Idle calls."  This driver
- * will make CPU Idle calls when Linux is idle (unless this feature is
- * turned off -- see below).  This should always save battery power, but
- * more complicated APM features will be dependent on your BIOS
- * implementation.  You may need to turn this option off if your computer
- * hangs at boot time when using APM support, or if it beeps continuously
- * instead of suspending.  Turn this off if you have a NEC UltraLite Versa
- * 33/C or a Toshiba T400CDT.  This is off by default since most machines
- * do fine without this feature.
- *
- * CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the
- * idle loop.  On some machines, this can activate improved power savings,
- * such as a slowed CPU clock rate, when the machine is idle.  These idle
- * call is made after the idle loop has run for some length of time (e.g.,
- * 333 mS).  On some machines, this will cause a hang at boot time or
- * whenever the CPU becomes idle.
- *
- * CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM.  Some
- * laptops can use this to turn of the LCD backlight when the VC screen
- * blanker blanks the screen.  Note that this is only used by the VC screen
- * blanker, and probably won't turn off the backlight when using X11.  Some
- * problems have been reported when using this option with gpm (if you'd
- * like to debug this, please do so).
- *
- * CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist
- * on returning multiple suspend/standby events whenever one occurs.  We
- * really only need one at a time, so just ignore any beyond the first.
- * This is probably safe on most laptops.
- *
- * If you are debugging the APM support for your laptop, note that code for
- * all of these options is contained in this file, so you can #define or
- * #undef these on the next line to avoid recompiling the whole kernel.
+/*
+ * See Documentation/Config.help for the configuration options.
  *
+ * Various options can be changed at boot time as follows:
+ *	apm=on/off			enable/disable APM
+ *	    [no-]debug			log some debugging messages
+ *	    [no-]power-off		power off on shutdown
  */
 
 /* KNOWN PROBLEM MACHINES:
@@ -207,11 +225,6 @@
  */
 
 /*
- * Define to have debug messages.
- */
-#undef APM_DEBUG
-
-/*
  * Define to always call the APM BIOS busy routine even if the clock was
  * not slowed by the idle routine.
  */
@@ -255,7 +268,7 @@
 
 /*
  * If CONFIG_APM_IGNORE_SUSPEND_BOUNCE is defined then
- * ignore suspend events for this amount of time
+ * ignore suspend events for this amount of time after a resume
  */
 #define BOUNCE_INTERVAL		(3 * HZ)
 
@@ -266,25 +279,32 @@
 		__asm__ __volatile__("movl %%" #seg ",%0" : "=m" (where))
 
 /*
- * Forward declarations
+ * Maximum number of events stored
  */
-static void	suspend(void);
-static void	standby(void);
-static void	set_time(void);
-
-static void	check_events(void);
-static void	do_apm_timer(unsigned long);
-
-static int	do_open(struct inode *, struct file *);
-static int	do_release(struct inode *, struct file *);
-static ssize_t	do_read(struct file *, char *, size_t , loff_t *);
-static unsigned int do_poll(struct file *, poll_table *);
-static int	do_ioctl(struct inode *, struct file *, u_int, u_long);
+#define APM_MAX_EVENTS		20
 
-static int	apm_get_info(char *, char **, off_t, int, int);
+/*
+ * The per-file APM data
+ */
+struct apm_user {
+	int		magic;
+	struct apm_user *	next;
+	int		suser: 1;
+	int		suspend_wait: 1;
+	int		suspend_result;
+	int		suspends_pending;
+	int		standbys_pending;
+	int		suspends_read;
+	int		standbys_read;
+	int		event_head;
+	int		event_tail;
+	apm_event_t	events[APM_MAX_EVENTS];
+};
 
-extern int	apm_register_callback(int (*)(apm_event_t));
-extern void	apm_unregister_callback(int (*)(apm_event_t));
+/*
+ * The magic number in apm_user
+ */
+#define APM_BIOS_MAGIC		0x4101
 
 /*
  * Local variables
@@ -293,8 +313,6 @@
 	unsigned long	offset;
 	unsigned short	segment;
 }				apm_bios_entry;
-static int			apm_enabled = 0;
-static int			smp_hack = 0;
 #ifdef CONFIG_APM_CPU_IDLE
 static int			clock_slowed = 0;
 #endif
@@ -313,15 +331,20 @@
 #endif
 static int			debug = 0;
 static int			apm_disabled = 0;
+#ifdef CONFIG_SMP
+static int			power_off_enabled = 0;
+#else
+static int			power_off_enabled = 1;
+#endif
 
-static struct wait_queue *	process_list = NULL;
-static struct apm_bios_struct *	user_list = NULL;
+static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
+static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
+static struct apm_user *	user_list = NULL;
 
 static struct timer_list	apm_timer;
 
-static char			driver_version[] = "1.9";	/* no spaces */
+static char			driver_version[] = "1.12";	/* no spaces */
 
-#ifdef APM_DEBUG
 static char *	apm_event_name[] = {
 	"system standby",
 	"system suspend",
@@ -338,28 +361,6 @@
 };
 #define NR_APM_EVENT_NAME	\
 		(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
-#endif
-
-static struct file_operations apm_bios_fops = {
-	NULL,		/* lseek */
-	do_read,
-	NULL,		/* write */
-	NULL,		/* readdir */
-	do_poll,
-	do_ioctl,
-	NULL,		/* mmap */
-	do_open,
-	NULL,		/* flush */
-	do_release,
-	NULL,		/* fsync */
-	NULL		/* fasync */
-};
-
-static struct miscdevice apm_device = {
-	APM_MINOR_DEV,
-	"apm",
-	&apm_bios_fops
-};
 
 typedef struct callback_list_t {
 	int (*				callback)(apm_event_t);
@@ -416,9 +417,10 @@
 #	define APM_DO_CLI
 #endif
 #ifdef APM_ZERO_SEGS
+#	define APM_DECL_SEGS \
+		unsigned int saved_fs; unsigned int saved_gs;
 #	define APM_DO_SAVE_SEGS \
-		savesegment(fs, saved_fs); \
-		savesegment(gs, saved_gs)
+		savesegment(fs, saved_fs); savesegment(gs, saved_gs)
 #	define APM_DO_ZERO_SEGS \
 		"pushl %%ds\n\t" \
 		"pushl %%es\n\t" \
@@ -431,25 +433,28 @@
 		"popl %%es\n\t" \
 		"popl %%ds\n\t"
 #	define APM_DO_RESTORE_SEGS \
-		loadsegment(fs, saved_fs); \
-		loadsegment(gs, saved_gs)
+		loadsegment(fs, saved_fs); loadsegment(gs, saved_gs)
 #else
+#	define APM_DECL_SEGS
 #	define APM_DO_SAVE_SEGS
 #	define APM_DO_ZERO_SEGS
 #	define APM_DO_POP_SEGS
 #	define APM_DO_RESTORE_SEGS
 #endif
 
-static u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
+static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
 	u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
 {
-	unsigned int	saved_fs;
-	unsigned int	saved_gs;
+	APM_DECL_SEGS
 	unsigned long	flags;
 
 	__save_flags(flags);
 	APM_DO_CLI;
 	APM_DO_SAVE_SEGS;
+	/*
+	 * N.B. We do NOT need a cld after the BIOS call
+	 * because we always save and restore the flags.
+	 */
 	__asm__ __volatile__(APM_DO_ZERO_SEGS
 		"pushl %%edi\n\t"
 		"pushl %%ebp\n\t"
@@ -460,7 +465,7 @@
 		APM_DO_POP_SEGS
 		: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
 		  "=S" (*esi)
-		: "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+		: "a" (func), "b" (ebx_in), "c" (ecx_in)
 		: "memory", "cc");
 	APM_DO_RESTORE_SEGS;
 	__restore_flags(flags);
@@ -471,12 +476,10 @@
  * This version only returns one value (usually an error code)
  */
 
-static u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
-	u32 *eax)
+static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
 {
 	u8		error;
-	unsigned int	saved_fs;
-	unsigned int	saved_gs;
+	APM_DECL_SEGS
 	unsigned long	flags;
 
 	__save_flags(flags);
@@ -485,6 +488,10 @@
 	{
 		int	cx, dx, si;
 
+		/*
+		 * N.B. We do NOT need a cld after the BIOS call
+		 * because we always save and restore the flags.
+		 */
 		__asm__ __volatile__(APM_DO_ZERO_SEGS
 			"pushl %%edi\n\t"
 			"pushl %%ebp\n\t"
@@ -495,7 +502,7 @@
 			APM_DO_POP_SEGS
 			: "=a" (*eax), "=b" (error), "=c" (cx), "=d" (dx),
 			  "=S" (si)
-			: "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+			: "a" (func), "b" (ebx_in), "c" (ecx_in)
 			: "memory", "cc");
 	}
 	APM_DO_RESTORE_SEGS;
@@ -503,11 +510,11 @@
 	return error;
 }
 
-static int apm_driver_version(u_short *val)
+static int __init apm_driver_version(u_short *val)
 {
 	u32	eax;
 
-	if (apm_bios_call_simple(0x530e, 0, *val, &eax))
+	if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax))
 		return (eax >> 8) & 0xff;
 	*val = eax;
 	return APM_SUCCESS;
@@ -520,7 +527,8 @@
 	u32	ecx;
 	u32	dummy;
 
-	if (apm_bios_call(0x530b, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy))
+	if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx,
+			&dummy, &dummy))
 		return (eax >> 8) & 0xff;
 	*event = ebx;
 	if (apm_bios_info.version < 0x0102)
@@ -534,58 +542,122 @@
 {
 	u32	eax;
 
-	if (apm_bios_call_simple(0x5307, what, state, &eax))
+	if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax))
 		return (eax >> 8) & 0xff;
 	return APM_SUCCESS;
 }
 
 static int apm_set_power_state(u_short state)
 {
-	return set_power_state(0x0001, state);
+	return set_power_state(APM_DEVICE_ALL, state);
 }
 
-void apm_power_off(void)
+#ifdef CONFIG_APM_CPU_IDLE
+static int apm_do_idle(void)
 {
-	/*
-	 * smp_hack == 2 means that we would have enabled APM support
-	 * except there is more than one processor and so most of
-	 * the APM stuff is unsafe.  We will still try power down
-	 * because is is useful to some people and they know what
-	 * they are doing because they booted with the smp-power-off
-	 * kernel option.
-	 */
-	if (apm_enabled || (smp_hack == 2))
-		(void) apm_set_power_state(APM_STATE_OFF);
+	u32	dummy;
+
+	if (apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &dummy))
+		return 0;
+
+#ifdef ALWAYS_CALL_BUSY
+	clock_slowed = 1;
+#else
+	clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
+#endif
+	return 1;
 }
 
-#ifdef CONFIG_APM_DISPLAY_BLANK
-/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
-static int apm_set_display_power_state(u_short state)
+static void apm_do_busy(void)
 {
-	int	error;
+	u32	dummy;
 
-	/* Blank the first display device */
-	error = set_power_state(0x0100, state);
-	if (error == APM_BAD_DEVICE)
-		/* try to blank them all instead */
-		error = set_power_state(0x01ff, state);
-	return error;
+	if (clock_slowed) {
+		(void) apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy);
+		clock_slowed = 0;
+	}
+}
+
+extern int hlt_counter;
+
+static void apm_cpu_idle(void)
+{
+	while (!current->need_resched) {
+		if (!boot_cpu_data.hlt_works_ok)
+			continue;
+		if (hlt_counter)
+			continue;
+		/* If there is an error calling the idle routine,
+		 we should hlt if possible.  We need to check
+		 need_resched again because an interrupt
+		 may have occurred in apm_do_idle(). */
+		start_bh_atomic();
+		if (!apm_do_idle() && !current->need_resched)
+			asm volatile("sti ; hlt" : : : "memory");
+		end_bh_atomic();
+ 		if (current->need_resched) 
+ 			break;
+		schedule();
+	}
+	apm_do_busy();
 }
 #endif
 
-#ifdef CONFIG_APM_DO_ENABLE
-static int __init apm_enable_power_management(void)
+#ifdef CONFIG_SMP
+static int apm_magic(void * unused)
+{
+	while (1)
+		schedule();
+}
+#endif
+
+static void apm_power_off(void)
+{
+#ifdef CONFIG_APM_REAL_MODE_POWER_OFF
+	unsigned char	po_bios_call[] = {
+		0xb8, 0x00, 0x10,	/* movw  $0x1000,ax  */
+		0x8e, 0xd0,		/* movw  ax,ss       */
+		0xbc, 0x00, 0xf0,	/* movw  $0xf000,sp  */
+		0xb8, 0x07, 0x53,	/* movw  $0x5307,ax  */
+		0xbb, 0x01, 0x00,	/* movw  $0x0001,bx  */
+		0xb9, 0x03, 0x00,	/* movw  $0x0003,cx  */
+		0xcd, 0x15		/* int   $0x15       */
+	};
+#endif
+
+	/*
+	 * This may be called on an SMP machine.
+	 */
+#ifdef CONFIG_SMP
+	/* Many bioses don't like being called from CPU != 0 */
+	while (cpu_number_map[smp_processor_id()] != 0) {
+		kernel_thread(apm_magic, NULL,
+			CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
+		schedule();
+	}
+#endif
+#ifdef CONFIG_APM_REAL_MODE_POWER_OFF
+	machine_real_restart(po_bios_call, sizeof(po_bios_call));
+#else
+	(void) apm_set_power_state(APM_STATE_OFF);
+#endif
+}
+
+static int apm_enable_power_management(int enable)
 {
 	u32	eax;
 
-	if (apm_bios_call_simple(0x5308,
-			(apm_bios_info.version > 0x100) ? 0x0001 : 0xffff,
-			1, &eax))
+	if ((enable == 0) && (apm_bios_info.flags & APM_BIOS_DISENGAGED))
+		return APM_NOT_ENGAGED;
+	if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
+			enable, &eax))
 		return (eax >> 8) & 0xff;
-	apm_bios_info.flags &= ~APM_BIOS_DISABLED;
+	if (enable)
+		apm_bios_info.flags &= ~APM_BIOS_DISABLED;
+	else
+		apm_bios_info.flags |= APM_BIOS_DISABLED;
 	return APM_SUCCESS;
 }
-#endif
 
 static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
 {
@@ -595,7 +667,8 @@
 	u32	edx;
 	u32	dummy;
 
-	if (apm_bios_call(0x530a, 1, 0, &eax, &ebx, &ecx, &edx, &dummy))
+	if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0,
+			&eax, &ebx, &ecx, &edx, &dummy))
 		return (eax >> 8) & 0xff;
 	*status = ebx;
 	*bat = ecx;
@@ -603,6 +676,7 @@
 	return APM_SUCCESS;
 }
 
+#if 0
 static int apm_get_battery_status(u_short which, u_short *status,
 				  u_short *bat, u_short *life, u_short *nbat)
 {
@@ -620,7 +694,7 @@
 		return apm_get_power_status(status, bat, life);
 	}
 
-	if (apm_bios_call(0x530a, (0x8000 | (which)), 0, &eax,
+	if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax,
 			&ebx, &ecx, &edx, &esi))
 		return (eax >> 8) & 0xff;
 	*status = ebx;
@@ -629,13 +703,23 @@
 	*nbat = esi;
 	return APM_SUCCESS;
 }
+#endif
 
-static int __init apm_engage_power_management(u_short device)
+static int apm_engage_power_management(u_short device, int enable)
 {
 	u32	eax;
 
-	if (apm_bios_call_simple(0x530f, device, 1, &eax))
+	if ((enable == 0) && (device == APM_DEVICE_ALL)
+	    && (apm_bios_info.flags & APM_BIOS_DISABLED))
+		return APM_DISABLED;
+	if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable, &eax))
 		return (eax >> 8) & 0xff;
+	if (device == APM_DEVICE_ALL) {
+		if (enable)
+			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
+		else
+			apm_bios_info.flags |= APM_BIOS_DISENGAGED;
+	}
 	return APM_SUCCESS;
 }
 
@@ -652,37 +736,28 @@
 			str, err);
 }
 
-/* Called from console driver -- must make sure apm_enabled. */
-int apm_display_blank(void)
+#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
+static int apm_console_blank(int blank)
 {
-#ifdef CONFIG_APM_DISPLAY_BLANK
 	int	error;
+	u_short	state;
 
-	if (!apm_enabled)
-		return 0;
-	error = apm_set_display_power_state(APM_STATE_STANDBY);
+	state = blank ? APM_STATE_STANDBY : APM_STATE_READY;
+	/* Blank the first display device */
+	error = set_power_state(0x100, state);
+	if ((error != APM_SUCCESS) && (error != APM_NO_ERROR)) {
+		/* try to blank them all instead */
+		error = set_power_state(0x1ff, state);
+		if ((error != APM_SUCCESS) && (error != APM_NO_ERROR))
+			/* try to blank device one instead */
+			error = set_power_state(0x101, state);
+	}
 	if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
 		return 1;
-	apm_error("set display standby", error);
-#endif
+	apm_error("set display", error);
 	return 0;
 }
-
-/* Called from console driver -- must make sure apm_enabled. */
-int apm_display_unblank(void)
-{
-#ifdef CONFIG_APM_DISPLAY_BLANK
-	int error;
-
-	if (!apm_enabled)
-		return 0;
-	error = apm_set_display_power_state(APM_STATE_READY);
-	if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
-		return 1;
-	apm_error("set display ready", error);
 #endif
-	return 0;
-}
 
 int apm_register_callback(int (*callback)(apm_event_t))
 {
@@ -710,23 +785,23 @@
 	kfree_s(old, sizeof(callback_list_t));
 }
 
-static int queue_empty(struct apm_bios_struct * as)
+static int queue_empty(struct apm_user *as)
 {
 	return as->event_head == as->event_tail;
 }
 
-static apm_event_t get_queued_event(struct apm_bios_struct * as)
+static apm_event_t get_queued_event(struct apm_user *as)
 {
 	as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
 	return as->events[as->event_tail];
 }
 
-static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
+static void queue_event(apm_event_t event, struct apm_user *sender)
 {
-	struct apm_bios_struct *	as;
+	struct apm_user *	as;
 
 	if (user_list == NULL)
-		return 0;
+		return;
 	for (as = user_list; as != NULL; as = as->next) {
 		if (as == sender)
 			continue;
@@ -734,10 +809,8 @@
 		if (as->event_head == as->event_tail) {
 			static int notified;
 
-			if (notified == 0) {
+			if (notified++ == 0)
 			    printk(KERN_ERR "apm: an event queue overflowed\n");
-			    notified = 1;
-			}
 			as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
 		}
 		as->events[as->event_head] = event;
@@ -757,29 +830,26 @@
 			break;
 		}
 	}
-	wake_up_interruptible(&process_list);
-	return 1;
+	wake_up_interruptible(&apm_waitqueue);
 }
 
 static void set_time(void)
 {
 	unsigned long	flags;
 
-	if (!got_clock_diff)	/* Don't know time zone, can't set clock */
-		return;
-
-	save_flags(flags);
-	cli();
-	CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
-	restore_flags(flags);
+	if (got_clock_diff) {	/* Must know time zone in order to set clock */
+		save_flags(flags);
+		cli();
+		CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
+		restore_flags(flags);
+	}
 }
 
-static void suspend(void)
+static void get_time_diff(void)
 {
+#ifndef CONFIG_APM_RTC_IS_GMT
 	unsigned long	flags;
-	int		err;
 
-#ifndef CONFIG_APM_RTC_IS_GMT
 	/*
 	 * Estimate time zone so that set_time can update the clock
 	 */
@@ -790,11 +860,13 @@
 	got_clock_diff = 1;
 	restore_flags(flags);
 #endif
+}
 
-	err = apm_set_power_state(APM_STATE_SUSPEND);
-	if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
-		apm_error("suspend", err);
+static void reinit_timer(void)
+{
 #ifdef INIT_TIMER_AFTER_SUSPEND
+	unsigned long	flags;
+
 	save_flags(flags);
 	cli();
 	/* set the clock to 100 Hz */
@@ -806,13 +878,34 @@
 	udelay(10);
 	restore_flags(flags);
 #endif
+}
+
+static int suspend(void)
+{
+	int		err;
+	int		ret;
+	struct apm_user	*as;
+
+	get_time_diff();
+	err = apm_set_power_state(APM_STATE_SUSPEND);
+	reinit_timer();
 	set_time();
+	ret = (err == APM_SUCCESS) || (err == APM_NO_ERROR);
+	if (!ret)
+		apm_error("suspend", err);
+	for (as = user_list; as != NULL; as = as->next) {
+		as->suspend_wait = 0;
+		as->suspend_result = (ret ? 0 : -EIO);
+	}
+	wake_up_interruptible(&apm_suspend_waitqueue);
+	return err;
 }
 
 static void standby(void)
 {
 	int	err;
 
+	get_time_diff();
 	err = apm_set_power_state(APM_STATE_STANDBY);
 	if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
 		apm_error("standby", err);
@@ -837,8 +930,8 @@
 	return 0;
 }
 
-static void send_event(apm_event_t event, apm_event_t undo,
-		       struct apm_bios_struct *sender)
+static int send_event(apm_event_t event, apm_event_t undo,
+		       struct apm_user *sender)
 {
 	callback_list_t *	call;
 	callback_list_t *	fix;
@@ -849,11 +942,12 @@
 				fix->callback(undo);
 			if (apm_bios_info.version > 0x100)
 				apm_set_power_state(APM_STATE_REJECT);
-			return;
+			return 0;
 		}
 	}
 
 	queue_event(event, sender);
+	return 1;
 }
 
 static void check_events(void)
@@ -865,14 +959,14 @@
 #endif
 
 	while ((event = get_event()) != 0) {
-#ifdef APM_DEBUG
-		if (event <= NR_APM_EVENT_NAME)
-			printk(KERN_DEBUG "apm: received %s notify\n",
-			       apm_event_name[event - 1]);
-		else
-			printk(KERN_DEBUG "apm: received unknown "
-			       "event 0x%02x\n", event);
-#endif
+		if (debug) {
+			if (event <= NR_APM_EVENT_NAME)
+				printk(KERN_DEBUG "apm: received %s notify\n",
+				       apm_event_name[event - 1]);
+			else
+				printk(KERN_DEBUG "apm: received unknown "
+				       "event 0x%02x\n", event);
+		}
 #ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
 		if (ignore_bounce
 		    && ((jiffies - last_resume) > BOUNCE_INTERVAL))
@@ -883,12 +977,15 @@
 		case APM_USER_STANDBY:
 #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
 			if (waiting_for_resume)
-				return;
-			waiting_for_resume = 1;
+				break;
+#endif
+			if (send_event(event, APM_STANDBY_RESUME, NULL)) {
+#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
+				waiting_for_resume = 1;
 #endif
-			send_event(event, APM_STANDBY_RESUME, NULL);
-			if (standbys_pending <= 0)
-				standby();
+				if (standbys_pending <= 0)
+					standby();
+			}
 			break;
 
 		case APM_USER_SUSPEND:
@@ -904,12 +1001,15 @@
 #endif
 #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
 			if (waiting_for_resume)
-				return;
-			waiting_for_resume = 1;
+				break;
 #endif
-			send_event(event, APM_NORMAL_RESUME, NULL);
-			if (suspends_pending <= 0)
-				suspend();
+			if (send_event(event, APM_NORMAL_RESUME, NULL)) {
+#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
+				waiting_for_resume = 1;
+#endif
+				if (suspends_pending <= 0)
+					(void) suspend();
+			}
 			break;
 
 		case APM_NORMAL_RESUME:
@@ -926,9 +1026,9 @@
 			send_event(event, 0, NULL);
 			break;
 
+		case APM_CAPABILITY_CHANGE:
 		case APM_LOW_BATTERY:
 		case APM_POWER_STATUS_CHANGE:
-		case APM_CAPABILITY_CHANGE:
 			send_event(event, 0, NULL);
 			break;
 
@@ -937,74 +1037,33 @@
 			break;
 
 		case APM_CRITICAL_SUSPEND:
-			suspend();
+			(void) suspend();
 			break;
 		}
 	}
 }
 
-static void do_apm_timer(unsigned long unused)
+static void apm_event_handler(unsigned long unused)
 {
-	int	err;
-
-	static int	pending_count = 0;
+	static int	pending_count = 4;
+	int		err;
 
-	if (((standbys_pending > 0) || (suspends_pending > 0))
-	    && (apm_bios_info.version > 0x100)
-	    && (pending_count-- <= 0)) {
+	if ((standbys_pending > 0) || (suspends_pending > 0)) {
+		if ((apm_bios_info.version > 0x100) && (pending_count-- < 0)) {
+			pending_count = 4;
+			err = apm_set_power_state(APM_STATE_BUSY);
+			if (err)
+				apm_error("busy", err);
+		}
+	} else
 		pending_count = 4;
-
-		err = apm_set_power_state(APM_STATE_BUSY);
-		if (err)
-			apm_error("busy", err);
-	}
-
-	if (!(((standbys_pending > 0) || (suspends_pending > 0))
-	      && (apm_bios_info.version == 0x100)))
-		check_events();
-
+	check_events();
 	init_timer(&apm_timer);
 	apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
 	add_timer(&apm_timer);
 }
 
-/* Called from sys_idle, must make sure apm_enabled. */
-int apm_do_idle(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
-	u32	dummy;
-
-	if (!apm_enabled)
-		return 0;
-
-	if (apm_bios_call_simple(0x5305, 0, 0, &dummy))
-		return 0;
-
-	clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
-	return 1;
-#else
-	return 0;
-#endif
-}
-
-/* Called from sys_idle, must make sure apm_enabled. */
-void apm_do_busy(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
-	u32	dummy;
-
-	if (apm_enabled
-#ifndef ALWAYS_CALL_BUSY
-		&& clock_slowed
-#endif
-	) {
-		(void) apm_bios_call_simple(0x5306, 0, 0, &dummy);
-		clock_slowed = 0;
-	}
-#endif
-}
-
-static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
+static int check_apm_user(struct apm_user *as, const char *func)
 {
 	if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
 		printk(KERN_ERR "apm: %s passed bad filp", func);
@@ -1015,33 +1074,37 @@
 
 static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
 {
-	struct apm_bios_struct *	as;
+	struct apm_user *	as;
 	int			i;
 	apm_event_t		event;
-	struct wait_queue	wait = { current,	NULL };
+	DECLARE_WAITQUEUE(wait, current);
 
 	as = fp->private_data;
-	if (check_apm_bios_struct(as, "read"))
+	if (check_apm_user(as, "read"))
 		return -EIO;
 	if (count < sizeof(apm_event_t))
 		return -EINVAL;
 	if (queue_empty(as)) {
 		if (fp->f_flags & O_NONBLOCK)
 			return -EAGAIN;
-		add_wait_queue(&process_list, &wait);
+		add_wait_queue(&apm_waitqueue, &wait);
 repeat:
-		current->state = TASK_INTERRUPTIBLE;
+		set_current_state(TASK_INTERRUPTIBLE);
 		if (queue_empty(as) && !signal_pending(current)) {
 			schedule();
 			goto repeat;
 		}
 		current->state = TASK_RUNNING;
-		remove_wait_queue(&process_list, &wait);
+		remove_wait_queue(&apm_waitqueue, &wait);
 	}
 	i = count;
 	while ((i >= sizeof(event)) && !queue_empty(as)) {
 		event = get_queued_event(as);
-		copy_to_user(buf, &event, sizeof(event));
+		if (copy_to_user(buf, &event, sizeof(event))) {
+			if (i < count)
+				break;
+			return -EFAULT;
+		}
 		switch (event) {
 		case APM_SYS_SUSPEND:
 		case APM_USER_SUSPEND:
@@ -1065,12 +1128,12 @@
 
 static unsigned int do_poll(struct file *fp, poll_table * wait)
 {
-	struct apm_bios_struct * as;
+	struct apm_user * as;
 
 	as = fp->private_data;
-	if (check_apm_bios_struct(as, "select"))
+	if (check_apm_user(as, "poll"))
 		return 0;
-	poll_wait(fp, &process_list, wait);
+	poll_wait(fp, &apm_waitqueue, wait);
 	if (!queue_empty(as))
 		return POLLIN | POLLRDNORM;
 	return 0;
@@ -1079,10 +1142,11 @@
 static int do_ioctl(struct inode * inode, struct file *filp,
 		    u_int cmd, u_long arg)
 {
-	struct apm_bios_struct *	as;
+	struct apm_user *	as;
+	DECLARE_WAITQUEUE(wait, current);
 
 	as = filp->private_data;
-	if (check_apm_bios_struct(as, "ioctl"))
+	if (check_apm_user(as, "ioctl"))
 		return -EIO;
 	if (!as->suser)
 		return -EPERM;
@@ -1092,9 +1156,8 @@
 			as->standbys_read--;
 			as->standbys_pending--;
 			standbys_pending--;
-		}
-		else
-			send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as);
+		} else if (!send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as))
+			return -EAGAIN;
 		if (standbys_pending <= 0)
 			standby();
 		break;
@@ -1103,11 +1166,25 @@
 			as->suspends_read--;
 			as->suspends_pending--;
 			suspends_pending--;
+		} else if (!send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as))
+			return -EAGAIN;
+		if (suspends_pending <= 0) {
+			if (!suspend())
+				return -EIO;
+		} else {
+			as->suspend_wait = 1;
+			add_wait_queue(&apm_suspend_waitqueue, &wait);
+			while (1) {
+				set_current_state(TASK_INTERRUPTIBLE);
+				if ((as->suspend_wait == 0)
+				    || signal_pending(current))
+					break;
+				schedule();
+			}
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&apm_suspend_waitqueue, &wait);
+			return as->suspend_result;
 		}
-		else
-			send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as);
-		if (suspends_pending <= 0)
-			suspend();
 		break;
 	default:
 		return -EINVAL;
@@ -1117,12 +1194,12 @@
 
 static int do_release(struct inode * inode, struct file * filp)
 {
-	struct apm_bios_struct *	as;
+	struct apm_user *	as;
 
 	as = filp->private_data;
-	filp->private_data = NULL;
-	if (check_apm_bios_struct(as, "release"))
+	if (check_apm_user(as, "release"))
 		return 0;
+	filp->private_data = NULL;
 	if (as->standbys_pending > 0) {
 		standbys_pending -= as->standbys_pending;
 		if (standbys_pending <= 0)
@@ -1131,12 +1208,12 @@
 	if (as->suspends_pending > 0) {
 		suspends_pending -= as->suspends_pending;
 		if (suspends_pending <= 0)
-			suspend();
+			(void) suspend();
 	}
 	if (user_list == as)
 		user_list = as->next;
 	else {
-		struct apm_bios_struct *	as1;
+		struct apm_user *	as1;
 
 		for (as1 = user_list;
 		     (as1 != NULL) && (as1->next != as);
@@ -1153,9 +1230,9 @@
 
 static int do_open(struct inode * inode, struct file * filp)
 {
-	struct apm_bios_struct *	as;
+	struct apm_user *	as;
 
-	as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL);
+	as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
 	if (as == NULL) {
 		printk(KERN_ERR "apm: cannot allocate struct of size %d bytes",
 		       sizeof(*as));
@@ -1179,13 +1256,12 @@
 	return 0;
 }
 
-int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
+static int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
 {
 	char *		p;
 	unsigned short	bx;
 	unsigned short	cx;
 	unsigned short	dx;
-	unsigned short	nbat;
 	unsigned short	error;
 	unsigned short  ac_line_status = 0xff;
 	unsigned short  battery_status = 0xff;
@@ -1194,11 +1270,10 @@
 	int             time_units     = -1;
 	char            *units         = "?";
 
-	if (!apm_enabled)
-		return 0;
 	p = buf;
 
-	if (!(error = apm_get_power_status(&bx, &cx, &dx))) {
+	if ((smp_num_cpus == 1) &&
+	    !(error = apm_get_power_status(&bx, &cx, &dx))) {
 		ac_line_status = (bx >> 8) & 0xff;
 		battery_status = bx & 0xff;
 		if ((cx & 0xff) != 0xff)
@@ -1216,7 +1291,7 @@
 	   from the Get Power Status (0x0a) call unless otherwise noted.
 
 	   0) Linux driver version (this will change if format changes)
-	   1) APM BIOS Version.  Usually 1.0 or 1.1.
+	   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
 	   2) APM flags from APM Installation Check (0x00):
 	      bit 0: APM_16_BIT_SUPPORT
 	      bit 1: APM_32_BIT_SUPPORT
@@ -1226,13 +1301,14 @@
 	   3) AC line status
 	      0x00: Off-line
 	      0x01: On-line
-	      0x02: On backup power (APM BIOS 1.1 only)
+	      0x02: On backup power (BIOS >= 1.1 only)
 	      0xff: Unknown
 	   4) Battery status
 	      0x00: High
 	      0x01: Low
 	      0x02: Critical
 	      0x03: Charging
+	      0x04: Selected battery not present (BIOS >= 1.2 only)
 	      0xff: Unknown
 	   5) Battery flag
 	      bit 0: High
@@ -1264,6 +1340,106 @@
 	return p - buf;
 }
 
+static int __init apm(void *unused)
+{
+	unsigned short	bx;
+	unsigned short	cx;
+	unsigned short	dx;
+	unsigned short	error;
+	char *		power_stat;
+	char *		bat_stat;
+
+	if (apm_bios_info.version > 0x100) {
+		/*
+		 * We only support BIOSs up to version 1.2
+		 */
+		if (apm_bios_info.version > 0x0102)
+			apm_bios_info.version = 0x0102;
+		if (apm_driver_version(&apm_bios_info.version) != APM_SUCCESS) {
+			/* Fall back to an APM 1.0 connection. */
+			apm_bios_info.version = 0x100;
+		}
+	}
+	if (debug && (smp_num_cpus == 1)) {
+		printk(KERN_INFO "apm: Connection version %d.%d\n",
+			(apm_bios_info.version >> 8) & 0xff,
+			apm_bios_info.version & 0xff);
+
+		error = apm_get_power_status(&bx, &cx, &dx);
+		if (error)
+			printk(KERN_INFO "apm: power status not available\n");
+		else {
+			switch ((bx >> 8) & 0xff) {
+			case 0: power_stat = "off line"; break;
+			case 1: power_stat = "on line"; break;
+			case 2: power_stat = "on backup power"; break;
+			default: power_stat = "unknown"; break;
+			}
+			switch (bx & 0xff) {
+			case 0: bat_stat = "high"; break;
+			case 1: bat_stat = "low"; break;
+			case 2: bat_stat = "critical"; break;
+			case 3: bat_stat = "charging"; break;
+			default: bat_stat = "unknown"; break;
+			}
+			printk(KERN_INFO
+			       "apm: AC %s, battery status %s, battery life ",
+			       power_stat, bat_stat);
+			if ((cx & 0xff) == 0xff)
+				printk("unknown\n");
+			else
+				printk("%d%%\n", cx & 0xff);
+			if (apm_bios_info.version > 0x100) {
+				printk(KERN_INFO
+				       "apm: battery flag 0x%02x, battery life ",
+				       (cx >> 8) & 0xff);
+				if (dx == 0xffff)
+					printk("unknown\n");
+				else
+					printk("%d %s\n", dx & 0x7fff,
+						(dx & 0x8000) ?
+						"minutes" : "seconds");
+			}
+		}
+	}
+
+#ifdef CONFIG_APM_DO_ENABLE
+	if (apm_bios_info.flags & APM_BIOS_DISABLED) {
+		/*
+		 * This call causes my NEC UltraLite Versa 33/C to hang if it
+		 * is booted with PM disabled but not in the docking station.
+		 * Unfortunate ...
+		 */
+		error = apm_enable_power_management(1);
+		if (error) {
+			apm_error("enable power management", error);
+			return -1;
+		}
+	}
+#endif
+	if ((apm_bios_info.flags & APM_BIOS_DISENGAGED)
+	    && (apm_bios_info.version > 0x0100)) {
+		error = apm_engage_power_management(APM_DEVICE_ALL, 1);
+		if (error) {
+			apm_error("engage power management", error);
+			return -1;
+		}
+	}
+
+	/* Install our power off handler.. */
+	if (power_off_enabled)
+		acpi_power_off = apm_power_off;
+#ifdef CONFIG_MAGIC_SYSRQ
+	sysrq_power_off = apm_power_off;
+#endif
+#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
+	if (smp_num_cpus == 1)
+		console_blank_hook = apm_console_blank;
+#endif
+
+	return 0;
+}
+
 void __init apm_setup(char *str, int *dummy)
 {
 	int	invert;
@@ -1278,27 +1454,37 @@
 			str += 3;
 		if (strncmp(str, "debug", 5) == 0)
 			debug = !invert;
-		if (strncmp(str, "smp-power-off", 13) == 0)
-			smp_hack = !invert;
+		if (strncmp(str, "power-off", 9) == 0)
+			power_off_enabled = !invert;
 		str = strchr(str, ',');
 		if (str != NULL)
 			str += strspn(str, ", \t");
 	}
 }
 
-void __init apm_bios_init(void)
-{
-	unsigned short	bx;
-	unsigned short	cx;
-	unsigned short	dx;
-	unsigned short	error;
-	char *		power_stat;
-	char *		bat_stat;
-	static struct proc_dir_entry *ent;
+__setup("apm=", apm_setup);
+
+static struct file_operations apm_bios_fops = {
+	read:		do_read,
+	poll:		do_poll,
+	ioctl:		do_ioctl,
+	open:		do_open,
+	release:	do_release,
+};
 
+static struct miscdevice apm_device = {
+	APM_MINOR_DEV,
+	"apm",
+	&apm_bios_fops
+};
+
+#define APM_INIT_ERROR_RETURN	return
+
+void __init apm_init(void)
+{
 	if (apm_bios_info.version == 0) {
 		printk(KERN_INFO "apm: BIOS not found.\n");
-		return;
+		APM_INIT_ERROR_RETURN;
 	}
 	printk(KERN_INFO
 		"apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n",
@@ -1308,7 +1494,7 @@
 		driver_version);
 	if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
 		printk(KERN_INFO "apm: no 32 bit BIOS support\n");
-		return;
+		APM_INIT_ERROR_RETURN;
 	}
 
 	/*
@@ -1337,7 +1523,11 @@
 
 	if (apm_disabled) {
 		printk(KERN_NOTICE "apm: disabled on user request.\n");
-		return;
+		APM_INIT_ERROR_RETURN;
+	}
+	if ((smp_num_cpus > 1) && !power_off_enabled) {
+		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
+		APM_INIT_ERROR_RETURN;
 	}
 
 	/*
@@ -1359,18 +1549,16 @@
 	set_base(gdt[APM_DS >> 3],
 		 __va((unsigned long)apm_bios_info.dseg << 4));
 #ifndef APM_RELAX_SEGMENTS
-	if (apm_bios_info.version == 0x100)
+	if (apm_bios_info.version == 0x100) {
 #endif
-	{
 		/* For ASUS motherboard, Award BIOS rev 110 (and others?) */
 		_set_limit((char *)&gdt[APM_CS >> 3], 64 * 1024 - 1);
 		/* For some unknown machine. */
 		_set_limit((char *)&gdt[APM_CS_16 >> 3], 64 * 1024 - 1);
 		/* For the DEC Hinote Ultra CT475 (and others?) */
 		_set_limit((char *)&gdt[APM_DS >> 3], 64 * 1024 - 1);
-	}
 #ifndef APM_RELAX_SEGMENTS
-	else {
+	} else {
 		_set_limit((char *)&gdt[APM_CS >> 3],
 			(apm_bios_info.cseg_len - 1) & 0xffff);
 		_set_limit((char *)&gdt[APM_CS_16 >> 3],
@@ -1379,98 +1567,27 @@
 			(apm_bios_info.dseg_len - 1) & 0xffff);
 	}
 #endif
-#ifdef CONFIG_SMP
-	if (smp_num_cpus > 1) {
-		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
-		if (smp_hack)
-			smp_hack = 2;
-		return;
-	}
-#endif
-	if (apm_bios_info.version > 0x100) {
-		/*
-		 * We only support BIOSs up to version 1.2
-		 */
-		if (apm_bios_info.version > 0x0102)
-			apm_bios_info.version = 0x0102;
-		if (apm_driver_version(&apm_bios_info.version) != APM_SUCCESS) {
-			/* Fall back to an APM 1.0 connection. */
-			apm_bios_info.version = 0x100;
-		}
-	}
-	if (debug) {
-		printk(KERN_INFO "apm: Connection version %d.%d\n",
-			(apm_bios_info.version >> 8) & 0xff,
-			apm_bios_info.version & 0xff );
 
-		error = apm_get_power_status(&bx, &cx, &dx);
-		if (error)
-			printk(KERN_INFO "apm: power status not available\n");
-		else {
-			switch ((bx >> 8) & 0xff) {
-			case 0: power_stat = "off line"; break;
-			case 1: power_stat = "on line"; break;
-			case 2: power_stat = "on backup power"; break;
-			default: power_stat = "unknown"; break;
-			}
-			switch (bx & 0xff) {
-			case 0: bat_stat = "high"; break;
-			case 1: bat_stat = "low"; break;
-			case 2: bat_stat = "critical"; break;
-			case 3: bat_stat = "charging"; break;
-			default: bat_stat = "unknown"; break;
-			}
-			printk(KERN_INFO
-			       "apm: AC %s, battery status %s, battery life ",
-			       power_stat, bat_stat);
-			if ((cx & 0xff) == 0xff)
-				printk("unknown\n");
-			else
-				printk("%d%%\n", cx & 0xff);
-			if (apm_bios_info.version > 0x100) {
-				printk(KERN_INFO
-				       "apm: battery flag 0x%02x, battery life ",
-				       (cx >> 8) & 0xff);
-				if (dx == 0xffff)
-					printk("unknown\n");
-				else
-					printk("%d %s\n", dx & 0x7fff,
-						(dx & 0x8000) ?
-						"minutes" : "seconds");
-			}
-		}
-	}
+	apm(0);
 
-#ifdef CONFIG_APM_DO_ENABLE
-	if (apm_bios_info.flags & APM_BIOS_DISABLED) {
-		/*
-		 * This call causes my NEC UltraLite Versa 33/C to hang if it
-		 * is booted with PM disabled but not in the docking station.
-		 * Unfortunate ...
-		 */
-		error = apm_enable_power_management();
-		if (error) {
-			apm_error("enable power management", error);
-			return;
-		}
-	}
-#endif
-	if (((apm_bios_info.flags & APM_BIOS_DISABLED) == 0)
-	    && (apm_bios_info.version > 0x0100)) {
-		if (apm_engage_power_management(0x0001) == APM_SUCCESS)
-			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
+	create_proc_info_entry("apm", 0, 0, apm_get_info);
+
+	if (smp_num_cpus > 1) {
+		printk(KERN_NOTICE
+		   "apm: disabled - APM is not SMP safe (power off active).\n");
+		APM_INIT_ERROR_RETURN;
 	}
 
 	init_timer(&apm_timer);
-	apm_timer.function = do_apm_timer;
+	apm_timer.function = apm_event_handler;
 	apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
 	add_timer(&apm_timer);
 
-	ent = create_proc_entry("apm", 0, 0);
-	if (ent != NULL)
-		ent->get_info = apm_get_info;
-
 	misc_register(&apm_device);
 
-	apm_enabled = 1;
+#ifdef CONFIG_APM_CPU_IDLE
+	acpi_idle = apm_cpu_idle;
+#endif
 }
+
+module_init(apm_init)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)