diff -Nru linux-2.6.15/Documentation/amd76x_pm.txt linux-2.6.15-jo/Documentation/amd76x_pm.txt
--- linux-2.6.15/Documentation/amd76x_pm.txt	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.15-jo/Documentation/amd76x_pm.txt	2006-01-08 19:56:03.000000000 +0100
@@ -0,0 +1,343 @@
+	ACPI style power management for SMP AMD-760MP(X) based systems
+	==============================================================
+
+For use until the ACPI project catches up. :-)
+
+Using this module saves about 70 - 90W of energy in the idle mode compared
+to the default idle mode. Waking up from the idle mode is fast to keep the
+system response time good. Currently no CPU load calculation is done, the
+system exits the idle mode after every C2 call.
+
+Known issues:
+-------------
+- Currently there's a bug somewhere where the reading the
+  P_LVL2 for the first time causes the system to sleep instead of 
+  idling. This means that you need to hit the power button once to
+  wake the system after loading the module for the first time after
+  reboot. After that the system idles as supposed.
+  (Only observed on Tony's system.)
+
+- On kernel up to and including 2.6.13 there might appear a weird
+  keyboard repeating after an uptime of 1-2 days.  This seems to
+  coincide with IRQ 0 being routed to CPU1: setting smp_affinity to 1 for
+  IRQ 0 soon after booting circumvents the problem.  On 2.6.14 interrupt
+  routing has changed and I didn't watch the problem anymore.
+
+- There might be reduced throughput of disk or network devices.  See
+  below for how to configure the "irq rate watcher" to avoid this.
+
+- Occasional hard lockups might be caused by amd76x_pm.
+
+Influenced by Vcool, and LVCool. Rewrote everything from scratch to
+use the PCI features in Linux, and to support SMP systems.
+
+Currently tested amongst others on a TYAN S2460 (760MP) system (Tony), an
+ASUS A7M266-D (760MPX) system (Johnathan) and a TYAN S2466 (760MPX)
+system (Jo). Adding support for other Athlon SMP or single processor
+systems should be easy if desired.  
+
+The file /sys/devices/pci0000:00/0000:00:00.0/C2_cnt shows the number of
+C2 calls since module load.
+
+There are some parameters for tuning the behaviour of amd76x_pm:
+lazy_idle, spin_idle, watch_irqs, watch_int and min_C1
+
+lazy_idle and spin_idle are closely related:
+
+- lazy_idle defines the number of idle calls into amd76x_smp_idle that are
+  needed to *enable* C2 mode.  This parameter is the maximum loop counter
+  for an outer loop with interrupts enabled that guarantees low latencies. 
+  The default for lazy_idle is 512.
+
+- spin_idle defines the maximum number of spin cycles in an inner idle loop
+  where one CPU waits for all others to get into C2-enabled mode. When all
+  CPUs are in C2-enable mode they (more ore less) simultaneously *enter* C2
+  mode. In this inner loop interrupts are disabled.  The loop is left
+  immediatly if there is something waiting to be scheduled.  The default
+  for spin_idle is 2*lazy_idle.
+
+lazy_idle and spin_idle define a "rubber measure" for the idling
+behaviour: lazy_idle defines the minimum "idling" needed to enter C2 and
+spin_idle defines when to give up.
+
+Low values for lazy_idle and high values for spin_idle give better
+cooling.  Higher values for lazy_idle simply give less cooling.  spin_idle
+is a kind of emergency break to leave C2-enable mode if CPUs don't
+synchronize.
+
+irq rate watcher:
+-----------------
+Interrupts are disabled in C2 mode.  The CPUs are woken up by timer
+interrups or by NMIs.  This causes a high interrupt latency for other
+interrupts that leads to a significant reduction in io or network
+throughput.  There has been introduced a "irq rate watcher" to reduce
+this effect.  If the irq rate watcher detects that an interrupt has a
+rate above a given limit, C2 idling is disabled and a low latency C1
+idling mode is used instead. The parameters watch_irqs, watch_int and
+min_C1 control this irq rate watcher:
+
+- watch_irqs defines which interrupts are to be watched and optionally
+  at which interrupt rate C2 mode shall be disabled.  The syntax for 
+  watch_irqs is irq1[:rate1],irq2[:rate2],...  The rate is measured in
+  interrupts per second and defaults to 128.  There is no default for
+  watch_irqs.  To enable the irq rate watcher you must specify this
+  parameter.  Enter the interrupts used by disk controllers or network
+  adapters here.
+  
+- watch_int defines the time interval (in milliseconds) at which the
+  interrupt rate is checked.  Too low values may result in an overhead
+  and too high values cause the C1 mode to "kick in" later.  The default
+  for watch_int is 1 second.
+
+- min_C1 defines the mininum number of check intervals with low
+  interrupt rates that are needed to leave the forced C1 mode.
+
+All parameters lazy_idle, spin_idle, watch_irqs and watch_int may be
+given as module parameters to amd76x_pm.  Furthermore, they may be
+queried or changed via their sysfs entries in
+/sys/module/amd76x_pm/parameters.  (The file location in sysfs has
+changed from earlier versions!)
+
+Setting watch_int to zero or removing all interrupts from watch_irqs
+causes the irq rate watcher to stop.  The module needs to be reloaded to
+start the watcher again.
+
+NB: In the past there was a major impact from amd76x_pm on the system
+clock stability.  At least on my box this effect has been reduced
+noticable.  This is caused by two changes:
+
+- The redesign of the idle function lead to a reduced time spent in
+  amd76x_smp_idle() and this propably caused lower latencies.
+  
+- The introduction of the irq rate watcher significantly reduced
+  latencies under load.
+
+Some hints on tuning lazy_idle and spin_idle:
+Watch the rate of C2 calls per second.  Anything below HZ seems to be
+completely useless.  Start with a moderate low value for lazy_idle, e.g.
+2^5 and a very high value for spin_idle, e.g. 2^20.  Then double lazy_idle
+until you see a significant increase in core temperature or C2 rate. 
+Go back to the last "good" value.  Then halve spin_idle until you see
+a decrease in C2 rate or a raising core temperature again and go back
+to the previous value.  There's a small program c2rate.c attached at the
+end of this file that you may use to watch the C2 rate.  The rtc driver
+must be enabled to use it.
+
+There is an -extra patch available that adds some optional features:
+- C3 idling: this works but is not well tested.
+- normal throttling: untested for a long time.
+- Power On Suspend: very experimental and untested.
+
+This software is licensed under GNU General Public License Version 2 
+as specified in file COPYING in the Linux kernel source tree main 
+directory.
+
+Copyright (C) 2002 - 2005	Johnathan Hicks <thetech@folkwolf.net>
+				Tony Lindgren <tony@atomide.com>
+				Joerg Sommrey <jo@sommrey.de>
+
+History:
+
+  20020702 - amd-smp-idle: Tony Lindgren <tony@atomide.com>
+Influenced by Vcool, and LVCool. Rewrote everything from scratch to
+use the PCI features in Linux, and to support SMP systems. Provides
+C2 idling on SMP AMD-760MP systems.
+
+  20020722: JH
+  	I adapted Tony's code for the AMD-765/766 southbridge and adapted it
+  	according to the AMD-768 data sheet to provide the same capability for
+  	SMP AMD-760MPX systems. Posted to acpi-devel list.
+  	
+  20020722: Alan Cox
+  	Replaces non-functional amd76x_pm code in -ac tree.
+  	
+  20020730: JH
+  	Added ability to do normal throttling (the non-thermal kind), C3 idling
+  	and Power On Suspend (S1 sleep). It would be very easy to tie swsusp
+  	into activate_amd76x_SLP(). C3 idling doesn't happen yet; see my note
+  	in amd76x_smp_idle(). I've noticed that when NTH and idling are both
+  	enabled, my hardware locks and requires a hard reset, so I have
+  	#ifndefed around the idle loop setting to prevent this. POS locks it up
+  	too, both ought to be fixable. I've also noticed that idling and NTH
+  	make some interference that is picked up by the onboard sound chip on
+  	my ASUS A7M266-D motherboard.
+
+  20030601: Pasi Savolainen
+     Simple port to 2.5
+     Added sysfs interface for making nice graphs with mrtg.
+     Look for /sys/devices/pci0/00:00.0/C2_cnt & lazy_idle (latter writable)
+
+  20050601: Joerg Sommrey (jo)
+     2.6 stuff
+     redesigned amd76x_smp_idle.  The algorithm is basically the same
+        but the implementation has changed.  This part is now independent
+        from the number of CPUs and data is locked against concurrent
+        updates from different CPUs.
+     use _smp_processor_id()
+     use cpu_idle_wait()
+     NTH and POS code not touched and not tested.
+
+  20050621: jo
+     separated C3, NTH and POS code into extra patch
+
+  20050812: jo
+     rewritten amd76x_smp_idle completely. It's much simpler now but
+     does a good job - the KISS approach.  Introduced a new tunable
+     spin_idle.
+
+  20050819: jo
+     new irq rate watcher task that forces C1-idling if irq-rate exceeds a
+     given limit.
+
+  20050820: jo
+     make all modules parameters r/w in sysfs.
+
+  20050906: jo
+     avoid some local_irq_disable/enable
+
+  20060108: jo
+     using percpu-variables
+  
+TODO: Thermal throttling (TTH).
+	 /proc interface for normal throttling level.
+	 /proc interface for POS.
+
+---- c2rate.c ---- snip here ----------------------------------------
+/* c2rate.c
+ * usage: c2rate [interval]
+ * interval is the time in seconds between two calculations and defaults
+ * to 16
+ * You may #define C3 if you're playing with C3 mode.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <linux/rtc.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define C2_CNT "/sys/devices/pci0000:00/0000:00:00.0/C2_cnt"
+#define C3_CNT "/sys/devices/pci0000:00/0000:00:00.0/C3_cnt"
+// #define C3
+int fd;
+
+void
+disable_rtc(void) {
+	int retval;
+	/* Disable alarm interrupts */
+	retval = ioctl(fd, RTC_UIE_OFF, 0);
+	if (retval == -1) {
+		perror("ioctl(RTC_UIE_OFF)");
+		_exit(errno);
+	}
+	close(fd);
+}
+
+int
+main(int argc, char **argv)
+{
+	int retval;
+	unsigned long data;
+	char idlebuffer[64];
+	int idlefd;
+	ssize_t bytes;
+	unsigned long  lastcount;
+	unsigned long  irqcount;
+	unsigned long c2_old, c2_new;
+#ifdef C3
+	unsigned long c3_old, c3_new;
+#endif
+	int rate = 0;
+	int firstloop = 1;
+
+	if(argc > 1)
+		rate = atoi(argv[1]);
+	if (rate <= 0)
+		rate = 16;
+
+	fd = open ("/dev/rtc", O_RDONLY);
+
+	if (fd ==  -1) {
+		perror("/dev/rtc");
+		exit(errno);
+	}
+
+	/* Set the alarm to every second */
+
+	retval = ioctl(fd, RTC_UIE_ON, 0);
+	if (retval == -1) {
+		perror("ioctl(RTC_UIE_ON)");
+		exit(errno);
+	}
+	atexit(disable_rtc);
+
+	irqcount = 0;
+	while (1) {
+		/* This blocks until the alarm ring causes an interrupt */
+		retval = read(fd, &data, sizeof data);
+		if (retval == -1) {
+			perror("read");
+			exit(errno);
+		}
+		if (firstloop) {
+			idlebuffer[0] = '\0';
+			idlefd = open(C2_CNT, O_RDONLY);
+			if (idlefd != -1) {
+				bytes = read(idlefd, idlebuffer, sizeof idlebuffer);
+				close(idlefd);
+				idlebuffer[bytes] = '\0';
+			}
+			c2_old = strtoul(idlebuffer, NULL, 10);
+#ifdef C3
+			idlebuffer[0] = '\0';
+			idlefd = open(C3_CNT, O_RDONLY);
+			if (idlefd != -1) {
+				bytes = read(idlefd, idlebuffer, sizeof idlebuffer);
+				close(idlefd);
+				idlebuffer[bytes] = '\0';
+			}
+			c3_old = strtoul(idlebuffer, NULL, 10);
+#endif
+			firstloop = 0;
+			continue;
+		}
+		lastcount = data >> 8;
+		irqcount += lastcount;
+
+		if (irqcount >= rate) {
+			idlebuffer[0] = '\0';
+			idlefd = open(C2_CNT, O_RDONLY);
+			if (idlefd != -1) {
+				bytes = read(idlefd, idlebuffer, sizeof idlebuffer);
+				close(idlefd);
+				idlebuffer[bytes] = '\0';
+			}
+			c2_new = strtoul(idlebuffer, NULL, 10);
+#ifdef C3
+			idlebuffer[0] = '\0';
+			idlefd = open(C3_CNT, O_RDONLY);
+			if (idlefd != -1) {
+				bytes = read(idlefd, idlebuffer, sizeof idlebuffer);
+				close(idlefd);
+				idlebuffer[bytes] = '\0';
+			}
+			c3_new = strtoul(idlebuffer, NULL, 10);
+			printf("%lu\t%lu\n", (c2_new - c2_old) / irqcount,
+					(c3_new - c3_old) / irqcount);
+			c3_old = c3_new;
+#else
+			printf("%lu\n", (c2_new - c2_old) / irqcount);
+#endif
+			c2_old = c2_new;
+			irqcount = 0;
+		}
+
+	}
+	
+	return 0;
+
+} /* end main */
+---- c2rate.c ---- snip here ----------------------------------------
diff -Nru linux-2.6.15/drivers/acpi/Kconfig linux-2.6.15-jo/drivers/acpi/Kconfig
--- linux-2.6.15/drivers/acpi/Kconfig	2006-01-03 21:07:59.000000000 +0100
+++ linux-2.6.15-jo/drivers/acpi/Kconfig	2006-01-08 18:26:57.000000000 +0100
@@ -340,4 +340,18 @@
 		$>modprobe acpi_memhotplug 
 endif	# ACPI
 
+config AMD76X_PM
+        tristate "AMD76x Native Power Management support"
+        default n
+        depends on X86 && PCI
+        ---help---
+          This driver enables Power Management on AMD760MP & AMD760MPX chipsets.          This is about same as ACPI C2, except that ACPI folks don't do SMP ATM.
+          See Documentation/amd76x_pm.txt for further details.
+
+          To compile this driver as a module ( = code which can be inserted in
+          and removed from the running kernel whenever you want), say M
+          here.  The module will be called amd76x_pm.
+
+          If unsure, say N.
+
 endmenu
diff -Nru linux-2.6.15/drivers/acpi/Kconfig.orig linux-2.6.15-jo/drivers/acpi/Kconfig.orig
--- linux-2.6.15/drivers/acpi/Kconfig.orig	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.15-jo/drivers/acpi/Kconfig.orig	2006-01-03 21:07:59.000000000 +0100
@@ -0,0 +1,343 @@
+#
+# ACPI Configuration
+#
+
+menu "ACPI (Advanced Configuration and Power Interface) Support"
+	depends on !X86_VISWS
+	depends on !IA64_HP_SIM
+	depends on IA64 || X86
+
+config ACPI
+	bool "ACPI Support"
+	depends on IA64 || X86
+	select PM
+	select PCI
+
+	default y
+	---help---
+	  Advanced Configuration and Power Interface (ACPI) support for 
+	  Linux requires an ACPI compliant platform (hardware/firmware),
+	  and assumes the presence of OS-directed configuration and power
+	  management (OSPM) software.  This option will enlarge your 
+	  kernel by about 70K.
+
+	  Linux ACPI provides a robust functional replacement for several 
+	  legacy configuration and power management interfaces, including
+	  the Plug-and-Play BIOS specification (PnP BIOS), the 
+	  MultiProcessor Specification (MPS), and the Advanced Power 
+	  Management (APM) specification.  If both ACPI and APM support 
+	  are configured, whichever is loaded first shall be used.
+
+	  The ACPI SourceForge project contains the latest source code, 
+	  documentation, tools, mailing list subscription, and other 
+	  information.  This project is available at:
+	  <http://sourceforge.net/projects/acpi>
+
+	  Linux support for ACPI is based on Intel Corporation's ACPI
+	  Component Architecture (ACPI CA).  For more information see:
+	  <http://developer.intel.com/technology/iapc/acpi>
+
+	  ACPI is an open industry specification co-developed by Compaq, 
+	  Intel, Microsoft, Phoenix, and Toshiba.  The specification is 
+	  available at:
+	  <http://www.acpi.info>
+
+if ACPI
+
+config ACPI_SLEEP
+	bool "Sleep States"
+	depends on X86 && (!SMP || SUSPEND_SMP)
+	depends on PM
+	default y
+	---help---
+	  This option adds support for ACPI suspend states. 
+
+	  With this option, you will be able to put the system "to sleep". 
+	  Sleep states are low power states for the system and devices. All
+	  of the system operating state is saved to either memory or disk
+	  (depending on the state), to allow the system to resume operation
+	  quickly at your request.
+
+	  Although this option sounds really nifty, barely any of the device
+	  drivers have been converted to the new driver model and hence few
+	  have proper power management support. 
+
+	  This option is not recommended for anyone except those doing driver
+	  power management development.
+
+config ACPI_SLEEP_PROC_FS
+	bool
+	depends on ACPI_SLEEP && PROC_FS
+	default y
+
+config ACPI_SLEEP_PROC_SLEEP
+	bool "/proc/acpi/sleep (deprecated)"
+	depends on ACPI_SLEEP_PROC_FS
+	default n
+	---help---
+	  Create /proc/acpi/sleep
+	  Deprecated by /sys/power/state
+
+config ACPI_AC
+	tristate "AC Adapter"
+	depends on X86
+	default y
+	help
+	  This driver adds support for the AC Adapter object, which indicates
+	  whether a system is on AC, or not. If you have a system that can
+	  switch between A/C and battery, say Y.
+
+config ACPI_BATTERY
+	tristate "Battery"
+	depends on X86
+	default y
+	help
+	  This driver adds support for battery information through
+	  /proc/acpi/battery. If you have a mobile system with a battery, 
+	  say Y.
+
+config ACPI_BUTTON
+	tristate "Button"
+	default y
+	help
+	  This driver handles events on the power, sleep and lid buttons.
+	  A daemon reads /proc/acpi/event and perform user-defined actions
+	  such as shutting down the system.  This is necessary for
+	  software controlled poweroff.
+
+config ACPI_VIDEO
+	tristate "Video"
+	depends on X86
+	default y
+	help
+	  This driver implement the ACPI Extensions For Display Adapters
+	  for integrated graphics devices on motherboard, as specified in
+	  ACPI 2.0 Specification, Appendix B, allowing to perform some basic
+	  control like defining the video POST device, retrieving EDID information
+	  or to setup a video output, etc.
+	  Note that this is an ref. implementation only.  It may or may not work
+	  for your integrated video device.
+
+config ACPI_HOTKEY
+	tristate "Generic Hotkey (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	depends on X86
+	default n
+	help
+	  Experimental consolidated hotkey driver.
+	  If you are unsure, say N.
+
+config ACPI_FAN
+	tristate "Fan"
+	default y
+	help
+	  This driver adds support for ACPI fan devices, allowing user-mode 
+	  applications to perform basic fan control (on, off, status).
+
+config ACPI_PROCESSOR
+	tristate "Processor"
+	default y
+	help
+	  This driver installs ACPI as the idle handler for Linux, and uses
+	  ACPI C2 and C3 processor states to save power, on systems that
+	  support it.  It is required by several flavors of cpufreq
+	  Performance-state drivers.
+
+config ACPI_HOTPLUG_CPU
+	bool
+	depends on ACPI_PROCESSOR && HOTPLUG_CPU
+	select ACPI_CONTAINER
+	default y
+
+config ACPI_THERMAL
+	tristate "Thermal Zone"
+	depends on ACPI_PROCESSOR
+	default y
+	help
+	  This driver adds support for ACPI thermal zones.  Most mobile and
+	  some desktop systems support ACPI thermal zones.  It is HIGHLY
+	  recommended that this option be enabled, as your processor(s)
+	  may be damaged without it.
+
+config ACPI_NUMA
+	bool "NUMA support"
+	depends on NUMA
+	depends on (IA64 || X86_64)
+	default y if IA64_GENERIC || IA64_SGI_SN2
+
+config ACPI_ASUS
+        tristate "ASUS/Medion Laptop Extras"
+	depends on X86
+	default y
+        ---help---
+          This driver provides support for extra features of ACPI-compatible
+          ASUS laptops. As some of Medion laptops are made by ASUS, it may also
+          support some Medion laptops (such as 9675 for example).  It makes all
+          the extra buttons generate standard ACPI events that go through
+          /proc/acpi/events, and (on some models) adds support for changing the
+          display brightness and output, switching the LCD backlight on and off,
+          and most importantly, allows you to blink those fancy LEDs intended
+          for reporting mail and wireless status.
+
+	  Note: display switching code is currently considered EXPERIMENTAL,
+	  toying with these values may even lock your machine.
+          
+          All settings are changed via /proc/acpi/asus directory entries. Owner
+          and group for these entries can be set with asus_uid and asus_gid
+          parameters.
+          
+          More information and a userspace daemon for handling the extra buttons
+          at <http://sourceforge.net/projects/acpi4asus/>.
+          
+          If you have an ACPI-compatible ASUS laptop, say Y or M here. This
+          driver is still under development, so if your laptop is unsupported or
+          something works not quite as expected, please use the mailing list
+          available on the above page (acpi4asus-user@lists.sourceforge.net)
+          
+config ACPI_IBM
+	tristate "IBM ThinkPad Laptop Extras"
+	depends on X86
+	---help---
+	  This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds
+	  support for Fn-Fx key combinations, Bluetooth control, video
+	  output switching, ThinkLight control, UltraBay eject and more.
+	  For more information about this driver see <file:Documentation/ibm-acpi.txt>
+	  and <http://ibm-acpi.sf.net/> .
+
+	  If you have an IBM ThinkPad laptop, say Y or M here.
+
+config ACPI_TOSHIBA
+	tristate "Toshiba Laptop Extras"
+	depends on X86
+	default y
+	---help---
+	  This driver adds support for access to certain system settings
+	  on "legacy free" Toshiba laptops.  These laptops can be recognized by
+	  their lack of a BIOS setup menu and APM support.
+
+	  On these machines, all system configuration is handled through the
+	  ACPI.  This driver is required for access to controls not covered
+	  by the general ACPI drivers, such as LCD brightness, video output,
+	  etc.
+
+	  This driver differs from the non-ACPI Toshiba laptop driver (located
+	  under "Processor type and features") in several aspects.
+	  Configuration is accessed by reading and writing text files in the
+	  /proc tree instead of by program interface to /dev.  Furthermore, no
+	  power management functions are exposed, as those are handled by the
+	  general ACPI drivers.
+
+	  More information about this driver is available at
+	  <http://memebeam.org/toys/ToshibaAcpiDriver>.
+
+	  If you have a legacy free Toshiba laptop (such as the Libretto L1
+	  series), say Y.
+
+config ACPI_CUSTOM_DSDT
+	bool "Include Custom DSDT"
+	depends on !STANDALONE
+	default n 
+	help
+	  Thist option is to load a custom ACPI DSDT
+	  If you don't know what that is, say N.
+
+config ACPI_CUSTOM_DSDT_FILE
+	string "Custom DSDT Table file to include"
+	depends on ACPI_CUSTOM_DSDT
+	default ""
+	help
+	  Enter the full path name to the file wich includes the AmlCode declaration.
+
+config ACPI_BLACKLIST_YEAR
+	int "Disable ACPI for systems before Jan 1st this year" if X86
+	default 0
+	help
+	  enter a 4-digit year, eg. 2001 to disable ACPI by default
+	  on platforms with DMI BIOS date before January 1st that year.
+	  "acpi=force" can be used to override this mechanism.
+
+	  Enter 0 to disable this mechanism and allow ACPI to
+	  run by default no matter what the year.  (default)
+
+config ACPI_DEBUG
+	bool "Debug Statements"
+	default n
+	help
+	  The ACPI driver can optionally report errors with a great deal
+	  of verbosity. Saying Y enables these statements. This will increase
+	  your kernel size by around 50K.
+
+config ACPI_EC
+	bool
+	depends on X86
+	default y
+	help
+	  This driver is required on some systems for the proper operation of
+	  the battery and thermal drivers.  If you are compiling for a 
+	  mobile system, say Y.
+
+config ACPI_POWER
+	bool
+	default y
+
+config ACPI_SYSTEM
+	bool
+	default y
+	help
+	  This driver will enable your system to shut down using ACPI, and
+	  dump your ACPI DSDT table using /proc/acpi/dsdt.
+
+config X86_PM_TIMER
+	bool "Power Management Timer Support"
+	depends on X86
+	depends on !X86_64
+	default y
+	help
+	  The Power Management Timer is available on all ACPI-capable,
+	  in most cases even if ACPI is unusable or blacklisted.
+
+	  This timing source is not affected by powermanagement features
+	  like aggressive processor idling, throttling, frequency and/or
+	  voltage scaling, unlike the commonly used Time Stamp Counter
+	  (TSC) timing source.
+
+	  So, if you see messages like 'Losing too many ticks!' in the
+	  kernel logs, and/or you are using this on a notebook which
+	  does not yet have an HPET, you should say "Y" here.
+
+config ACPI_CONTAINER
+	tristate "ACPI0004,PNP0A05 and PNP0A06 Container Driver (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	default (ACPI_HOTPLUG_MEMORY || ACPI_HOTPLUG_CPU || ACPI_HOTPLUG_IO)
+	 ---help---
+	  This allows _physical_ insertion and removal of CPUs and memory.
+	  This can be useful, for example, on NUMA machines that support
+	  ACPI based physical hotplug of nodes, or non-NUMA machines that
+	  support physical cpu/memory hot-plug.
+
+	  If one selects "m", this driver can be loaded with
+	  "modprobe acpi_container".
+
+config ACPI_HOTPLUG_MEMORY
+	tristate "Memory Hotplug"
+	depends on ACPI
+	depends on MEMORY_HOTPLUG
+	default n
+	help
+	  This driver adds supports for ACPI Memory Hotplug.  This driver
+	  provides support for fielding notifications on ACPI memory
+	  devices (PNP0C80) which represent memory ranges that may be
+	  onlined or offlined during runtime.  
+
+	  Enabling this driver assumes that your platform hardware
+	  and firmware have support for hot-plugging physical memory. If
+	  your system does not support physically adding or ripping out 
+	  memory DIMMs at some platfrom defined granularity (individually 
+	  or as a bank) at runtime, then you need not enable this driver.
+
+	  If one selects "m," this driver can be loaded using the following
+	  command: 
+		$>modprobe acpi_memhotplug 
+endif	# ACPI
+
+endmenu
diff -Nru linux-2.6.15/drivers/acpi/Makefile linux-2.6.15-jo/drivers/acpi/Makefile
--- linux-2.6.15/drivers/acpi/Makefile	2006-01-03 21:07:59.000000000 +0100
+++ linux-2.6.15-jo/drivers/acpi/Makefile	2006-01-08 18:26:57.000000000 +0100
@@ -57,3 +57,8 @@
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-y				+= scan.o motherboard.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)	+= acpi_memhotplug.o
+
+#
+# not really ACPI thing, until they handle SMP.
+#
+obj-$(CONFIG_AMD76X_PM)               += amd76x_pm.o
diff -Nru linux-2.6.15/drivers/acpi/amd76x_pm.c linux-2.6.15-jo/drivers/acpi/amd76x_pm.c
--- linux-2.6.15/drivers/acpi/amd76x_pm.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.15-jo/drivers/acpi/amd76x_pm.c	2006-01-08 19:38:07.000000000 +0100
@@ -0,0 +1,642 @@
+/*
+ * ACPI style PM for SMP AMD-760MP(X) based systems.
+ * For use until the ACPI project catches up. :-)
+ *
+ * Copyright (C) 2002 - 2005	Johnathan Hicks <thetech@folkwolf.net>
+ * 				Tony Lindgren <tony@atomide.com>
+ * 				Joerg Sommrey <jo@sommrey.de>
+ *
+ *    <Notes from 20020722-ac revision>
+ *
+ * Processor idle mode module for AMD SMP 760MP(X) based systems
+ *
+ * Copyright (C) 2002 Tony Lindgren <tony@atomide.com>
+ *                    Johnathan Hicks (768 support)
+ *
+ * Using this module saves about 70 - 90W of energy in the idle mode compared
+ * to the default idle mode. Waking up from the idle mode is fast to keep the
+ * system response time good. Currently no CPU load calculation is done, the
+ * system exits the idle mode if the idle function runs twice on the same
+ * processor in a row. This only works on SMP systems, but maybe the idle mode
+ * enabling can be integrated to ACPI to provide C2 mode at some point.
+ *
+ * NOTE: Currently there's a bug somewhere where the reading the
+ *       P_LVL2 for the first time causes the system to sleep instead of 
+ *       idling. This means that you need to hit the power button once to
+ *       wake the system after loading the module for the first time after
+ *       reboot. After that the system idles as supposed.
+ *
+ *
+ * Influenced by Vcool, and LVCool. Rewrote everything from scratch to
+ * use the PCI features in Linux, and to support SMP systems.
+ * 
+ * Currently only tested on a TYAN S2460 (760MP) system (Tony) and an
+ * ASUS A7M266-D (760MPX) system (Johnathan). Adding support for other Athlon
+ * SMP or single processor systems should be easy if desired.
+ *
+ * This software is licensed under GNU General Public License Version 2 
+ * as specified in file COPYING in the Linux kernel source tree main 
+ * directory.
+ * 
+ *   </Notes from 20020722-ac revision>
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <asm/atomic.h>
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/kernel_stat.h>
+
+#include <linux/amd76x_pm.h>
+
+#define VERSION	"20060108"
+
+// #define AMD76X_LOG_C1 1
+
+extern void default_idle(void);
+static void amd76x_smp_idle(void);
+static int amd76x_pm_main(void);
+
+static unsigned long lazy_idle = 0;
+static unsigned long spin_idle = 0;
+static unsigned long watch_int = 0;
+static unsigned long min_C1 = AMD76X_MIN_C1;
+
+static int show_watch_irqs(char *, struct kernel_param *);
+static int set_watch_irqs(const char *, struct kernel_param *);
+
+module_param(lazy_idle, long, S_IRUGO | S_IWUSR);
+
+MODULE_PARM_DESC(lazy_idle,
+		"\tnumber of idle cycles before entering power saving mode");
+
+module_param(spin_idle, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(spin_idle,
+		"\tnumber of spin cycles to wait for other CPUs to become idle");
+
+module_param(watch_int, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watch_int,
+		"\twatch interval (in milliseconds) for interrupts");
+
+module_param_call(watch_irqs, set_watch_irqs, show_watch_irqs,
+	NULL, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watch_irqs,
+		"\tlist of irqs (and optional their limit per second) that "
+		"cause fallback to C1 mode. "
+		"Syntax: irq0[:limit0],irq1[:limit1],...");
+
+module_param(min_C1, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(min_C1,
+		"\tminimum irq rate watch intervals spent in C1 mode");
+
+MODULE_AUTHOR("Tony Lindgren, Johnathan Hicks, Joerg Sommrey, others");
+MODULE_DESCRIPTION("ACPI style power management for SMP AMD-760MP(X) "
+		"based systems, version " VERSION);
+
+
+static struct pci_dev *pdev_nb;
+static struct pci_dev *pdev_sb;
+
+struct PM_cfg {
+	unsigned int status_reg;
+	unsigned int C2_reg;
+	unsigned int slp_reg;
+	unsigned int resume_reg;
+	void (*orig_idle) (void);
+	void (*curr_idle) (void);
+};
+
+static struct PM_cfg amd76x_pm_cfg __read_mostly;
+
+struct cpu_stat {
+	int idle_count;
+	int C2_cnt;
+};
+
+struct idle_stat {
+	atomic_t num_idle;
+};
+
+static struct idle_stat amd76x_stat __cacheline_aligned_in_smp = {
+	.num_idle = ATOMIC_INIT(0),
+};
+
+static void *prs_ref __read_mostly;
+
+struct watch_item {
+	int irq;
+	int count;
+	int limit;
+};
+
+static int schedule_watch;
+static int force_C1 __read_mostly;
+static struct watch_item watch_item[AMD76X_WATCH_MAX] = {{-1, 0}};
+
+static struct work_struct work;
+static void watch_irq(void *);
+
+static DECLARE_WORK(work, watch_irq, NULL);
+
+static struct pci_device_id  __devinitdata amd_nb_tbl[] = {
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FE_GATE_700C, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{0,}
+};
+
+static struct pci_device_id  __devinitdata amd_sb_tbl[] = {
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7413, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7443, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{0,}
+};
+
+/*
+ * Configures the AMD-762 northbridge to support PM calls
+ */
+static int
+config_amd762(int enable)
+{
+	unsigned int regdword;
+
+	/* Enable STPGNT in BIU Status/Control for cpu0 */
+	pci_read_config_dword(pdev_nb, 0x60, &regdword);
+	regdword |= (1 << 17);
+	pci_write_config_dword(pdev_nb, 0x60, regdword);
+
+	/* Enable STPGNT in BIU Status/Control for cpu1 */
+	pci_read_config_dword(pdev_nb, 0x68, &regdword);
+	regdword |= (1 << 17);
+	pci_write_config_dword(pdev_nb, 0x68, regdword);
+
+	/* DRAM refresh enable */
+	pci_read_config_dword(pdev_nb, 0x58, &regdword);
+	regdword &= ~(1 << 19);
+	pci_write_config_dword(pdev_nb, 0x58, regdword);
+
+	/* Self refresh enable */
+	pci_read_config_dword(pdev_nb, 0x70, &regdword);
+	regdword |= (1 << 18);
+	pci_write_config_dword(pdev_nb, 0x70, regdword);
+
+	return 0;
+}
+
+
+/*
+ * Get the base PMIO address and set the pm registers in amd76x_pm_cfg.
+ */
+static void
+amd76x_get_PM(void)
+{
+	unsigned int regdword;
+
+	/* Get the address for pm status, P_LVL2, etc */
+	pci_read_config_dword(pdev_sb, 0x58, &regdword);
+	regdword &= 0xff80;
+	amd76x_pm_cfg.status_reg = (regdword + 0x00);
+	amd76x_pm_cfg.slp_reg =    (regdword + 0x04);
+	amd76x_pm_cfg.C2_reg =     (regdword + 0x14);
+	amd76x_pm_cfg.resume_reg = (regdword + 0x16); /* N/A for 768 */
+}
+
+
+/*
+ * En/Disable PMIO and configure W4SG & STPGNT.
+ */
+static int
+config_PMIO_amd76x(int is_766, int enable)
+{
+	unsigned char regbyte;
+
+	/* Clear W4SG, and set PMIOEN, if using a 765/766 set STPGNT as well.
+	 * AMD-766: C3A41; page 59 in AMD-766 doc
+	 * AMD-768: DevB:3x41C; page 94 in AMD-768 doc */
+	pci_read_config_byte(pdev_sb, 0x41, &regbyte);
+	if(enable) {
+		regbyte |= ((is_766 << 1) | (1 << 7));
+	}
+	pci_write_config_byte(pdev_sb, 0x41, regbyte);
+	return 0;
+}
+
+/*
+ * C2 idle support for AMD-766.
+ */
+static void
+config_amd766_C2(int enable)
+{
+	unsigned int regdword;
+
+	/* Set C2 options in C3A50, page 63 in AMD-766 doc */
+	pci_read_config_dword(pdev_sb, 0x50, &regdword);
+	if(enable) {
+		regdword &= ~((DCSTOP_EN | CPUSTP_EN | PCISTP_EN | SUSPND_EN |
+					CPURST_EN) << C2_REGS);
+		regdword |= (STPCLK_EN	/* ~ 20 Watt savings max */
+			 |  CPUSLP_EN)	/* Additional ~ 70 Watts max! */
+			 << C2_REGS;
+	}
+	else
+		regdword &= ~((STPCLK_EN | CPUSLP_EN) << C2_REGS);
+	pci_write_config_dword(pdev_sb, 0x50, regdword);
+}
+
+/*
+ * Configures the 765 & 766 southbridges.
+ */
+static int
+config_amd766(int enable)
+{
+	amd76x_get_PM();
+	config_PMIO_amd76x(1, 1);
+	config_amd766_C2(enable);
+
+	return 0;
+}
+
+
+/*
+ * C2 idling support for AMD-768.
+ */
+static void
+config_amd768_C2(int enable)
+{
+	unsigned char regbyte;
+
+	/* Set C2 options in DevB:3x4F, page 100 in AMD-768 doc */
+	pci_read_config_byte(pdev_sb, 0x4F, &regbyte);
+	if(enable)
+		regbyte |= C2EN;
+	else
+		regbyte ^= C2EN;
+	pci_write_config_byte(pdev_sb, 0x4F, regbyte);
+}
+
+/*
+ * Configures the 768 southbridge to support idle calls, and gets
+ * the processor idle call register location.
+ */
+static int
+config_amd768(int enable)
+{
+	amd76x_get_PM();
+
+	config_PMIO_amd76x(0, 1);
+
+	config_amd768_C2(enable);
+
+	return 0;
+}
+
+/*
+ * Idle loop for single processor systems
+ */
+void
+amd76x_up_idle(void)
+{
+	/* ACPI knows how to do C2 on SMP when cpu_count < 2
+	 * we really shouldn't end up here anyway. 
+	 */
+	amd76x_pm_cfg.orig_idle();
+}
+
+/*
+ * Idle loop for SMP systems
+ *
+ * Locking is done using atomic_t variables, no spin locks needed.
+ *
+ */
+static void
+amd76x_smp_idle(void)
+{
+	int i;
+	int num_online;
+	struct cpu_stat *prs;
+
+	if (unlikely(force_C1)) {
+		local_irq_disable();
+		safe_halt();
+		return;
+	}
+
+	prs = per_cpu_ptr(prs_ref, raw_smp_processor_id());
+	/* Spin inside (outer) idle loop until lazy_idle cycles
+	 * are reached.
+	 */
+	if (likely(++(prs->idle_count) <= lazy_idle)) {
+		return;
+	}
+
+	/* Now we are ready do go C2. */
+	local_irq_disable();
+	atomic_inc(&amd76x_stat.num_idle);
+	num_online = num_online_cpus();
+
+	/* Spin inside inner loop until either
+	 * - spin_idle cycles are reached
+	 * - there is work
+	 * - all CPUs are idle
+	 */
+	for (i = 0; i < spin_idle; i++) {
+		if (unlikely(need_resched()))
+			break;
+
+		smp_mb();
+		if (unlikely(atomic_read(&amd76x_stat.num_idle)
+					== num_online)) {
+			/* Invoke C2 */
+			prs->C2_cnt++;
+			inb(amd76x_pm_cfg.C2_reg);
+			break;
+		}
+	}
+
+	smp_mb();
+	atomic_dec(&amd76x_stat.num_idle);
+	prs->idle_count = 0;
+	local_irq_enable();
+}
+
+/*
+ *   sysfs support, RW
+ */
+static int
+show_watch_irqs(char *buf, struct kernel_param *kp)
+{
+	int i;
+	ssize_t ret = 0;
+	for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1 ; i++) {
+		if (i > 0)
+			ret += sprintf(buf + ret, ",");
+		ret += sprintf(buf + ret, "%d:%d",
+			watch_item[i].irq,
+			watch_item[i].limit);
+	}
+	return ret;
+}
+
+static int
+set_watch_irqs(const char *val, struct kernel_param *kp)
+{
+	const char *s;
+	char *e;
+	long irq, limit;
+	int i;
+	
+	for (i = 0, s = val; i < AMD76X_WATCH_MAX && s && *s; i++) {
+		irq = simple_strtol(s, &e, 0);
+		if (e == s)
+			break;
+		if (*e == ':') {
+			s = e + 1;
+			limit = simple_strtol(s, &e, 0);
+		} else
+			limit = AMD76X_WATCH_LIM;
+		if (irq >= 0)
+			watch_item[i].irq = irq;
+		else {
+			watch_item[i].irq = -1;
+			break;
+		}
+		watch_item[i].limit = limit;
+		watch_item[i].count = 0;
+		if (*e == ',')
+			s = e + 1;
+		else
+			s = e;
+	}
+	if (i < AMD76X_WATCH_MAX)
+		watch_item[i].irq = -1;
+	if (watch_item[0].irq != -1) {
+		schedule_watch = 1;
+		printk(KERN_INFO "amd76x_pm: irq rate watcher parms:\n");
+		for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1; i++)
+			printk(KERN_INFO
+			"amd76x_pm: irq %d: limit = %d/s\n",
+				watch_item[i].irq,
+				watch_item[i].limit);
+	} else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher disabled.\n");
+
+	return 0;
+}
+
+static ssize_t 
+show_C2_cnt (struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	unsigned int C2_cnt = 0;
+	struct cpu_stat *prs;
+	int i;
+
+	for_each_online_cpu(i) {
+		prs = per_cpu_ptr(prs_ref, i);
+		C2_cnt += prs->C2_cnt;
+	}
+	return sprintf(buf,"%u\n", C2_cnt);  
+}
+
+static DEVICE_ATTR(C2_cnt, S_IRUGO,
+		   show_C2_cnt, NULL);
+
+static void
+setup_watch(void)
+{
+	if (watch_int <= 0)
+		watch_int = AMD76X_WATCH_INT;
+	if (watch_item[0].irq != -1 && watch_int) {
+		schedule_work(&work);
+		printk(KERN_INFO "amd76x_pm: irq rate watcher started. "
+				"watch_int = %lu ms, min_C1 = %lu\n",
+				watch_int, min_C1);
+	} else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher disabled.\n");
+}
+
+static void
+watch_irq(void *parm)
+{
+	int i;
+	int irq_cnt;
+	int set_force_C1 = 0;
+
+	for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1; i++) {
+		irq_cnt = kstat_irqs(watch_item[i].irq);
+		if ((irq_cnt - watch_item[i].count) * 1000 >
+				watch_item[i].limit * watch_int) {
+			set_force_C1 = 1;
+		}
+		watch_item[i].count = irq_cnt;
+	}
+
+#ifdef AMD76X_LOG_C1
+        if (set_force_C1 && !force_C1)
+                printk(KERN_INFO "amd76x_pm: C1 on\n");
+        if (!set_force_C1 && force_C1 == 1)
+                printk(KERN_INFO "amd76x_pm: C1 off\n");
+#endif
+
+	if (set_force_C1)
+		force_C1 = min_C1;
+	else if (force_C1)
+		force_C1--;
+
+	if (schedule_watch && watch_int > 0)
+		schedule_delayed_work(&work,
+			msecs_to_jiffies(watch_int));
+	else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher stopped.\n");
+}
+
+
+/*
+ * Finds and initializes the bridges, and then sets the idle function
+ */
+static int
+amd76x_pm_main(void)
+{
+	int i;
+	struct cpu_stat *prs;
+
+	amd76x_pm_cfg.orig_idle = 0;
+	if(lazy_idle == 0)
+	    lazy_idle = AMD76X_LAZY_IDLE;
+	printk(KERN_INFO "amd76x_pm: lazy_idle = %lu\n", lazy_idle);
+	if(spin_idle == 0)
+		spin_idle = 2 * lazy_idle;
+	printk(KERN_INFO "amd76x_pm: spin_idle = %lu\n", spin_idle);
+
+	/* Find southbridge */
+	pdev_sb = NULL;
+	while((pdev_sb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_sb)) != NULL) {
+		if(pci_match_id(amd_sb_tbl, pdev_sb) != NULL)
+			goto found_sb;
+	}
+	printk(KERN_ERR "amd76x_pm: Could not find southbridge\n");
+	return -ENODEV;
+
+found_sb:
+	/* Find northbridge */
+	pdev_nb = NULL;
+	while((pdev_nb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_nb)) != NULL) {
+		if(pci_match_id(amd_nb_tbl, pdev_nb) != NULL)
+			goto found_nb;
+	}
+	printk(KERN_ERR "amd76x_pm: Could not find northbridge\n");
+	return -ENODEV;
+
+ found_nb:	
+	
+	/* Init southbridge */
+	switch (pdev_sb->device) {
+	case PCI_DEVICE_ID_AMD_VIPER_7413:	/* AMD-765 or 766 */
+		config_amd766(1);
+		break;
+	case PCI_DEVICE_ID_AMD_VIPER_7443:	/* AMD-768 */
+		config_amd768(1);
+		break;
+	default:
+		printk(KERN_ERR "amd76x_pm: No southbridge to initialize\n");		
+		break;
+	}
+
+	/* Init northbridge and queue the new idle function */
+	if(!pdev_nb) {
+		printk("amd76x_pm: No northbridge found.\n");
+		return -ENODEV;
+	}
+	switch (pdev_nb->device) {
+	case PCI_DEVICE_ID_AMD_FE_GATE_700C:	/* AMD-762 */
+		config_amd762(1);
+		amd76x_pm_cfg.curr_idle = amd76x_smp_idle;
+		break;
+	default:
+		printk(KERN_ERR "amd76x_pm: No northbridge to initialize\n");
+		break;
+	}
+
+	if(num_online_cpus() == 1) {
+		amd76x_pm_cfg.curr_idle = amd76x_up_idle;
+		printk(KERN_ERR "amd76x_pm: UP machine detected. ACPI is your friend.\n");
+	}
+	if (!amd76x_pm_cfg.curr_idle) {
+		printk(KERN_ERR "amd76x_pm: Idle function not changed\n");
+		return 1;
+	}
+
+	prs_ref = alloc_percpu(struct cpu_stat);
+	
+	for (i = 0; i < NR_CPUS; i++) {
+		prs = per_cpu_ptr(prs_ref, i);
+		prs->idle_count = 0;
+		prs->C2_cnt = 0;
+	}
+
+	amd76x_pm_cfg.orig_idle = pm_idle;
+	pm_idle = amd76x_pm_cfg.curr_idle;
+
+	wmb();
+	
+	/* sysfs */
+	device_create_file(&pdev_nb->dev, &dev_attr_C2_cnt);
+
+	setup_watch();
+
+	return 0;
+}
+
+
+static int __init
+amd76x_pm_init(void)
+{
+	printk(KERN_INFO "amd76x_pm: Version %s loaded.\n", VERSION);
+	return amd76x_pm_main();
+}
+
+
+static void __exit
+amd76x_pm_cleanup(void)
+{
+	int i;
+	unsigned int C2_cnt = 0;
+	struct cpu_stat *prs;
+	
+	pm_idle = amd76x_pm_cfg.orig_idle;
+
+	cpu_idle_wait();
+
+
+	/* This isn't really needed. */
+	for_each_online_cpu(i) {
+		prs = per_cpu_ptr(prs_ref, i);
+		C2_cnt += prs->C2_cnt;
+	}
+	printk(KERN_INFO "amd76x_pm: %u C2 calls\n", C2_cnt);
+	
+	/* remove sysfs */
+	device_remove_file(&pdev_nb->dev, &dev_attr_C2_cnt);
+
+	schedule_watch = 0;
+        flush_scheduled_work();
+        cancel_delayed_work(&work);
+        flush_scheduled_work();
+	
+	free_percpu(prs_ref);
+}
+
+
+MODULE_LICENSE("GPL");
+module_init(amd76x_pm_init);
+module_exit(amd76x_pm_cleanup);
diff -Nru linux-2.6.15/drivers/acpi/amd76x_pm.c.extra linux-2.6.15-jo/drivers/acpi/amd76x_pm.c.extra
--- linux-2.6.15/drivers/acpi/amd76x_pm.c.extra	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.15-jo/drivers/acpi/amd76x_pm.c.extra	2006-01-08 19:39:52.000000000 +0100
@@ -0,0 +1,948 @@
+/*
+ * ACPI style PM for SMP AMD-760MP(X) based systems.
+ * For use until the ACPI project catches up. :-)
+ *
+ * Copyright (C) 2002 - 2005	Johnathan Hicks <thetech@folkwolf.net>
+ * 				Tony Lindgren <tony@atomide.com>
+ * 				Joerg Sommrey <jo@sommrey.de>
+ *
+ *    <Notes from 20020722-ac revision>
+ *
+ * Processor idle mode module for AMD SMP 760MP(X) based systems
+ *
+ * Copyright (C) 2002 Tony Lindgren <tony@atomide.com>
+ *                    Johnathan Hicks (768 support)
+ *
+ * Using this module saves about 70 - 90W of energy in the idle mode compared
+ * to the default idle mode. Waking up from the idle mode is fast to keep the
+ * system response time good. Currently no CPU load calculation is done, the
+ * system exits the idle mode if the idle function runs twice on the same
+ * processor in a row. This only works on SMP systems, but maybe the idle mode
+ * enabling can be integrated to ACPI to provide C2 mode at some point.
+ *
+ * NOTE: Currently there's a bug somewhere where the reading the
+ *       P_LVL2 for the first time causes the system to sleep instead of 
+ *       idling. This means that you need to hit the power button once to
+ *       wake the system after loading the module for the first time after
+ *       reboot. After that the system idles as supposed.
+ *
+ *
+ * Influenced by Vcool, and LVCool. Rewrote everything from scratch to
+ * use the PCI features in Linux, and to support SMP systems.
+ * 
+ * Currently only tested on a TYAN S2460 (760MP) system (Tony) and an
+ * ASUS A7M266-D (760MPX) system (Johnathan). Adding support for other Athlon
+ * SMP or single processor systems should be easy if desired.
+ *
+ * This software is licensed under GNU General Public License Version 2 
+ * as specified in file COPYING in the Linux kernel source tree main 
+ * directory.
+ * 
+ *   </Notes from 20020722-ac revision>
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <asm/atomic.h>
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/kernel_stat.h>
+
+#include <linux/amd76x_pm.h>
+
+#define VERSION	"20060108-extra"
+
+// #define AMD76X_NTH 1
+// #define AMD76X_POS 1
+// #define AMD76X_C3 1
+// #define AMD76X_LOG_C1 1
+
+extern void default_idle(void);
+#ifndef AMD76X_NTH
+static void amd76x_smp_idle(void);
+#endif
+static int amd76x_pm_main(void);
+
+static unsigned long lazy_idle = 0;
+static unsigned long spin_idle = 0;
+static unsigned long watch_int = 0;
+static unsigned long min_C1 = AMD76X_MIN_C1;
+
+#ifndef AMD76X_NTH
+static int show_watch_irqs(char *, struct kernel_param *);
+static int set_watch_irqs(const char *, struct kernel_param *);
+#endif
+
+module_param(lazy_idle, long, S_IRUGO | S_IWUSR);
+
+MODULE_PARM_DESC(lazy_idle,
+		"\tnumber of idle cycles before entering power saving mode");
+
+module_param(spin_idle, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(spin_idle,
+		"\tnumber of spin cycles to wait for other CPUs to become idle");
+
+#ifndef AMD76X_NTH
+module_param(watch_int, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watch_int,
+		"\twatch interval (in milliseconds) for interrupts");
+
+module_param_call(watch_irqs, set_watch_irqs, show_watch_irqs,
+	NULL, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watch_irqs,
+		"\tlist of irqs (and optional their limit per second) that "
+		"cause fallback to C1 mode. "
+		"Syntax: irq0[:limit0],irq1[:limit1],...");
+#endif
+
+module_param(min_C1, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(min_C1,
+		"\tminimum irq rate watch intervals spent in C1 mode");
+
+MODULE_AUTHOR("Tony Lindgren, Johnathan Hicks, Joerg Sommrey, others");
+MODULE_DESCRIPTION("ACPI style power management for SMP AMD-760MP(X) "
+		"based systems, version " VERSION);
+
+
+static struct pci_dev *pdev_nb;
+static struct pci_dev *pdev_sb;
+
+struct PM_cfg {
+	unsigned int status_reg;
+	unsigned int C2_reg;
+#ifdef AMD76X_C3
+	unsigned int C3_reg;
+#endif
+#ifdef AMD76X_NTH
+	unsigned int NTH_reg;
+#endif
+	unsigned int slp_reg;
+	unsigned int resume_reg;
+	void (*orig_idle) (void);
+	void (*curr_idle) (void);
+};
+
+static struct PM_cfg amd76x_pm_cfg __read_mostly;
+
+#ifndef AMD76X_NTH
+struct cpu_stat {
+	int idle_count;
+	int C2_cnt;
+#ifdef AMD76X_C3
+	int C3_cnt;
+	int C2_active;
+#endif
+};
+
+struct idle_stat {
+	atomic_t num_idle;
+#ifdef AMD76X_C3
+	atomic_t num_C2;
+#endif
+};
+
+static struct idle_stat amd76x_stat __cacheline_aligned_in_smp = {
+	.num_idle = ATOMIC_INIT(0),
+#ifdef AMD76X_C3
+	.num_C2 = ATOMIC_INIT(0)
+#endif
+};
+
+static void *prs_ref __read_mostly;
+#endif
+
+struct watch_item {
+	int irq;
+	int count;
+	int limit;
+};
+
+static int schedule_watch;
+static int force_C1 __read_mostly;
+static struct watch_item watch_item[AMD76X_WATCH_MAX] = {{-1, 0}};
+
+static struct work_struct work;
+static void watch_irq(void *);
+
+static DECLARE_WORK(work, watch_irq, NULL);
+
+static struct pci_device_id  __devinitdata amd_nb_tbl[] = {
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FE_GATE_700C, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{0,}
+};
+
+static struct pci_device_id  __devinitdata amd_sb_tbl[] = {
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7413, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7443, PCI_ANY_ID,
+		PCI_ANY_ID,},
+	{0,}
+};
+
+/*
+ * Configures the AMD-762 northbridge to support PM calls
+ */
+static int
+config_amd762(int enable)
+{
+	unsigned int regdword;
+
+	/* Enable STPGNT in BIU Status/Control for cpu0 */
+	pci_read_config_dword(pdev_nb, 0x60, &regdword);
+	regdword |= (1 << 17);
+	pci_write_config_dword(pdev_nb, 0x60, regdword);
+
+	/* Enable STPGNT in BIU Status/Control for cpu1 */
+	pci_read_config_dword(pdev_nb, 0x68, &regdword);
+	regdword |= (1 << 17);
+	pci_write_config_dword(pdev_nb, 0x68, regdword);
+
+	/* DRAM refresh enable */
+	pci_read_config_dword(pdev_nb, 0x58, &regdword);
+	regdword &= ~(1 << 19);
+	pci_write_config_dword(pdev_nb, 0x58, regdword);
+
+	/* Self refresh enable */
+	pci_read_config_dword(pdev_nb, 0x70, &regdword);
+	regdword |= (1 << 18);
+	pci_write_config_dword(pdev_nb, 0x70, regdword);
+
+	return 0;
+}
+
+
+/*
+ * Get the base PMIO address and set the pm registers in amd76x_pm_cfg.
+ */
+static void
+amd76x_get_PM(void)
+{
+	unsigned int regdword;
+
+	/* Get the address for pm status, P_LVL2, etc */
+	pci_read_config_dword(pdev_sb, 0x58, &regdword);
+	regdword &= 0xff80;
+	amd76x_pm_cfg.status_reg = (regdword + 0x00);
+	amd76x_pm_cfg.slp_reg =    (regdword + 0x04);
+#ifdef AMD76X_NTH
+	amd76x_pm_cfg.NTH_reg =    (regdword + 0x10);
+#endif
+	amd76x_pm_cfg.C2_reg =     (regdword + 0x14);
+#ifdef AMD76X_C3
+	amd76x_pm_cfg.C3_reg =     (regdword + 0x15);
+#endif
+	amd76x_pm_cfg.resume_reg = (regdword + 0x16); /* N/A for 768 */
+}
+
+
+/*
+ * En/Disable PMIO and configure W4SG & STPGNT.
+ */
+static int
+config_PMIO_amd76x(int is_766, int enable)
+{
+	unsigned char regbyte;
+
+	/* Clear W4SG, and set PMIOEN, if using a 765/766 set STPGNT as well.
+	 * AMD-766: C3A41; page 59 in AMD-766 doc
+	 * AMD-768: DevB:3x41C; page 94 in AMD-768 doc */
+	pci_read_config_byte(pdev_sb, 0x41, &regbyte);
+	if(enable) {
+		regbyte |= ((is_766 << 1) | (1 << 7));
+	}
+	pci_write_config_byte(pdev_sb, 0x41, regbyte);
+	return 0;
+}
+
+/*
+ * C2 idle support for AMD-766.
+ */
+static void
+config_amd766_C2(int enable)
+{
+	unsigned int regdword;
+
+	/* Set C2 options in C3A50, page 63 in AMD-766 doc */
+	pci_read_config_dword(pdev_sb, 0x50, &regdword);
+	if(enable) {
+		regdword &= ~((DCSTOP_EN | CPUSTP_EN | PCISTP_EN | SUSPND_EN |
+					CPURST_EN) << C2_REGS);
+		regdword |= (STPCLK_EN	/* ~ 20 Watt savings max */
+			 |  CPUSLP_EN)	/* Additional ~ 70 Watts max! */
+			 << C2_REGS;
+	}
+	else
+		regdword &= ~((STPCLK_EN | CPUSLP_EN) << C2_REGS);
+	pci_write_config_dword(pdev_sb, 0x50, regdword);
+}
+#ifdef AMD76X_C3
+/*
+ * Untested C3 idle support for AMD-766.
+ */
+static void
+config_amd766_C3(int enable)
+{
+        unsigned int regdword;
+
+        /* Set C3 options in C3A50, page 63 in AMD-766 doc */
+        pci_read_config_dword(pdev_sb, 0x50, &regdword);
+        if(enable) {
+                regdword &= ~((DCSTOP_EN | PCISTP_EN | SUSPND_EN | CPURST_EN)
+                                << C3_REGS);
+                regdword |= (STPCLK_EN  /* ~ 20 Watt savings max */
+                         |  CPUSLP_EN   /* Additional ~ 70 Watts max! */
+                         |  CPUSTP_EN)  /* yet more savings! */
+                         << C3_REGS;
+        }
+        else
+                regdword &= ~((STPCLK_EN | CPUSLP_EN | CPUSTP_EN) << C3_REGS);
+        pci_write_config_dword(pdev_sb, 0x50, regdword);
+}
+#endif
+
+
+#ifdef AMD76X_POS
+static void
+config_amd766_POS(int enable)
+{
+	unsigned int regdword;
+
+	/* Set C3 options in C3A50, page 63 in AMD-766 doc */
+	pci_read_config_dword(pdev_sb, 0x50, &regdword);
+	if(enable) {
+		regdword &= ~((ZZ_CACHE_EN | CPURST_EN) << POS_REGS);
+		regdword |= ((DCSTOP_EN | STPCLK_EN | CPUSTP_EN | PCISTP_EN |
+					CPUSLP_EN | SUSPND_EN) << POS_REGS);
+	}
+	else
+		regdword ^= (0xff << POS_REGS);
+	pci_write_config_dword(pdev_sb, 0x50, regdword);
+}
+#endif
+
+
+
+/*
+ * Configures the 765 & 766 southbridges.
+ */
+static int
+config_amd766(int enable)
+{
+	amd76x_get_PM();
+	config_PMIO_amd76x(1, 1);
+	config_amd766_C2(enable);
+#ifdef AMD76X_C3
+        config_amd766_C3(enable);
+#endif
+
+#ifdef AMD76X_POS
+	config_amd766_POS(enable);
+#endif
+
+	return 0;
+}
+
+
+/*
+ * C2 idling support for AMD-768.
+ */
+static void
+config_amd768_C2(int enable)
+{
+	unsigned char regbyte;
+
+	/* Set C2 options in DevB:3x4F, page 100 in AMD-768 doc */
+	pci_read_config_byte(pdev_sb, 0x4F, &regbyte);
+	if(enable)
+		regbyte |= C2EN;
+	else
+		regbyte ^= C2EN;
+	pci_write_config_byte(pdev_sb, 0x4F, regbyte);
+}
+
+#ifdef AMD76X_C3
+/*
+ * C3 idle support for AMD-768. The idle loop would need some extra
+ * handling for C3, but it would make more sense for ACPI to handle CX level
+ * transitions like it is supposed to. Unfortunately ACPI doesn't do CX
+ * levels on SMP systems yet.
+ */
+static void
+config_amd768_C3(int enable)
+{
+        unsigned char regbyte;
+
+        /* Set C3 options in DevB:3x4F, page 100 in AMD-768 doc */
+        pci_read_config_byte(pdev_sb, 0x4F, &regbyte);
+        if(enable)
+                regbyte |= (C3EN /* | ZZ_C3EN | CSLP_C3EN | CSTP_C3EN */);
+        else
+                regbyte ^= C3EN;
+        pci_write_config_byte(pdev_sb, 0x4F, regbyte);
+}
+#endif
+
+#ifdef AMD76X_POS
+/*
+ * Untested Power On Suspend support for AMD-768. This should also be handled
+ * by ACPI.
+ */
+static void
+config_amd768_POS(int enable)
+{
+	unsigned int regdword;
+
+	/* Set POS options in DevB:3x50, page 101 in AMD-768 doc */
+	pci_read_config_dword(pdev_sb, 0x50, &regdword);
+	if(enable) 
+		regdword |= (POSEN | CSTP | PSTP | ASTP | DCSTP | CSLP | SUSP);
+	else
+		regdword ^= POSEN;
+	pci_write_config_dword(pdev_sb, 0x50, regdword);
+}
+#endif
+
+
+#ifdef AMD76X_NTH
+/*
+ * Normal Throttling support for AMD-768. There are several settings
+ * that can be set depending on how long you want some of the delays to be.
+ * I'm not sure if this is even neccessary at all as the 766 doesn't need this.
+ */
+static void
+config_amd768_NTH(int enable, int ntper, int thminen)
+{
+	unsigned char regbyte;
+
+	/* DevB:3x40, pg 93 of 768 doc */
+	pci_read_config_byte(pdev_sb, 0x40, &regbyte);
+	/* Is it neccessary to use THMINEN at ANY time? */
+	regbyte |= (NTPER(ntper) | THMINEN(thminen));
+	pci_write_config_byte(pdev_sb, 0x40, regbyte);
+}
+#endif
+
+
+/*
+ * Configures the 768 southbridge to support idle calls, and gets
+ * the processor idle call register location.
+ */
+static int
+config_amd768(int enable)
+{
+	amd76x_get_PM();
+
+	config_PMIO_amd76x(0, 1);
+
+	config_amd768_C2(enable);
+#ifdef AMD76X_C3
+	config_amd768_C3(enable);
+#endif
+#ifdef AMD76X_POS
+	config_amd768_POS(enable);
+#endif
+#ifdef AMD76X_NTH
+	config_amd768_NTH(enable, 1, 2);
+#endif
+
+	return 0;
+}
+
+#ifdef AMD76X_NTH
+/*
+ * Activate normal throttling via its ACPI register (P_CNT).
+ */
+static void
+activate_amd76x_NTH(int enable, int ratio)
+{
+	unsigned int regdword;
+
+	/* PM10, pg 110 of 768 doc, pg 70 of 766 doc */
+	regdword=inl(amd76x_pm_cfg.NTH_reg);
+	if(enable)
+		regdword |= (NTH_EN | NTH_RATIO(ratio));
+	else
+		regdword ^= NTH_EN;
+	outl(regdword, amd76x_pm_cfg.NTH_reg);
+}
+#endif
+
+#ifdef AMD76X_POS
+/*
+ * Activate sleep state via its ACPI register (PM1_CNT).
+ */
+static void
+activate_amd76x_SLP(int type)
+{
+	unsigned short regshort;
+
+	/* PM04, pg 109 of 768 doc, pg 69 of 766 doc */
+	regshort=inw(amd76x_pm_cfg.slp_reg);
+	regshort |= (SLP_EN | SLP_TYP(type)) ;
+	outw(regshort, amd76x_pm_cfg.slp_reg);
+}
+#endif /* AMD76X_POS */
+#ifdef AMD76X_POS
+/*
+ * Wrapper function to activate POS sleep state.
+ */
+static void
+activate_amd76x_POS(void)
+{
+	activate_amd76x_SLP(1);
+}
+#endif
+
+/*
+ * Idle loop for single processor systems
+ */
+void
+amd76x_up_idle(void)
+{
+	/* ACPI knows how to do C2 on SMP when cpu_count < 2
+	 * we really shouldn't end up here anyway. 
+	 */
+	amd76x_pm_cfg.orig_idle();
+}
+
+#ifndef AMD76X_NTH
+/*
+ * Idle loop for SMP systems
+ *
+ * Locking is done using atomic_t variables, no spin locks needed.
+ *
+ */
+static void
+amd76x_smp_idle(void)
+{
+	int i;
+	int num_online;
+	struct cpu_stat *prs;
+
+	if (unlikely(force_C1)) {
+		local_irq_disable();
+		safe_halt();
+		return;
+	}
+
+	prs = per_cpu_ptr(prs_ref, raw_smp_processor_id());
+	/* Spin inside (outer) idle loop until lazy_idle cycles
+	 * are reached.
+	 */
+	if (likely(++(prs->idle_count) <= lazy_idle)) {
+		return;
+	}
+
+	/* Now we are ready do go C2. */
+	local_irq_disable();
+	atomic_inc(&amd76x_stat.num_idle);
+	num_online = num_online_cpus();
+
+	/* Spin inside inner loop until either
+	 * - spin_idle cycles are reached
+	 * - there is work
+	 * - all CPUs are idle
+	 */
+	for (i = 0; i < spin_idle; i++) {
+		if (unlikely(need_resched()))
+			break;
+
+		smp_mb();
+		if (unlikely(atomic_read(&amd76x_stat.num_idle)
+					== num_online)) {
+		#ifdef AMD76X_C3
+			/* Enter C3 if all CPUs are ready for C3.
+			 * Enter C2 otherwise.
+			 */
+			if (unlikely(atomic_read(&amd76x_stat.num_C2) ==
+					num_online)) {
+				/* Invoke C3 */
+				prs->C3_cnt++;
+				inb(amd76x_pm_cfg.C3_reg);
+				goto been_idle;
+			} else {
+				/* Invoke C2 */
+				prs->C2_cnt++;
+				inb(amd76x_pm_cfg.C2_reg);
+				/* Now we are ready for C3 */
+				if (!prs->C2_active) {
+					prs->C2_active = 1;
+					atomic_inc(&amd76x_stat.num_C2);
+				}
+				goto been_idle;
+			}
+		#else
+
+			/* Invoke C2 */
+			prs->C2_cnt++;
+			inb(amd76x_pm_cfg.C2_reg);
+		#endif
+			goto been_idle;
+		}
+	}
+#ifdef AMD76X_C3
+	/* We haven't been in C2/C3 if we are here. We are no longer
+	 * ready for C3.
+	 */
+	if (prs->C2_active) {
+		smp_mb();
+		prs->C2_active = 0;
+		atomic_dec(&amd76x_stat.num_C2);
+	}
+#endif
+
+been_idle:
+	smp_mb();
+	atomic_dec(&amd76x_stat.num_idle);
+	prs->idle_count = 0;
+	local_irq_enable();
+}
+#endif
+
+/*
+ *   sysfs support, RW
+ */
+#ifndef AMD76X_NTH
+static int
+show_watch_irqs(char *buf, struct kernel_param *kp)
+{
+	int i;
+	ssize_t ret = 0;
+	for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1 ; i++) {
+		if (i > 0)
+			ret += sprintf(buf + ret, ",");
+		ret += sprintf(buf + ret, "%d:%d",
+			watch_item[i].irq,
+			watch_item[i].limit);
+	}
+	return ret;
+}
+
+static int
+set_watch_irqs(const char *val, struct kernel_param *kp)
+{
+	const char *s;
+	char *e;
+	long irq, limit;
+	int i;
+	
+	for (i = 0, s = val; i < AMD76X_WATCH_MAX && s && *s; i++) {
+		irq = simple_strtol(s, &e, 0);
+		if (e == s)
+			break;
+		if (*e == ':') {
+			s = e + 1;
+			limit = simple_strtol(s, &e, 0);
+		} else
+			limit = AMD76X_WATCH_LIM;
+		if (irq >= 0)
+			watch_item[i].irq = irq;
+		else {
+			watch_item[i].irq = -1;
+			break;
+		}
+		watch_item[i].limit = limit;
+		watch_item[i].count = 0;
+		if (*e == ',')
+			s = e + 1;
+		else
+			s = e;
+	}
+	if (i < AMD76X_WATCH_MAX)
+		watch_item[i].irq = -1;
+	if (watch_item[0].irq != -1) {
+		schedule_watch = 1;
+		printk(KERN_INFO "amd76x_pm: irq rate watcher parms:\n");
+		for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1; i++)
+			printk(KERN_INFO
+			"amd76x_pm: irq %d: limit = %d/s\n",
+				watch_item[i].irq,
+				watch_item[i].limit);
+	} else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher disabled.\n");
+
+	return 0;
+}
+
+static ssize_t 
+show_C2_cnt (struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	unsigned int C2_cnt = 0;
+	struct cpu_stat *prs;
+	int i;
+
+	for_each_online_cpu(i) {
+		prs = per_cpu_ptr(prs_ref, i);
+		C2_cnt += prs->C2_cnt;
+	}
+	return sprintf(buf,"%u\n", C2_cnt);  
+}
+
+#ifdef AMD76X_C3
+static ssize_t 
+show_C3_cnt (struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	unsigned int C3_cnt = 0;
+	struct cpu_stat *prs;
+	int i;
+	for_each_online_cpu(i)
+		prs = per_cpu_ptr(prs_ref, i);
+		C3_cnt += prs->C3_cnt;
+	return sprintf(buf,"%u\n", C3_cnt);  
+}
+#endif
+
+static DEVICE_ATTR(C2_cnt, S_IRUGO,
+		   show_C2_cnt, NULL);
+#ifdef AMD76X_C3
+static DEVICE_ATTR(C3_cnt, S_IRUGO,
+		   show_C3_cnt, NULL);
+#endif
+#endif
+
+static void
+setup_watch(void)
+{
+	if (watch_int <= 0)
+		watch_int = AMD76X_WATCH_INT;
+	if (watch_item[0].irq != -1 && watch_int) {
+		schedule_work(&work);
+		printk(KERN_INFO "amd76x_pm: irq rate watcher started. "
+				"watch_int = %lu ms, min_C1 = %lu\n",
+				watch_int, min_C1);
+	} else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher disabled.\n");
+}
+
+static void
+watch_irq(void *parm)
+{
+	int i;
+	int irq_cnt;
+	int set_force_C1 = 0;
+
+	for (i = 0; i < AMD76X_WATCH_MAX && watch_item[i].irq != -1; i++) {
+		irq_cnt = kstat_irqs(watch_item[i].irq);
+		if ((irq_cnt - watch_item[i].count) * 1000 >
+				watch_item[i].limit * watch_int) {
+			set_force_C1 = 1;
+		}
+		watch_item[i].count = irq_cnt;
+	}
+
+#ifdef AMD76X_LOG_C1
+        if (set_force_C1 && !force_C1)
+                printk(KERN_INFO "amd76x_pm: C1 on\n");
+        if (!set_force_C1 && force_C1 == 1)
+                printk(KERN_INFO "amd76x_pm: C1 off\n");
+#endif
+
+	if (set_force_C1)
+		force_C1 = min_C1;
+	else if (force_C1)
+		force_C1--;
+
+	if (schedule_watch && watch_int > 0)
+		schedule_delayed_work(&work,
+			msecs_to_jiffies(watch_int));
+	else
+		printk(KERN_INFO "amd76x_pm: irq rate watcher stopped.\n");
+}
+
+
+/*
+ * Finds and initializes the bridges, and then sets the idle function
+ */
+static int
+amd76x_pm_main(void)
+{
+#ifndef AMD76X_NTH
+	int i;
+#endif
+	struct cpu_stat *prs;
+
+	amd76x_pm_cfg.orig_idle = 0;
+	if(lazy_idle == 0)
+	    lazy_idle = AMD76X_LAZY_IDLE;
+	printk(KERN_INFO "amd76x_pm: lazy_idle = %lu\n", lazy_idle);
+	if(spin_idle == 0)
+		spin_idle = 2 * lazy_idle;
+	printk(KERN_INFO "amd76x_pm: spin_idle = %lu\n", spin_idle);
+
+	/* Find southbridge */
+	pdev_sb = NULL;
+	while((pdev_sb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_sb)) != NULL) {
+		if(pci_match_id(amd_sb_tbl, pdev_sb) != NULL)
+			goto found_sb;
+	}
+	printk(KERN_ERR "amd76x_pm: Could not find southbridge\n");
+	return -ENODEV;
+
+found_sb:
+	/* Find northbridge */
+	pdev_nb = NULL;
+	while((pdev_nb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_nb)) != NULL) {
+		if(pci_match_id(amd_nb_tbl, pdev_nb) != NULL)
+			goto found_nb;
+	}
+	printk(KERN_ERR "amd76x_pm: Could not find northbridge\n");
+	return -ENODEV;
+
+ found_nb:	
+	
+	/* Init southbridge */
+	switch (pdev_sb->device) {
+	case PCI_DEVICE_ID_AMD_VIPER_7413:	/* AMD-765 or 766 */
+		config_amd766(1);
+		break;
+	case PCI_DEVICE_ID_AMD_VIPER_7443:	/* AMD-768 */
+		config_amd768(1);
+		break;
+	default:
+		printk(KERN_ERR "amd76x_pm: No southbridge to initialize\n");		
+		break;
+	}
+
+	/* Init northbridge and queue the new idle function */
+	if(!pdev_nb) {
+		printk("amd76x_pm: No northbridge found.\n");
+		return -ENODEV;
+	}
+	switch (pdev_nb->device) {
+	case PCI_DEVICE_ID_AMD_FE_GATE_700C:	/* AMD-762 */
+		config_amd762(1);
+#ifndef AMD76X_NTH
+		amd76x_pm_cfg.curr_idle = amd76x_smp_idle;
+#endif
+		break;
+	default:
+		printk(KERN_ERR "amd76x_pm: No northbridge to initialize\n");
+		break;
+	}
+
+#ifndef AMD76X_NTH
+	if(num_online_cpus() == 1) {
+		amd76x_pm_cfg.curr_idle = amd76x_up_idle;
+		printk(KERN_ERR "amd76x_pm: UP machine detected. ACPI is your friend.\n");
+	}
+	if (!amd76x_pm_cfg.curr_idle) {
+		printk(KERN_ERR "amd76x_pm: Idle function not changed\n");
+		return 1;
+	}
+
+	prs_ref = alloc_percpu(struct cpu_stat);
+	
+	for (i = 0; i < NR_CPUS; i++) {
+		prs = per_cpu_ptr(prs_ref, i);
+		prs->idle_count = 0;
+		prs->C2_cnt = 0;
+	#ifdef AMD76X_C3
+		prs->C2_cnt = 0;
+		prs->C2_active = 0;
+	#endif
+	}
+
+	amd76x_pm_cfg.orig_idle = pm_idle;
+	pm_idle = amd76x_pm_cfg.curr_idle;
+
+	wmb();
+	
+	/* sysfs */
+	device_create_file(&pdev_nb->dev, &dev_attr_C2_cnt);
+#ifdef AMD76X_C3
+	device_create_file(&pdev_nb->dev, &dev_attr_C3_cnt);
+#endif
+#endif
+
+#ifdef AMD76X_NTH
+	/* Turn NTH on with maxium throttling for testing. */
+	activate_amd76x_NTH(1, 1);
+#endif
+
+#ifdef AMD76X_POS
+	/* Testing here only. */
+	activate_amd76x_POS();
+#endif
+	setup_watch();
+
+	return 0;
+}
+
+
+static int __init
+amd76x_pm_init(void)
+{
+	printk(KERN_INFO "amd76x_pm: Version %s loaded.\n", VERSION);
+	return amd76x_pm_main();
+}
+
+
+static void __exit
+amd76x_pm_cleanup(void)
+{
+#ifndef AMD76X_NTH
+	int i;
+	unsigned int C2_cnt = 0;
+#ifdef AMD76X_C3
+	unsigned int C3_cnt = 0;
+#endif
+#endif
+	struct cpu_stat *prs;
+	
+#ifndef AMD76X_NTH
+	pm_idle = amd76x_pm_cfg.orig_idle;
+
+	cpu_idle_wait();
+
+
+	/* This isn't really needed. */
+	for_each_online_cpu(i) {
+		prs = per_cpu_ptr(prs_ref, i);
+		C2_cnt += prs->C2_cnt;
+	#ifdef AMD76X_C3
+		C3_cnt += prs->C3_cnt;
+	#endif
+	}
+#ifdef AMD76X_C3
+	 printk(KERN_INFO "amd76x_pm: %u C2 calls, %u C3 calls\n",
+			C2_cnt, C3_cnt);
+#else
+	printk(KERN_INFO "amd76x_pm: %u C2 calls\n", C2_cnt);
+#endif
+	
+	/* remove sysfs */
+	device_remove_file(&pdev_nb->dev, &dev_attr_C2_cnt);
+#ifdef AMD76X_C3
+	device_remove_file(&pdev_nb->dev, &dev_attr_C3_cnt);
+#endif
+#endif
+
+#ifdef AMD76X_NTH
+	/* Turn NTH off */
+	activate_amd76x_NTH(0, 0);
+#endif
+
+	schedule_watch = 0;
+        flush_scheduled_work();
+        cancel_delayed_work(&work);
+        flush_scheduled_work();
+	
+	free_percpu(prs_ref);
+}
+
+
+MODULE_LICENSE("GPL");
+module_init(amd76x_pm_init);
+module_exit(amd76x_pm_cleanup);
diff -Nru linux-2.6.15/include/linux/amd76x_pm.h linux-2.6.15-jo/include/linux/amd76x_pm.h
--- linux-2.6.15/include/linux/amd76x_pm.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.15-jo/include/linux/amd76x_pm.h	2006-01-08 18:26:57.000000000 +0100
@@ -0,0 +1,63 @@
+/* 
+ * Begin 765/766
+ */
+/* C2/C3/POS options in C3A50, page 63 in AMD-766 doc */
+#define ZZ_CACHE_EN	1
+#define DCSTOP_EN	(1 << 1)
+#define STPCLK_EN	(1 << 2)
+#define CPUSTP_EN	(1 << 3)
+#define PCISTP_EN	(1 << 4)
+#define CPUSLP_EN	(1 << 5)
+#define SUSPND_EN	(1 << 6)
+#define CPURST_EN	(1 << 7)
+
+#define C2_REGS		0
+#define C3_REGS		8
+#define POS_REGS	16	
+/*
+ * End 765/766
+ */
+
+
+/*
+ * Begin 768
+ */
+/* C2/C3 options in DevB:3x4F, page 100 in AMD-768 doc */
+#define C2EN		1
+#define C3EN		(1 << 1)
+#define ZZ_C3EN		(1 << 2)
+#define CSLP_C3EN	(1 << 3)
+#define CSTP_C3EN	(1 << 4)
+
+/* POS options in DevB:3x50, page 101 in AMD-768 doc */
+#define POSEN	1
+#define CSTP	(1 << 2)
+#define PSTP	(1 << 3)
+#define ASTP	(1 << 4)
+#define DCSTP	(1 << 5)
+#define CSLP	(1 << 6)
+#define SUSP	(1 << 8)
+#define MSRSM	(1 << 14)
+#define PITRSM	(1 << 15)
+
+/* NTH options DevB:3x40, pg 93 of 768 doc */
+#define NTPER(x) (x << 3)
+#define THMINEN(x) (x << 4)
+
+/*
+ * End 768
+ */
+
+/* NTH activate. PM10, pg 110 of 768 doc, pg 70 of 766 doc */
+#define NTH_RATIO(x) (x << 1)
+#define NTH_EN (1 << 4)
+
+/* Sleep state. PM04, pg 109 of 768 doc, pg 69 of 766 doc */
+#define SLP_EN (1 << 13)
+#define SLP_TYP(x) (x << 10)
+
+#define AMD76X_LAZY_IDLE 512
+#define AMD76X_WATCH_INT 500
+#define AMD76X_WATCH_LIM 128
+#define AMD76X_WATCH_MAX 8
+#define AMD76X_MIN_C1 2
