[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/Kconfig b/src/kernel/linux/v4.14/drivers/macintosh/Kconfig
new file mode 100644
index 0000000..97a420c
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/Kconfig
@@ -0,0 +1,283 @@
+
+menuconfig MACINTOSH_DRIVERS
+	bool "Macintosh device drivers"
+	depends on PPC || MAC || X86
+	default y if (PPC_PMAC || MAC)
+	---help---
+	  Say Y here to get to see options for devices used with Macintosh
+	  computers. This option alone does not add any kernel code.
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if MACINTOSH_DRIVERS
+
+config ADB
+	bool "Apple Desktop Bus (ADB) support"
+	depends on MAC || (PPC_PMAC && PPC32)
+	help
+	  Apple Desktop Bus (ADB) support is for support of devices which
+	  are connected to an ADB port.  ADB devices tend to have 4 pins.
+	  If you have an Apple Macintosh prior to the iMac, an iBook or
+	  PowerBook, or a "Blue and White G3", you probably want to say Y
+	  here.  Otherwise say N.
+
+config ADB_MACII
+	bool "Include Mac II ADB driver"
+	depends on ADB && MAC
+	help
+	  Say Y here if want your kernel to support Macintosh systems that use
+	  the Mac II style ADB.  This includes the II, IIx, IIcx, SE/30, IIci,
+	  Quadra 610, Quadra 650, Quadra 700, Quadra 800, Centris 610 and
+	  Centris 650.
+
+config ADB_IOP
+	bool "Include IOP (IIfx/Quadra 9x0) ADB driver"
+	depends on ADB && MAC
+	help
+	  The I/O Processor (IOP) is an Apple custom IC designed to provide
+	  intelligent support for I/O controllers.  It is described at
+	  <http://www.angelfire.com/ca2/dev68k/iopdesc.html> to enable direct
+	  support for it, say 'Y' here.
+
+config ADB_PMU68K
+	bool "Include PMU (Powerbook) ADB driver"
+	depends on ADB && MAC
+	help
+	  Say Y here if want your kernel to support the m68k based Powerbooks.
+	  This includes the PowerBook 140, PowerBook 145, PowerBook 150,
+	  PowerBook 160, PowerBook 165, PowerBook 165c, PowerBook 170,
+	  PowerBook 180, PowerBook, 180c, PowerBook 190cs, PowerBook 520,
+	  PowerBook Duo 210, PowerBook Duo 230, PowerBook Duo 250,
+	  PowerBook Duo 270c, PowerBook Duo 280 and PowerBook Duo 280c.
+
+# we want to change this to something like CONFIG_SYSCTRL_CUDA/PMU
+config ADB_CUDA
+	bool "Support for Cuda/Egret based Macs and PowerMacs"
+	depends on (ADB || PPC_PMAC) && !PPC_PMAC64
+	help
+	  This provides support for Cuda/Egret based Macintosh and
+	  Power Macintosh systems. This includes most m68k based Macs,
+	  most Old World PowerMacs, the first generation iMacs, the
+	  Blue & White G3 and the "Yikes" G4 (PCI Graphics). All later
+	  models should use CONFIG_ADB_PMU instead. It is safe to say Y
+	  here even if your machine doesn't have a Cuda or Egret device.
+
+	  If unsure say Y.
+
+config ADB_PMU
+	bool "Support for PMU  based PowerMacs"
+	depends on PPC_PMAC
+	help
+	  On PowerBooks, iBooks, and recent iMacs and Power Macintoshes, the
+	  PMU is an embedded microprocessor whose primary function is to
+	  control system power, and battery charging on the portable models.
+	  The PMU also controls the ADB (Apple Desktop Bus) which connects to
+	  the keyboard and mouse on some machines, as well as the non-volatile
+	  RAM and the RTC (real time clock) chip.  Say Y to enable support for
+	  this device; you should do so if your machine is one of those
+	  mentioned above.
+
+config ADB_PMU_LED
+	bool "Support for the Power/iBook front LED"
+	depends on ADB_PMU
+	select NEW_LEDS
+	select LEDS_CLASS
+	help
+	  Support the front LED on Power/iBooks as a generic LED that can
+	  be triggered by any of the supported triggers. To get the
+	  behaviour of the old CONFIG_BLK_DEV_IDE_PMAC_BLINK, select this
+	  and the disk LED trigger and configure appropriately through sysfs.
+
+config ADB_PMU_LED_DISK
+	bool "Use front LED as DISK LED by default"
+	depends on ADB_PMU_LED
+	depends on LEDS_CLASS
+	select LEDS_TRIGGERS
+	select LEDS_TRIGGER_DISK
+	help
+	  This option makes the front LED default to the disk trigger
+	  so that it blinks on disk activity.
+
+config PMAC_SMU
+	bool "Support for SMU  based PowerMacs"
+	depends on PPC_PMAC64
+	help
+	  This option adds support for the newer G5 iMacs and PowerMacs based
+	  on the "SMU" system control chip which replaces the old PMU.
+	  If you don't know, say Y.
+
+config PMAC_APM_EMU
+	tristate "APM emulation"
+	select APM_EMULATION
+	depends on ADB_PMU && PM && PPC32
+
+config PMAC_MEDIABAY
+	bool "Support PowerBook hotswap media bay"
+	depends on PPC_PMAC && PPC32 && BLOCK
+	help
+	  This option adds support for older PowerBook's hotswap media bay
+	  that can contains batteries, floppy drives, or IDE devices. PCI
+	  devices are not fully supported in the bay as I never had one to
+	  try with
+
+config PMAC_BACKLIGHT
+	bool "Backlight control for LCD screens"
+	depends on ADB_PMU && FB = y && (BROKEN || !PPC64)
+	select FB_BACKLIGHT
+	help
+	  Say Y here to enable Macintosh specific extensions of the generic
+	  backlight code. With this enabled, the brightness keys on older
+	  PowerBooks will be enabled so you can change the screen brightness.
+	  Newer models should use a userspace daemon like pbbuttonsd.
+
+config PMAC_BACKLIGHT_LEGACY
+	bool "Provide legacy ioctl's on /dev/pmu for the backlight"
+	depends on PMAC_BACKLIGHT && (BROKEN || !PPC64)
+	help
+	  Say Y if you want to enable legacy ioctl's on /dev/pmu. This is for
+	  programs which use this old interface. New and updated programs
+	  should use the backlight classes in sysfs.
+
+config ADB_MACIO
+	bool "Include MacIO (CHRP) ADB driver"
+	depends on ADB && PPC_CHRP && !PPC_PMAC64
+	help
+	  Say Y here to include direct support for the ADB controller in the
+	  Hydra chip used on PowerPC Macintoshes of the CHRP type.  (The Hydra
+	  also includes a MESH II SCSI controller, DBDMA controller, VIA chip,
+	  OpenPIC controller and two RS422/Geoports.)
+
+config INPUT_ADBHID
+	bool "Support for ADB input devices (keyboard, mice, ...)"
+	depends on ADB && INPUT=y
+	help
+	  Say Y here if you want to have ADB (Apple Desktop Bus) HID devices
+	  such as keyboards, mice, joysticks, trackpads  or graphic tablets
+	  handled by the input layer.  If you say Y here, make sure to say Y to
+	  the corresponding drivers "Mouse Support" (CONFIG_INPUT_MOUSEDEV) and
+	  "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+	  If unsure, say Y.
+
+config MAC_EMUMOUSEBTN
+	tristate "Support for mouse button 2+3 emulation"
+	depends on SYSCTL && INPUT
+	help
+	  This provides generic support for emulating the 2nd and 3rd mouse
+	  button with keypresses.  If you say Y here, the emulation is still
+	  disabled by default.  The emulation is controlled by these sysctl
+	  entries:
+	  /proc/sys/dev/mac_hid/mouse_button_emulation
+	  /proc/sys/dev/mac_hid/mouse_button2_keycode
+	  /proc/sys/dev/mac_hid/mouse_button3_keycode
+
+	  If you have an Apple machine with a 1-button mouse, say Y here.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mac_hid.
+
+config THERM_WINDTUNNEL
+	tristate "Support for thermal management on Windtunnel G4s"
+	depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64
+	help
+	  This driver provides some thermostat and fan control for the desktop
+	  G4 "Windtunnel"
+
+config THERM_ADT746X
+	tristate "Support for thermal mgmnt on laptops with ADT 746x chipset"
+	depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64
+	help
+	  This driver provides some thermostat and fan control for the
+          iBook G4, and the ATI based aluminium PowerBooks, allowing slightly
+	  better fan behaviour by default, and some manual control.
+
+config WINDFARM
+	tristate "New PowerMac thermal control infrastructure"
+	depends on PPC
+
+config WINDFARM_PM81
+	tristate "Support for thermal management on iMac G5"
+	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the iMacG5
+
+config WINDFARM_PM72
+	tristate "Support for thermal management on PowerMac G5 (AGP)"
+	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the PowerMac G5
+	  "AGP" variants (PowerMac 7,2 and 7,3)
+
+config WINDFARM_RM31
+	tristate "Support for thermal management on Xserve G5"
+	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the Xserve G5
+	  (RackMac3,1)
+
+config WINDFARM_PM91
+	tristate "Support for thermal management on PowerMac9,1"
+	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the PowerMac9,1
+          which is the recent (SMU based) single CPU desktop G5
+
+config WINDFARM_PM112
+	tristate "Support for thermal management on PowerMac11,2"
+	depends on WINDFARM && I2C && PMAC_SMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the PowerMac11,2
+	  which are the recent dual and quad G5 machines using the
+	  970MP dual-core processor.
+
+config WINDFARM_PM121
+	tristate "Support for thermal management on PowerMac12,1"
+	depends on WINDFARM && I2C && PMAC_SMU
+	select I2C_POWERMAC
+	help
+	  This driver provides thermal control for the PowerMac12,1
+	  which is the iMac G5 (iSight).
+
+config ANSLCD
+	tristate "Support for ANS LCD display"
+	depends on ADB_CUDA && PPC_PMAC
+
+config PMAC_RACKMETER
+	tristate "Support for Apple XServe front panel LEDs"
+	depends on PPC_PMAC
+	help
+	  This driver provides some support to control the front panel
+          blue LEDs "vu-meter" of the XServer macs.
+
+config SENSORS_AMS
+	tristate "Apple Motion Sensor driver"
+	depends on PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C)
+	select INPUT_POLLDEV
+	help
+	  Support for the motion sensor included in PowerBooks. Includes
+	  implementations for PMU and I2C.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called ams.
+
+config SENSORS_AMS_PMU
+	bool "PMU variant"
+	depends on SENSORS_AMS && ADB_PMU
+	default y
+	help
+	  PMU variant of motion sensor, found in late 2005 PowerBooks.
+
+config SENSORS_AMS_I2C
+	bool "I2C variant"
+	depends on SENSORS_AMS && I2C
+	default y
+	help
+	  I2C variant of motion sensor, found in early 2005 PowerBooks and
+	  iBooks.
+
+endif # MACINTOSH_DRIVERS
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/Makefile b/src/kernel/linux/v4.14/drivers/macintosh/Makefile
new file mode 100644
index 0000000..ee80363
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/Makefile
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Macintosh-specific device drivers.
+#
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_PPC_PMAC)		+= macio_asic.o macio_sysfs.o
+
+obj-$(CONFIG_PMAC_MEDIABAY)	+= mediabay.o
+obj-$(CONFIG_MAC_EMUMOUSEBTN)	+= mac_hid.o
+obj-$(CONFIG_INPUT_ADBHID)	+= adbhid.o
+obj-$(CONFIG_ANSLCD)		+= ans-lcd.o
+
+obj-$(CONFIG_ADB_PMU)		+= via-pmu.o via-pmu-event.o
+obj-$(CONFIG_ADB_PMU_LED)	+= via-pmu-led.o
+obj-$(CONFIG_PMAC_BACKLIGHT)	+= via-pmu-backlight.o
+obj-$(CONFIG_ADB_CUDA)		+= via-cuda.o
+obj-$(CONFIG_PMAC_APM_EMU)	+= apm_emu.o
+obj-$(CONFIG_PMAC_SMU)		+= smu.o
+
+obj-$(CONFIG_ADB)		+= adb.o
+obj-$(CONFIG_ADB_MACII)		+= via-macii.o
+obj-$(CONFIG_ADB_IOP)		+= adb-iop.o
+obj-$(CONFIG_ADB_PMU68K)	+= via-pmu68k.o
+obj-$(CONFIG_ADB_MACIO)		+= macio-adb.o
+
+obj-$(CONFIG_THERM_WINDTUNNEL)	+= therm_windtunnel.o
+obj-$(CONFIG_THERM_ADT746X)	+= therm_adt746x.o
+obj-$(CONFIG_WINDFARM)	        += windfarm_core.o
+obj-$(CONFIG_WINDFARM_PM72)     += windfarm_fcu_controls.o \
+				   windfarm_ad7417_sensor.o \
+				   windfarm_lm75_sensor.o \
+				   windfarm_max6690_sensor.o \
+				   windfarm_pid.o \
+				   windfarm_cpufreq_clamp.o \
+				   windfarm_pm72.o
+obj-$(CONFIG_WINDFARM_RM31)     += windfarm_fcu_controls.o \
+				   windfarm_ad7417_sensor.o \
+				   windfarm_lm75_sensor.o \
+				   windfarm_lm87_sensor.o \
+				   windfarm_max6690_sensor.o \
+				   windfarm_pid.o \
+				   windfarm_cpufreq_clamp.o \
+				   windfarm_rm31.o
+obj-$(CONFIG_WINDFARM_PM81)     += windfarm_smu_controls.o \
+				   windfarm_smu_sensors.o \
+				   windfarm_lm75_sensor.o windfarm_pid.o \
+				   windfarm_cpufreq_clamp.o windfarm_pm81.o
+obj-$(CONFIG_WINDFARM_PM91)     += windfarm_smu_controls.o \
+				   windfarm_smu_sensors.o \
+				   windfarm_lm75_sensor.o windfarm_pid.o \
+				   windfarm_cpufreq_clamp.o windfarm_pm91.o
+obj-$(CONFIG_WINDFARM_PM112)	+= windfarm_pm112.o windfarm_smu_sat.o \
+				   windfarm_smu_controls.o \
+				   windfarm_smu_sensors.o \
+				   windfarm_max6690_sensor.o \
+				   windfarm_lm75_sensor.o windfarm_pid.o
+obj-$(CONFIG_WINDFARM_PM121)	+= windfarm_pm121.o windfarm_smu_sat.o \
+				   windfarm_smu_controls.o \
+				   windfarm_smu_sensors.o \
+				   windfarm_max6690_sensor.o \
+				   windfarm_lm75_sensor.o windfarm_pid.o
+obj-$(CONFIG_PMAC_RACKMETER)	+= rack-meter.o
+
+obj-$(CONFIG_SENSORS_AMS)	+= ams/
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/adb-iop.c b/src/kernel/linux/v4.14/drivers/macintosh/adb-iop.c
new file mode 100644
index 0000000..dbc4a3e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/adb-iop.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I/O Processor (IOP) ADB Driver
+ * Written and (C) 1999 by Joshua M. Thompson (funaho@jurai.org)
+ * Based on via-cuda.c by Paul Mackerras.
+ *
+ * 1999-07-01 (jmt) - First implementation for new driver architecture.
+ *
+ * 1999-07-31 (jmt) - First working version.
+ *
+ * TODO:
+ *
+ * o Implement SRQ handling.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#include <asm/macintosh.h> 
+#include <asm/macints.h> 
+#include <asm/mac_iop.h>
+#include <asm/mac_oss.h>
+#include <asm/adb_iop.h>
+
+#include <linux/adb.h> 
+
+/*#define DEBUG_ADB_IOP*/
+
+extern void iop_ism_irq(int, void *);
+
+static struct adb_request *current_req;
+static struct adb_request *last_req;
+#if 0
+static unsigned char reply_buff[16];
+static unsigned char *reply_ptr;
+#endif
+
+static enum adb_iop_state {
+    idle,
+    sending,
+    awaiting_reply
+} adb_iop_state;
+
+static void adb_iop_start(void);
+static int adb_iop_probe(void);
+static int adb_iop_init(void);
+static int adb_iop_send_request(struct adb_request *, int);
+static int adb_iop_write(struct adb_request *);
+static int adb_iop_autopoll(int);
+static void adb_iop_poll(void);
+static int adb_iop_reset_bus(void);
+
+struct adb_driver adb_iop_driver = {
+	"ISM IOP",
+	adb_iop_probe,
+	adb_iop_init,
+	adb_iop_send_request,
+	adb_iop_autopoll,
+	adb_iop_poll,
+	adb_iop_reset_bus
+};
+
+static void adb_iop_end_req(struct adb_request *req, int state)
+{
+	req->complete = 1;
+	current_req = req->next;
+	if (req->done) (*req->done)(req);
+	adb_iop_state = state;
+}
+
+/*
+ * Completion routine for ADB commands sent to the IOP.
+ *
+ * This will be called when a packet has been successfully sent.
+ */
+
+static void adb_iop_complete(struct iop_msg *msg)
+{
+	struct adb_request *req;
+	unsigned long flags;
+
+	local_irq_save(flags);
+
+	req = current_req;
+	if ((adb_iop_state == sending) && req && req->reply_expected) {
+		adb_iop_state = awaiting_reply;
+	}
+
+	local_irq_restore(flags);
+}
+
+/*
+ * Listen for ADB messages from the IOP.
+ *
+ * This will be called when unsolicited messages (usually replies to TALK
+ * commands or autopoll packets) are received.
+ */
+
+static void adb_iop_listen(struct iop_msg *msg)
+{
+	struct adb_iopmsg *amsg = (struct adb_iopmsg *) msg->message;
+	struct adb_request *req;
+	unsigned long flags;
+#ifdef DEBUG_ADB_IOP
+	int i;
+#endif
+
+	local_irq_save(flags);
+
+	req = current_req;
+
+#ifdef DEBUG_ADB_IOP
+	printk("adb_iop_listen %p: rcvd packet, %d bytes: %02X %02X", req,
+		(uint) amsg->count + 2, (uint) amsg->flags, (uint) amsg->cmd);
+	for (i = 0; i < amsg->count; i++)
+		printk(" %02X", (uint) amsg->data[i]);
+	printk("\n");
+#endif
+
+	/* Handle a timeout. Timeout packets seem to occur even after */
+	/* we've gotten a valid reply to a TALK, so I'm assuming that */
+	/* a "timeout" is actually more like an "end-of-data" signal. */
+	/* We need to send back a timeout packet to the IOP to shut   */
+	/* it up, plus complete the current request, if any.          */
+
+	if (amsg->flags & ADB_IOP_TIMEOUT) {
+		msg->reply[0] = ADB_IOP_TIMEOUT | ADB_IOP_AUTOPOLL;
+		msg->reply[1] = 0;
+		msg->reply[2] = 0;
+		if (req && (adb_iop_state != idle)) {
+			adb_iop_end_req(req, idle);
+		}
+	} else {
+		/* TODO: is it possible for more than one chunk of data  */
+		/*       to arrive before the timeout? If so we need to */
+		/*       use reply_ptr here like the other drivers do.  */
+		if ((adb_iop_state == awaiting_reply) &&
+		    (amsg->flags & ADB_IOP_EXPLICIT)) {
+			req->reply_len = amsg->count + 1;
+			memcpy(req->reply, &amsg->cmd, req->reply_len);
+		} else {
+			adb_input(&amsg->cmd, amsg->count + 1,
+				  amsg->flags & ADB_IOP_AUTOPOLL);
+		}
+		memcpy(msg->reply, msg->message, IOP_MSG_LEN);
+	}
+	iop_complete_message(msg);
+	local_irq_restore(flags);
+}
+
+/*
+ * Start sending an ADB packet, IOP style
+ *
+ * There isn't much to do other than hand the packet over to the IOP
+ * after encapsulating it in an adb_iopmsg.
+ */
+
+static void adb_iop_start(void)
+{
+	unsigned long flags;
+	struct adb_request *req;
+	struct adb_iopmsg amsg;
+#ifdef DEBUG_ADB_IOP
+	int i;
+#endif
+
+	/* get the packet to send */
+	req = current_req;
+	if (!req) return;
+
+	local_irq_save(flags);
+
+#ifdef DEBUG_ADB_IOP
+	printk("adb_iop_start %p: sending packet, %d bytes:", req, req->nbytes);
+	for (i = 0 ; i < req->nbytes ; i++)
+		printk(" %02X", (uint) req->data[i]);
+	printk("\n");
+#endif
+
+	/* The IOP takes MacII-style packets, so */
+	/* strip the initial ADB_PACKET byte.    */
+
+	amsg.flags = ADB_IOP_EXPLICIT;
+	amsg.count = req->nbytes - 2;
+
+	/* amsg.data immediately follows amsg.cmd, effectively making */
+	/* amsg.cmd a pointer to the beginning of a full ADB packet.  */
+	memcpy(&amsg.cmd, req->data + 1, req->nbytes - 1);
+
+	req->sent = 1;
+	adb_iop_state = sending;
+	local_irq_restore(flags);
+
+	/* Now send it. The IOP manager will call adb_iop_complete */
+	/* when the packet has been sent.                          */
+
+	iop_send_message(ADB_IOP, ADB_CHAN, req,
+			 sizeof(amsg), (__u8 *) &amsg, adb_iop_complete);
+}
+
+int adb_iop_probe(void)
+{
+	if (!iop_ism_present) return -ENODEV;
+	return 0;
+}
+
+int adb_iop_init(void)
+{
+	printk("adb: IOP ISM driver v0.4 for Unified ADB.\n");
+	iop_listen(ADB_IOP, ADB_CHAN, adb_iop_listen, "ADB");
+	return 0;
+}
+
+int adb_iop_send_request(struct adb_request *req, int sync)
+{
+	int err;
+
+	err = adb_iop_write(req);
+	if (err) return err;
+
+	if (sync) {
+		while (!req->complete) adb_iop_poll();
+	}
+	return 0;
+}
+
+static int adb_iop_write(struct adb_request *req)
+{
+	unsigned long flags;
+
+	if ((req->nbytes < 2) || (req->data[0] != ADB_PACKET)) {
+		req->complete = 1;
+		return -EINVAL;
+	}
+
+	local_irq_save(flags);
+
+	req->next = NULL;
+	req->sent = 0;
+	req->complete = 0;
+	req->reply_len = 0;
+
+	if (current_req != 0) {
+		last_req->next = req;
+		last_req = req;
+	} else {
+		current_req = req;
+		last_req = req;
+	}
+
+	local_irq_restore(flags);
+	if (adb_iop_state == idle) adb_iop_start();
+	return 0;
+}
+
+int adb_iop_autopoll(int devs)
+{
+	/* TODO: how do we enable/disable autopoll? */
+	return 0;
+}
+
+void adb_iop_poll(void)
+{
+	if (adb_iop_state == idle) adb_iop_start();
+	iop_ism_irq(0, (void *) ADB_IOP);
+}
+
+int adb_iop_reset_bus(void)
+{
+	struct adb_request req = {
+		.reply_expected = 0,
+		.nbytes = 2,
+		.data = { ADB_PACKET, 0 },
+	};
+
+	adb_iop_write(&req);
+	while (!req.complete) {
+		adb_iop_poll();
+		schedule();
+	}
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/adb.c b/src/kernel/linux/v4.14/drivers/macintosh/adb.c
new file mode 100644
index 0000000..289800b
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/adb.c
@@ -0,0 +1,898 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for the Apple Desktop Bus
+ * and the /dev/adb device on macintoshes.
+ *
+ * Copyright (C) 1996 Paul Mackerras.
+ *
+ * Modified to declare controllers as structures, added
+ * client notification of bus reset and handles PowerBook
+ * sleep, by Benjamin Herrenschmidt.
+ *
+ * To do:
+ *
+ * - /sys/bus/adb to list the devices and infos
+ * - more /dev/adb to allow userland to receive the
+ *   flow of auto-polling datas from a given device.
+ * - move bus probe to a kernel thread
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/adb.h>
+#include <linux/cuda.h>
+#include <linux/pmu.h>
+#include <linux/notifier.h>
+#include <linux/wait.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/kthread.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+
+#include <linux/uaccess.h>
+#ifdef CONFIG_PPC
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#endif
+
+
+EXPORT_SYMBOL(adb_client_list);
+
+extern struct adb_driver via_macii_driver;
+extern struct adb_driver via_cuda_driver;
+extern struct adb_driver adb_iop_driver;
+extern struct adb_driver via_pmu_driver;
+extern struct adb_driver macio_adb_driver;
+
+static DEFINE_MUTEX(adb_mutex);
+static struct adb_driver *adb_driver_list[] = {
+#ifdef CONFIG_ADB_MACII
+	&via_macii_driver,
+#endif
+#ifdef CONFIG_ADB_CUDA
+	&via_cuda_driver,
+#endif
+#ifdef CONFIG_ADB_IOP
+	&adb_iop_driver,
+#endif
+#if defined(CONFIG_ADB_PMU) || defined(CONFIG_ADB_PMU68K)
+	&via_pmu_driver,
+#endif
+#ifdef CONFIG_ADB_MACIO
+	&macio_adb_driver,
+#endif
+	NULL
+};
+
+static struct class *adb_dev_class;
+
+static struct adb_driver *adb_controller;
+BLOCKING_NOTIFIER_HEAD(adb_client_list);
+static int adb_got_sleep;
+static int adb_inited;
+static DEFINE_SEMAPHORE(adb_probe_mutex);
+static int sleepy_trackpad;
+static int autopoll_devs;
+int __adb_probe_sync;
+
+static int adb_scan_bus(void);
+static int do_adb_reset_bus(void);
+static void adbdev_init(void);
+static int try_handler_change(int, int);
+
+static struct adb_handler {
+	void (*handler)(unsigned char *, int, int);
+	int original_address;
+	int handler_id;
+	int busy;
+} adb_handler[16];
+
+/*
+ * The adb_handler_mutex mutex protects all accesses to the original_address
+ * and handler_id fields of adb_handler[i] for all i, and changes to the
+ * handler field.
+ * Accesses to the handler field are protected by the adb_handler_lock
+ * rwlock.  It is held across all calls to any handler, so that by the
+ * time adb_unregister returns, we know that the old handler isn't being
+ * called.
+ */
+static DEFINE_MUTEX(adb_handler_mutex);
+static DEFINE_RWLOCK(adb_handler_lock);
+
+#if 0
+static void printADBreply(struct adb_request *req)
+{
+        int i;
+
+        printk("adb reply (%d)", req->reply_len);
+        for(i = 0; i < req->reply_len; i++)
+                printk(" %x", req->reply[i]);
+        printk("\n");
+
+}
+#endif
+
+static int adb_scan_bus(void)
+{
+	int i, highFree=0, noMovement;
+	int devmask = 0;
+	struct adb_request req;
+	
+	/* assumes adb_handler[] is all zeroes at this point */
+	for (i = 1; i < 16; i++) {
+		/* see if there is anything at address i */
+		adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+                            (i << 4) | 0xf);
+		if (req.reply_len > 1)
+			/* one or more devices at this address */
+			adb_handler[i].original_address = i;
+		else if (i > highFree)
+			highFree = i;
+	}
+
+	/* Note we reset noMovement to 0 each time we move a device */
+	for (noMovement = 1; noMovement < 2 && highFree > 0; noMovement++) {
+		for (i = 1; i < 16; i++) {
+			if (adb_handler[i].original_address == 0)
+				continue;
+			/*
+			 * Send a "talk register 3" command to address i
+			 * to provoke a collision if there is more than
+			 * one device at this address.
+			 */
+			adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+				    (i << 4) | 0xf);
+			/*
+			 * Move the device(s) which didn't detect a
+			 * collision to address `highFree'.  Hopefully
+			 * this only moves one device.
+			 */
+			adb_request(&req, NULL, ADBREQ_SYNC, 3,
+				    (i<< 4) | 0xb, (highFree | 0x60), 0xfe);
+			/*
+			 * See if anybody actually moved. This is suggested
+			 * by HW TechNote 01:
+			 *
+			 * http://developer.apple.com/technotes/hw/hw_01.html
+			 */
+			adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+				    (highFree << 4) | 0xf);
+			if (req.reply_len <= 1) continue;
+			/*
+			 * Test whether there are any device(s) left
+			 * at address i.
+			 */
+			adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+				    (i << 4) | 0xf);
+			if (req.reply_len > 1) {
+				/*
+				 * There are still one or more devices
+				 * left at address i.  Register the one(s)
+				 * we moved to `highFree', and find a new
+				 * value for highFree.
+				 */
+				adb_handler[highFree].original_address =
+					adb_handler[i].original_address;
+				while (highFree > 0 &&
+				       adb_handler[highFree].original_address)
+					highFree--;
+				if (highFree <= 0)
+					break;
+
+				noMovement = 0;
+			} else {
+				/*
+				 * No devices left at address i; move the
+				 * one(s) we moved to `highFree' back to i.
+				 */
+				adb_request(&req, NULL, ADBREQ_SYNC, 3,
+					    (highFree << 4) | 0xb,
+					    (i | 0x60), 0xfe);
+			}
+		}	
+	}
+
+	/* Now fill in the handler_id field of the adb_handler entries. */
+	printk(KERN_DEBUG "adb devices:");
+	for (i = 1; i < 16; i++) {
+		if (adb_handler[i].original_address == 0)
+			continue;
+		adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+			    (i << 4) | 0xf);
+		adb_handler[i].handler_id = req.reply[2];
+		printk(" [%d]: %d %x", i, adb_handler[i].original_address,
+		       adb_handler[i].handler_id);
+		devmask |= 1 << i;
+	}
+	printk("\n");
+	return devmask;
+}
+
+/*
+ * This kernel task handles ADB probing. It dies once probing is
+ * completed.
+ */
+static int
+adb_probe_task(void *x)
+{
+	printk(KERN_INFO "adb: starting probe task...\n");
+	do_adb_reset_bus();
+	printk(KERN_INFO "adb: finished probe task...\n");
+
+	up(&adb_probe_mutex);
+
+	return 0;
+}
+
+static void
+__adb_probe_task(struct work_struct *bullshit)
+{
+	kthread_run(adb_probe_task, NULL, "kadbprobe");
+}
+
+static DECLARE_WORK(adb_reset_work, __adb_probe_task);
+
+int
+adb_reset_bus(void)
+{
+	if (__adb_probe_sync) {
+		do_adb_reset_bus();
+		return 0;
+	}
+
+	down(&adb_probe_mutex);
+	schedule_work(&adb_reset_work);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * notify clients before sleep
+ */
+static int __adb_suspend(struct platform_device *dev, pm_message_t state)
+{
+	adb_got_sleep = 1;
+	/* We need to get a lock on the probe thread */
+	down(&adb_probe_mutex);
+	/* Stop autopoll */
+	if (adb_controller->autopoll)
+		adb_controller->autopoll(0);
+	blocking_notifier_call_chain(&adb_client_list, ADB_MSG_POWERDOWN, NULL);
+
+	return 0;
+}
+
+static int adb_suspend(struct device *dev)
+{
+	return __adb_suspend(to_platform_device(dev), PMSG_SUSPEND);
+}
+
+static int adb_freeze(struct device *dev)
+{
+	return __adb_suspend(to_platform_device(dev), PMSG_FREEZE);
+}
+
+static int adb_poweroff(struct device *dev)
+{
+	return __adb_suspend(to_platform_device(dev), PMSG_HIBERNATE);
+}
+
+/*
+ * reset bus after sleep
+ */
+static int __adb_resume(struct platform_device *dev)
+{
+	adb_got_sleep = 0;
+	up(&adb_probe_mutex);
+	adb_reset_bus();
+
+	return 0;
+}
+
+static int adb_resume(struct device *dev)
+{
+	return __adb_resume(to_platform_device(dev));
+}
+#endif /* CONFIG_PM */
+
+static int __init adb_init(void)
+{
+	struct adb_driver *driver;
+	int i;
+
+#ifdef CONFIG_PPC32
+	if (!machine_is(chrp) && !machine_is(powermac))
+		return 0;
+#endif
+#ifdef CONFIG_MAC
+	if (!MACH_IS_MAC)
+		return 0;
+#endif
+
+	/* xmon may do early-init */
+	if (adb_inited)
+		return 0;
+	adb_inited = 1;
+		
+	adb_controller = NULL;
+
+	i = 0;
+	while ((driver = adb_driver_list[i++]) != NULL) {
+		if (!driver->probe()) {
+			adb_controller = driver;
+			break;
+		}
+	}
+	if (adb_controller != NULL && adb_controller->init &&
+	    adb_controller->init())
+		adb_controller = NULL;
+	if (adb_controller == NULL) {
+		printk(KERN_WARNING "Warning: no ADB interface detected\n");
+	} else {
+#ifdef CONFIG_PPC
+		if (of_machine_is_compatible("AAPL,PowerBook1998") ||
+			of_machine_is_compatible("PowerBook1,1"))
+			sleepy_trackpad = 1;
+#endif /* CONFIG_PPC */
+
+		adbdev_init();
+		adb_reset_bus();
+	}
+	return 0;
+}
+
+device_initcall(adb_init);
+
+static int
+do_adb_reset_bus(void)
+{
+	int ret;
+	
+	if (adb_controller == NULL)
+		return -ENXIO;
+		
+	if (adb_controller->autopoll)
+		adb_controller->autopoll(0);
+
+	blocking_notifier_call_chain(&adb_client_list,
+		ADB_MSG_PRE_RESET, NULL);
+
+	if (sleepy_trackpad) {
+		/* Let the trackpad settle down */
+		msleep(500);
+	}
+
+	mutex_lock(&adb_handler_mutex);
+	write_lock_irq(&adb_handler_lock);
+	memset(adb_handler, 0, sizeof(adb_handler));
+	write_unlock_irq(&adb_handler_lock);
+
+	/* That one is still a bit synchronous, oh well... */
+	if (adb_controller->reset_bus)
+		ret = adb_controller->reset_bus();
+	else
+		ret = 0;
+
+	if (sleepy_trackpad) {
+		/* Let the trackpad settle down */
+		msleep(1500);
+	}
+
+	if (!ret) {
+		autopoll_devs = adb_scan_bus();
+		if (adb_controller->autopoll)
+			adb_controller->autopoll(autopoll_devs);
+	}
+	mutex_unlock(&adb_handler_mutex);
+
+	blocking_notifier_call_chain(&adb_client_list,
+		ADB_MSG_POST_RESET, NULL);
+	
+	return ret;
+}
+
+void
+adb_poll(void)
+{
+	if ((adb_controller == NULL)||(adb_controller->poll == NULL))
+		return;
+	adb_controller->poll();
+}
+EXPORT_SYMBOL(adb_poll);
+
+static void adb_sync_req_done(struct adb_request *req)
+{
+	struct completion *comp = req->arg;
+
+	complete(comp);
+}
+
+int
+adb_request(struct adb_request *req, void (*done)(struct adb_request *),
+	    int flags, int nbytes, ...)
+{
+	va_list list;
+	int i;
+	int rc;
+	struct completion comp;
+
+	if ((adb_controller == NULL) || (adb_controller->send_request == NULL))
+		return -ENXIO;
+	if (nbytes < 1)
+		return -EINVAL;
+
+	req->nbytes = nbytes+1;
+	req->done = done;
+	req->reply_expected = flags & ADBREQ_REPLY;
+	req->data[0] = ADB_PACKET;
+	va_start(list, nbytes);
+	for (i = 0; i < nbytes; ++i)
+		req->data[i+1] = va_arg(list, int);
+	va_end(list);
+
+	if (flags & ADBREQ_NOSEND)
+		return 0;
+
+	/* Synchronous requests block using an on-stack completion */
+	if (flags & ADBREQ_SYNC) {
+		WARN_ON(done);
+		req->done = adb_sync_req_done;
+		req->arg = &comp;
+		init_completion(&comp);
+	}
+
+	rc = adb_controller->send_request(req, 0);
+
+	if ((flags & ADBREQ_SYNC) && !rc && !req->complete)
+		wait_for_completion(&comp);
+
+	return rc;
+}
+EXPORT_SYMBOL(adb_request);
+
+ /* Ultimately this should return the number of devices with
+    the given default id.
+    And it does it now ! Note: changed behaviour: This function
+    will now register if default_id _and_ handler_id both match
+    but handler_id can be left to 0 to match with default_id only.
+    When handler_id is set, this function will try to adjust
+    the handler_id id it doesn't match. */
+int
+adb_register(int default_id, int handler_id, struct adb_ids *ids,
+	     void (*handler)(unsigned char *, int, int))
+{
+	int i;
+
+	mutex_lock(&adb_handler_mutex);
+	ids->nids = 0;
+	for (i = 1; i < 16; i++) {
+		if ((adb_handler[i].original_address == default_id) &&
+		    (!handler_id || (handler_id == adb_handler[i].handler_id) || 
+		    try_handler_change(i, handler_id))) {
+			if (adb_handler[i].handler != 0) {
+				printk(KERN_ERR
+				       "Two handlers for ADB device %d\n",
+				       default_id);
+				continue;
+			}
+			write_lock_irq(&adb_handler_lock);
+			adb_handler[i].handler = handler;
+			write_unlock_irq(&adb_handler_lock);
+			ids->id[ids->nids++] = i;
+		}
+	}
+	mutex_unlock(&adb_handler_mutex);
+	return ids->nids;
+}
+EXPORT_SYMBOL(adb_register);
+
+int
+adb_unregister(int index)
+{
+	int ret = -ENODEV;
+
+	mutex_lock(&adb_handler_mutex);
+	write_lock_irq(&adb_handler_lock);
+	if (adb_handler[index].handler) {
+		while(adb_handler[index].busy) {
+			write_unlock_irq(&adb_handler_lock);
+			yield();
+			write_lock_irq(&adb_handler_lock);
+		}
+		ret = 0;
+		adb_handler[index].handler = NULL;
+	}
+	write_unlock_irq(&adb_handler_lock);
+	mutex_unlock(&adb_handler_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(adb_unregister);
+
+void
+adb_input(unsigned char *buf, int nb, int autopoll)
+{
+	int i, id;
+	static int dump_adb_input;
+	unsigned long flags;
+	
+	void (*handler)(unsigned char *, int, int);
+
+	/* We skip keystrokes and mouse moves when the sleep process
+	 * has been started. We stop autopoll, but this is another security
+	 */
+	if (adb_got_sleep)
+		return;
+		
+	id = buf[0] >> 4;
+	if (dump_adb_input) {
+		printk(KERN_INFO "adb packet: ");
+		for (i = 0; i < nb; ++i)
+			printk(" %x", buf[i]);
+		printk(", id = %d\n", id);
+	}
+	write_lock_irqsave(&adb_handler_lock, flags);
+	handler = adb_handler[id].handler;
+	if (handler != NULL)
+		adb_handler[id].busy = 1;
+	write_unlock_irqrestore(&adb_handler_lock, flags);
+	if (handler != NULL) {
+		(*handler)(buf, nb, autopoll);
+		wmb();
+		adb_handler[id].busy = 0;
+	}
+		
+}
+
+/* Try to change handler to new_id. Will return 1 if successful. */
+static int try_handler_change(int address, int new_id)
+{
+	struct adb_request req;
+
+	if (adb_handler[address].handler_id == new_id)
+	    return 1;
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	    ADB_WRITEREG(address, 3), address | 0x20, new_id);
+	adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+	    ADB_READREG(address, 3));
+	if (req.reply_len < 2)
+	    return 0;
+	if (req.reply[2] != new_id)
+	    return 0;
+	adb_handler[address].handler_id = req.reply[2];
+
+	return 1;
+}
+
+int
+adb_try_handler_change(int address, int new_id)
+{
+	int ret;
+
+	mutex_lock(&adb_handler_mutex);
+	ret = try_handler_change(address, new_id);
+	mutex_unlock(&adb_handler_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(adb_try_handler_change);
+
+int
+adb_get_infos(int address, int *original_address, int *handler_id)
+{
+	mutex_lock(&adb_handler_mutex);
+	*original_address = adb_handler[address].original_address;
+	*handler_id = adb_handler[address].handler_id;
+	mutex_unlock(&adb_handler_mutex);
+
+	return (*original_address != 0);
+}
+
+
+/*
+ * /dev/adb device driver.
+ */
+
+#define ADB_MAJOR	56	/* major number for /dev/adb */
+
+struct adbdev_state {
+	spinlock_t	lock;
+	atomic_t	n_pending;
+	struct adb_request *completed;
+  	wait_queue_head_t wait_queue;
+	int		inuse;
+};
+
+static void adb_write_done(struct adb_request *req)
+{
+	struct adbdev_state *state = (struct adbdev_state *) req->arg;
+	unsigned long flags;
+
+	if (!req->complete) {
+		req->reply_len = 0;
+		req->complete = 1;
+	}
+	spin_lock_irqsave(&state->lock, flags);
+	atomic_dec(&state->n_pending);
+	if (!state->inuse) {
+		kfree(req);
+		if (atomic_read(&state->n_pending) == 0) {
+			spin_unlock_irqrestore(&state->lock, flags);
+			kfree(state);
+			return;
+		}
+	} else {
+		struct adb_request **ap = &state->completed;
+		while (*ap != NULL)
+			ap = &(*ap)->next;
+		req->next = NULL;
+		*ap = req;
+		wake_up_interruptible(&state->wait_queue);
+	}
+	spin_unlock_irqrestore(&state->lock, flags);
+}
+
+static int
+do_adb_query(struct adb_request *req)
+{
+	int	ret = -EINVAL;
+
+	switch(req->data[1]) {
+	case ADB_QUERY_GETDEVINFO:
+		if (req->nbytes < 3)
+			break;
+		mutex_lock(&adb_handler_mutex);
+		req->reply[0] = adb_handler[req->data[2]].original_address;
+		req->reply[1] = adb_handler[req->data[2]].handler_id;
+		mutex_unlock(&adb_handler_mutex);
+		req->complete = 1;
+		req->reply_len = 2;
+		adb_write_done(req);
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+static int adb_open(struct inode *inode, struct file *file)
+{
+	struct adbdev_state *state;
+	int ret = 0;
+
+	mutex_lock(&adb_mutex);
+	if (iminor(inode) > 0 || adb_controller == NULL) {
+		ret = -ENXIO;
+		goto out;
+	}
+	state = kmalloc(sizeof(struct adbdev_state), GFP_KERNEL);
+	if (state == 0) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	file->private_data = state;
+	spin_lock_init(&state->lock);
+	atomic_set(&state->n_pending, 0);
+	state->completed = NULL;
+	init_waitqueue_head(&state->wait_queue);
+	state->inuse = 1;
+
+out:
+	mutex_unlock(&adb_mutex);
+	return ret;
+}
+
+static int adb_release(struct inode *inode, struct file *file)
+{
+	struct adbdev_state *state = file->private_data;
+	unsigned long flags;
+
+	mutex_lock(&adb_mutex);
+	if (state) {
+		file->private_data = NULL;
+		spin_lock_irqsave(&state->lock, flags);
+		if (atomic_read(&state->n_pending) == 0
+		    && state->completed == NULL) {
+			spin_unlock_irqrestore(&state->lock, flags);
+			kfree(state);
+		} else {
+			state->inuse = 0;
+			spin_unlock_irqrestore(&state->lock, flags);
+		}
+	}
+	mutex_unlock(&adb_mutex);
+	return 0;
+}
+
+static ssize_t adb_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	int ret = 0;
+	struct adbdev_state *state = file->private_data;
+	struct adb_request *req;
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+
+	if (count < 2)
+		return -EINVAL;
+	if (count > sizeof(req->reply))
+		count = sizeof(req->reply);
+
+	req = NULL;
+	spin_lock_irqsave(&state->lock, flags);
+	add_wait_queue(&state->wait_queue, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+
+	for (;;) {
+		req = state->completed;
+		if (req != NULL)
+			state->completed = req->next;
+		else if (atomic_read(&state->n_pending) == 0)
+			ret = -EIO;
+		if (req != NULL || ret != 0)
+			break;
+		
+		if (file->f_flags & O_NONBLOCK) {
+			ret = -EAGAIN;
+			break;
+		}
+		if (signal_pending(current)) {
+			ret = -ERESTARTSYS;
+			break;
+		}
+		spin_unlock_irqrestore(&state->lock, flags);
+		schedule();
+		spin_lock_irqsave(&state->lock, flags);
+	}
+
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&state->wait_queue, &wait);
+	spin_unlock_irqrestore(&state->lock, flags);
+	
+	if (ret)
+		return ret;
+
+	ret = req->reply_len;
+	if (ret > count)
+		ret = count;
+	if (ret > 0 && copy_to_user(buf, req->reply, ret))
+		ret = -EFAULT;
+
+	kfree(req);
+	return ret;
+}
+
+static ssize_t adb_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	int ret/*, i*/;
+	struct adbdev_state *state = file->private_data;
+	struct adb_request *req;
+
+	if (count < 2 || count > sizeof(req->data))
+		return -EINVAL;
+	if (adb_controller == NULL)
+		return -ENXIO;
+
+	req = kmalloc(sizeof(struct adb_request),
+					     GFP_KERNEL);
+	if (req == NULL)
+		return -ENOMEM;
+
+	req->nbytes = count;
+	req->done = adb_write_done;
+	req->arg = (void *) state;
+	req->complete = 0;
+	
+	ret = -EFAULT;
+	if (copy_from_user(req->data, buf, count))
+		goto out;
+
+	atomic_inc(&state->n_pending);
+
+	/* If a probe is in progress or we are sleeping, wait for it to complete */
+	down(&adb_probe_mutex);
+
+	/* Queries are special requests sent to the ADB driver itself */
+	if (req->data[0] == ADB_QUERY) {
+		if (count > 1)
+			ret = do_adb_query(req);
+		else
+			ret = -EINVAL;
+		up(&adb_probe_mutex);
+	}
+	/* Special case for ADB_BUSRESET request, all others are sent to
+	   the controller */
+	else if ((req->data[0] == ADB_PACKET) && (count > 1)
+		&& (req->data[1] == ADB_BUSRESET)) {
+		ret = do_adb_reset_bus();
+		up(&adb_probe_mutex);
+		atomic_dec(&state->n_pending);
+		if (ret == 0)
+			ret = count;
+		goto out;
+	} else {	
+		req->reply_expected = ((req->data[1] & 0xc) == 0xc);
+		if (adb_controller && adb_controller->send_request)
+			ret = adb_controller->send_request(req, 0);
+		else
+			ret = -ENXIO;
+		up(&adb_probe_mutex);
+	}
+
+	if (ret != 0) {
+		atomic_dec(&state->n_pending);
+		goto out;
+	}
+	return count;
+
+out:
+	kfree(req);
+	return ret;
+}
+
+static const struct file_operations adb_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= adb_read,
+	.write		= adb_write,
+	.open		= adb_open,
+	.release	= adb_release,
+};
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops adb_dev_pm_ops = {
+	.suspend = adb_suspend,
+	.resume = adb_resume,
+	/* Hibernate hooks */
+	.freeze = adb_freeze,
+	.thaw = adb_resume,
+	.poweroff = adb_poweroff,
+	.restore = adb_resume,
+};
+#endif
+
+static struct platform_driver adb_pfdrv = {
+	.driver = {
+		.name = "adb",
+#ifdef CONFIG_PM
+		.pm = &adb_dev_pm_ops,
+#endif
+	},
+};
+
+static struct platform_device adb_pfdev = {
+	.name = "adb",
+};
+
+static int __init
+adb_dummy_probe(struct platform_device *dev)
+{
+	if (dev == &adb_pfdev)
+		return 0;
+	return -ENODEV;
+}
+
+static void __init
+adbdev_init(void)
+{
+	if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) {
+		printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
+		return;
+	}
+
+	adb_dev_class = class_create(THIS_MODULE, "adb");
+	if (IS_ERR(adb_dev_class))
+		return;
+	device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
+
+	platform_device_register(&adb_pfdev);
+	platform_driver_probe(&adb_pfdrv, adb_dummy_probe);
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/adbhid.c b/src/kernel/linux/v4.14/drivers/macintosh/adbhid.c
new file mode 100644
index 0000000..e091193
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/adbhid.c
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/macintosh/adbhid.c
+ *
+ * ADB HID driver for Power Macintosh computers.
+ *
+ * Adapted from drivers/macintosh/mac_keyb.c by Franz Sirl.
+ * drivers/macintosh/mac_keyb.c was Copyright (C) 1996 Paul Mackerras
+ * with considerable contributions from Ben Herrenschmidt and others.
+ *
+ * Copyright (C) 2000 Franz Sirl.
+ *
+ * Adapted to ADB changes and support for more devices by
+ * Benjamin Herrenschmidt. Adapted from code in MkLinux
+ * and reworked.
+ * 
+ * Supported devices:
+ *
+ * - Standard 1 button mouse
+ * - All standard Apple Extended protocol (handler ID 4)
+ * - mouseman and trackman mice & trackballs 
+ * - PowerBook Trackpad (default setup: enable tapping)
+ * - MicroSpeed mouse & trackball (needs testing)
+ * - CH Products Trackball Pro (needs testing)
+ * - Contour Design (Contour Mouse)
+ * - Hunter digital (NoHandsMouse)
+ * - Kensignton TurboMouse 5 (needs testing)
+ * - Mouse Systems A3 mice and trackballs <aidan@kublai.com>
+ * - MacAlly 2-buttons mouse (needs testing) <pochini@denise.shiny.it>
+ *
+ * To do:
+ *
+ * Improve Kensington support.
+ * Split mouse/kbd
+ * Move to syfs
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/input.h>
+
+#include <linux/adb.h>
+#include <linux/cuda.h>
+#include <linux/pmu.h>
+
+#include <asm/machdep.h>
+#ifdef CONFIG_PPC_PMAC
+#include <asm/backlight.h>
+#include <asm/pmac_feature.h>
+#endif
+
+MODULE_AUTHOR("Franz Sirl <Franz.Sirl-kernel@lauterbach.com>");
+
+static int restore_capslock_events;
+module_param(restore_capslock_events, int, 0644);
+MODULE_PARM_DESC(restore_capslock_events,
+	"Produce keypress events for capslock on both keyup and keydown.");
+
+#define KEYB_KEYREG	0	/* register # for key up/down data */
+#define KEYB_LEDREG	2	/* register # for leds on ADB keyboard */
+#define MOUSE_DATAREG	0	/* reg# for movement/button codes from mouse */
+
+static int adb_message_handler(struct notifier_block *, unsigned long, void *);
+static struct notifier_block adbhid_adb_notifier = {
+	.notifier_call	= adb_message_handler,
+};
+
+/* Some special keys */
+#define ADB_KEY_DEL		0x33
+#define ADB_KEY_CMD		0x37
+#define ADB_KEY_CAPSLOCK	0x39
+#define ADB_KEY_FN		0x3f
+#define ADB_KEY_FWDEL		0x75
+#define ADB_KEY_POWER_OLD	0x7e
+#define ADB_KEY_POWER		0x7f
+
+static const u16 adb_to_linux_keycodes[128] = {
+	/* 0x00 */ KEY_A, 		/*  30 */
+	/* 0x01 */ KEY_S, 		/*  31 */
+	/* 0x02 */ KEY_D,		/*  32 */
+	/* 0x03 */ KEY_F,		/*  33 */
+	/* 0x04 */ KEY_H,		/*  35 */
+	/* 0x05 */ KEY_G,		/*  34 */
+	/* 0x06 */ KEY_Z,		/*  44 */
+	/* 0x07 */ KEY_X,		/*  45 */
+	/* 0x08 */ KEY_C,		/*  46 */
+	/* 0x09 */ KEY_V,		/*  47 */
+	/* 0x0a */ KEY_102ND,		/*  86 */
+	/* 0x0b */ KEY_B,		/*  48 */
+	/* 0x0c */ KEY_Q,		/*  16 */
+	/* 0x0d */ KEY_W,		/*  17 */
+	/* 0x0e */ KEY_E,		/*  18 */
+	/* 0x0f */ KEY_R,		/*  19 */
+	/* 0x10 */ KEY_Y,		/*  21 */
+	/* 0x11 */ KEY_T,		/*  20 */
+	/* 0x12 */ KEY_1,		/*   2 */
+	/* 0x13 */ KEY_2,		/*   3 */
+	/* 0x14 */ KEY_3,		/*   4 */
+	/* 0x15 */ KEY_4,		/*   5 */
+	/* 0x16 */ KEY_6,		/*   7 */
+	/* 0x17 */ KEY_5,		/*   6 */
+	/* 0x18 */ KEY_EQUAL,		/*  13 */
+	/* 0x19 */ KEY_9,		/*  10 */
+	/* 0x1a */ KEY_7,		/*   8 */
+	/* 0x1b */ KEY_MINUS,		/*  12 */
+	/* 0x1c */ KEY_8,		/*   9 */
+	/* 0x1d */ KEY_0,		/*  11 */
+	/* 0x1e */ KEY_RIGHTBRACE,	/*  27 */
+	/* 0x1f */ KEY_O,		/*  24 */
+	/* 0x20 */ KEY_U,		/*  22 */
+	/* 0x21 */ KEY_LEFTBRACE,	/*  26 */
+	/* 0x22 */ KEY_I,		/*  23 */
+	/* 0x23 */ KEY_P,		/*  25 */
+	/* 0x24 */ KEY_ENTER,		/*  28 */
+	/* 0x25 */ KEY_L,		/*  38 */
+	/* 0x26 */ KEY_J,		/*  36 */
+	/* 0x27 */ KEY_APOSTROPHE,	/*  40 */
+	/* 0x28 */ KEY_K,		/*  37 */
+	/* 0x29 */ KEY_SEMICOLON,	/*  39 */
+	/* 0x2a */ KEY_BACKSLASH,	/*  43 */
+	/* 0x2b */ KEY_COMMA,		/*  51 */
+	/* 0x2c */ KEY_SLASH,		/*  53 */
+	/* 0x2d */ KEY_N,		/*  49 */
+	/* 0x2e */ KEY_M,		/*  50 */
+	/* 0x2f */ KEY_DOT,		/*  52 */
+	/* 0x30 */ KEY_TAB,		/*  15 */
+	/* 0x31 */ KEY_SPACE,		/*  57 */
+	/* 0x32 */ KEY_GRAVE,		/*  41 */
+	/* 0x33 */ KEY_BACKSPACE,	/*  14 */
+	/* 0x34 */ KEY_KPENTER,		/*  96 */
+	/* 0x35 */ KEY_ESC,		/*   1 */
+	/* 0x36 */ KEY_LEFTCTRL,	/*  29 */
+	/* 0x37 */ KEY_LEFTMETA,	/* 125 */
+	/* 0x38 */ KEY_LEFTSHIFT,	/*  42 */
+	/* 0x39 */ KEY_CAPSLOCK,	/*  58 */
+	/* 0x3a */ KEY_LEFTALT,		/*  56 */
+	/* 0x3b */ KEY_LEFT,		/* 105 */
+	/* 0x3c */ KEY_RIGHT,		/* 106 */
+	/* 0x3d */ KEY_DOWN,		/* 108 */
+	/* 0x3e */ KEY_UP,		/* 103 */
+	/* 0x3f */ KEY_FN,		/* 0x1d0 */
+	/* 0x40 */ 0,
+	/* 0x41 */ KEY_KPDOT,		/*  83 */
+	/* 0x42 */ 0,
+	/* 0x43 */ KEY_KPASTERISK,	/*  55 */
+	/* 0x44 */ 0,
+	/* 0x45 */ KEY_KPPLUS,		/*  78 */
+	/* 0x46 */ 0,
+	/* 0x47 */ KEY_NUMLOCK,		/*  69 */
+	/* 0x48 */ 0,
+	/* 0x49 */ 0,
+	/* 0x4a */ 0,
+	/* 0x4b */ KEY_KPSLASH,		/*  98 */
+	/* 0x4c */ KEY_KPENTER,		/*  96 */
+	/* 0x4d */ 0,
+	/* 0x4e */ KEY_KPMINUS,		/*  74 */
+	/* 0x4f */ 0,
+	/* 0x50 */ 0,
+	/* 0x51 */ KEY_KPEQUAL,		/* 117 */
+	/* 0x52 */ KEY_KP0,		/*  82 */
+	/* 0x53 */ KEY_KP1,		/*  79 */
+	/* 0x54 */ KEY_KP2,		/*  80 */
+	/* 0x55 */ KEY_KP3,		/*  81 */
+	/* 0x56 */ KEY_KP4,		/*  75 */
+	/* 0x57 */ KEY_KP5,		/*  76 */
+	/* 0x58 */ KEY_KP6,		/*  77 */
+	/* 0x59 */ KEY_KP7,		/*  71 */
+	/* 0x5a */ 0,
+	/* 0x5b */ KEY_KP8,		/*  72 */
+	/* 0x5c */ KEY_KP9,		/*  73 */
+	/* 0x5d */ KEY_YEN,		/* 124 */
+	/* 0x5e */ KEY_RO,		/*  89 */
+	/* 0x5f */ KEY_KPCOMMA,		/* 121 */
+	/* 0x60 */ KEY_F5,		/*  63 */
+	/* 0x61 */ KEY_F6,		/*  64 */
+	/* 0x62 */ KEY_F7,		/*  65 */
+	/* 0x63 */ KEY_F3,		/*  61 */
+	/* 0x64 */ KEY_F8,		/*  66 */
+	/* 0x65 */ KEY_F9,		/*  67 */
+	/* 0x66 */ KEY_HANJA,		/* 123 */
+	/* 0x67 */ KEY_F11,		/*  87 */
+	/* 0x68 */ KEY_HANGEUL,		/* 122 */
+	/* 0x69 */ KEY_SYSRQ,		/*  99 */
+	/* 0x6a */ 0,
+	/* 0x6b */ KEY_SCROLLLOCK,	/*  70 */
+	/* 0x6c */ 0,
+	/* 0x6d */ KEY_F10,		/*  68 */
+	/* 0x6e */ KEY_COMPOSE,		/* 127 */
+	/* 0x6f */ KEY_F12,		/*  88 */
+	/* 0x70 */ 0,
+	/* 0x71 */ KEY_PAUSE,		/* 119 */
+	/* 0x72 */ KEY_INSERT,		/* 110 */
+	/* 0x73 */ KEY_HOME,		/* 102 */
+	/* 0x74 */ KEY_PAGEUP,		/* 104 */
+	/* 0x75 */ KEY_DELETE,		/* 111 */
+	/* 0x76 */ KEY_F4,		/*  62 */
+	/* 0x77 */ KEY_END,		/* 107 */
+	/* 0x78 */ KEY_F2,		/*  60 */
+	/* 0x79 */ KEY_PAGEDOWN,	/* 109 */
+	/* 0x7a */ KEY_F1,		/*  59 */
+	/* 0x7b */ KEY_RIGHTSHIFT,	/*  54 */
+	/* 0x7c */ KEY_RIGHTALT,	/* 100 */
+	/* 0x7d */ KEY_RIGHTCTRL,	/*  97 */
+	/* 0x7e */ KEY_RIGHTMETA,	/* 126 */
+	/* 0x7f */ KEY_POWER,		/* 116 */
+};
+
+struct adbhid {
+	struct input_dev *input;
+	int id;
+	int default_id;
+	int original_handler_id;
+	int current_handler_id;
+	int mouse_kind;
+	u16 *keycode;
+	char name[64];
+	char phys[32];
+	int flags;
+};
+
+#define FLAG_FN_KEY_PRESSED		0x00000001
+#define FLAG_POWER_FROM_FN		0x00000002
+#define FLAG_EMU_FWDEL_DOWN		0x00000004
+#define FLAG_CAPSLOCK_TRANSLATE		0x00000008
+#define FLAG_CAPSLOCK_DOWN		0x00000010
+#define FLAG_CAPSLOCK_IGNORE_NEXT	0x00000020
+#define FLAG_POWER_KEY_PRESSED		0x00000040
+
+static struct adbhid *adbhid[16];
+
+static void adbhid_probe(void);
+
+static void adbhid_input_keycode(int, int, int);
+
+static void init_trackpad(int id);
+static void init_trackball(int id);
+static void init_turbomouse(int id);
+static void init_microspeed(int id);
+static void init_ms_a3(int id);
+
+static struct adb_ids keyboard_ids;
+static struct adb_ids mouse_ids;
+static struct adb_ids buttons_ids;
+
+/* Kind of keyboard, see Apple technote 1152  */
+#define ADB_KEYBOARD_UNKNOWN	0
+#define ADB_KEYBOARD_ANSI	0x0100
+#define ADB_KEYBOARD_ISO	0x0200
+#define ADB_KEYBOARD_JIS	0x0300
+
+/* Kind of mouse  */
+#define ADBMOUSE_STANDARD_100	0	/* Standard 100cpi mouse (handler 1) */
+#define ADBMOUSE_STANDARD_200	1	/* Standard 200cpi mouse (handler 2) */
+#define ADBMOUSE_EXTENDED	2	/* Apple Extended mouse (handler 4) */
+#define ADBMOUSE_TRACKBALL	3	/* TrackBall (handler 4) */
+#define ADBMOUSE_TRACKPAD       4	/* Apple's PowerBook trackpad (handler 4) */
+#define ADBMOUSE_TURBOMOUSE5    5	/* Turbomouse 5 (previously req. mousehack) */
+#define ADBMOUSE_MICROSPEED	6	/* Microspeed mouse (&trackball ?), MacPoint */
+#define ADBMOUSE_TRACKBALLPRO	7	/* Trackball Pro (special buttons) */
+#define ADBMOUSE_MS_A3		8	/* Mouse systems A3 trackball (handler 3) */
+#define ADBMOUSE_MACALLY2	9	/* MacAlly 2-button mouse */
+
+static void
+adbhid_keyboard_input(unsigned char *data, int nb, int apoll)
+{
+	int id = (data[0] >> 4) & 0x0f;
+
+	if (!adbhid[id]) {
+		printk(KERN_ERR "ADB HID on ID %d not yet registered, packet %#02x, %#02x, %#02x, %#02x\n",
+		       id, data[0], data[1], data[2], data[3]);
+		return;
+	}
+
+	/* first check this is from register 0 */
+	if (nb != 3 || (data[0] & 3) != KEYB_KEYREG)
+		return;		/* ignore it */
+	adbhid_input_keycode(id, data[1], 0);
+	if (!(data[2] == 0xff || (data[2] == 0x7f && data[1] == 0x7f)))
+		adbhid_input_keycode(id, data[2], 0);
+}
+
+static void
+adbhid_input_keycode(int id, int scancode, int repeat)
+{
+	struct adbhid *ahid = adbhid[id];
+	int keycode, up_flag, key;
+
+	keycode = scancode & 0x7f;
+	up_flag = scancode & 0x80;
+
+	if (restore_capslock_events) {
+		if (keycode == ADB_KEY_CAPSLOCK && !up_flag) {
+			/* Key pressed, turning on the CapsLock LED.
+			 * The next 0xff will be interpreted as a release. */
+			if (ahid->flags & FLAG_CAPSLOCK_IGNORE_NEXT) {
+				/* Throw away this key event if it happens
+				 * just after resume. */
+				ahid->flags &= ~FLAG_CAPSLOCK_IGNORE_NEXT;
+				return;
+			} else {
+				ahid->flags |= FLAG_CAPSLOCK_TRANSLATE
+					| FLAG_CAPSLOCK_DOWN;
+			}
+		} else if (scancode == 0xff &&
+			   !(ahid->flags & FLAG_POWER_KEY_PRESSED)) {
+			/* Scancode 0xff usually signifies that the capslock
+			 * key was either pressed or released, or that the
+			 * power button was released. */
+			if (ahid->flags & FLAG_CAPSLOCK_TRANSLATE) {
+				keycode = ADB_KEY_CAPSLOCK;
+				if (ahid->flags & FLAG_CAPSLOCK_DOWN) {
+					/* Key released */
+					up_flag = 1;
+					ahid->flags &= ~FLAG_CAPSLOCK_DOWN;
+				} else {
+					/* Key pressed */
+					up_flag = 0;
+					ahid->flags &= ~FLAG_CAPSLOCK_TRANSLATE;
+				}
+			} else {
+				printk(KERN_INFO "Spurious caps lock event "
+						 "(scancode 0xff).\n");
+			}
+		}
+	}
+
+	switch (keycode) {
+	case ADB_KEY_CAPSLOCK:
+		if (!restore_capslock_events) {
+			/* Generate down/up events for CapsLock every time. */
+			input_report_key(ahid->input, KEY_CAPSLOCK, 1);
+			input_sync(ahid->input);
+			input_report_key(ahid->input, KEY_CAPSLOCK, 0);
+			input_sync(ahid->input);
+			return;
+		}
+		break;
+#ifdef CONFIG_PPC_PMAC
+	case ADB_KEY_POWER_OLD: /* Power key on PBook 3400 needs remapping */
+		switch(pmac_call_feature(PMAC_FTR_GET_MB_INFO,
+			NULL, PMAC_MB_INFO_MODEL, 0)) {
+		case PMAC_TYPE_COMET:
+		case PMAC_TYPE_HOOPER:
+		case PMAC_TYPE_KANGA:
+			keycode = ADB_KEY_POWER;
+		}
+		break;
+	case ADB_KEY_POWER:
+		/* Keep track of the power key state */
+		if (up_flag)
+			ahid->flags &= ~FLAG_POWER_KEY_PRESSED;
+		else
+			ahid->flags |= FLAG_POWER_KEY_PRESSED;
+
+		/* Fn + Command will produce a bogus "power" keycode */
+		if (ahid->flags & FLAG_FN_KEY_PRESSED) {
+			keycode = ADB_KEY_CMD;
+			if (up_flag)
+				ahid->flags &= ~FLAG_POWER_FROM_FN;
+			else
+				ahid->flags |= FLAG_POWER_FROM_FN;
+		} else if (ahid->flags & FLAG_POWER_FROM_FN) {
+			keycode = ADB_KEY_CMD;
+			ahid->flags &= ~FLAG_POWER_FROM_FN;
+		}
+		break;
+	case ADB_KEY_FN:
+		/* Keep track of the Fn key state */
+		if (up_flag) {
+			ahid->flags &= ~FLAG_FN_KEY_PRESSED;
+			/* Emulate Fn+delete = forward delete */
+			if (ahid->flags & FLAG_EMU_FWDEL_DOWN) {
+				ahid->flags &= ~FLAG_EMU_FWDEL_DOWN;
+				keycode = ADB_KEY_FWDEL;
+				break;
+			}
+		} else
+			ahid->flags |= FLAG_FN_KEY_PRESSED;
+		break;
+	case ADB_KEY_DEL:
+		/* Emulate Fn+delete = forward delete */
+		if (ahid->flags & FLAG_FN_KEY_PRESSED) {
+			keycode = ADB_KEY_FWDEL;
+			if (up_flag)
+				ahid->flags &= ~FLAG_EMU_FWDEL_DOWN;
+			else
+				ahid->flags |= FLAG_EMU_FWDEL_DOWN;
+		}
+		break;
+#endif /* CONFIG_PPC_PMAC */
+	}
+
+	key = adbhid[id]->keycode[keycode];
+	if (key) {
+		input_report_key(adbhid[id]->input, key, !up_flag);
+		input_sync(adbhid[id]->input);
+	} else
+		printk(KERN_INFO "Unhandled ADB key (scancode %#02x) %s.\n", keycode,
+		       up_flag ? "released" : "pressed");
+
+}
+
+static void
+adbhid_mouse_input(unsigned char *data, int nb, int autopoll)
+{
+	int id = (data[0] >> 4) & 0x0f;
+
+	if (!adbhid[id]) {
+		printk(KERN_ERR "ADB HID on ID %d not yet registered\n", id);
+		return;
+	}
+
+  /*
+    Handler 1 -- 100cpi original Apple mouse protocol.
+    Handler 2 -- 200cpi original Apple mouse protocol.
+
+    For Apple's standard one-button mouse protocol the data array will
+    contain the following values:
+
+                BITS    COMMENTS
+    data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd.
+    data[1] = bxxx xxxx First button and x-axis motion.
+    data[2] = byyy yyyy Second button and y-axis motion.
+
+    Handler 4 -- Apple Extended mouse protocol.
+
+    For Apple's 3-button mouse protocol the data array will contain the
+    following values:
+
+		BITS    COMMENTS
+    data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd.
+    data[1] = bxxx xxxx Left button and x-axis motion.
+    data[2] = byyy yyyy Second button and y-axis motion.
+    data[3] = byyy bxxx Third button and fourth button.  Y is additional
+	      high bits of y-axis motion.  XY is additional
+	      high bits of x-axis motion.
+
+    MacAlly 2-button mouse protocol.
+
+    For MacAlly 2-button mouse protocol the data array will contain the
+    following values:
+
+		BITS    COMMENTS
+    data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd.
+    data[1] = bxxx xxxx Left button and x-axis motion.
+    data[2] = byyy yyyy Right button and y-axis motion.
+    data[3] = ???? ???? unknown
+    data[4] = ???? ???? unknown
+
+  */
+
+	/* If it's a trackpad, we alias the second button to the first.
+	   NOTE: Apple sends an ADB flush command to the trackpad when
+	         the first (the real) button is released. We could do
+		 this here using async flush requests.
+	*/
+	switch (adbhid[id]->mouse_kind)
+	{
+	    case ADBMOUSE_TRACKPAD:
+		data[1] = (data[1] & 0x7f) | ((data[1] & data[2]) & 0x80);
+		data[2] = data[2] | 0x80;
+		break;
+	    case ADBMOUSE_MICROSPEED:
+		data[1] = (data[1] & 0x7f) | ((data[3] & 0x01) << 7);
+		data[2] = (data[2] & 0x7f) | ((data[3] & 0x02) << 6);
+		data[3] = (data[3] & 0x77) | ((data[3] & 0x04) << 5)
+			| (data[3] & 0x08);
+		break;
+	    case ADBMOUSE_TRACKBALLPRO:
+		data[1] = (data[1] & 0x7f) | (((data[3] & 0x04) << 5)
+			& ((data[3] & 0x08) << 4));
+		data[2] = (data[2] & 0x7f) | ((data[3] & 0x01) << 7);
+		data[3] = (data[3] & 0x77) | ((data[3] & 0x02) << 6);
+		break;
+	    case ADBMOUSE_MS_A3:
+		data[1] = (data[1] & 0x7f) | ((data[3] & 0x01) << 7);
+		data[2] = (data[2] & 0x7f) | ((data[3] & 0x02) << 6);
+		data[3] = ((data[3] & 0x04) << 5);
+		break;
+            case ADBMOUSE_MACALLY2:
+		data[3] = (data[2] & 0x80) ? 0x80 : 0x00;
+		data[2] |= 0x80;  /* Right button is mapped as button 3 */
+		nb=4;
+                break;
+	}
+
+	input_report_key(adbhid[id]->input, BTN_LEFT,   !((data[1] >> 7) & 1));
+	input_report_key(adbhid[id]->input, BTN_MIDDLE, !((data[2] >> 7) & 1));
+
+	if (nb >= 4 && adbhid[id]->mouse_kind != ADBMOUSE_TRACKPAD)
+		input_report_key(adbhid[id]->input, BTN_RIGHT,  !((data[3] >> 7) & 1));
+
+	input_report_rel(adbhid[id]->input, REL_X,
+			 ((data[2]&0x7f) < 64 ? (data[2]&0x7f) : (data[2]&0x7f)-128 ));
+	input_report_rel(adbhid[id]->input, REL_Y,
+			 ((data[1]&0x7f) < 64 ? (data[1]&0x7f) : (data[1]&0x7f)-128 ));
+
+	input_sync(adbhid[id]->input);
+}
+
+static void
+adbhid_buttons_input(unsigned char *data, int nb, int autopoll)
+{
+	int id = (data[0] >> 4) & 0x0f;
+
+	if (!adbhid[id]) {
+		printk(KERN_ERR "ADB HID on ID %d not yet registered\n", id);
+		return;
+	}
+
+	switch (adbhid[id]->original_handler_id) {
+	default:
+	case 0x02: /* Adjustable keyboard button device */
+	  {
+		int down = (data[1] == (data[1] & 0xf));
+
+		switch (data[1] & 0x0f) {
+		case 0x0:	/* microphone */
+			input_report_key(adbhid[id]->input, KEY_SOUND, down);
+			break;
+
+		case 0x1:	/* mute */
+			input_report_key(adbhid[id]->input, KEY_MUTE, down);
+			break;
+
+		case 0x2:	/* volume decrease */
+			input_report_key(adbhid[id]->input, KEY_VOLUMEDOWN, down);
+			break;
+
+		case 0x3:	/* volume increase */
+			input_report_key(adbhid[id]->input, KEY_VOLUMEUP, down);
+			break;
+
+		default:
+			printk(KERN_INFO "Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n",
+			       data[0], data[1], data[2], data[3]);
+			break;
+		}
+	  }
+	  break;
+
+	case 0x1f: /* Powerbook button device */
+	  {
+		int down = (data[1] == (data[1] & 0xf));
+
+		/*
+		 * XXX: Where is the contrast control for the passive?
+		 *  -- Cort
+		 */
+
+		switch (data[1] & 0x0f) {
+		case 0x8:	/* mute */
+			input_report_key(adbhid[id]->input, KEY_MUTE, down);
+			break;
+
+		case 0x7:	/* volume decrease */
+			input_report_key(adbhid[id]->input, KEY_VOLUMEDOWN, down);
+			break;
+
+		case 0x6:	/* volume increase */
+			input_report_key(adbhid[id]->input, KEY_VOLUMEUP, down);
+			break;
+
+		case 0xb:	/* eject */
+			input_report_key(adbhid[id]->input, KEY_EJECTCD, down);
+			break;
+
+		case 0xa:	/* brightness decrease */
+#ifdef CONFIG_PMAC_BACKLIGHT
+			if (down)
+				pmac_backlight_key_down();
+#endif
+			input_report_key(adbhid[id]->input, KEY_BRIGHTNESSDOWN, down);
+			break;
+
+		case 0x9:	/* brightness increase */
+#ifdef CONFIG_PMAC_BACKLIGHT
+			if (down)
+				pmac_backlight_key_up();
+#endif
+			input_report_key(adbhid[id]->input, KEY_BRIGHTNESSUP, down);
+			break;
+
+		case 0xc:	/* videomode switch */
+			input_report_key(adbhid[id]->input, KEY_SWITCHVIDEOMODE, down);
+			break;
+
+		case 0xd:	/* keyboard illumination toggle */
+			input_report_key(adbhid[id]->input, KEY_KBDILLUMTOGGLE, down);
+			break;
+
+		case 0xe:	/* keyboard illumination decrease */
+			input_report_key(adbhid[id]->input, KEY_KBDILLUMDOWN, down);
+			break;
+
+		case 0xf:
+			switch (data[1]) {
+			case 0x8f:
+			case 0x0f:
+				/* keyboard illumination increase */
+				input_report_key(adbhid[id]->input, KEY_KBDILLUMUP, down);
+				break;
+
+			case 0x7f:
+			case 0xff:
+				/* keypad overlay toogle */
+				break;
+
+			default:
+				printk(KERN_INFO "Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n",
+				       data[0], data[1], data[2], data[3]);
+				break;
+			}
+			break;
+		default:
+			printk(KERN_INFO "Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n",
+			       data[0], data[1], data[2], data[3]);
+			break;
+		}
+	  }
+	  break;
+	}
+
+	input_sync(adbhid[id]->input);
+}
+
+static struct adb_request led_request;
+static int leds_pending[16];
+static int leds_req_pending;
+static int pending_devs[16];
+static int pending_led_start;
+static int pending_led_end;
+static DEFINE_SPINLOCK(leds_lock);
+
+static void leds_done(struct adb_request *req)
+{
+	int leds = 0, device = 0, pending = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&leds_lock, flags);
+
+	if (pending_led_start != pending_led_end) {
+		device = pending_devs[pending_led_start];
+		leds = leds_pending[device] & 0xff;
+		leds_pending[device] = 0;
+		pending_led_start++;
+		pending_led_start = (pending_led_start < 16) ? pending_led_start : 0;
+		pending = leds_req_pending;
+	} else
+		leds_req_pending = 0;
+	spin_unlock_irqrestore(&leds_lock, flags);
+	if (pending)
+		adb_request(&led_request, leds_done, 0, 3,
+			    ADB_WRITEREG(device, KEYB_LEDREG), 0xff, ~leds);
+}
+
+static void real_leds(unsigned char leds, int device)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&leds_lock, flags);
+	if (!leds_req_pending) {
+		leds_req_pending = 1;
+		spin_unlock_irqrestore(&leds_lock, flags);	       
+		adb_request(&led_request, leds_done, 0, 3,
+			    ADB_WRITEREG(device, KEYB_LEDREG), 0xff, ~leds);
+		return;
+	} else {
+		if (!(leds_pending[device] & 0x100)) {
+			pending_devs[pending_led_end] = device;
+			pending_led_end++;
+			pending_led_end = (pending_led_end < 16) ? pending_led_end : 0;
+		}
+		leds_pending[device] = leds | 0x100;
+	}
+	spin_unlock_irqrestore(&leds_lock, flags);	       
+}
+
+/*
+ * Event callback from the input module. Events that change the state of
+ * the hardware are processed here.
+ */
+static int adbhid_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+	struct adbhid *adbhid = input_get_drvdata(dev);
+	unsigned char leds;
+
+	switch (type) {
+	case EV_LED:
+		leds =  (test_bit(LED_SCROLLL, dev->led) ? 4 : 0) |
+			(test_bit(LED_NUML,    dev->led) ? 1 : 0) |
+			(test_bit(LED_CAPSL,   dev->led) ? 2 : 0);
+		real_leds(leds, adbhid->id);
+		return 0;
+	}
+
+	return -1;
+}
+
+static void
+adbhid_kbd_capslock_remember(void)
+{
+	struct adbhid *ahid;
+	int i;
+
+	for (i = 1; i < 16; i++) {
+		ahid = adbhid[i];
+
+		if (ahid && ahid->id == ADB_KEYBOARD)
+			if (ahid->flags & FLAG_CAPSLOCK_TRANSLATE)
+				ahid->flags |= FLAG_CAPSLOCK_IGNORE_NEXT;
+	}
+}
+
+static int
+adb_message_handler(struct notifier_block *this, unsigned long code, void *x)
+{
+	switch (code) {
+	case ADB_MSG_PRE_RESET:
+	case ADB_MSG_POWERDOWN:
+		/* Stop the repeat timer. Autopoll is already off at this point */
+		{
+			int i;
+			for (i = 1; i < 16; i++) {
+				if (adbhid[i])
+					del_timer_sync(&adbhid[i]->input->timer);
+			}
+		}
+
+		/* Stop pending led requests */
+		while (leds_req_pending)
+			adb_poll();
+
+		/* After resume, and if the capslock LED is on, the PMU will
+		 * send a "capslock down" key event. This confuses the
+		 * restore_capslock_events logic. Remember if the capslock
+		 * LED was on before suspend so the unwanted key event can
+		 * be ignored after resume. */
+		if (restore_capslock_events)
+			adbhid_kbd_capslock_remember();
+
+		break;
+
+	case ADB_MSG_POST_RESET:
+		adbhid_probe();
+		break;
+	}
+	return NOTIFY_DONE;
+}
+
+static int
+adbhid_input_register(int id, int default_id, int original_handler_id,
+		      int current_handler_id, int mouse_kind)
+{
+	struct adbhid *hid;
+	struct input_dev *input_dev;
+	int err;
+	int i;
+
+	if (adbhid[id]) {
+		printk(KERN_ERR "Trying to reregister ADB HID on ID %d\n", id);
+		return -EEXIST;
+	}
+
+	adbhid[id] = hid = kzalloc(sizeof(struct adbhid), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!hid || !input_dev) {
+		err = -ENOMEM;
+		goto fail;
+	}
+
+	sprintf(hid->phys, "adb%d:%d.%02x/input", id, default_id, original_handler_id);
+
+	hid->input = input_dev;
+	hid->id = default_id;
+	hid->original_handler_id = original_handler_id;
+	hid->current_handler_id = current_handler_id;
+	hid->mouse_kind = mouse_kind;
+	hid->flags = 0;
+	input_set_drvdata(input_dev, hid);
+	input_dev->name = hid->name;
+	input_dev->phys = hid->phys;
+	input_dev->id.bustype = BUS_ADB;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = (id << 12) | (default_id << 8) | original_handler_id;
+	input_dev->id.version = 0x0100;
+
+	switch (default_id) {
+	case ADB_KEYBOARD:
+		hid->keycode = kmalloc(sizeof(adb_to_linux_keycodes), GFP_KERNEL);
+		if (!hid->keycode) {
+			err = -ENOMEM;
+			goto fail;
+		}
+
+		sprintf(hid->name, "ADB keyboard");
+
+		memcpy(hid->keycode, adb_to_linux_keycodes, sizeof(adb_to_linux_keycodes));
+
+		printk(KERN_INFO "Detected ADB keyboard, type ");
+		switch (original_handler_id) {
+		default:
+			printk("<unknown>.\n");
+			input_dev->id.version = ADB_KEYBOARD_UNKNOWN;
+			break;
+
+		case 0x01: case 0x02: case 0x03: case 0x06: case 0x08:
+		case 0x0C: case 0x10: case 0x18: case 0x1B: case 0x1C:
+		case 0xC0: case 0xC3: case 0xC6:
+			printk("ANSI.\n");
+			input_dev->id.version = ADB_KEYBOARD_ANSI;
+			break;
+
+		case 0x04: case 0x05: case 0x07: case 0x09: case 0x0D:
+		case 0x11: case 0x14: case 0x19: case 0x1D: case 0xC1:
+		case 0xC4: case 0xC7:
+			printk("ISO, swapping keys.\n");
+			input_dev->id.version = ADB_KEYBOARD_ISO;
+			i = hid->keycode[10];
+			hid->keycode[10] = hid->keycode[50];
+			hid->keycode[50] = i;
+			break;
+
+		case 0x12: case 0x15: case 0x16: case 0x17: case 0x1A:
+		case 0x1E: case 0xC2: case 0xC5: case 0xC8: case 0xC9:
+			printk("JIS.\n");
+			input_dev->id.version = ADB_KEYBOARD_JIS;
+			break;
+		}
+
+		for (i = 0; i < 128; i++)
+			if (hid->keycode[i])
+				set_bit(hid->keycode[i], input_dev->keybit);
+
+		input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
+			BIT_MASK(EV_REP);
+		input_dev->ledbit[0] = BIT_MASK(LED_SCROLLL) |
+			BIT_MASK(LED_CAPSL) | BIT_MASK(LED_NUML);
+		input_dev->event = adbhid_kbd_event;
+		input_dev->keycodemax = KEY_FN;
+		input_dev->keycodesize = sizeof(hid->keycode[0]);
+		break;
+
+	case ADB_MOUSE:
+		sprintf(hid->name, "ADB mouse");
+
+		input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+		input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
+			BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT);
+		input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+		break;
+
+	case ADB_MISC:
+		switch (original_handler_id) {
+		case 0x02: /* Adjustable keyboard button device */
+			sprintf(hid->name, "ADB adjustable keyboard buttons");
+			input_dev->evbit[0] = BIT_MASK(EV_KEY) |
+				BIT_MASK(EV_REP);
+			set_bit(KEY_SOUND, input_dev->keybit);
+			set_bit(KEY_MUTE, input_dev->keybit);
+			set_bit(KEY_VOLUMEUP, input_dev->keybit);
+			set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
+			break;
+		case 0x1f: /* Powerbook button device */
+			sprintf(hid->name, "ADB Powerbook buttons");
+			input_dev->evbit[0] = BIT_MASK(EV_KEY) |
+				BIT_MASK(EV_REP);
+			set_bit(KEY_MUTE, input_dev->keybit);
+			set_bit(KEY_VOLUMEUP, input_dev->keybit);
+			set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
+			set_bit(KEY_BRIGHTNESSUP, input_dev->keybit);
+			set_bit(KEY_BRIGHTNESSDOWN, input_dev->keybit);
+			set_bit(KEY_EJECTCD, input_dev->keybit);
+			set_bit(KEY_SWITCHVIDEOMODE, input_dev->keybit);
+			set_bit(KEY_KBDILLUMTOGGLE, input_dev->keybit);
+			set_bit(KEY_KBDILLUMDOWN, input_dev->keybit);
+			set_bit(KEY_KBDILLUMUP, input_dev->keybit);
+			break;
+		}
+		if (hid->name[0])
+			break;
+		/* else fall through */
+
+	default:
+		printk(KERN_INFO "Trying to register unknown ADB device to input layer.\n");
+		err = -ENODEV;
+		goto fail;
+	}
+
+	input_dev->keycode = hid->keycode;
+
+	err = input_register_device(input_dev);
+	if (err)
+		goto fail;
+
+	if (default_id == ADB_KEYBOARD) {
+		/* HACK WARNING!! This should go away as soon there is an utility
+		 * to control that for event devices.
+		 */
+		input_dev->rep[REP_DELAY] = 500;   /* input layer default: 250 */
+		input_dev->rep[REP_PERIOD] = 66; /* input layer default: 33 */
+	}
+
+	return 0;
+
+ fail:	input_free_device(input_dev);
+	if (hid) {
+		kfree(hid->keycode);
+		kfree(hid);
+	}
+	adbhid[id] = NULL;
+	return err;
+}
+
+static void adbhid_input_unregister(int id)
+{
+	input_unregister_device(adbhid[id]->input);
+	kfree(adbhid[id]->keycode);
+	kfree(adbhid[id]);
+	adbhid[id] = NULL;
+}
+
+
+static u16
+adbhid_input_reregister(int id, int default_id, int org_handler_id,
+			int cur_handler_id, int mk)
+{
+	if (adbhid[id]) {
+		if (adbhid[id]->input->id.product !=
+		    ((id << 12)|(default_id << 8)|org_handler_id)) {
+			adbhid_input_unregister(id);
+			adbhid_input_register(id, default_id, org_handler_id,
+					      cur_handler_id, mk);
+		}
+	} else
+		adbhid_input_register(id, default_id, org_handler_id,
+				      cur_handler_id, mk);
+	return 1<<id;
+}
+
+static void
+adbhid_input_devcleanup(u16 exist)
+{
+	int i;
+	for(i=1; i<16; i++)
+		if (adbhid[i] && !(exist&(1<<i)))
+			adbhid_input_unregister(i);
+}
+
+static void
+adbhid_probe(void)
+{
+	struct adb_request req;
+	int i, default_id, org_handler_id, cur_handler_id;
+	u16 reg = 0;
+
+	adb_register(ADB_MOUSE, 0, &mouse_ids, adbhid_mouse_input);
+	adb_register(ADB_KEYBOARD, 0, &keyboard_ids, adbhid_keyboard_input);
+	adb_register(ADB_MISC, 0, &buttons_ids, adbhid_buttons_input);
+
+	for (i = 0; i < keyboard_ids.nids; i++) {
+		int id = keyboard_ids.id[i];
+
+		adb_get_infos(id, &default_id, &org_handler_id);
+
+		/* turn off all leds */
+		adb_request(&req, NULL, ADBREQ_SYNC, 3,
+			    ADB_WRITEREG(id, KEYB_LEDREG), 0xff, 0xff);
+
+		/* Enable full feature set of the keyboard
+		   ->get it to send separate codes for left and right shift,
+		   control, option keys */
+#if 0		/* handler 5 doesn't send separate codes for R modifiers */
+		if (adb_try_handler_change(id, 5))
+			printk("ADB keyboard at %d, handler set to 5\n", id);
+		else
+#endif
+		if (adb_try_handler_change(id, 3))
+			printk("ADB keyboard at %d, handler set to 3\n", id);
+		else
+			printk("ADB keyboard at %d, handler 1\n", id);
+
+		adb_get_infos(id, &default_id, &cur_handler_id);
+		reg |= adbhid_input_reregister(id, default_id, org_handler_id,
+					       cur_handler_id, 0);
+	}
+
+	for (i = 0; i < buttons_ids.nids; i++) {
+		int id = buttons_ids.id[i];
+
+		adb_get_infos(id, &default_id, &org_handler_id);
+		reg |= adbhid_input_reregister(id, default_id, org_handler_id,
+					       org_handler_id, 0);
+	}
+
+	/* Try to switch all mice to handler 4, or 2 for three-button
+	   mode and full resolution. */
+	for (i = 0; i < mouse_ids.nids; i++) {
+		int id = mouse_ids.id[i];
+		int mouse_kind;
+
+		adb_get_infos(id, &default_id, &org_handler_id);
+
+		if (adb_try_handler_change(id, 4)) {
+			printk("ADB mouse at %d, handler set to 4", id);
+			mouse_kind = ADBMOUSE_EXTENDED;
+		}
+		else if (adb_try_handler_change(id, 0x2F)) {
+			printk("ADB mouse at %d, handler set to 0x2F", id);
+			mouse_kind = ADBMOUSE_MICROSPEED;
+		}
+		else if (adb_try_handler_change(id, 0x42)) {
+			printk("ADB mouse at %d, handler set to 0x42", id);
+			mouse_kind = ADBMOUSE_TRACKBALLPRO;
+		}
+		else if (adb_try_handler_change(id, 0x66)) {
+			printk("ADB mouse at %d, handler set to 0x66", id);
+			mouse_kind = ADBMOUSE_MICROSPEED;
+		}
+		else if (adb_try_handler_change(id, 0x5F)) {
+			printk("ADB mouse at %d, handler set to 0x5F", id);
+			mouse_kind = ADBMOUSE_MICROSPEED;
+		}
+		else if (adb_try_handler_change(id, 3)) {
+			printk("ADB mouse at %d, handler set to 3", id);
+			mouse_kind = ADBMOUSE_MS_A3;
+		}
+		else if (adb_try_handler_change(id, 2)) {
+			printk("ADB mouse at %d, handler set to 2", id);
+			mouse_kind = ADBMOUSE_STANDARD_200;
+		}
+		else {
+			printk("ADB mouse at %d, handler 1", id);
+			mouse_kind = ADBMOUSE_STANDARD_100;
+		}
+
+		if ((mouse_kind == ADBMOUSE_TRACKBALLPRO)
+		    || (mouse_kind == ADBMOUSE_MICROSPEED)) {
+			init_microspeed(id);
+		} else if (mouse_kind == ADBMOUSE_MS_A3) {
+			init_ms_a3(id);
+		} else if (mouse_kind ==  ADBMOUSE_EXTENDED) {
+			/*
+			 * Register 1 is usually used for device
+			 * identification.  Here, we try to identify
+			 * a known device and call the appropriate
+			 * init function.
+			 */
+			adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+				    ADB_READREG(id, 1));
+
+			if ((req.reply_len) &&
+			    (req.reply[1] == 0x9a) && ((req.reply[2] == 0x21)
+			    	|| (req.reply[2] == 0x20))) {
+				mouse_kind = ADBMOUSE_TRACKBALL;
+				init_trackball(id);
+			}
+			else if ((req.reply_len >= 4) &&
+			    (req.reply[1] == 0x74) && (req.reply[2] == 0x70) &&
+			    (req.reply[3] == 0x61) && (req.reply[4] == 0x64)) {
+				mouse_kind = ADBMOUSE_TRACKPAD;
+				init_trackpad(id);
+			}
+			else if ((req.reply_len >= 4) &&
+			    (req.reply[1] == 0x4b) && (req.reply[2] == 0x4d) &&
+			    (req.reply[3] == 0x4c) && (req.reply[4] == 0x31)) {
+				mouse_kind = ADBMOUSE_TURBOMOUSE5;
+				init_turbomouse(id);
+			}
+			else if ((req.reply_len == 9) &&
+			    (req.reply[1] == 0x4b) && (req.reply[2] == 0x4f) &&
+			    (req.reply[3] == 0x49) && (req.reply[4] == 0x54)) {
+				if (adb_try_handler_change(id, 0x42)) {
+					printk("\nADB MacAlly 2-button mouse at %d, handler set to 0x42", id);
+					mouse_kind = ADBMOUSE_MACALLY2;
+				}
+			}
+		}
+		printk("\n");
+
+		adb_get_infos(id, &default_id, &cur_handler_id);
+		reg |= adbhid_input_reregister(id, default_id, org_handler_id,
+					       cur_handler_id, mouse_kind);
+	}
+	adbhid_input_devcleanup(reg);
+}
+
+static void 
+init_trackpad(int id)
+{
+	struct adb_request req;
+	unsigned char r1_buffer[8];
+
+	printk(" (trackpad)");
+
+	adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
+		    ADB_READREG(id,1));
+	if (req.reply_len < 8)
+	    printk("bad length for reg. 1\n");
+	else
+	{
+	    memcpy(r1_buffer, &req.reply[1], 8);
+
+	    adb_request(&req, NULL, ADBREQ_SYNC, 9,
+	        ADB_WRITEREG(id,1),
+	            r1_buffer[0],
+	            r1_buffer[1],
+	            r1_buffer[2],
+	            r1_buffer[3],
+	            r1_buffer[4],
+	            r1_buffer[5],
+	            0x0d,
+	            r1_buffer[7]);
+
+            adb_request(&req, NULL, ADBREQ_SYNC, 9,
+	        ADB_WRITEREG(id,2),
+	    	    0x99,
+	    	    0x94,
+	    	    0x19,
+	    	    0xff,
+	    	    0xb2,
+	    	    0x8a,
+	    	    0x1b,
+	    	    0x50);
+
+	    adb_request(&req, NULL, ADBREQ_SYNC, 9,
+	        ADB_WRITEREG(id,1),
+	            r1_buffer[0],
+	            r1_buffer[1],
+	            r1_buffer[2],
+	            r1_buffer[3],
+	            r1_buffer[4],
+	            r1_buffer[5],
+	            0x03, /*r1_buffer[6],*/
+	            r1_buffer[7]);
+
+	    /* Without this flush, the trackpad may be locked up */
+	    adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id));
+        }
+}
+
+static void 
+init_trackball(int id)
+{
+	struct adb_request req;
+
+	printk(" (trackman/mouseman)");
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 00,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 01,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 02,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 03,0x38);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 00,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 01,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 02,0x81);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id,1), 03,0x38);
+}
+
+static void
+init_turbomouse(int id)
+{
+	struct adb_request req;
+
+        printk(" (TurboMouse 5)");
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id));
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(3));
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 9,
+	ADB_WRITEREG(3,2),
+	    0xe7,
+	    0x8c,
+	    0,
+	    0,
+	    0,
+	    0xff,
+	    0xff,
+	    0x94);
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(3));
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 9,
+	ADB_WRITEREG(3,2),
+	    0xa5,
+	    0x14,
+	    0,
+	    0,
+	    0x69,
+	    0xff,
+	    0xff,
+	    0x27);
+}
+
+static void
+init_microspeed(int id)
+{
+	struct adb_request req;
+
+        printk(" (Microspeed/MacPoint or compatible)");
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id));
+
+	/* This will initialize mice using the Microspeed, MacPoint and
+	   other compatible firmware. Bit 12 enables extended protocol.
+	   
+	   Register 1 Listen (4 Bytes)
+            0 -  3     Button is mouse (set also for double clicking!!!)
+            4 -  7     Button is locking (affects change speed also)
+            8 - 11     Button changes speed
+           12          1 = Extended mouse mode, 0 = normal mouse mode
+           13 - 15     unused 0
+           16 - 23     normal speed
+           24 - 31     changed speed
+
+       Register 1 talk holds version and product identification information.
+       Register 1 Talk (4 Bytes):
+            0 -  7     Product code
+            8 - 23     undefined, reserved
+           24 - 31     Version number
+        
+       Speed 0 is max. 1 to 255 set speed in increments of 1/256 of max.
+ */
+	adb_request(&req, NULL, ADBREQ_SYNC, 5,
+	ADB_WRITEREG(id,1),
+	    0x20,	/* alt speed = 0x20 (rather slow) */
+	    0x00,	/* norm speed = 0x00 (fastest) */
+	    0x10,	/* extended protocol, no speed change */
+	    0x07);	/* all buttons enabled as mouse buttons, no locking */
+
+
+	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id));
+}
+
+static void
+init_ms_a3(int id)
+{
+	struct adb_request req;
+
+	printk(" (Mouse Systems A3 Mouse, or compatible)");
+	adb_request(&req, NULL, ADBREQ_SYNC, 3,
+	ADB_WRITEREG(id, 0x2),
+	    0x00,
+	    0x07);
+ 
+ 	adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id));
+}
+
+static int __init adbhid_init(void)
+{
+#ifndef CONFIG_MAC
+	if (!machine_is(chrp) && !machine_is(powermac))
+		return 0;
+#endif
+
+	led_request.complete = 1;
+
+	adbhid_probe();
+
+	blocking_notifier_chain_register(&adb_client_list,
+			&adbhid_adb_notifier);
+
+	return 0;
+}
+
+static void __exit adbhid_exit(void)
+{
+}
+ 
+module_init(adbhid_init);
+module_exit(adbhid_exit);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/Makefile b/src/kernel/linux/v4.14/drivers/macintosh/ams/Makefile
new file mode 100644
index 0000000..41c95b2
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for Apple Motion Sensor driver
+#
+
+ams-y					:= ams-core.o ams-input.o
+ams-$(CONFIG_SENSORS_AMS_PMU)		+= ams-pmu.o
+ams-$(CONFIG_SENSORS_AMS_I2C)		+= ams-i2c.o
+obj-$(CONFIG_SENSORS_AMS)		+= ams.o
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-core.c b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-core.c
new file mode 100644
index 0000000..36a4fdd
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-core.c
@@ -0,0 +1,250 @@
+/*
+ * Apple Motion Sensor driver
+ *
+ * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/of_platform.h>
+#include <asm/pmac_pfunc.h>
+
+#include "ams.h"
+
+/* There is only one motion sensor per machine */
+struct ams ams_info;
+
+static bool verbose;
+module_param(verbose, bool, 0644);
+MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
+
+/* Call with ams_info.lock held! */
+void ams_sensors(s8 *x, s8 *y, s8 *z)
+{
+	u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2;
+
+	if (orient & 0x80)
+		/* X and Y swapped */
+		ams_info.get_xyz(y, x, z);
+	else
+		ams_info.get_xyz(x, y, z);
+
+	if (orient & 0x04)
+		*z = ~(*z);
+	if (orient & 0x02)
+		*y = ~(*y);
+	if (orient & 0x01)
+		*x = ~(*x);
+}
+
+static ssize_t ams_show_current(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	s8 x, y, z;
+
+	mutex_lock(&ams_info.lock);
+	ams_sensors(&x, &y, &z);
+	mutex_unlock(&ams_info.lock);
+
+	return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z);
+}
+
+static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
+
+static void ams_handle_irq(void *data)
+{
+	enum ams_irq irq = *((enum ams_irq *)data);
+
+	spin_lock(&ams_info.irq_lock);
+
+	ams_info.worker_irqs |= irq;
+	schedule_work(&ams_info.worker);
+
+	spin_unlock(&ams_info.irq_lock);
+}
+
+static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
+static struct pmf_irq_client ams_freefall_client = {
+	.owner = THIS_MODULE,
+	.handler = ams_handle_irq,
+	.data = &ams_freefall_irq_data,
+};
+
+static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
+static struct pmf_irq_client ams_shock_client = {
+	.owner = THIS_MODULE,
+	.handler = ams_handle_irq,
+	.data = &ams_shock_irq_data,
+};
+
+/* Once hard disk parking is implemented in the kernel, this function can
+ * trigger it.
+ */
+static void ams_worker(struct work_struct *work)
+{
+	unsigned long flags;
+	u8 irqs_to_clear;
+
+	mutex_lock(&ams_info.lock);
+
+	spin_lock_irqsave(&ams_info.irq_lock, flags);
+	irqs_to_clear = ams_info.worker_irqs;
+
+	if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
+		if (verbose)
+			printk(KERN_INFO "ams: freefall detected!\n");
+
+		ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
+	}
+
+	if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
+		if (verbose)
+			printk(KERN_INFO "ams: shock detected!\n");
+
+		ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
+	}
+
+	spin_unlock_irqrestore(&ams_info.irq_lock, flags);
+
+	ams_info.clear_irq(irqs_to_clear);
+
+	mutex_unlock(&ams_info.lock);
+}
+
+/* Call with ams_info.lock held! */
+int ams_sensor_attach(void)
+{
+	int result;
+	const u32 *prop;
+
+	/* Get orientation */
+	prop = of_get_property(ams_info.of_node, "orientation", NULL);
+	if (!prop)
+		return -ENODEV;
+	ams_info.orient1 = *prop;
+	ams_info.orient2 = *(prop + 1);
+
+	/* Register freefall interrupt handler */
+	result = pmf_register_irq_client(ams_info.of_node,
+			"accel-int-1",
+			&ams_freefall_client);
+	if (result < 0)
+		return -ENODEV;
+
+	/* Reset saved irqs */
+	ams_info.worker_irqs = 0;
+
+	/* Register shock interrupt handler */
+	result = pmf_register_irq_client(ams_info.of_node,
+			"accel-int-2",
+			&ams_shock_client);
+	if (result < 0)
+		goto release_freefall;
+
+	/* Create device */
+	ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL);
+	if (!ams_info.of_dev) {
+		result = -ENODEV;
+		goto release_shock;
+	}
+
+	/* Create attributes */
+	result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current);
+	if (result)
+		goto release_of;
+
+	ams_info.vflag = !!(ams_info.get_vendor() & 0x10);
+
+	/* Init input device */
+	result = ams_input_init();
+	if (result)
+		goto release_device_file;
+
+	return result;
+release_device_file:
+	device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
+release_of:
+	of_device_unregister(ams_info.of_dev);
+release_shock:
+	pmf_unregister_irq_client(&ams_shock_client);
+release_freefall:
+	pmf_unregister_irq_client(&ams_freefall_client);
+	return result;
+}
+
+int __init ams_init(void)
+{
+	struct device_node *np;
+
+	spin_lock_init(&ams_info.irq_lock);
+	mutex_init(&ams_info.lock);
+	INIT_WORK(&ams_info.worker, ams_worker);
+
+#ifdef CONFIG_SENSORS_AMS_I2C
+	np = of_find_node_by_name(NULL, "accelerometer");
+	if (np && of_device_is_compatible(np, "AAPL,accelerometer_1"))
+		/* Found I2C motion sensor */
+		return ams_i2c_init(np);
+#endif
+
+#ifdef CONFIG_SENSORS_AMS_PMU
+	np = of_find_node_by_name(NULL, "sms");
+	if (np && of_device_is_compatible(np, "sms"))
+		/* Found PMU motion sensor */
+		return ams_pmu_init(np);
+#endif
+	return -ENODEV;
+}
+
+void ams_sensor_detach(void)
+{
+	/* Remove input device */
+	ams_input_exit();
+
+	/* Remove attributes */
+	device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
+
+	/* Flush interrupt worker
+	 *
+	 * We do this after ams_info.exit(), because an interrupt might
+	 * have arrived before disabling them.
+	 */
+	flush_work(&ams_info.worker);
+
+	/* Remove device */
+	of_device_unregister(ams_info.of_dev);
+
+	/* Remove handler */
+	pmf_unregister_irq_client(&ams_shock_client);
+	pmf_unregister_irq_client(&ams_freefall_client);
+}
+
+static void __exit ams_exit(void)
+{
+	/* Shut down implementation */
+	ams_info.exit();
+}
+
+MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
+MODULE_DESCRIPTION("Apple Motion Sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(ams_init);
+module_exit(ams_exit);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-i2c.c b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-i2c.c
new file mode 100644
index 0000000..8a3ba56
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-i2c.c
@@ -0,0 +1,276 @@
+/*
+ * Apple Motion Sensor driver (I2C variant)
+ *
+ * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
+ *
+ * Clean room implementation based on the reverse engineered Mac OS X driver by
+ * Johannes Berg <johannes@sipsolutions.net>, documentation available at
+ * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "ams.h"
+
+/* AMS registers */
+#define AMS_COMMAND	0x00	/* command register */
+#define AMS_STATUS	0x01	/* status register */
+#define AMS_CTRL1	0x02	/* read control 1 (number of values) */
+#define AMS_CTRL2	0x03	/* read control 2 (offset?) */
+#define AMS_CTRL3	0x04	/* read control 3 (size of each value?) */
+#define AMS_DATA1	0x05	/* read data 1 */
+#define AMS_DATA2	0x06	/* read data 2 */
+#define AMS_DATA3	0x07	/* read data 3 */
+#define AMS_DATA4	0x08	/* read data 4 */
+#define AMS_DATAX	0x20	/* data X */
+#define AMS_DATAY	0x21	/* data Y */
+#define AMS_DATAZ	0x22	/* data Z */
+#define AMS_FREEFALL	0x24	/* freefall int control */
+#define AMS_SHOCK	0x25	/* shock int control */
+#define AMS_SENSLOW	0x26	/* sensitivity low limit */
+#define AMS_SENSHIGH	0x27	/* sensitivity high limit */
+#define AMS_CTRLX	0x28	/* control X */
+#define AMS_CTRLY	0x29	/* control Y */
+#define AMS_CTRLZ	0x2A	/* control Z */
+#define AMS_UNKNOWN1	0x2B	/* unknown 1 */
+#define AMS_UNKNOWN2	0x2C	/* unknown 2 */
+#define AMS_UNKNOWN3	0x2D	/* unknown 3 */
+#define AMS_VENDOR	0x2E	/* vendor */
+
+/* AMS commands - use with the AMS_COMMAND register */
+enum ams_i2c_cmd {
+	AMS_CMD_NOOP = 0,
+	AMS_CMD_VERSION,
+	AMS_CMD_READMEM,
+	AMS_CMD_WRITEMEM,
+	AMS_CMD_ERASEMEM,
+	AMS_CMD_READEE,
+	AMS_CMD_WRITEEE,
+	AMS_CMD_RESET,
+	AMS_CMD_START,
+};
+
+static int ams_i2c_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id);
+static int ams_i2c_remove(struct i2c_client *client);
+
+static const struct i2c_device_id ams_id[] = {
+	{ "MAC,accelerometer_1", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ams_id);
+
+static struct i2c_driver ams_i2c_driver = {
+	.driver = {
+		.name   = "ams",
+	},
+	.probe          = ams_i2c_probe,
+	.remove         = ams_i2c_remove,
+	.id_table       = ams_id,
+};
+
+static s32 ams_i2c_read(u8 reg)
+{
+	return i2c_smbus_read_byte_data(ams_info.i2c_client, reg);
+}
+
+static int ams_i2c_write(u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value);
+}
+
+static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
+{
+	s32 result;
+	int count = 3;
+
+	ams_i2c_write(AMS_COMMAND, cmd);
+	msleep(5);
+
+	while (count--) {
+		result = ams_i2c_read(AMS_COMMAND);
+		if (result == 0 || result & 0x80)
+			return 0;
+
+		schedule_timeout_uninterruptible(HZ / 20);
+	}
+
+	return -1;
+}
+
+static void ams_i2c_set_irq(enum ams_irq reg, char enable)
+{
+	if (reg & AMS_IRQ_FREEFALL) {
+		u8 val = ams_i2c_read(AMS_CTRLX);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_i2c_write(AMS_CTRLX, val);
+	}
+
+	if (reg & AMS_IRQ_SHOCK) {
+		u8 val = ams_i2c_read(AMS_CTRLY);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_i2c_write(AMS_CTRLY, val);
+	}
+
+	if (reg & AMS_IRQ_GLOBAL) {
+		u8 val = ams_i2c_read(AMS_CTRLZ);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_i2c_write(AMS_CTRLZ, val);
+	}
+}
+
+static void ams_i2c_clear_irq(enum ams_irq reg)
+{
+	if (reg & AMS_IRQ_FREEFALL)
+		ams_i2c_write(AMS_FREEFALL, 0);
+
+	if (reg & AMS_IRQ_SHOCK)
+		ams_i2c_write(AMS_SHOCK, 0);
+}
+
+static u8 ams_i2c_get_vendor(void)
+{
+	return ams_i2c_read(AMS_VENDOR);
+}
+
+static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
+{
+	*x = ams_i2c_read(AMS_DATAX);
+	*y = ams_i2c_read(AMS_DATAY);
+	*z = ams_i2c_read(AMS_DATAZ);
+}
+
+static int ams_i2c_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	int vmaj, vmin;
+	int result;
+
+	/* There can be only one */
+	if (unlikely(ams_info.has_device))
+		return -ENODEV;
+
+	ams_info.i2c_client = client;
+
+	if (ams_i2c_cmd(AMS_CMD_RESET)) {
+		printk(KERN_INFO "ams: Failed to reset the device\n");
+		return -ENODEV;
+	}
+
+	if (ams_i2c_cmd(AMS_CMD_START)) {
+		printk(KERN_INFO "ams: Failed to start the device\n");
+		return -ENODEV;
+	}
+
+	/* get version/vendor information */
+	ams_i2c_write(AMS_CTRL1, 0x02);
+	ams_i2c_write(AMS_CTRL2, 0x85);
+	ams_i2c_write(AMS_CTRL3, 0x01);
+
+	ams_i2c_cmd(AMS_CMD_READMEM);
+
+	vmaj = ams_i2c_read(AMS_DATA1);
+	vmin = ams_i2c_read(AMS_DATA2);
+	if (vmaj != 1 || vmin != 52) {
+		printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
+			vmaj, vmin);
+		return -ENODEV;
+	}
+
+	ams_i2c_cmd(AMS_CMD_VERSION);
+
+	vmaj = ams_i2c_read(AMS_DATA1);
+	vmin = ams_i2c_read(AMS_DATA2);
+	if (vmaj != 0 || vmin != 1) {
+		printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
+			vmaj, vmin);
+		return -ENODEV;
+	}
+
+	/* Disable interrupts */
+	ams_i2c_set_irq(AMS_IRQ_ALL, 0);
+
+	result = ams_sensor_attach();
+	if (result < 0)
+		return result;
+
+	/* Set default values */
+	ams_i2c_write(AMS_SENSLOW, 0x15);
+	ams_i2c_write(AMS_SENSHIGH, 0x60);
+	ams_i2c_write(AMS_CTRLX, 0x08);
+	ams_i2c_write(AMS_CTRLY, 0x0F);
+	ams_i2c_write(AMS_CTRLZ, 0x4F);
+	ams_i2c_write(AMS_UNKNOWN1, 0x14);
+
+	/* Clear interrupts */
+	ams_i2c_clear_irq(AMS_IRQ_ALL);
+
+	ams_info.has_device = 1;
+
+	/* Enable interrupts */
+	ams_i2c_set_irq(AMS_IRQ_ALL, 1);
+
+	printk(KERN_INFO "ams: Found I2C based motion sensor\n");
+
+	return 0;
+}
+
+static int ams_i2c_remove(struct i2c_client *client)
+{
+	if (ams_info.has_device) {
+		ams_sensor_detach();
+
+		/* Disable interrupts */
+		ams_i2c_set_irq(AMS_IRQ_ALL, 0);
+
+		/* Clear interrupts */
+		ams_i2c_clear_irq(AMS_IRQ_ALL);
+
+		printk(KERN_INFO "ams: Unloading\n");
+
+		ams_info.has_device = 0;
+	}
+
+	return 0;
+}
+
+static void ams_i2c_exit(void)
+{
+	i2c_del_driver(&ams_i2c_driver);
+}
+
+int __init ams_i2c_init(struct device_node *np)
+{
+	int result;
+
+	/* Set implementation stuff */
+	ams_info.of_node = np;
+	ams_info.exit = ams_i2c_exit;
+	ams_info.get_vendor = ams_i2c_get_vendor;
+	ams_info.get_xyz = ams_i2c_get_xyz;
+	ams_info.clear_irq = ams_i2c_clear_irq;
+	ams_info.bustype = BUS_I2C;
+
+	result = i2c_add_driver(&ams_i2c_driver);
+
+	return result;
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-input.c b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-input.c
new file mode 100644
index 0000000..2edae7d
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-input.c
@@ -0,0 +1,161 @@
+/*
+ * Apple Motion Sensor driver (joystick emulation)
+ *
+ * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include "ams.h"
+
+static bool joystick;
+module_param(joystick, bool, S_IRUGO);
+MODULE_PARM_DESC(joystick, "Enable the input class device on module load");
+
+static bool invert;
+module_param(invert, bool, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(invert, "Invert input data on X and Y axis");
+
+static DEFINE_MUTEX(ams_input_mutex);
+
+static void ams_idev_poll(struct input_polled_dev *dev)
+{
+	struct input_dev *idev = dev->input;
+	s8 x, y, z;
+
+	mutex_lock(&ams_info.lock);
+
+	ams_sensors(&x, &y, &z);
+
+	x -= ams_info.xcalib;
+	y -= ams_info.ycalib;
+	z -= ams_info.zcalib;
+
+	input_report_abs(idev, ABS_X, invert ? -x : x);
+	input_report_abs(idev, ABS_Y, invert ? -y : y);
+	input_report_abs(idev, ABS_Z, z);
+
+	input_sync(idev);
+
+	mutex_unlock(&ams_info.lock);
+}
+
+/* Call with ams_info.lock held! */
+static int ams_input_enable(void)
+{
+	struct input_dev *input;
+	s8 x, y, z;
+	int error;
+
+	ams_sensors(&x, &y, &z);
+	ams_info.xcalib = x;
+	ams_info.ycalib = y;
+	ams_info.zcalib = z;
+
+	ams_info.idev = input_allocate_polled_device();
+	if (!ams_info.idev)
+		return -ENOMEM;
+
+	ams_info.idev->poll = ams_idev_poll;
+	ams_info.idev->poll_interval = 25;
+
+	input = ams_info.idev->input;
+	input->name = "Apple Motion Sensor";
+	input->id.bustype = ams_info.bustype;
+	input->id.vendor = 0;
+	input->dev.parent = &ams_info.of_dev->dev;
+
+	input_set_abs_params(input, ABS_X, -50, 50, 3, 0);
+	input_set_abs_params(input, ABS_Y, -50, 50, 3, 0);
+	input_set_abs_params(input, ABS_Z, -50, 50, 3, 0);
+
+	set_bit(EV_ABS, input->evbit);
+	set_bit(EV_KEY, input->evbit);
+	set_bit(BTN_TOUCH, input->keybit);
+
+	error = input_register_polled_device(ams_info.idev);
+	if (error) {
+		input_free_polled_device(ams_info.idev);
+		ams_info.idev = NULL;
+		return error;
+	}
+
+	joystick = 1;
+
+	return 0;
+}
+
+static void ams_input_disable(void)
+{
+	if (ams_info.idev) {
+		input_unregister_polled_device(ams_info.idev);
+		input_free_polled_device(ams_info.idev);
+		ams_info.idev = NULL;
+	}
+
+	joystick = 0;
+}
+
+static ssize_t ams_input_show_joystick(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", joystick);
+}
+
+static ssize_t ams_input_store_joystick(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long enable;
+	int error = 0;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &enable);
+	if (ret)
+		return ret;
+	if (enable > 1)
+		return -EINVAL;
+
+	mutex_lock(&ams_input_mutex);
+
+	if (enable != joystick) {
+		if (enable)
+			error = ams_input_enable();
+		else
+			ams_input_disable();
+	}
+
+	mutex_unlock(&ams_input_mutex);
+
+	return error ? error : count;
+}
+
+static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR,
+	ams_input_show_joystick, ams_input_store_joystick);
+
+int ams_input_init(void)
+{
+	if (joystick)
+		ams_input_enable();
+
+	return device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick);
+}
+
+void ams_input_exit(void)
+{
+	device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick);
+
+	mutex_lock(&ams_input_mutex);
+	ams_input_disable();
+	mutex_unlock(&ams_input_mutex);
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-pmu.c b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-pmu.c
new file mode 100644
index 0000000..4f61b3e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams-pmu.c
@@ -0,0 +1,201 @@
+/*
+ * Apple Motion Sensor driver (PMU variant)
+ *
+ * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+
+#include "ams.h"
+
+/* Attitude */
+#define AMS_X			0x00
+#define AMS_Y			0x01
+#define AMS_Z			0x02
+
+/* Not exactly known, maybe chip vendor */
+#define AMS_VENDOR		0x03
+
+/* Freefall registers */
+#define AMS_FF_CLEAR		0x04
+#define AMS_FF_ENABLE		0x05
+#define AMS_FF_LOW_LIMIT	0x06
+#define AMS_FF_DEBOUNCE		0x07
+
+/* Shock registers */
+#define AMS_SHOCK_CLEAR		0x08
+#define AMS_SHOCK_ENABLE	0x09
+#define AMS_SHOCK_HIGH_LIMIT	0x0a
+#define AMS_SHOCK_DEBOUNCE	0x0b
+
+/* Global interrupt and power control register */
+#define AMS_CONTROL		0x0c
+
+static u8 ams_pmu_cmd;
+
+static void ams_pmu_req_complete(struct adb_request *req)
+{
+	complete((struct completion *)req->arg);
+}
+
+/* Only call this function from task context */
+static void ams_pmu_set_register(u8 reg, u8 value)
+{
+	static struct adb_request req;
+	DECLARE_COMPLETION(req_complete);
+
+	req.arg = &req_complete;
+	if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value))
+		return;
+
+	wait_for_completion(&req_complete);
+}
+
+/* Only call this function from task context */
+static u8 ams_pmu_get_register(u8 reg)
+{
+	static struct adb_request req;
+	DECLARE_COMPLETION(req_complete);
+
+	req.arg = &req_complete;
+	if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg))
+		return 0;
+
+	wait_for_completion(&req_complete);
+
+	if (req.reply_len > 0)
+		return req.reply[0];
+	else
+		return 0;
+}
+
+/* Enables or disables the specified interrupts */
+static void ams_pmu_set_irq(enum ams_irq reg, char enable)
+{
+	if (reg & AMS_IRQ_FREEFALL) {
+		u8 val = ams_pmu_get_register(AMS_FF_ENABLE);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_pmu_set_register(AMS_FF_ENABLE, val);
+	}
+
+	if (reg & AMS_IRQ_SHOCK) {
+		u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_pmu_set_register(AMS_SHOCK_ENABLE, val);
+	}
+
+	if (reg & AMS_IRQ_GLOBAL) {
+		u8 val = ams_pmu_get_register(AMS_CONTROL);
+		if (enable)
+			val |= 0x80;
+		else
+			val &= ~0x80;
+		ams_pmu_set_register(AMS_CONTROL, val);
+	}
+}
+
+static void ams_pmu_clear_irq(enum ams_irq reg)
+{
+	if (reg & AMS_IRQ_FREEFALL)
+		ams_pmu_set_register(AMS_FF_CLEAR, 0x00);
+
+	if (reg & AMS_IRQ_SHOCK)
+		ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00);
+}
+
+static u8 ams_pmu_get_vendor(void)
+{
+	return ams_pmu_get_register(AMS_VENDOR);
+}
+
+static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z)
+{
+	*x = ams_pmu_get_register(AMS_X);
+	*y = ams_pmu_get_register(AMS_Y);
+	*z = ams_pmu_get_register(AMS_Z);
+}
+
+static void ams_pmu_exit(void)
+{
+	ams_sensor_detach();
+
+	/* Disable interrupts */
+	ams_pmu_set_irq(AMS_IRQ_ALL, 0);
+
+	/* Clear interrupts */
+	ams_pmu_clear_irq(AMS_IRQ_ALL);
+
+	ams_info.has_device = 0;
+
+	printk(KERN_INFO "ams: Unloading\n");
+}
+
+int __init ams_pmu_init(struct device_node *np)
+{
+	const u32 *prop;
+	int result;
+
+	/* Set implementation stuff */
+	ams_info.of_node = np;
+	ams_info.exit = ams_pmu_exit;
+	ams_info.get_vendor = ams_pmu_get_vendor;
+	ams_info.get_xyz = ams_pmu_get_xyz;
+	ams_info.clear_irq = ams_pmu_clear_irq;
+	ams_info.bustype = BUS_HOST;
+
+	/* Get PMU command, should be 0x4e, but we can never know */
+	prop = of_get_property(ams_info.of_node, "reg", NULL);
+	if (!prop)
+		return -ENODEV;
+
+	ams_pmu_cmd = ((*prop) >> 8) & 0xff;
+
+	/* Disable interrupts */
+	ams_pmu_set_irq(AMS_IRQ_ALL, 0);
+
+	/* Clear interrupts */
+	ams_pmu_clear_irq(AMS_IRQ_ALL);
+
+	result = ams_sensor_attach();
+	if (result < 0)
+		return result;
+
+	/* Set default values */
+	ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15);
+	ams_pmu_set_register(AMS_FF_ENABLE, 0x08);
+	ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14);
+
+	ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60);
+	ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f);
+	ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14);
+
+	ams_pmu_set_register(AMS_CONTROL, 0x4f);
+
+	/* Clear interrupts */
+	ams_pmu_clear_irq(AMS_IRQ_ALL);
+
+	ams_info.has_device = 1;
+
+	/* Enable interrupts */
+	ams_pmu_set_irq(AMS_IRQ_ALL, 1);
+
+	printk(KERN_INFO "ams: Found PMU based motion sensor\n");
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ams/ams.h b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams.h
new file mode 100644
index 0000000..fe8d596
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ams/ams.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/i2c.h>
+#include <linux/input-polldev.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+
+enum ams_irq {
+	AMS_IRQ_FREEFALL = 0x01,
+	AMS_IRQ_SHOCK = 0x02,
+	AMS_IRQ_GLOBAL = 0x04,
+	AMS_IRQ_ALL =
+		AMS_IRQ_FREEFALL |
+		AMS_IRQ_SHOCK |
+		AMS_IRQ_GLOBAL,
+};
+
+struct ams {
+	/* Locks */
+	spinlock_t irq_lock;
+	struct mutex lock;
+
+	/* General properties */
+	struct device_node *of_node;
+	struct platform_device *of_dev;
+	char has_device;
+	char vflag;
+	u32 orient1;
+	u32 orient2;
+
+	/* Interrupt worker */
+	struct work_struct worker;
+	u8 worker_irqs;
+
+	/* Implementation
+	 *
+	 * Only call these functions with the main lock held.
+	 */
+	void (*exit)(void);
+
+	void (*get_xyz)(s8 *x, s8 *y, s8 *z);
+	u8 (*get_vendor)(void);
+
+	void (*clear_irq)(enum ams_irq reg);
+
+#ifdef CONFIG_SENSORS_AMS_I2C
+	/* I2C properties */
+	struct i2c_client *i2c_client;
+#endif
+
+	/* Joystick emulation */
+	struct input_polled_dev *idev;
+	__u16 bustype;
+
+	/* calibrated null values */
+	int xcalib, ycalib, zcalib;
+};
+
+extern struct ams ams_info;
+
+extern void ams_sensors(s8 *x, s8 *y, s8 *z);
+extern int ams_sensor_attach(void);
+extern void ams_sensor_detach(void);
+
+extern int ams_pmu_init(struct device_node *np);
+extern int ams_i2c_init(struct device_node *np);
+
+extern int ams_input_init(void);
+extern void ams_input_exit(void);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.c b/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.c
new file mode 100644
index 0000000..1de81d9
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * /dev/lcd driver for Apple Network Servers.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+
+#include <linux/uaccess.h>
+#include <asm/sections.h>
+#include <asm/prom.h>
+#include <asm/io.h>
+
+#include "ans-lcd.h"
+
+#define ANSLCD_ADDR		0xf301c000
+#define ANSLCD_CTRL_IX 0x00
+#define ANSLCD_DATA_IX 0x10
+
+static unsigned long anslcd_short_delay = 80;
+static unsigned long anslcd_long_delay = 3280;
+static volatile unsigned char __iomem *anslcd_ptr;
+static DEFINE_MUTEX(anslcd_mutex);
+
+#undef DEBUG
+
+static void
+anslcd_write_byte_ctrl ( unsigned char c )
+{
+#ifdef DEBUG
+	printk(KERN_DEBUG "LCD: CTRL byte: %02x\n",c);
+#endif
+	out_8(anslcd_ptr + ANSLCD_CTRL_IX, c);
+	switch(c) {
+		case 1:
+		case 2:
+		case 3:
+			udelay(anslcd_long_delay); break;
+		default: udelay(anslcd_short_delay);
+	}
+}
+
+static void
+anslcd_write_byte_data ( unsigned char c )
+{
+	out_8(anslcd_ptr + ANSLCD_DATA_IX, c);
+	udelay(anslcd_short_delay);
+}
+
+static ssize_t
+anslcd_write( struct file * file, const char __user * buf, 
+				size_t count, loff_t *ppos )
+{
+	const char __user *p = buf;
+	int i;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "LCD: write\n");
+#endif
+
+	if (!access_ok(VERIFY_READ, buf, count))
+		return -EFAULT;
+
+	mutex_lock(&anslcd_mutex);
+	for ( i = *ppos; count > 0; ++i, ++p, --count ) 
+	{
+		char c;
+		__get_user(c, p);
+		anslcd_write_byte_data( c );
+	}
+	mutex_unlock(&anslcd_mutex);
+	*ppos = i;
+	return p - buf;
+}
+
+static long
+anslcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	char ch, __user *temp;
+	long ret = 0;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg);
+#endif
+
+	mutex_lock(&anslcd_mutex);
+
+	switch ( cmd )
+	{
+	case ANSLCD_CLEAR:
+		anslcd_write_byte_ctrl ( 0x38 );
+		anslcd_write_byte_ctrl ( 0x0f );
+		anslcd_write_byte_ctrl ( 0x06 );
+		anslcd_write_byte_ctrl ( 0x01 );
+		anslcd_write_byte_ctrl ( 0x02 );
+		break;
+	case ANSLCD_SENDCTRL:
+		temp = (char __user *) arg;
+		__get_user(ch, temp);
+		for (; ch; temp++) { /* FIXME: This is ugly, but should work, as a \0 byte is not a valid command code */
+			anslcd_write_byte_ctrl ( ch );
+			__get_user(ch, temp);
+		}
+		break;
+	case ANSLCD_SETSHORTDELAY:
+		if (!capable(CAP_SYS_ADMIN))
+			ret =-EACCES;
+		else
+			anslcd_short_delay=arg;
+		break;
+	case ANSLCD_SETLONGDELAY:
+		if (!capable(CAP_SYS_ADMIN))
+			ret = -EACCES;
+		else
+			anslcd_long_delay=arg;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&anslcd_mutex);
+	return ret;
+}
+
+static int
+anslcd_open( struct inode * inode, struct file * file )
+{
+	return 0;
+}
+
+const struct file_operations anslcd_fops = {
+	.write		= anslcd_write,
+	.unlocked_ioctl	= anslcd_ioctl,
+	.open		= anslcd_open,
+	.llseek		= default_llseek,
+};
+
+static struct miscdevice anslcd_dev = {
+	ANSLCD_MINOR,
+	"anslcd",
+	&anslcd_fops
+};
+
+const char anslcd_logo[] =	"********************"  /* Line #1 */
+				"*      LINUX!      *"  /* Line #3 */
+				"*    Welcome to    *"  /* Line #2 */
+				"********************"; /* Line #4 */
+
+static int __init
+anslcd_init(void)
+{
+	int a;
+	int retval;
+	struct device_node* node;
+
+	node = of_find_node_by_name(NULL, "lcd");
+	if (!node || !node->parent || strcmp(node->parent->name, "gc")) {
+		of_node_put(node);
+		return -ENODEV;
+	}
+	of_node_put(node);
+
+	anslcd_ptr = ioremap(ANSLCD_ADDR, 0x20);
+	
+	retval = misc_register(&anslcd_dev);
+	if(retval < 0){
+		printk(KERN_INFO "LCD: misc_register failed\n");
+		iounmap(anslcd_ptr);
+		return retval;
+	}
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "LCD: init\n");
+#endif
+
+	mutex_lock(&anslcd_mutex);
+	anslcd_write_byte_ctrl ( 0x38 );
+	anslcd_write_byte_ctrl ( 0x0c );
+	anslcd_write_byte_ctrl ( 0x06 );
+	anslcd_write_byte_ctrl ( 0x01 );
+	anslcd_write_byte_ctrl ( 0x02 );
+	for(a=0;a<80;a++) {
+		anslcd_write_byte_data(anslcd_logo[a]);
+	}
+	mutex_unlock(&anslcd_mutex);
+	return 0;
+}
+
+static void __exit
+anslcd_exit(void)
+{
+	misc_deregister(&anslcd_dev);
+	iounmap(anslcd_ptr);
+}
+
+module_init(anslcd_init);
+module_exit(anslcd_exit);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.h b/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.h
new file mode 100644
index 0000000..f0a6e4c
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/ans-lcd.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PPC_ANS_LCD_H
+#define _PPC_ANS_LCD_H
+
+#define ANSLCD_MINOR		156
+
+#define ANSLCD_CLEAR		0x01
+#define ANSLCD_SENDCTRL		0x02
+#define ANSLCD_SETSHORTDELAY	0x03
+#define ANSLCD_SETLONGDELAY	0x04
+
+#endif
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/apm_emu.c b/src/kernel/linux/v4.14/drivers/macintosh/apm_emu.c
new file mode 100644
index 0000000..9821e63
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/apm_emu.c
@@ -0,0 +1,119 @@
+/*
+ * APM emulation for PMU-based machines
+ *
+ * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/apm-emulation.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+
+#define APM_CRITICAL		10
+#define APM_LOW			30
+
+static void pmu_apm_get_power_status(struct apm_power_info *info)
+{
+	int percentage = -1;
+	int batteries = 0;
+	int time_units = -1;
+	int real_count = 0;
+	int i;
+	char charging = 0;
+	long charge = -1;
+	long amperage = 0;
+	unsigned long btype = 0;
+
+	info->battery_status = APM_BATTERY_STATUS_UNKNOWN;
+	info->battery_flag = APM_BATTERY_FLAG_UNKNOWN;
+	info->units = APM_UNITS_MINS;
+
+	if (pmu_power_flags & PMU_PWR_AC_PRESENT)
+		info->ac_line_status = APM_AC_ONLINE;
+	else
+		info->ac_line_status = APM_AC_OFFLINE;
+
+	for (i=0; i<pmu_battery_count; i++) {
+		if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
+			batteries++;
+			if (percentage < 0)
+				percentage = 0;
+			if (charge < 0)
+				charge = 0;
+			percentage += (pmu_batteries[i].charge * 100) /
+				pmu_batteries[i].max_charge;
+			charge += pmu_batteries[i].charge;
+			amperage += pmu_batteries[i].amperage;
+			if (btype == 0)
+				btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
+			real_count++;
+			if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
+				charging++;
+		}
+	}
+	if (batteries == 0)
+		info->ac_line_status = APM_AC_ONLINE;
+
+	if (real_count) {
+		if (amperage < 0) {
+			if (btype == PMU_BATT_TYPE_SMART)
+				time_units = (charge * 59) / (amperage * -1);
+			else
+				time_units = (charge * 16440) / (amperage * -60);
+		}
+		percentage /= real_count;
+		if (charging > 0) {
+			info->battery_status = APM_BATTERY_STATUS_CHARGING;
+			info->battery_flag = APM_BATTERY_FLAG_CHARGING;
+		} else if (percentage <= APM_CRITICAL) {
+			info->battery_status = APM_BATTERY_STATUS_CRITICAL;
+			info->battery_flag = APM_BATTERY_FLAG_CRITICAL;
+		} else if (percentage <= APM_LOW) {
+			info->battery_status = APM_BATTERY_STATUS_LOW;
+			info->battery_flag = APM_BATTERY_FLAG_LOW;
+		} else {
+			info->battery_status = APM_BATTERY_STATUS_HIGH;
+			info->battery_flag = APM_BATTERY_FLAG_HIGH;
+		}
+	}
+
+	info->battery_life = percentage;
+	info->time = time_units;
+}
+
+static int __init apm_emu_init(void)
+{
+	apm_get_power_status = pmu_apm_get_power_status;
+
+	printk(KERN_INFO "apm_emu: PMU APM Emulation initialized.\n");
+
+	return 0;
+}
+
+static void __exit apm_emu_exit(void)
+{
+	if (apm_get_power_status == pmu_apm_get_power_status)
+		apm_get_power_status = NULL;
+
+	printk(KERN_INFO "apm_emu: PMU APM Emulation removed.\n");
+}
+
+module_init(apm_emu_init);
+module_exit(apm_emu_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt");
+MODULE_DESCRIPTION("APM emulation for PowerMac");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/mac_hid.c b/src/kernel/linux/v4.14/drivers/macintosh/mac_hid.c
new file mode 100644
index 0000000..80d30e8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/mac_hid.c
@@ -0,0 +1,283 @@
+/*
+ * drivers/macintosh/mac_hid.c
+ *
+ * HID support stuff for Macintosh computers.
+ *
+ * Copyright (C) 2000 Franz Sirl.
+ *
+ * This file will soon be removed in favor of an uinput userspace tool.
+ */
+
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/sysctl.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+MODULE_LICENSE("GPL");
+
+static int mouse_emulate_buttons;
+static int mouse_button2_keycode = KEY_RIGHTCTRL;	/* right control key */
+static int mouse_button3_keycode = KEY_RIGHTALT;	/* right option key */
+
+static struct input_dev *mac_hid_emumouse_dev;
+
+static DEFINE_MUTEX(mac_hid_emumouse_mutex);
+
+static int mac_hid_create_emumouse(void)
+{
+	static struct lock_class_key mac_hid_emumouse_dev_event_class;
+	static struct lock_class_key mac_hid_emumouse_dev_mutex_class;
+	int err;
+
+	mac_hid_emumouse_dev = input_allocate_device();
+	if (!mac_hid_emumouse_dev)
+		return -ENOMEM;
+
+	lockdep_set_class(&mac_hid_emumouse_dev->event_lock,
+			  &mac_hid_emumouse_dev_event_class);
+	lockdep_set_class(&mac_hid_emumouse_dev->mutex,
+			  &mac_hid_emumouse_dev_mutex_class);
+
+	mac_hid_emumouse_dev->name = "Macintosh mouse button emulation";
+	mac_hid_emumouse_dev->id.bustype = BUS_ADB;
+	mac_hid_emumouse_dev->id.vendor = 0x0001;
+	mac_hid_emumouse_dev->id.product = 0x0001;
+	mac_hid_emumouse_dev->id.version = 0x0100;
+
+	mac_hid_emumouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+	mac_hid_emumouse_dev->keybit[BIT_WORD(BTN_MOUSE)] =
+		BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT);
+	mac_hid_emumouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+
+	err = input_register_device(mac_hid_emumouse_dev);
+	if (err) {
+		input_free_device(mac_hid_emumouse_dev);
+		mac_hid_emumouse_dev = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+static void mac_hid_destroy_emumouse(void)
+{
+	input_unregister_device(mac_hid_emumouse_dev);
+	mac_hid_emumouse_dev = NULL;
+}
+
+static bool mac_hid_emumouse_filter(struct input_handle *handle,
+				    unsigned int type, unsigned int code,
+				    int value)
+{
+	unsigned int btn;
+
+	if (type != EV_KEY)
+		return false;
+
+	if (code == mouse_button2_keycode)
+		btn = BTN_MIDDLE;
+	else if (code == mouse_button3_keycode)
+		btn = BTN_RIGHT;
+	else
+		return false;
+
+	input_report_key(mac_hid_emumouse_dev, btn, value);
+	input_sync(mac_hid_emumouse_dev);
+
+	return true;
+}
+
+static int mac_hid_emumouse_connect(struct input_handler *handler,
+				    struct input_dev *dev,
+				    const struct input_device_id *id)
+{
+	struct input_handle *handle;
+	int error;
+
+	/* Don't bind to ourselves */
+	if (dev == mac_hid_emumouse_dev)
+		return -ENODEV;
+
+	handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
+	if (!handle)
+		return -ENOMEM;
+
+	handle->dev = dev;
+	handle->handler = handler;
+	handle->name = "mac-button-emul";
+
+	error = input_register_handle(handle);
+	if (error) {
+		printk(KERN_ERR
+			"mac_hid: Failed to register button emulation handle, "
+			"error %d\n", error);
+		goto err_free;
+	}
+
+	error = input_open_device(handle);
+	if (error) {
+		printk(KERN_ERR
+			"mac_hid: Failed to open input device, error %d\n",
+			error);
+		goto err_unregister;
+	}
+
+	return 0;
+
+ err_unregister:
+	input_unregister_handle(handle);
+ err_free:
+	kfree(handle);
+	return error;
+}
+
+static void mac_hid_emumouse_disconnect(struct input_handle *handle)
+{
+	input_close_device(handle);
+	input_unregister_handle(handle);
+	kfree(handle);
+}
+
+static const struct input_device_id mac_hid_emumouse_ids[] = {
+	{
+		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+		.evbit = { BIT_MASK(EV_KEY) },
+	},
+	{ },
+};
+
+MODULE_DEVICE_TABLE(input, mac_hid_emumouse_ids);
+
+static struct input_handler mac_hid_emumouse_handler = {
+	.filter		= mac_hid_emumouse_filter,
+	.connect	= mac_hid_emumouse_connect,
+	.disconnect	= mac_hid_emumouse_disconnect,
+	.name		= "mac-button-emul",
+	.id_table	= mac_hid_emumouse_ids,
+};
+
+static int mac_hid_start_emulation(void)
+{
+	int err;
+
+	err = mac_hid_create_emumouse();
+	if (err)
+		return err;
+
+	err = input_register_handler(&mac_hid_emumouse_handler);
+	if (err) {
+		mac_hid_destroy_emumouse();
+		return err;
+	}
+
+	return 0;
+}
+
+static void mac_hid_stop_emulation(void)
+{
+	input_unregister_handler(&mac_hid_emumouse_handler);
+	mac_hid_destroy_emumouse();
+}
+
+static int mac_hid_toggle_emumouse(struct ctl_table *table, int write,
+				   void __user *buffer, size_t *lenp,
+				   loff_t *ppos)
+{
+	int *valp = table->data;
+	int old_val = *valp;
+	int rc;
+
+	rc = mutex_lock_killable(&mac_hid_emumouse_mutex);
+	if (rc)
+		return rc;
+
+	rc = proc_dointvec(table, write, buffer, lenp, ppos);
+
+	if (rc == 0 && write && *valp != old_val) {
+		if (*valp == 1)
+			rc = mac_hid_start_emulation();
+		else if (*valp == 0)
+			mac_hid_stop_emulation();
+		else
+			rc = -EINVAL;
+	}
+
+	/* Restore the old value in case of error */
+	if (rc)
+		*valp = old_val;
+
+	mutex_unlock(&mac_hid_emumouse_mutex);
+
+	return rc;
+}
+
+/* file(s) in /proc/sys/dev/mac_hid */
+static struct ctl_table mac_hid_files[] = {
+	{
+		.procname	= "mouse_button_emulation",
+		.data		= &mouse_emulate_buttons,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= mac_hid_toggle_emumouse,
+	},
+	{
+		.procname	= "mouse_button2_keycode",
+		.data		= &mouse_button2_keycode,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec,
+	},
+	{
+		.procname	= "mouse_button3_keycode",
+		.data		= &mouse_button3_keycode,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec,
+	},
+	{ }
+};
+
+/* dir in /proc/sys/dev */
+static struct ctl_table mac_hid_dir[] = {
+	{
+		.procname	= "mac_hid",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= mac_hid_files,
+	},
+	{ }
+};
+
+/* /proc/sys/dev itself, in case that is not there yet */
+static struct ctl_table mac_hid_root_dir[] = {
+	{
+		.procname	= "dev",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= mac_hid_dir,
+	},
+	{ }
+};
+
+static struct ctl_table_header *mac_hid_sysctl_header;
+
+static int __init mac_hid_init(void)
+{
+	mac_hid_sysctl_header = register_sysctl_table(mac_hid_root_dir);
+	if (!mac_hid_sysctl_header)
+		return -ENOMEM;
+
+	return 0;
+}
+module_init(mac_hid_init);
+
+static void __exit mac_hid_exit(void)
+{
+	unregister_sysctl_table(mac_hid_sysctl_header);
+
+	if (mouse_emulate_buttons)
+		mac_hid_stop_emulation();
+}
+module_exit(mac_hid_exit);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/macio-adb.c b/src/kernel/linux/v4.14/drivers/macintosh/macio-adb.c
new file mode 100644
index 0000000..9a6223a
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/macio-adb.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the ADB controller in the Mac I/O (Hydra) chip.
+ */
+#include <stdarg.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <asm/prom.h>
+#include <linux/adb.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/hydra.h>
+#include <asm/irq.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+
+struct preg {
+	unsigned char r;
+	char pad[15];
+};
+
+struct adb_regs {
+	struct preg intr;
+	struct preg data[9];
+	struct preg intr_enb;
+	struct preg dcount;
+	struct preg error;
+	struct preg ctrl;
+	struct preg autopoll;
+	struct preg active_hi;
+	struct preg active_lo;
+	struct preg test;
+};
+
+/* Bits in intr and intr_enb registers */
+#define DFB	1		/* data from bus */
+#define TAG	2		/* transfer access grant */
+
+/* Bits in dcount register */
+#define HMB	0x0f		/* how many bytes */
+#define APD	0x10		/* auto-poll data */
+
+/* Bits in error register */
+#define NRE	1		/* no response error */
+#define DLE	2		/* data lost error */
+
+/* Bits in ctrl register */
+#define TAR	1		/* transfer access request */
+#define DTB	2		/* data to bus */
+#define CRE	4		/* command response expected */
+#define ADB_RST	8		/* ADB reset */
+
+/* Bits in autopoll register */
+#define APE	1		/* autopoll enable */
+
+static volatile struct adb_regs __iomem *adb;
+static struct adb_request *current_req, *last_req;
+static DEFINE_SPINLOCK(macio_lock);
+
+static int macio_probe(void);
+static int macio_init(void);
+static irqreturn_t macio_adb_interrupt(int irq, void *arg);
+static int macio_send_request(struct adb_request *req, int sync);
+static int macio_adb_autopoll(int devs);
+static void macio_adb_poll(void);
+static int macio_adb_reset_bus(void);
+
+struct adb_driver macio_adb_driver = {
+	"MACIO",
+	macio_probe,
+	macio_init,
+	macio_send_request,
+	/*macio_write,*/
+	macio_adb_autopoll,
+	macio_adb_poll,
+	macio_adb_reset_bus
+};
+
+int macio_probe(void)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, "adb", "chrp,adb0");
+	if (np) {
+		of_node_put(np);
+		return 0;
+	}
+	return -ENODEV;
+}
+
+int macio_init(void)
+{
+	struct device_node *adbs;
+	struct resource r;
+	unsigned int irq;
+
+	adbs = of_find_compatible_node(NULL, "adb", "chrp,adb0");
+	if (adbs == 0)
+		return -ENXIO;
+
+	if (of_address_to_resource(adbs, 0, &r)) {
+		of_node_put(adbs);
+		return -ENXIO;
+	}
+	adb = ioremap(r.start, sizeof(struct adb_regs));
+
+	out_8(&adb->ctrl.r, 0);
+	out_8(&adb->intr.r, 0);
+	out_8(&adb->error.r, 0);
+	out_8(&adb->active_hi.r, 0xff); /* for now, set all devices active */
+	out_8(&adb->active_lo.r, 0xff);
+	out_8(&adb->autopoll.r, APE);
+
+	irq = irq_of_parse_and_map(adbs, 0);
+	of_node_put(adbs);
+	if (request_irq(irq, macio_adb_interrupt, 0, "ADB", (void *)0)) {
+		printk(KERN_ERR "ADB: can't get irq %d\n", irq);
+		return -EAGAIN;
+	}
+	out_8(&adb->intr_enb.r, DFB | TAG);
+
+	printk("adb: mac-io driver 1.0 for unified ADB\n");
+
+	return 0;
+}
+
+static int macio_adb_autopoll(int devs)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&macio_lock, flags);
+	out_8(&adb->active_hi.r, devs >> 8);
+	out_8(&adb->active_lo.r, devs);
+	out_8(&adb->autopoll.r, devs? APE: 0);
+	spin_unlock_irqrestore(&macio_lock, flags);
+	return 0;
+}
+
+static int macio_adb_reset_bus(void)
+{
+	unsigned long flags;
+	int timeout = 1000000;
+
+	/* Hrm... we may want to not lock interrupts for so
+	 * long ... oh well, who uses that chip anyway ? :)
+	 * That function will be seldom used during boot
+	 * on rare machines, so...
+	 */
+	spin_lock_irqsave(&macio_lock, flags);
+	out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | ADB_RST);
+	while ((in_8(&adb->ctrl.r) & ADB_RST) != 0) {
+		if (--timeout == 0) {
+			out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) & ~ADB_RST);
+			spin_unlock_irqrestore(&macio_lock, flags);
+			return -1;
+		}
+	}
+	spin_unlock_irqrestore(&macio_lock, flags);
+	return 0;
+}
+
+/* Send an ADB command */
+static int macio_send_request(struct adb_request *req, int sync)
+{
+	unsigned long flags;
+	int i;
+	
+	if (req->data[0] != ADB_PACKET)
+		return -EINVAL;
+	
+	for (i = 0; i < req->nbytes - 1; ++i)
+		req->data[i] = req->data[i+1];
+	--req->nbytes;
+	
+	req->next = NULL;
+	req->sent = 0;
+	req->complete = 0;
+	req->reply_len = 0;
+
+	spin_lock_irqsave(&macio_lock, flags);
+	if (current_req != 0) {
+		last_req->next = req;
+		last_req = req;
+	} else {
+		current_req = last_req = req;
+		out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
+	}
+	spin_unlock_irqrestore(&macio_lock, flags);
+	
+	if (sync) {
+		while (!req->complete)
+			macio_adb_poll();
+	}
+
+	return 0;
+}
+
+static irqreturn_t macio_adb_interrupt(int irq, void *arg)
+{
+	int i, n, err;
+	struct adb_request *req = NULL;
+	unsigned char ibuf[16];
+	int ibuf_len = 0;
+	int complete = 0;
+	int autopoll = 0;
+	int handled = 0;
+
+	spin_lock(&macio_lock);
+	if (in_8(&adb->intr.r) & TAG) {
+		handled = 1;
+		if ((req = current_req) != 0) {
+			/* put the current request in */
+			for (i = 0; i < req->nbytes; ++i)
+				out_8(&adb->data[i].r, req->data[i]);
+			out_8(&adb->dcount.r, req->nbytes & HMB);
+			req->sent = 1;
+			if (req->reply_expected) {
+				out_8(&adb->ctrl.r, DTB + CRE);
+			} else {
+				out_8(&adb->ctrl.r, DTB);
+				current_req = req->next;
+				complete = 1;
+				if (current_req)
+					out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
+			}
+		}
+		out_8(&adb->intr.r, 0);
+	}
+
+	if (in_8(&adb->intr.r) & DFB) {
+		handled = 1;
+		err = in_8(&adb->error.r);
+		if (current_req && current_req->sent) {
+			/* this is the response to a command */
+			req = current_req;
+			if (err == 0) {
+				req->reply_len = in_8(&adb->dcount.r) & HMB;
+				for (i = 0; i < req->reply_len; ++i)
+					req->reply[i] = in_8(&adb->data[i].r);
+			}
+			current_req = req->next;
+			complete = 1;
+			if (current_req)
+				out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
+		} else if (err == 0) {
+			/* autopoll data */
+			n = in_8(&adb->dcount.r) & HMB;
+			for (i = 0; i < n; ++i)
+				ibuf[i] = in_8(&adb->data[i].r);
+			ibuf_len = n;
+			autopoll = (in_8(&adb->dcount.r) & APD) != 0;
+		}
+		out_8(&adb->error.r, 0);
+		out_8(&adb->intr.r, 0);
+	}
+	spin_unlock(&macio_lock);
+	if (complete && req) {
+	    void (*done)(struct adb_request *) = req->done;
+	    mb();
+	    req->complete = 1;
+	    /* Here, we assume that if the request has a done member, the
+    	     * struct request will survive to setting req->complete to 1
+	     */
+	    if (done)
+		(*done)(req);
+	}
+	if (ibuf_len)
+		adb_input(ibuf, ibuf_len, autopoll);
+
+	return IRQ_RETVAL(handled);
+}
+
+static void macio_adb_poll(void)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+	if (in_8(&adb->intr.r) != 0)
+		macio_adb_interrupt(0, NULL);
+	local_irq_restore(flags);
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/macio_asic.c b/src/kernel/linux/v4.14/drivers/macintosh/macio_asic.c
new file mode 100644
index 0000000..62f541f
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/macio_asic.c
@@ -0,0 +1,797 @@
+/*
+ * Bus & driver management routines for devices within
+ * a MacIO ASIC. Interface to new driver model mostly
+ * stolen from the PCI version.
+ * 
+ *  Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.org)
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version
+ *  2 of the License, or (at your option) any later version.
+ *
+ * TODO:
+ * 
+ *  - Don't probe below media bay by default, but instead provide
+ *    some hooks for media bay to dynamically add/remove it's own
+ *    sub-devices.
+ */
+ 
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <asm/machdep.h>
+#include <asm/macio.h>
+#include <asm/pmac_feature.h>
+#include <asm/prom.h>
+
+#undef DEBUG
+
+#define MAX_NODE_NAME_SIZE (20 - 12)
+
+static struct macio_chip      *macio_on_hold;
+
+static int macio_bus_match(struct device *dev, struct device_driver *drv) 
+{
+	const struct of_device_id * matches = drv->of_match_table;
+
+	if (!matches) 
+		return 0;
+
+	return of_match_device(matches, dev) != NULL;
+}
+
+struct macio_dev *macio_dev_get(struct macio_dev *dev)
+{
+	struct device *tmp;
+
+	if (!dev)
+		return NULL;
+	tmp = get_device(&dev->ofdev.dev);
+	if (tmp)
+		return to_macio_device(tmp);
+	else
+		return NULL;
+}
+
+void macio_dev_put(struct macio_dev *dev)
+{
+	if (dev)
+		put_device(&dev->ofdev.dev);
+}
+
+
+static int macio_device_probe(struct device *dev)
+{
+	int error = -ENODEV;
+	struct macio_driver *drv;
+	struct macio_dev *macio_dev;
+	const struct of_device_id *match;
+
+	drv = to_macio_driver(dev->driver);
+	macio_dev = to_macio_device(dev);
+
+	if (!drv->probe)
+		return error;
+
+	macio_dev_get(macio_dev);
+
+	match = of_match_device(drv->driver.of_match_table, dev);
+	if (match)
+		error = drv->probe(macio_dev, match);
+	if (error)
+		macio_dev_put(macio_dev);
+
+	return error;
+}
+
+static int macio_device_remove(struct device *dev)
+{
+	struct macio_dev * macio_dev = to_macio_device(dev);
+	struct macio_driver * drv = to_macio_driver(dev->driver);
+
+	if (dev->driver && drv->remove)
+		drv->remove(macio_dev);
+	macio_dev_put(macio_dev);
+
+	return 0;
+}
+
+static void macio_device_shutdown(struct device *dev)
+{
+	struct macio_dev * macio_dev = to_macio_device(dev);
+	struct macio_driver * drv = to_macio_driver(dev->driver);
+
+	if (dev->driver && drv->shutdown)
+		drv->shutdown(macio_dev);
+}
+
+static int macio_device_suspend(struct device *dev, pm_message_t state)
+{
+	struct macio_dev * macio_dev = to_macio_device(dev);
+	struct macio_driver * drv = to_macio_driver(dev->driver);
+
+	if (dev->driver && drv->suspend)
+		return drv->suspend(macio_dev, state);
+	return 0;
+}
+
+static int macio_device_resume(struct device * dev)
+{
+	struct macio_dev * macio_dev = to_macio_device(dev);
+	struct macio_driver * drv = to_macio_driver(dev->driver);
+
+	if (dev->driver && drv->resume)
+		return drv->resume(macio_dev);
+	return 0;
+}
+
+extern const struct attribute_group *macio_dev_groups[];
+
+struct bus_type macio_bus_type = {
+       .name	= "macio",
+       .match	= macio_bus_match,
+       .uevent = of_device_uevent_modalias,
+       .probe	= macio_device_probe,
+       .remove	= macio_device_remove,
+       .shutdown = macio_device_shutdown,
+       .suspend	= macio_device_suspend,
+       .resume	= macio_device_resume,
+       .dev_groups = macio_dev_groups,
+};
+
+static int __init macio_bus_driver_init(void)
+{
+	return bus_register(&macio_bus_type);
+}
+
+postcore_initcall(macio_bus_driver_init);
+
+
+/**
+ * macio_release_dev - free a macio device structure when all users of it are
+ * finished.
+ * @dev: device that's been disconnected
+ *
+ * Will be called only by the device core when all users of this macio device
+ * are done. This currently means never as we don't hot remove any macio
+ * device yet, though that will happen with mediabay based devices in a later
+ * implementation.
+ */
+static void macio_release_dev(struct device *dev)
+{
+	struct macio_dev *mdev;
+
+        mdev = to_macio_device(dev);
+	kfree(mdev);
+}
+
+/**
+ * macio_resource_quirks - tweak or skip some resources for a device
+ * @np: pointer to the device node
+ * @res: resulting resource
+ * @index: index of resource in node
+ *
+ * If this routine returns non-null, then the resource is completely
+ * skipped.
+ */
+static int macio_resource_quirks(struct device_node *np, struct resource *res,
+				 int index)
+{
+	/* Only quirks for memory resources for now */
+	if ((res->flags & IORESOURCE_MEM) == 0)
+		return 0;
+
+	/* Grand Central has too large resource 0 on some machines */
+	if (index == 0 && !strcmp(np->name, "gc"))
+		res->end = res->start + 0x1ffff;
+
+	/* Airport has bogus resource 2 */
+	if (index >= 2 && !strcmp(np->name, "radio"))
+		return 1;
+
+#ifndef CONFIG_PPC64
+	/* DBDMAs may have bogus sizes */
+	if ((res->start & 0x0001f000) == 0x00008000)
+		res->end = res->start + 0xff;
+#endif /* CONFIG_PPC64 */
+
+	/* ESCC parent eats child resources. We could have added a
+	 * level of hierarchy, but I don't really feel the need
+	 * for it
+	 */
+	if (!strcmp(np->name, "escc"))
+		return 1;
+
+	/* ESCC has bogus resources >= 3 */
+	if (index >= 3 && !(strcmp(np->name, "ch-a") &&
+			    strcmp(np->name, "ch-b")))
+		return 1;
+
+	/* Media bay has too many resources, keep only first one */
+	if (index > 0 && !strcmp(np->name, "media-bay"))
+		return 1;
+
+	/* Some older IDE resources have bogus sizes */
+	if (!(strcmp(np->name, "IDE") && strcmp(np->name, "ATA") &&
+	      strcmp(np->type, "ide") && strcmp(np->type, "ata"))) {
+		if (index == 0 && (res->end - res->start) > 0xfff)
+			res->end = res->start + 0xfff;
+		if (index == 1 && (res->end - res->start) > 0xff)
+			res->end = res->start + 0xff;
+	}
+	return 0;
+}
+
+static void macio_create_fixup_irq(struct macio_dev *dev, int index,
+				   unsigned int line)
+{
+	unsigned int irq;
+
+	irq = irq_create_mapping(NULL, line);
+	if (!irq) {
+		dev->interrupt[index].start = irq;
+		dev->interrupt[index].flags = IORESOURCE_IRQ;
+		dev->interrupt[index].name = dev_name(&dev->ofdev.dev);
+	}
+	if (dev->n_interrupts <= index)
+		dev->n_interrupts = index + 1;
+}
+
+static void macio_add_missing_resources(struct macio_dev *dev)
+{
+	struct device_node *np = dev->ofdev.dev.of_node;
+	unsigned int irq_base;
+
+	/* Gatwick has some missing interrupts on child nodes */
+	if (dev->bus->chip->type != macio_gatwick)
+		return;
+
+	/* irq_base is always 64 on gatwick. I have no cleaner way to get
+	 * that value from here at this point
+	 */
+	irq_base = 64;
+
+	/* Fix SCC */
+	if (strcmp(np->name, "ch-a") == 0) {
+		macio_create_fixup_irq(dev, 0, 15 + irq_base);
+		macio_create_fixup_irq(dev, 1,  4 + irq_base);
+		macio_create_fixup_irq(dev, 2,  5 + irq_base);
+		printk(KERN_INFO "macio: fixed SCC irqs on gatwick\n");
+	}
+
+	/* Fix media-bay */
+	if (strcmp(np->name, "media-bay") == 0) {
+		macio_create_fixup_irq(dev, 0, 29 + irq_base);
+		printk(KERN_INFO "macio: fixed media-bay irq on gatwick\n");
+	}
+
+	/* Fix left media bay childs */
+	if (dev->media_bay != NULL && strcmp(np->name, "floppy") == 0) {
+		macio_create_fixup_irq(dev, 0, 19 + irq_base);
+		macio_create_fixup_irq(dev, 1,  1 + irq_base);
+		printk(KERN_INFO "macio: fixed left floppy irqs\n");
+	}
+	if (dev->media_bay != NULL && strcasecmp(np->name, "ata4") == 0) {
+		macio_create_fixup_irq(dev, 0, 14 + irq_base);
+		macio_create_fixup_irq(dev, 0,  3 + irq_base);
+		printk(KERN_INFO "macio: fixed left ide irqs\n");
+	}
+}
+
+static void macio_setup_interrupts(struct macio_dev *dev)
+{
+	struct device_node *np = dev->ofdev.dev.of_node;
+	unsigned int irq;
+	int i = 0, j = 0;
+
+	for (;;) {
+		struct resource *res;
+
+		if (j >= MACIO_DEV_COUNT_IRQS)
+			break;
+		res = &dev->interrupt[j];
+		irq = irq_of_parse_and_map(np, i++);
+		if (!irq)
+			break;
+		res->start = irq;
+		res->flags = IORESOURCE_IRQ;
+		res->name = dev_name(&dev->ofdev.dev);
+		if (macio_resource_quirks(np, res, i - 1)) {
+			memset(res, 0, sizeof(struct resource));
+			continue;
+		} else
+			j++;
+	}
+	dev->n_interrupts = j;
+}
+
+static void macio_setup_resources(struct macio_dev *dev,
+				  struct resource *parent_res)
+{
+	struct device_node *np = dev->ofdev.dev.of_node;
+	struct resource r;
+	int index;
+
+	for (index = 0; of_address_to_resource(np, index, &r) == 0; index++) {
+		struct resource *res;
+		if (index >= MACIO_DEV_COUNT_RESOURCES)
+			break;
+		res = &dev->resource[index];
+		*res = r;
+		res->name = dev_name(&dev->ofdev.dev);
+
+		if (macio_resource_quirks(np, res, index)) {
+			memset(res, 0, sizeof(struct resource));
+			continue;
+		}
+		/* Currently, we consider failure as harmless, this may
+		 * change in the future, once I've found all the device
+		 * tree bugs in older machines & worked around them
+		 */
+		if (insert_resource(parent_res, res)) {
+			printk(KERN_WARNING "Can't request resource "
+			       "%d for MacIO device %s\n",
+			       index, dev_name(&dev->ofdev.dev));
+		}
+	}
+	dev->n_resources = index;
+}
+
+/**
+ * macio_add_one_device - Add one device from OF node to the device tree
+ * @chip: pointer to the macio_chip holding the device
+ * @np: pointer to the device node in the OF tree
+ * @in_bay: set to 1 if device is part of a media-bay
+ *
+ * When media-bay is changed to hotswap drivers, this function will
+ * be exposed to the bay driver some way...
+ */
+static struct macio_dev * macio_add_one_device(struct macio_chip *chip,
+					       struct device *parent,
+					       struct device_node *np,
+					       struct macio_dev *in_bay,
+					       struct resource *parent_res)
+{
+	struct macio_dev *dev;
+	const u32 *reg;
+	
+	if (np == NULL)
+		return NULL;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return NULL;
+
+	dev->bus = &chip->lbus;
+	dev->media_bay = in_bay;
+	dev->ofdev.dev.of_node = np;
+	dev->ofdev.archdata.dma_mask = 0xffffffffUL;
+	dev->ofdev.dev.dma_mask = &dev->ofdev.archdata.dma_mask;
+	dev->ofdev.dev.parent = parent;
+	dev->ofdev.dev.bus = &macio_bus_type;
+	dev->ofdev.dev.release = macio_release_dev;
+	dev->ofdev.dev.dma_parms = &dev->dma_parms;
+
+	/* Standard DMA paremeters */
+	dma_set_max_seg_size(&dev->ofdev.dev, 65536);
+	dma_set_seg_boundary(&dev->ofdev.dev, 0xffffffff);
+
+#ifdef CONFIG_PCI
+	/* Set the DMA ops to the ones from the PCI device, this could be
+	 * fishy if we didn't know that on PowerMac it's always direct ops
+	 * or iommu ops that will work fine
+	 *
+	 * To get all the fields, copy all archdata
+	 */
+	dev->ofdev.dev.archdata = chip->lbus.pdev->dev.archdata;
+	dev->ofdev.dev.dma_ops = chip->lbus.pdev->dev.dma_ops;
+#endif /* CONFIG_PCI */
+
+#ifdef DEBUG
+	printk("preparing mdev @%p, ofdev @%p, dev @%p, kobj @%p\n",
+	       dev, &dev->ofdev, &dev->ofdev.dev, &dev->ofdev.dev.kobj);
+#endif
+
+	/* MacIO itself has a different reg, we use it's PCI base */
+	if (np == chip->of_node) {
+		dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s",
+			     chip->lbus.index,
+#ifdef CONFIG_PCI
+			(unsigned int)pci_resource_start(chip->lbus.pdev, 0),
+#else
+			0, /* NuBus may want to do something better here */
+#endif
+			MAX_NODE_NAME_SIZE, np->name);
+	} else {
+		reg = of_get_property(np, "reg", NULL);
+		dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s",
+			     chip->lbus.index,
+			     reg ? *reg : 0, MAX_NODE_NAME_SIZE, np->name);
+	}
+
+	/* Setup interrupts & resources */
+	macio_setup_interrupts(dev);
+	macio_setup_resources(dev, parent_res);
+	macio_add_missing_resources(dev);
+
+	/* Register with core */
+	if (of_device_register(&dev->ofdev) != 0) {
+		printk(KERN_DEBUG"macio: device registration error for %s!\n",
+		       dev_name(&dev->ofdev.dev));
+		kfree(dev);
+		return NULL;
+	}
+
+	return dev;
+}
+
+static int macio_skip_device(struct device_node *np)
+{
+	if (strncmp(np->name, "battery", 7) == 0)
+		return 1;
+	if (strncmp(np->name, "escc-legacy", 11) == 0)
+		return 1;
+	return 0;
+}
+
+/**
+ * macio_pci_add_devices - Adds sub-devices of mac-io to the device tree
+ * @chip: pointer to the macio_chip holding the devices
+ * 
+ * This function will do the job of extracting devices from the
+ * Open Firmware device tree, build macio_dev structures and add
+ * them to the Linux device tree.
+ * 
+ * For now, childs of media-bay are added now as well. This will
+ * change rsn though.
+ */
+static void macio_pci_add_devices(struct macio_chip *chip)
+{
+	struct device_node *np, *pnode;
+	struct macio_dev *rdev, *mdev, *mbdev = NULL, *sdev = NULL;
+	struct device *parent = NULL;
+	struct resource *root_res = &iomem_resource;
+	
+	/* Add a node for the macio bus itself */
+#ifdef CONFIG_PCI
+	if (chip->lbus.pdev) {
+		parent = &chip->lbus.pdev->dev;
+		root_res = &chip->lbus.pdev->resource[0];
+	}
+#endif
+	pnode = of_node_get(chip->of_node);
+	if (pnode == NULL)
+		return;
+
+	/* Add macio itself to hierarchy */
+	rdev = macio_add_one_device(chip, parent, pnode, NULL, root_res);
+	if (rdev == NULL)
+		return;
+	root_res = &rdev->resource[0];
+
+	/* First scan 1st level */
+	for (np = NULL; (np = of_get_next_child(pnode, np)) != NULL;) {
+		if (macio_skip_device(np))
+			continue;
+		of_node_get(np);
+		mdev = macio_add_one_device(chip, &rdev->ofdev.dev, np, NULL,
+					    root_res);
+		if (mdev == NULL)
+			of_node_put(np);
+		else if (strncmp(np->name, "media-bay", 9) == 0)
+			mbdev = mdev;
+		else if (strncmp(np->name, "escc", 4) == 0)
+			sdev = mdev;
+	}
+
+	/* Add media bay devices if any */
+	if (mbdev) {
+		pnode = mbdev->ofdev.dev.of_node;
+		for (np = NULL; (np = of_get_next_child(pnode, np)) != NULL;) {
+			if (macio_skip_device(np))
+				continue;
+			of_node_get(np);
+			if (macio_add_one_device(chip, &mbdev->ofdev.dev, np,
+						 mbdev,  root_res) == NULL)
+				of_node_put(np);
+		}
+	}
+
+	/* Add serial ports if any */
+	if (sdev) {
+		pnode = sdev->ofdev.dev.of_node;
+		for (np = NULL; (np = of_get_next_child(pnode, np)) != NULL;) {
+			if (macio_skip_device(np))
+				continue;
+			of_node_get(np);
+			if (macio_add_one_device(chip, &sdev->ofdev.dev, np,
+						 NULL, root_res) == NULL)
+				of_node_put(np);
+		}
+	}
+}
+
+
+/**
+ * macio_register_driver - Registers a new MacIO device driver
+ * @drv: pointer to the driver definition structure
+ */
+int macio_register_driver(struct macio_driver *drv)
+{
+	/* initialize common driver fields */
+	drv->driver.bus = &macio_bus_type;
+
+	/* register with core */
+	return driver_register(&drv->driver);
+}
+
+/**
+ * macio_unregister_driver - Unregisters a new MacIO device driver
+ * @drv: pointer to the driver definition structure
+ */
+void macio_unregister_driver(struct macio_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+
+/* Managed MacIO resources */
+struct macio_devres {
+	u32	res_mask;
+};
+
+static void maciom_release(struct device *gendev, void *res)
+{
+	struct macio_dev *dev = to_macio_device(gendev);
+	struct macio_devres *dr = res;
+	int i, max;
+
+	max = min(dev->n_resources, 32);
+	for (i = 0; i < max; i++) {
+		if (dr->res_mask & (1 << i))
+			macio_release_resource(dev, i);
+	}
+}
+
+int macio_enable_devres(struct macio_dev *dev)
+{
+	struct macio_devres *dr;
+
+	dr = devres_find(&dev->ofdev.dev, maciom_release, NULL, NULL);
+	if (!dr) {
+		dr = devres_alloc(maciom_release, sizeof(*dr), GFP_KERNEL);
+		if (!dr)
+			return -ENOMEM;
+	}
+	return devres_get(&dev->ofdev.dev, dr, NULL, NULL) != NULL;
+}
+
+static struct macio_devres * find_macio_dr(struct macio_dev *dev)
+{
+	return devres_find(&dev->ofdev.dev, maciom_release, NULL, NULL);
+}
+
+/**
+ *	macio_request_resource - Request an MMIO resource
+ * 	@dev: pointer to the device holding the resource
+ *	@resource_no: resource number to request
+ *	@name: resource name
+ *
+ *	Mark  memory region number @resource_no associated with MacIO
+ *	device @dev as being reserved by owner @name.  Do not access
+ *	any address inside the memory regions unless this call returns
+ *	successfully.
+ *
+ *	Returns 0 on success, or %EBUSY on error.  A warning
+ *	message is also printed on failure.
+ */
+int macio_request_resource(struct macio_dev *dev, int resource_no,
+			   const char *name)
+{
+	struct macio_devres *dr = find_macio_dr(dev);
+
+	if (macio_resource_len(dev, resource_no) == 0)
+		return 0;
+		
+	if (!request_mem_region(macio_resource_start(dev, resource_no),
+				macio_resource_len(dev, resource_no),
+				name))
+		goto err_out;
+
+	if (dr && resource_no < 32)
+		dr->res_mask |= 1 << resource_no;
+	
+	return 0;
+
+err_out:
+	printk (KERN_WARNING "MacIO: Unable to reserve resource #%d:%lx@%lx"
+		" for device %s\n",
+		resource_no,
+		macio_resource_len(dev, resource_no),
+		macio_resource_start(dev, resource_no),
+		dev_name(&dev->ofdev.dev));
+	return -EBUSY;
+}
+
+/**
+ * macio_release_resource - Release an MMIO resource
+ * @dev: pointer to the device holding the resource
+ * @resource_no: resource number to release
+ */
+void macio_release_resource(struct macio_dev *dev, int resource_no)
+{
+	struct macio_devres *dr = find_macio_dr(dev);
+
+	if (macio_resource_len(dev, resource_no) == 0)
+		return;
+	release_mem_region(macio_resource_start(dev, resource_no),
+			   macio_resource_len(dev, resource_no));
+	if (dr && resource_no < 32)
+		dr->res_mask &= ~(1 << resource_no);
+}
+
+/**
+ *	macio_request_resources - Reserve all memory resources
+ *	@dev: MacIO device whose resources are to be reserved
+ *	@name: Name to be associated with resource.
+ *
+ *	Mark all memory regions associated with MacIO device @dev as
+ *	being reserved by owner @name.  Do not access any address inside
+ *	the memory regions unless this call returns successfully.
+ *
+ *	Returns 0 on success, or %EBUSY on error.  A warning
+ *	message is also printed on failure.
+ */
+int macio_request_resources(struct macio_dev *dev, const char *name)
+{
+	int i;
+	
+	for (i = 0; i < dev->n_resources; i++)
+		if (macio_request_resource(dev, i, name))
+			goto err_out;
+	return 0;
+
+err_out:
+	while(--i >= 0)
+		macio_release_resource(dev, i);
+		
+	return -EBUSY;
+}
+
+/**
+ *	macio_release_resources - Release reserved memory resources
+ *	@dev: MacIO device whose resources were previously reserved
+ */
+
+void macio_release_resources(struct macio_dev *dev)
+{
+	int i;
+	
+	for (i = 0; i < dev->n_resources; i++)
+		macio_release_resource(dev, i);
+}
+
+
+#ifdef CONFIG_PCI
+
+static int macio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct device_node* np;
+	struct macio_chip* chip;
+	
+	if (ent->vendor != PCI_VENDOR_ID_APPLE)
+		return -ENODEV;
+
+	/* Note regarding refcounting: We assume pci_device_to_OF_node() is
+	 * ported to new OF APIs and returns a node with refcount incremented.
+	 */
+	np = pci_device_to_OF_node(pdev);
+	if (np == NULL)
+		return -ENODEV;
+
+	/* The above assumption is wrong !!!
+	 * fix that here for now until I fix the arch code
+	 */
+	of_node_get(np);
+
+	/* We also assume that pmac_feature will have done a get() on nodes
+	 * stored in the macio chips array
+	 */
+	chip = macio_find(np, macio_unknown);
+       	of_node_put(np);
+	if (chip == NULL)
+		return -ENODEV;
+
+	/* XXX Need locking ??? */
+	if (chip->lbus.pdev == NULL) {
+		chip->lbus.pdev = pdev;
+		chip->lbus.chip = chip;
+		pci_set_drvdata(pdev, &chip->lbus);
+		pci_set_master(pdev);
+	}
+
+	printk(KERN_INFO "MacIO PCI driver attached to %s chipset\n",
+		chip->name);
+
+	/*
+	 * HACK ALERT: The WallStreet PowerBook and some OHare based machines
+	 * have 2 macio ASICs. I must probe the "main" one first or IDE
+	 * ordering will be incorrect. So I put on "hold" the second one since
+	 * it seem to appear first on PCI
+	 */
+	if (chip->type == macio_gatwick || chip->type == macio_ohareII)
+		if (macio_chips[0].lbus.pdev == NULL) {
+			macio_on_hold = chip;
+			return 0;
+		}
+
+	macio_pci_add_devices(chip);
+	if (macio_on_hold && macio_chips[0].lbus.pdev != NULL) {
+		macio_pci_add_devices(macio_on_hold);
+		macio_on_hold = NULL;
+	}
+
+	return 0;
+}
+
+static void macio_pci_remove(struct pci_dev* pdev)
+{
+	panic("removing of macio-asic not supported !\n");
+}
+
+/*
+ * MacIO is matched against any Apple ID, it's probe() function
+ * will then decide wether it applies or not
+ */
+static const struct pci_device_id pci_ids[] = { {
+	.vendor		= PCI_VENDOR_ID_APPLE,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+
+	}, { /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE (pci, pci_ids);
+
+/* pci driver glue; this is a "new style" PCI driver module */
+static struct pci_driver macio_pci_driver = {
+	.name		= (char *) "macio",
+	.id_table	= pci_ids,
+
+	.probe		= macio_pci_probe,
+	.remove		= macio_pci_remove,
+};
+
+#endif /* CONFIG_PCI */
+
+static int __init macio_module_init (void) 
+{
+#ifdef CONFIG_PCI
+	int rc;
+
+	rc = pci_register_driver(&macio_pci_driver);
+	if (rc)
+		return rc;
+#endif /* CONFIG_PCI */
+	return 0;
+}
+
+module_init(macio_module_init);
+
+EXPORT_SYMBOL(macio_register_driver);
+EXPORT_SYMBOL(macio_unregister_driver);
+EXPORT_SYMBOL(macio_dev_get);
+EXPORT_SYMBOL(macio_dev_put);
+EXPORT_SYMBOL(macio_request_resource);
+EXPORT_SYMBOL(macio_release_resource);
+EXPORT_SYMBOL(macio_request_resources);
+EXPORT_SYMBOL(macio_release_resources);
+EXPORT_SYMBOL(macio_enable_devres);
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/macio_sysfs.c b/src/kernel/linux/v4.14/drivers/macintosh/macio_sysfs.c
new file mode 100644
index 0000000..ca4fcff
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/macio_sysfs.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/stat.h>
+#include <asm/macio.h>
+
+
+#define macio_config_of_attr(field, format_string)			\
+static ssize_t								\
+field##_show (struct device *dev, struct device_attribute *attr,	\
+              char *buf)						\
+{									\
+	struct macio_dev *mdev = to_macio_device (dev);			\
+	return sprintf (buf, format_string, mdev->ofdev.dev.of_node->field); \
+}									\
+static DEVICE_ATTR_RO(field);
+
+static ssize_t
+compatible_show (struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct platform_device *of;
+	const char *compat;
+	int cplen;
+	int length = 0;
+
+	of = &to_macio_device (dev)->ofdev;
+	compat = of_get_property(of->dev.of_node, "compatible", &cplen);
+	if (!compat) {
+		*buf = '\0';
+		return 0;
+	}
+	while (cplen > 0) {
+		int l;
+		length += sprintf (buf, "%s\n", compat);
+		buf += length;
+		l = strlen (compat) + 1;
+		compat += l;
+		cplen -= l;
+	}
+
+	return length;
+}
+static DEVICE_ATTR_RO(compatible);
+
+static ssize_t modalias_show (struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	return of_device_modalias(dev, buf, PAGE_SIZE);
+}
+
+static ssize_t devspec_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct platform_device *ofdev;
+
+	ofdev = to_platform_device(dev);
+	return sprintf(buf, "%pOF\n", ofdev->dev.of_node);
+}
+static DEVICE_ATTR_RO(modalias);
+static DEVICE_ATTR_RO(devspec);
+
+macio_config_of_attr (name, "%s\n");
+macio_config_of_attr (type, "%s\n");
+
+static struct attribute *macio_dev_attrs[] = {
+	&dev_attr_name.attr,
+	&dev_attr_type.attr,
+	&dev_attr_compatible.attr,
+	&dev_attr_modalias.attr,
+	&dev_attr_devspec.attr,
+	NULL,
+};
+
+static const struct attribute_group macio_dev_group = {
+	.attrs = macio_dev_attrs,
+};
+
+const struct attribute_group *macio_dev_groups[] = {
+	&macio_dev_group,
+	NULL,
+};
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/mediabay.c b/src/kernel/linux/v4.14/drivers/macintosh/mediabay.c
new file mode 100644
index 0000000..d98e566
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/mediabay.c
@@ -0,0 +1,757 @@
+/*
+ * Driver for the media bay on the PowerBook 3400 and 2400.
+ *
+ * Copyright (C) 1998 Paul Mackerras.
+ *
+ * Various evolutions by Benjamin Herrenschmidt & Henry Worth
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version
+ *  2 of the License, or (at your option) any later version.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/stddef.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <asm/prom.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/machdep.h>
+#include <asm/pmac_feature.h>
+#include <asm/mediabay.h>
+#include <asm/sections.h>
+#include <asm/ohare.h>
+#include <asm/heathrow.h>
+#include <asm/keylargo.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+
+#define MB_FCR32(bay, r)	((bay)->base + ((r) >> 2))
+#define MB_FCR8(bay, r)		(((volatile u8 __iomem *)((bay)->base)) + (r))
+
+#define MB_IN32(bay,r)		(in_le32(MB_FCR32(bay,r)))
+#define MB_OUT32(bay,r,v)	(out_le32(MB_FCR32(bay,r), (v)))
+#define MB_BIS(bay,r,v)		(MB_OUT32((bay), (r), MB_IN32((bay), r) | (v)))
+#define MB_BIC(bay,r,v)		(MB_OUT32((bay), (r), MB_IN32((bay), r) & ~(v)))
+#define MB_IN8(bay,r)		(in_8(MB_FCR8(bay,r)))
+#define MB_OUT8(bay,r,v)	(out_8(MB_FCR8(bay,r), (v)))
+
+struct media_bay_info;
+
+struct mb_ops {
+	char*	name;
+	void	(*init)(struct media_bay_info *bay);
+	u8	(*content)(struct media_bay_info *bay);
+	void	(*power)(struct media_bay_info *bay, int on_off);
+	int	(*setup_bus)(struct media_bay_info *bay, u8 device_id);
+	void	(*un_reset)(struct media_bay_info *bay);
+	void	(*un_reset_ide)(struct media_bay_info *bay);
+};
+
+struct media_bay_info {
+	u32 __iomem			*base;
+	int				content_id;
+	int				state;
+	int				last_value;
+	int				value_count;
+	int				timer;
+	struct macio_dev		*mdev;
+	const struct mb_ops*		ops;
+	int				index;
+	int				cached_gpio;
+	int				sleeping;
+	int				user_lock;
+	struct mutex			lock;
+};
+
+#define MAX_BAYS	2
+
+static struct media_bay_info media_bays[MAX_BAYS];
+static int media_bay_count = 0;
+
+/*
+ * Wait that number of ms between each step in normal polling mode
+ */
+#define MB_POLL_DELAY	25
+
+/*
+ * Consider the media-bay ID value stable if it is the same for
+ * this number of milliseconds
+ */
+#define MB_STABLE_DELAY	100
+
+/* Wait after powering up the media bay this delay in ms
+ * timeout bumped for some powerbooks
+ */
+#define MB_POWER_DELAY	200
+
+/*
+ * Hold the media-bay reset signal true for this many ticks
+ * after a device is inserted before releasing it.
+ */
+#define MB_RESET_DELAY	50
+
+/*
+ * Wait this long after the reset signal is released and before doing
+ * further operations. After this delay, the IDE reset signal is released
+ * too for an IDE device
+ */
+#define MB_SETUP_DELAY	100
+
+/*
+ * Wait this many ticks after an IDE device (e.g. CD-ROM) is inserted
+ * (or until the device is ready) before calling into the driver
+ */
+#define MB_IDE_WAIT	1000
+
+/*
+ * States of a media bay
+ */
+enum {
+	mb_empty = 0,		/* Idle */
+	mb_powering_up,		/* power bit set, waiting MB_POWER_DELAY */
+	mb_enabling_bay,	/* enable bits set, waiting MB_RESET_DELAY */
+	mb_resetting,		/* reset bit unset, waiting MB_SETUP_DELAY */
+	mb_ide_resetting,	/* IDE reset bit unser, waiting MB_IDE_WAIT */
+	mb_up,			/* Media bay full */
+	mb_powering_down	/* Powering down (avoid too fast down/up) */
+};
+
+#define MB_POWER_SOUND		0x08
+#define MB_POWER_FLOPPY		0x04
+#define MB_POWER_ATA		0x02
+#define MB_POWER_PCI		0x01
+#define MB_POWER_OFF		0x00
+
+/*
+ * Functions for polling content of media bay
+ */
+ 
+static u8
+ohare_mb_content(struct media_bay_info *bay)
+{
+	return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7;
+}
+
+static u8
+heathrow_mb_content(struct media_bay_info *bay)
+{
+	return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7;
+}
+
+static u8
+keylargo_mb_content(struct media_bay_info *bay)
+{
+	int new_gpio;
+
+	new_gpio = MB_IN8(bay, KL_GPIO_MEDIABAY_IRQ) & KEYLARGO_GPIO_INPUT_DATA;
+	if (new_gpio) {
+		bay->cached_gpio = new_gpio;
+		return MB_NO;
+	} else if (bay->cached_gpio != new_gpio) {
+		MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
+		(void)MB_IN32(bay, KEYLARGO_MBCR);
+		udelay(5);
+		MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
+		(void)MB_IN32(bay, KEYLARGO_MBCR);
+		udelay(5);
+		bay->cached_gpio = new_gpio;
+	}
+	return (MB_IN32(bay, KEYLARGO_MBCR) >> 4) & 7;
+}
+
+/*
+ * Functions for powering up/down the bay, puts the bay device
+ * into reset state as well
+ */
+
+static void
+ohare_mb_power(struct media_bay_info* bay, int on_off)
+{
+	if (on_off) {
+		/* Power up device, assert it's reset line */
+		MB_BIC(bay, OHARE_FCR, OH_BAY_RESET_N);
+		MB_BIC(bay, OHARE_FCR, OH_BAY_POWER_N);
+	} else {
+		/* Disable all devices */
+		MB_BIC(bay, OHARE_FCR, OH_BAY_DEV_MASK);
+		MB_BIC(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
+		/* Cut power from bay, release reset line */
+		MB_BIS(bay, OHARE_FCR, OH_BAY_POWER_N);
+		MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
+		MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
+	}
+	MB_BIC(bay, OHARE_MBCR, 0x00000F00);
+}
+
+static void
+heathrow_mb_power(struct media_bay_info* bay, int on_off)
+{
+	if (on_off) {
+		/* Power up device, assert it's reset line */
+		MB_BIC(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
+		MB_BIC(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
+	} else {
+		/* Disable all devices */
+		MB_BIC(bay, HEATHROW_FCR, HRW_BAY_DEV_MASK);
+		MB_BIC(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
+		/* Cut power from bay, release reset line */
+		MB_BIS(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
+		MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
+		MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
+	}
+	MB_BIC(bay, HEATHROW_MBCR, 0x00000F00);
+}
+
+static void
+keylargo_mb_power(struct media_bay_info* bay, int on_off)
+{
+	if (on_off) {
+		/* Power up device, assert it's reset line */
+            	MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
+            	MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
+	} else {
+		/* Disable all devices */
+		MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_MASK);
+		MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
+		/* Cut power from bay, release reset line */
+		MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
+		MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
+		MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
+	}
+	MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
+}
+
+/*
+ * Functions for configuring the media bay for a given type of device,
+ * enable the related busses
+ */
+
+static int
+ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
+{
+	switch(device_id) {
+		case MB_FD:
+		case MB_FD1:
+			MB_BIS(bay, OHARE_FCR, OH_BAY_FLOPPY_ENABLE);
+			MB_BIS(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
+			return 0;
+		case MB_CD:
+			MB_BIC(bay, OHARE_FCR, OH_IDE1_RESET_N);
+			MB_BIS(bay, OHARE_FCR, OH_BAY_IDE_ENABLE);
+			return 0;
+		case MB_PCI:
+			MB_BIS(bay, OHARE_FCR, OH_BAY_PCI_ENABLE);
+			return 0;
+	}
+	return -ENODEV;
+}
+
+static int
+heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
+{
+	switch(device_id) {
+		case MB_FD:
+		case MB_FD1:
+			MB_BIS(bay, HEATHROW_FCR, HRW_BAY_FLOPPY_ENABLE);
+			MB_BIS(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
+			return 0;
+		case MB_CD:
+			MB_BIC(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
+			MB_BIS(bay, HEATHROW_FCR, HRW_BAY_IDE_ENABLE);
+			return 0;
+		case MB_PCI:
+			MB_BIS(bay, HEATHROW_FCR, HRW_BAY_PCI_ENABLE);
+			return 0;
+	}
+	return -ENODEV;
+}
+
+static int
+keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
+{
+	switch(device_id) {
+		case MB_CD:
+			MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_IDE_ENABLE);
+			MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
+			MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
+			return 0;
+		case MB_PCI:
+			MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_PCI_ENABLE);
+			return 0;
+		case MB_SOUND:
+			MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_SOUND_ENABLE);
+			return 0;
+	}
+	return -ENODEV;
+}
+
+/*
+ * Functions for tweaking resets
+ */
+
+static void
+ohare_mb_un_reset(struct media_bay_info* bay)
+{
+	MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
+}
+
+static void keylargo_mb_init(struct media_bay_info *bay)
+{
+	MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
+}
+
+static void heathrow_mb_un_reset(struct media_bay_info* bay)
+{
+	MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
+}
+
+static void keylargo_mb_un_reset(struct media_bay_info* bay)
+{
+	MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
+}
+
+static void ohare_mb_un_reset_ide(struct media_bay_info* bay)
+{
+	MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
+}
+
+static void heathrow_mb_un_reset_ide(struct media_bay_info* bay)
+{
+	MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
+}
+
+static void keylargo_mb_un_reset_ide(struct media_bay_info* bay)
+{
+	MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
+}
+
+static inline void set_mb_power(struct media_bay_info* bay, int onoff)
+{
+	/* Power up up and assert the bay reset line */
+	if (onoff) {
+		bay->ops->power(bay, 1);
+		bay->state = mb_powering_up;
+		pr_debug("mediabay%d: powering up\n", bay->index);
+	} else { 
+		/* Make sure everything is powered down & disabled */
+		bay->ops->power(bay, 0);
+		bay->state = mb_powering_down;
+		pr_debug("mediabay%d: powering down\n", bay->index);
+	}
+	bay->timer = msecs_to_jiffies(MB_POWER_DELAY);
+}
+
+static void poll_media_bay(struct media_bay_info* bay)
+{
+	int id = bay->ops->content(bay);
+
+	static char *mb_content_types[] = {
+		"a floppy drive",
+		"a floppy drive",
+		"an unsupported audio device",
+		"an ATA device",
+		"an unsupported PCI device",
+		"an unknown device",
+	};
+
+	if (id != bay->last_value) {
+		bay->last_value = id;
+		bay->value_count = 0;
+		return;
+	}
+	if (id == bay->content_id)
+		return;
+
+	bay->value_count += msecs_to_jiffies(MB_POLL_DELAY);
+	if (bay->value_count >= msecs_to_jiffies(MB_STABLE_DELAY)) {
+		/* If the device type changes without going thru
+		 * "MB_NO", we force a pass by "MB_NO" to make sure
+		 * things are properly reset
+		 */
+		if ((id != MB_NO) && (bay->content_id != MB_NO)) {
+			id = MB_NO;
+			pr_debug("mediabay%d: forcing MB_NO\n", bay->index);
+		}
+		pr_debug("mediabay%d: switching to %d\n", bay->index, id);
+		set_mb_power(bay, id != MB_NO);
+		bay->content_id = id;
+		if (id >= MB_NO || id < 0)
+			printk(KERN_INFO "mediabay%d: Bay is now empty\n", bay->index);
+		else
+			printk(KERN_INFO "mediabay%d: Bay contains %s\n",
+			       bay->index, mb_content_types[id]);
+	}
+}
+
+int check_media_bay(struct macio_dev *baydev)
+{
+	struct media_bay_info* bay;
+	int id;
+
+	if (baydev == NULL)
+		return MB_NO;
+
+	/* This returns an instant snapshot, not locking, sine
+	 * we may be called with the bay lock held. The resulting
+	 * fuzzyness of the result if called at the wrong time is
+	 * not actually a huge deal
+	 */
+	bay = macio_get_drvdata(baydev);
+	if (bay == NULL)
+		return MB_NO;
+	id = bay->content_id;
+	if (bay->state != mb_up)
+		return MB_NO;
+	if (id == MB_FD1)
+		return MB_FD;
+	return id;
+}
+EXPORT_SYMBOL_GPL(check_media_bay);
+
+void lock_media_bay(struct macio_dev *baydev)
+{
+	struct media_bay_info* bay;
+
+	if (baydev == NULL)
+		return;
+	bay = macio_get_drvdata(baydev);
+	if (bay == NULL)
+		return;
+	mutex_lock(&bay->lock);
+	bay->user_lock = 1;
+}
+EXPORT_SYMBOL_GPL(lock_media_bay);
+
+void unlock_media_bay(struct macio_dev *baydev)
+{
+	struct media_bay_info* bay;
+
+	if (baydev == NULL)
+		return;
+	bay = macio_get_drvdata(baydev);
+	if (bay == NULL)
+		return;
+	if (bay->user_lock) {
+		bay->user_lock = 0;
+		mutex_unlock(&bay->lock);
+	}
+}
+EXPORT_SYMBOL_GPL(unlock_media_bay);
+
+static int mb_broadcast_hotplug(struct device *dev, void *data)
+{
+	struct media_bay_info* bay = data;
+	struct macio_dev *mdev;
+	struct macio_driver *drv;
+	int state;
+
+	if (dev->bus != &macio_bus_type)
+		return 0;
+
+	state = bay->state == mb_up ? bay->content_id : MB_NO;
+	if (state == MB_FD1)
+		state = MB_FD;
+	mdev = to_macio_device(dev);
+	drv = to_macio_driver(dev->driver);
+	if (dev->driver && drv->mediabay_event)
+		drv->mediabay_event(mdev, state);
+	return 0;
+}
+
+static void media_bay_step(int i)
+{
+	struct media_bay_info* bay = &media_bays[i];
+
+	/* We don't poll when powering down */
+	if (bay->state != mb_powering_down)
+	    poll_media_bay(bay);
+
+	/* If timer expired run state machine */
+	if (bay->timer != 0) {
+		bay->timer -= msecs_to_jiffies(MB_POLL_DELAY);
+		if (bay->timer > 0)
+			return;
+		bay->timer = 0;
+	}
+
+	switch(bay->state) {
+	case mb_powering_up:
+	    	if (bay->ops->setup_bus(bay, bay->last_value) < 0) {
+			pr_debug("mediabay%d: device not supported (kind:%d)\n",
+				 i, bay->content_id);
+	    		set_mb_power(bay, 0);
+	    		break;
+	    	}
+	    	bay->timer = msecs_to_jiffies(MB_RESET_DELAY);
+	    	bay->state = mb_enabling_bay;
+		pr_debug("mediabay%d: enabling (kind:%d)\n", i, bay->content_id);
+		break;
+	case mb_enabling_bay:
+		bay->ops->un_reset(bay);
+	    	bay->timer = msecs_to_jiffies(MB_SETUP_DELAY);
+	    	bay->state = mb_resetting;
+		pr_debug("mediabay%d: releasing bay reset (kind:%d)\n",
+			 i, bay->content_id);
+	    	break;
+	case mb_resetting:
+		if (bay->content_id != MB_CD) {
+			pr_debug("mediabay%d: bay is up (kind:%d)\n", i,
+				 bay->content_id);
+			bay->state = mb_up;
+			device_for_each_child(&bay->mdev->ofdev.dev,
+					      bay, mb_broadcast_hotplug);
+			break;
+	    	}
+		pr_debug("mediabay%d: releasing ATA reset (kind:%d)\n",
+			 i, bay->content_id);
+		bay->ops->un_reset_ide(bay);
+	    	bay->timer = msecs_to_jiffies(MB_IDE_WAIT);
+	    	bay->state = mb_ide_resetting;
+	    	break;
+
+	case mb_ide_resetting:
+		pr_debug("mediabay%d: bay is up (kind:%d)\n", i, bay->content_id);
+		bay->state = mb_up;
+		device_for_each_child(&bay->mdev->ofdev.dev,
+				      bay, mb_broadcast_hotplug);
+	    	break;
+
+	case mb_powering_down:
+	    	bay->state = mb_empty;
+		device_for_each_child(&bay->mdev->ofdev.dev,
+				      bay, mb_broadcast_hotplug);
+		pr_debug("mediabay%d: end of power down\n", i);
+	    	break;
+	}
+}
+
+/*
+ * This procedure runs as a kernel thread to poll the media bay
+ * once each tick and register and unregister the IDE interface
+ * with the IDE driver.  It needs to be a thread because
+ * ide_register can't be called from interrupt context.
+ */
+static int media_bay_task(void *x)
+{
+	int	i;
+
+	while (!kthread_should_stop()) {
+		for (i = 0; i < media_bay_count; ++i) {
+			mutex_lock(&media_bays[i].lock);
+			if (!media_bays[i].sleeping)
+				media_bay_step(i);
+			mutex_unlock(&media_bays[i].lock);
+		}
+
+		msleep_interruptible(MB_POLL_DELAY);
+	}
+	return 0;
+}
+
+static int media_bay_attach(struct macio_dev *mdev,
+			    const struct of_device_id *match)
+{
+	struct media_bay_info* bay;
+	u32 __iomem *regbase;
+	struct device_node *ofnode;
+	unsigned long base;
+	int i;
+
+	ofnode = mdev->ofdev.dev.of_node;
+
+	if (macio_resource_count(mdev) < 1)
+		return -ENODEV;
+	if (macio_request_resources(mdev, "media-bay"))
+		return -EBUSY;
+	/* Media bay registers are located at the beginning of the
+         * mac-io chip, for now, we trick and align down the first
+	 * resource passed in
+         */
+	base = macio_resource_start(mdev, 0) & 0xffff0000u;
+	regbase = (u32 __iomem *)ioremap(base, 0x100);
+	if (regbase == NULL) {
+		macio_release_resources(mdev);
+		return -ENOMEM;
+	}
+	
+	i = media_bay_count++;
+	bay = &media_bays[i];
+	bay->mdev = mdev;
+	bay->base = regbase;
+	bay->index = i;
+	bay->ops = match->data;
+	bay->sleeping = 0;
+	mutex_init(&bay->lock);
+
+	/* Init HW probing */
+	if (bay->ops->init)
+		bay->ops->init(bay);
+
+	printk(KERN_INFO "mediabay%d: Registered %s media-bay\n", i, bay->ops->name);
+
+	/* Force an immediate detect */
+	set_mb_power(bay, 0);
+	msleep(MB_POWER_DELAY);
+	bay->content_id = MB_NO;
+	bay->last_value = bay->ops->content(bay);
+	bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY);
+	bay->state = mb_empty;
+
+	/* Mark us ready by filling our mdev data */
+	macio_set_drvdata(mdev, bay);
+
+	/* Startup kernel thread */
+	if (i == 0)
+		kthread_run(media_bay_task, NULL, "media-bay");
+
+	return 0;
+
+}
+
+static int media_bay_suspend(struct macio_dev *mdev, pm_message_t state)
+{
+	struct media_bay_info	*bay = macio_get_drvdata(mdev);
+
+	if (state.event != mdev->ofdev.dev.power.power_state.event
+	    && (state.event & PM_EVENT_SLEEP)) {
+		mutex_lock(&bay->lock);
+		bay->sleeping = 1;
+		set_mb_power(bay, 0);
+		mutex_unlock(&bay->lock);
+		msleep(MB_POLL_DELAY);
+		mdev->ofdev.dev.power.power_state = state;
+	}
+	return 0;
+}
+
+static int media_bay_resume(struct macio_dev *mdev)
+{
+	struct media_bay_info	*bay = macio_get_drvdata(mdev);
+
+	if (mdev->ofdev.dev.power.power_state.event != PM_EVENT_ON) {
+		mdev->ofdev.dev.power.power_state = PMSG_ON;
+
+	       	/* We re-enable the bay using it's previous content
+	       	   only if it did not change. Note those bozo timings,
+	       	   they seem to help the 3400 get it right.
+	       	 */
+	       	/* Force MB power to 0 */
+		mutex_lock(&bay->lock);
+	       	set_mb_power(bay, 0);
+		msleep(MB_POWER_DELAY);
+	       	if (bay->ops->content(bay) != bay->content_id) {
+			printk("mediabay%d: Content changed during sleep...\n", bay->index);
+			mutex_unlock(&bay->lock);
+	       		return 0;
+		}
+	       	set_mb_power(bay, 1);
+	       	bay->last_value = bay->content_id;
+	       	bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY);
+	       	bay->timer = msecs_to_jiffies(MB_POWER_DELAY);
+	       	do {
+			msleep(MB_POLL_DELAY);
+	       		media_bay_step(bay->index);
+	       	} while((bay->state != mb_empty) &&
+	       		(bay->state != mb_up));
+		bay->sleeping = 0;
+		mutex_unlock(&bay->lock);
+	}
+	return 0;
+}
+
+
+/* Definitions of "ops" structures.
+ */
+static const struct mb_ops ohare_mb_ops = {
+	.name		= "Ohare",
+	.content	= ohare_mb_content,
+	.power		= ohare_mb_power,
+	.setup_bus	= ohare_mb_setup_bus,
+	.un_reset	= ohare_mb_un_reset,
+	.un_reset_ide	= ohare_mb_un_reset_ide,
+};
+
+static const struct mb_ops heathrow_mb_ops = {
+	.name		= "Heathrow",
+	.content	= heathrow_mb_content,
+	.power		= heathrow_mb_power,
+	.setup_bus	= heathrow_mb_setup_bus,
+	.un_reset	= heathrow_mb_un_reset,
+	.un_reset_ide	= heathrow_mb_un_reset_ide,
+};
+
+static const struct mb_ops keylargo_mb_ops = {
+	.name		= "KeyLargo",
+	.init		= keylargo_mb_init,
+	.content	= keylargo_mb_content,
+	.power		= keylargo_mb_power,
+	.setup_bus	= keylargo_mb_setup_bus,
+	.un_reset	= keylargo_mb_un_reset,
+	.un_reset_ide	= keylargo_mb_un_reset_ide,
+};
+
+/*
+ * It seems that the bit for the media-bay interrupt in the IRQ_LEVEL
+ * register is always set when there is something in the media bay.
+ * This causes problems for the interrupt code if we attach an interrupt
+ * handler to the media-bay interrupt, because it tends to go into
+ * an infinite loop calling the media bay interrupt handler.
+ * Therefore we do it all by polling the media bay once each tick.
+ */
+
+static struct of_device_id media_bay_match[] =
+{
+	{
+	.name		= "media-bay",
+	.compatible	= "keylargo-media-bay",
+	.data		= &keylargo_mb_ops,
+	},
+	{
+	.name		= "media-bay",
+	.compatible	= "heathrow-media-bay",
+	.data		= &heathrow_mb_ops,
+	},
+	{
+	.name		= "media-bay",
+	.compatible	= "ohare-media-bay",
+	.data		= &ohare_mb_ops,
+	},
+	{},
+};
+
+static struct macio_driver media_bay_driver =
+{
+	.driver = {
+		.name		= "media-bay",
+		.of_match_table	= media_bay_match,
+	},
+	.probe		= media_bay_attach,
+	.suspend	= media_bay_suspend,
+	.resume		= media_bay_resume
+};
+
+static int __init media_bay_init(void)
+{
+	int i;
+
+	for (i=0; i<MAX_BAYS; i++) {
+		memset((char *)&media_bays[i], 0, sizeof(struct media_bay_info));
+		media_bays[i].content_id	= -1;
+	}
+	if (!machine_is(powermac))
+		return 0;
+
+	macio_register_driver(&media_bay_driver);	
+
+	return 0;
+}
+
+device_initcall(media_bay_init);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/rack-meter.c b/src/kernel/linux/v4.14/drivers/macintosh/rack-meter.c
new file mode 100644
index 0000000..eb65b6e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/rack-meter.c
@@ -0,0 +1,620 @@
+/*
+ * RackMac vu-meter driver
+ *
+ * (c) Copyright 2006 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * Support the CPU-meter LEDs of the Xserve G5
+ *
+ * TODO: Implement PWM to do variable intensity and provide userland
+ * interface for fun. Also, the CPU-meter could be made nicer by being
+ * a bit less "immediate" but giving instead a more average load over
+ * time. Patches welcome :-)
+ *
+ */
+#undef DEBUG
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel_stat.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/pmac_feature.h>
+#include <asm/dbdma.h>
+#include <asm/macio.h>
+#include <asm/keylargo.h>
+
+/* Number of samples in a sample buffer */
+#define SAMPLE_COUNT		256
+
+/* CPU meter sampling rate in ms */
+#define CPU_SAMPLING_RATE	250
+
+struct rackmeter_dma {
+	struct dbdma_cmd	cmd[4]			____cacheline_aligned;
+	u32			mark			____cacheline_aligned;
+	u32			buf1[SAMPLE_COUNT]	____cacheline_aligned;
+	u32			buf2[SAMPLE_COUNT]	____cacheline_aligned;
+} ____cacheline_aligned;
+
+struct rackmeter_cpu {
+	struct delayed_work	sniffer;
+	struct rackmeter	*rm;
+	u64			prev_wall;
+	u64			prev_idle;
+	int			zero;
+} ____cacheline_aligned;
+
+struct rackmeter {
+	struct macio_dev		*mdev;
+	unsigned int			irq;
+	struct device_node		*i2s;
+	u8				*ubuf;
+	struct dbdma_regs __iomem	*dma_regs;
+	void __iomem			*i2s_regs;
+	dma_addr_t			dma_buf_p;
+	struct rackmeter_dma		*dma_buf_v;
+	int				stale_irq;
+	struct rackmeter_cpu		cpu[2];
+	int				paused;
+	struct mutex			sem;
+};
+
+/* To be set as a tunable */
+static int rackmeter_ignore_nice;
+
+/* This GPIO is whacked by the OS X driver when initializing */
+#define RACKMETER_MAGIC_GPIO	0x78
+
+/* This is copied from cpufreq_ondemand, maybe we should put it in
+ * a common header somewhere
+ */
+static inline u64 get_cpu_idle_time(unsigned int cpu)
+{
+	u64 retval;
+
+	retval = kcpustat_cpu(cpu).cpustat[CPUTIME_IDLE] +
+		 kcpustat_cpu(cpu).cpustat[CPUTIME_IOWAIT];
+
+	if (rackmeter_ignore_nice)
+		retval += kcpustat_cpu(cpu).cpustat[CPUTIME_NICE];
+
+	return retval;
+}
+
+static void rackmeter_setup_i2s(struct rackmeter *rm)
+{
+	struct macio_chip *macio = rm->mdev->bus->chip;
+
+	/* First whack magic GPIO */
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, RACKMETER_MAGIC_GPIO, 5);
+
+
+	/* Call feature code to enable the sound channel and the proper
+	 * clock sources
+	 */
+	pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, rm->i2s, 0, 1);
+
+	/* Power i2s and stop i2s clock. We whack MacIO FCRs directly for now.
+	 * This is a bit racy, thus we should add new platform functions to
+	 * handle that. snd-aoa needs that too
+	 */
+	MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
+	MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
+	(void)MACIO_IN32(KEYLARGO_FCR1);
+	udelay(10);
+
+	/* Then setup i2s. For now, we use the same magic value that
+	 * the OS X driver seems to use. We might want to play around
+	 * with the clock divisors later
+	 */
+	out_le32(rm->i2s_regs + 0x10, 0x01fa0000);
+	(void)in_le32(rm->i2s_regs + 0x10);
+	udelay(10);
+
+	/* Fully restart i2s*/
+	MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE |
+		  KL1_I2S0_CLK_ENABLE_BIT);
+	(void)MACIO_IN32(KEYLARGO_FCR1);
+	udelay(10);
+}
+
+static void rackmeter_set_default_pattern(struct rackmeter *rm)
+{
+	int i;
+
+	for (i = 0; i < 16; i++) {
+		if (i < 8)
+			rm->ubuf[i] = (i & 1) * 255;
+		else
+			rm->ubuf[i] = ((~i) & 1) * 255;
+	}
+}
+
+static void rackmeter_do_pause(struct rackmeter *rm, int pause)
+{
+	struct rackmeter_dma *rdma = rm->dma_buf_v;
+
+	pr_debug("rackmeter: %s\n", pause ? "paused" : "started");
+
+	rm->paused = pause;
+	if (pause) {
+		DBDMA_DO_STOP(rm->dma_regs);
+		return;
+	}
+	memset(rdma->buf1, 0, sizeof(rdma->buf1));
+	memset(rdma->buf2, 0, sizeof(rdma->buf2));
+
+	rm->dma_buf_v->mark = 0;
+
+	mb();
+	out_le32(&rm->dma_regs->cmdptr_hi, 0);
+	out_le32(&rm->dma_regs->cmdptr, rm->dma_buf_p);
+	out_le32(&rm->dma_regs->control, (RUN << 16) | RUN);
+}
+
+static void rackmeter_setup_dbdma(struct rackmeter *rm)
+{
+	struct rackmeter_dma *db = rm->dma_buf_v;
+	struct dbdma_cmd *cmd = db->cmd;
+
+	/* Make sure dbdma is reset */
+	DBDMA_DO_RESET(rm->dma_regs);
+
+	pr_debug("rackmeter: mark offset=0x%zx\n",
+		 offsetof(struct rackmeter_dma, mark));
+	pr_debug("rackmeter: buf1 offset=0x%zx\n",
+		 offsetof(struct rackmeter_dma, buf1));
+	pr_debug("rackmeter: buf2 offset=0x%zx\n",
+		 offsetof(struct rackmeter_dma, buf2));
+
+	/* Prepare 4 dbdma commands for the 2 buffers */
+	memset(cmd, 0, 4 * sizeof(struct dbdma_cmd));
+	cmd->req_count = cpu_to_le16(4);
+	cmd->command = cpu_to_le16(STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
+	cmd->phy_addr = cpu_to_le32(rm->dma_buf_p +
+		offsetof(struct rackmeter_dma, mark));
+	cmd->cmd_dep = cpu_to_le32(0x02000000);
+	cmd++;
+
+	cmd->req_count = cpu_to_le16(SAMPLE_COUNT * 4);
+	cmd->command = cpu_to_le16(OUTPUT_MORE);
+	cmd->phy_addr = cpu_to_le32(rm->dma_buf_p +
+		offsetof(struct rackmeter_dma, buf1));
+	cmd++;
+
+	cmd->req_count = cpu_to_le16(4);
+	cmd->command = cpu_to_le16(STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
+	cmd->phy_addr = cpu_to_le32(rm->dma_buf_p +
+		offsetof(struct rackmeter_dma, mark));
+	cmd->cmd_dep = cpu_to_le32(0x01000000);
+	cmd++;
+
+	cmd->req_count = cpu_to_le16(SAMPLE_COUNT * 4);
+	cmd->command = cpu_to_le16(OUTPUT_MORE | BR_ALWAYS);
+	cmd->phy_addr = cpu_to_le32(rm->dma_buf_p +
+		offsetof(struct rackmeter_dma, buf2));
+	cmd->cmd_dep = cpu_to_le32(rm->dma_buf_p);
+
+	rackmeter_do_pause(rm, 0);
+}
+
+static void rackmeter_do_timer(struct work_struct *work)
+{
+	struct rackmeter_cpu *rcpu =
+		container_of(work, struct rackmeter_cpu, sniffer.work);
+	struct rackmeter *rm = rcpu->rm;
+	unsigned int cpu = smp_processor_id();
+	u64 cur_nsecs, total_idle_nsecs;
+	u64 total_nsecs, idle_nsecs;
+	int i, offset, load, cumm, pause;
+
+	cur_nsecs = jiffies64_to_nsecs(get_jiffies_64());
+	total_nsecs = cur_nsecs - rcpu->prev_wall;
+	rcpu->prev_wall = cur_nsecs;
+
+	total_idle_nsecs = get_cpu_idle_time(cpu);
+	idle_nsecs = total_idle_nsecs - rcpu->prev_idle;
+	idle_nsecs = min(idle_nsecs, total_nsecs);
+	rcpu->prev_idle = total_idle_nsecs;
+
+	/* We do a very dumb calculation to update the LEDs for now,
+	 * we'll do better once we have actual PWM implemented
+	 */
+	load = div64_u64(9 * (total_nsecs - idle_nsecs), total_nsecs);
+
+	offset = cpu << 3;
+	cumm = 0;
+	for (i = 0; i < 8; i++) {
+		u8 ub = (load > i) ? 0xff : 0;
+		rm->ubuf[i + offset] = ub;
+		cumm |= ub;
+	}
+	rcpu->zero = (cumm == 0);
+
+	/* Now check if LEDs are all 0, we can stop DMA */
+	pause = (rm->cpu[0].zero && rm->cpu[1].zero);
+	if (pause != rm->paused) {
+		mutex_lock(&rm->sem);
+		pause = (rm->cpu[0].zero && rm->cpu[1].zero);
+		rackmeter_do_pause(rm, pause);
+		mutex_unlock(&rm->sem);
+	}
+	schedule_delayed_work_on(cpu, &rcpu->sniffer,
+				 msecs_to_jiffies(CPU_SAMPLING_RATE));
+}
+
+static void rackmeter_init_cpu_sniffer(struct rackmeter *rm)
+{
+	unsigned int cpu;
+
+	/* This driver works only with 1 or 2 CPUs numbered 0 and 1,
+	 * but that's really all we have on Apple Xserve. It doesn't
+	 * play very nice with CPU hotplug neither but we don't do that
+	 * on those machines yet
+	 */
+
+	rm->cpu[0].rm = rm;
+	INIT_DELAYED_WORK(&rm->cpu[0].sniffer, rackmeter_do_timer);
+	rm->cpu[1].rm = rm;
+	INIT_DELAYED_WORK(&rm->cpu[1].sniffer, rackmeter_do_timer);
+
+	for_each_online_cpu(cpu) {
+		struct rackmeter_cpu *rcpu;
+
+		if (cpu > 1)
+			continue;
+		rcpu = &rm->cpu[cpu];
+		rcpu->prev_idle = get_cpu_idle_time(cpu);
+		rcpu->prev_wall = jiffies64_to_nsecs(get_jiffies_64());
+		schedule_delayed_work_on(cpu, &rm->cpu[cpu].sniffer,
+					 msecs_to_jiffies(CPU_SAMPLING_RATE));
+	}
+}
+
+static void rackmeter_stop_cpu_sniffer(struct rackmeter *rm)
+{
+	cancel_delayed_work_sync(&rm->cpu[0].sniffer);
+	cancel_delayed_work_sync(&rm->cpu[1].sniffer);
+}
+
+static int rackmeter_setup(struct rackmeter *rm)
+{
+	pr_debug("rackmeter: setting up i2s..\n");
+	rackmeter_setup_i2s(rm);
+
+	pr_debug("rackmeter: setting up default pattern..\n");
+	rackmeter_set_default_pattern(rm);
+
+	pr_debug("rackmeter: setting up dbdma..\n");
+	rackmeter_setup_dbdma(rm);
+
+	pr_debug("rackmeter: start CPU measurements..\n");
+	rackmeter_init_cpu_sniffer(rm);
+
+	printk(KERN_INFO "RackMeter initialized\n");
+
+	return 0;
+}
+
+/*  XXX FIXME: No PWM yet, this is 0/1 */
+static u32 rackmeter_calc_sample(struct rackmeter *rm, unsigned int index)
+{
+	int led;
+	u32 sample = 0;
+
+	for (led = 0; led < 16; led++) {
+		sample >>= 1;
+		sample |= ((rm->ubuf[led] >= 0x80) << 15);
+	}
+	return (sample << 17) | (sample >> 15);
+}
+
+static irqreturn_t rackmeter_irq(int irq, void *arg)
+{
+	struct rackmeter *rm = arg;
+	struct rackmeter_dma *db = rm->dma_buf_v;
+	unsigned int mark, i;
+	u32 *buf;
+
+	/* Flush PCI buffers with an MMIO read. Maybe we could actually
+	 * check the status one day ... in case things go wrong, though
+	 * this never happened to me
+	 */
+	(void)in_le32(&rm->dma_regs->status);
+
+	/* Make sure the CPU gets us in order */
+	rmb();
+
+	/* Read mark */
+	mark = db->mark;
+	if (mark != 1 && mark != 2) {
+		printk(KERN_WARNING "rackmeter: Incorrect DMA mark 0x%08x\n",
+		       mark);
+		/* We allow for 3 errors like that (stale DBDMA irqs) */
+		if (++rm->stale_irq > 3) {
+			printk(KERN_ERR "rackmeter: Too many errors,"
+			       " stopping DMA\n");
+			DBDMA_DO_RESET(rm->dma_regs);
+		}
+		return IRQ_HANDLED;
+	}
+
+	/* Next buffer we need to fill is mark value */
+	buf = mark == 1 ? db->buf1 : db->buf2;
+
+	/* Fill it now. This routine converts the 8 bits depth sample array
+	 * into the PWM bitmap for each LED.
+	 */
+	for (i = 0; i < SAMPLE_COUNT; i++)
+		buf[i] = rackmeter_calc_sample(rm, i);
+
+
+	return IRQ_HANDLED;
+}
+
+static int rackmeter_probe(struct macio_dev* mdev,
+			   const struct of_device_id *match)
+{
+	struct device_node *i2s = NULL, *np = NULL;
+	struct rackmeter *rm = NULL;
+	struct resource ri2s, rdma;
+	int rc = -ENODEV;
+
+	pr_debug("rackmeter_probe()\n");
+
+	/* Get i2s-a node */
+	while ((i2s = of_get_next_child(mdev->ofdev.dev.of_node, i2s)) != NULL)
+	       if (strcmp(i2s->name, "i2s-a") == 0)
+		       break;
+	if (i2s == NULL) {
+		pr_debug("  i2s-a child not found\n");
+		goto bail;
+	}
+	/* Get lightshow or virtual sound */
+	while ((np = of_get_next_child(i2s, np)) != NULL) {
+	       if (strcmp(np->name, "lightshow") == 0)
+		       break;
+	       if ((strcmp(np->name, "sound") == 0) &&
+		   of_get_property(np, "virtual", NULL) != NULL)
+		       break;
+	}
+	if (np == NULL) {
+		pr_debug("  lightshow or sound+virtual child not found\n");
+		goto bail;
+	}
+
+	/* Create and initialize our instance data */
+	rm = kzalloc(sizeof(struct rackmeter), GFP_KERNEL);
+	if (rm == NULL) {
+		printk(KERN_ERR "rackmeter: failed to allocate memory !\n");
+		rc = -ENOMEM;
+		goto bail_release;
+	}
+	rm->mdev = mdev;
+	rm->i2s = i2s;
+	mutex_init(&rm->sem);
+	dev_set_drvdata(&mdev->ofdev.dev, rm);
+	/* Check resources availability. We need at least resource 0 and 1 */
+#if 0 /* Use that when i2s-a is finally an mdev per-se */
+	if (macio_resource_count(mdev) < 2 || macio_irq_count(mdev) < 2) {
+		printk(KERN_ERR
+		       "rackmeter: found match but lacks resources: %pOF"
+		       " (%d resources, %d interrupts)\n",
+		       mdev->ofdev.dev.of_node);
+		rc = -ENXIO;
+		goto bail_free;
+	}
+	if (macio_request_resources(mdev, "rackmeter")) {
+		printk(KERN_ERR
+		       "rackmeter: failed to request resources: %pOF\n",
+		       mdev->ofdev.dev.of_node);
+		rc = -EBUSY;
+		goto bail_free;
+	}
+	rm->irq = macio_irq(mdev, 1);
+#else
+	rm->irq = irq_of_parse_and_map(i2s, 1);
+	if (!rm->irq ||
+	    of_address_to_resource(i2s, 0, &ri2s) ||
+	    of_address_to_resource(i2s, 1, &rdma)) {
+		printk(KERN_ERR
+		       "rackmeter: found match but lacks resources: %pOF",
+		       mdev->ofdev.dev.of_node);
+		rc = -ENXIO;
+		goto bail_free;
+	}
+#endif
+
+	pr_debug("  i2s @0x%08x\n", (unsigned int)ri2s.start);
+	pr_debug("  dma @0x%08x\n", (unsigned int)rdma.start);
+	pr_debug("  irq %d\n", rm->irq);
+
+	rm->ubuf = (u8 *)__get_free_page(GFP_KERNEL);
+	if (rm->ubuf == NULL) {
+		printk(KERN_ERR
+		       "rackmeter: failed to allocate samples page !\n");
+		rc = -ENOMEM;
+		goto bail_release;
+	}
+
+	rm->dma_buf_v = dma_alloc_coherent(&macio_get_pci_dev(mdev)->dev,
+					   sizeof(struct rackmeter_dma),
+					   &rm->dma_buf_p, GFP_KERNEL);
+	if (rm->dma_buf_v == NULL) {
+		printk(KERN_ERR
+		       "rackmeter: failed to allocate dma buffer !\n");
+		rc = -ENOMEM;
+		goto bail_free_samples;
+	}
+#if 0
+	rm->i2s_regs = ioremap(macio_resource_start(mdev, 0), 0x1000);
+#else
+	rm->i2s_regs = ioremap(ri2s.start, 0x1000);
+#endif
+	if (rm->i2s_regs == NULL) {
+		printk(KERN_ERR
+		       "rackmeter: failed to map i2s registers !\n");
+		rc = -ENXIO;
+		goto bail_free_dma;
+	}
+#if 0
+	rm->dma_regs = ioremap(macio_resource_start(mdev, 1), 0x100);
+#else
+	rm->dma_regs = ioremap(rdma.start, 0x100);
+#endif
+	if (rm->dma_regs == NULL) {
+		printk(KERN_ERR
+		       "rackmeter: failed to map dma registers !\n");
+		rc = -ENXIO;
+		goto bail_unmap_i2s;
+	}
+
+	rc = rackmeter_setup(rm);
+	if (rc) {
+		printk(KERN_ERR
+		       "rackmeter: failed to initialize !\n");
+		rc = -ENXIO;
+		goto bail_unmap_dma;
+	}
+
+	rc = request_irq(rm->irq, rackmeter_irq, 0, "rackmeter", rm);
+	if (rc != 0) {
+		printk(KERN_ERR
+		       "rackmeter: failed to request interrupt !\n");
+		goto bail_stop_dma;
+	}
+	of_node_put(np);
+	return 0;
+
+ bail_stop_dma:
+	DBDMA_DO_RESET(rm->dma_regs);
+ bail_unmap_dma:
+	iounmap(rm->dma_regs);
+ bail_unmap_i2s:
+	iounmap(rm->i2s_regs);
+ bail_free_dma:
+	dma_free_coherent(&macio_get_pci_dev(mdev)->dev,
+			  sizeof(struct rackmeter_dma),
+			  rm->dma_buf_v, rm->dma_buf_p);
+ bail_free_samples:
+	free_page((unsigned long)rm->ubuf);
+ bail_release:
+#if 0
+	macio_release_resources(mdev);
+#endif
+ bail_free:
+	kfree(rm);
+ bail:
+	of_node_put(i2s);
+	of_node_put(np);
+	dev_set_drvdata(&mdev->ofdev.dev, NULL);
+	return rc;
+}
+
+static int rackmeter_remove(struct macio_dev* mdev)
+{
+	struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev);
+
+	/* Stop CPU sniffer timer & work queues */
+	rackmeter_stop_cpu_sniffer(rm);
+
+	/* Clear reference to private data */
+	dev_set_drvdata(&mdev->ofdev.dev, NULL);
+
+	/* Stop/reset dbdma */
+	DBDMA_DO_RESET(rm->dma_regs);
+
+	/* Release the IRQ */
+	free_irq(rm->irq, rm);
+
+	/* Unmap registers */
+	iounmap(rm->dma_regs);
+	iounmap(rm->i2s_regs);
+
+	/* Free DMA */
+	dma_free_coherent(&macio_get_pci_dev(mdev)->dev,
+			  sizeof(struct rackmeter_dma),
+			  rm->dma_buf_v, rm->dma_buf_p);
+
+	/* Free samples */
+	free_page((unsigned long)rm->ubuf);
+
+#if 0
+	/* Release resources */
+	macio_release_resources(mdev);
+#endif
+
+	/* Get rid of me */
+	kfree(rm);
+
+	return 0;
+}
+
+static int rackmeter_shutdown(struct macio_dev* mdev)
+{
+	struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev);
+
+	if (rm == NULL)
+		return -ENODEV;
+
+	/* Stop CPU sniffer timer & work queues */
+	rackmeter_stop_cpu_sniffer(rm);
+
+	/* Stop/reset dbdma */
+	DBDMA_DO_RESET(rm->dma_regs);
+
+	return 0;
+}
+
+static const struct of_device_id rackmeter_match[] = {
+	{ .name = "i2s" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rackmeter_match);
+
+static struct macio_driver rackmeter_driver = {
+	.driver = {
+		.name = "rackmeter",
+		.owner = THIS_MODULE,
+		.of_match_table = rackmeter_match,
+	},
+	.probe = rackmeter_probe,
+	.remove = rackmeter_remove,
+	.shutdown = rackmeter_shutdown,
+};
+
+
+static int __init rackmeter_init(void)
+{
+	pr_debug("rackmeter_init()\n");
+
+	return macio_register_driver(&rackmeter_driver);
+}
+
+static void __exit rackmeter_exit(void)
+{
+	pr_debug("rackmeter_exit()\n");
+
+	macio_unregister_driver(&rackmeter_driver);
+}
+
+module_init(rackmeter_init);
+module_exit(rackmeter_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("RackMeter: Support vu-meter on XServe front panel");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/smu.c b/src/kernel/linux/v4.14/drivers/macintosh/smu.c
new file mode 100644
index 0000000..ea9bdc8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/smu.c
@@ -0,0 +1,1337 @@
+/*
+ * PowerMac G5 SMU driver
+ *
+ * Copyright 2004 J. Mayer <l_indien@magic.fr>
+ * Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+/*
+ * TODO:
+ *  - maybe add timeout to commands ?
+ *  - blocking version of time functions
+ *  - polling version of i2c commands (including timer that works with
+ *    interrupts off)
+ *  - maybe avoid some data copies with i2c by directly using the smu cmd
+ *    buffer and a lower level internal interface
+ *  - understand SMU -> CPU events and implement reception of them via
+ *    the userland interface
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/dmapool.h>
+#include <linux/bootmem.h>
+#include <linux/vmalloc.h>
+#include <linux/highmem.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/completion.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/memblock.h>
+#include <linux/sched/signal.h>
+
+#include <asm/byteorder.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/pmac_feature.h>
+#include <asm/smu.h>
+#include <asm/sections.h>
+#include <linux/uaccess.h>
+
+#define VERSION "0.7"
+#define AUTHOR  "(c) 2005 Benjamin Herrenschmidt, IBM Corp."
+
+#undef DEBUG_SMU
+
+#ifdef DEBUG_SMU
+#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
+#else
+#define DPRINTK(fmt, args...) do { } while (0)
+#endif
+
+/*
+ * This is the command buffer passed to the SMU hardware
+ */
+#define SMU_MAX_DATA	254
+
+struct smu_cmd_buf {
+	u8 cmd;
+	u8 length;
+	u8 data[SMU_MAX_DATA];
+};
+
+struct smu_device {
+	spinlock_t		lock;
+	struct device_node	*of_node;
+	struct platform_device	*of_dev;
+	int			doorbell;	/* doorbell gpio */
+	u32 __iomem		*db_buf;	/* doorbell buffer */
+	struct device_node	*db_node;
+	unsigned int		db_irq;
+	int			msg;
+	struct device_node	*msg_node;
+	unsigned int		msg_irq;
+	struct smu_cmd_buf	*cmd_buf;	/* command buffer virtual */
+	u32			cmd_buf_abs;	/* command buffer absolute */
+	struct list_head	cmd_list;
+	struct smu_cmd		*cmd_cur;	/* pending command */
+	int			broken_nap;
+	struct list_head	cmd_i2c_list;
+	struct smu_i2c_cmd	*cmd_i2c_cur;	/* pending i2c command */
+	struct timer_list	i2c_timer;
+};
+
+/*
+ * I don't think there will ever be more than one SMU, so
+ * for now, just hard code that
+ */
+static DEFINE_MUTEX(smu_mutex);
+static struct smu_device	*smu;
+static DEFINE_MUTEX(smu_part_access);
+static int smu_irq_inited;
+static unsigned long smu_cmdbuf_abs;
+
+static void smu_i2c_retry(unsigned long data);
+
+/*
+ * SMU driver low level stuff
+ */
+
+static void smu_start_cmd(void)
+{
+	unsigned long faddr, fend;
+	struct smu_cmd *cmd;
+
+	if (list_empty(&smu->cmd_list))
+		return;
+
+	/* Fetch first command in queue */
+	cmd = list_entry(smu->cmd_list.next, struct smu_cmd, link);
+	smu->cmd_cur = cmd;
+	list_del(&cmd->link);
+
+	DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd,
+		cmd->data_len);
+	DPRINTK("SMU: data buffer: %8ph\n", cmd->data_buf);
+
+	/* Fill the SMU command buffer */
+	smu->cmd_buf->cmd = cmd->cmd;
+	smu->cmd_buf->length = cmd->data_len;
+	memcpy(smu->cmd_buf->data, cmd->data_buf, cmd->data_len);
+
+	/* Flush command and data to RAM */
+	faddr = (unsigned long)smu->cmd_buf;
+	fend = faddr + smu->cmd_buf->length + 2;
+	flush_inval_dcache_range(faddr, fend);
+
+
+	/* We also disable NAP mode for the duration of the command
+	 * on U3 based machines.
+	 * This is slightly racy as it can be written back to 1 by a sysctl
+	 * but that never happens in practice. There seem to be an issue with
+	 * U3 based machines such as the iMac G5 where napping for the
+	 * whole duration of the command prevents the SMU from fetching it
+	 * from memory. This might be related to the strange i2c based
+	 * mechanism the SMU uses to access memory.
+	 */
+	if (smu->broken_nap)
+		powersave_nap = 0;
+
+	/* This isn't exactly a DMA mapping here, I suspect
+	 * the SMU is actually communicating with us via i2c to the
+	 * northbridge or the CPU to access RAM.
+	 */
+	writel(smu->cmd_buf_abs, smu->db_buf);
+
+	/* Ring the SMU doorbell */
+	pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4);
+}
+
+
+static irqreturn_t smu_db_intr(int irq, void *arg)
+{
+	unsigned long flags;
+	struct smu_cmd *cmd;
+	void (*done)(struct smu_cmd *cmd, void *misc) = NULL;
+	void *misc = NULL;
+	u8 gpio;
+	int rc = 0;
+
+	/* SMU completed the command, well, we hope, let's make sure
+	 * of it
+	 */
+	spin_lock_irqsave(&smu->lock, flags);
+
+	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell);
+	if ((gpio & 7) != 7) {
+		spin_unlock_irqrestore(&smu->lock, flags);
+		return IRQ_HANDLED;
+	}
+
+	cmd = smu->cmd_cur;
+	smu->cmd_cur = NULL;
+	if (cmd == NULL)
+		goto bail;
+
+	if (rc == 0) {
+		unsigned long faddr;
+		int reply_len;
+		u8 ack;
+
+		/* CPU might have brought back the cache line, so we need
+		 * to flush again before peeking at the SMU response. We
+		 * flush the entire buffer for now as we haven't read the
+		 * reply length (it's only 2 cache lines anyway)
+		 */
+		faddr = (unsigned long)smu->cmd_buf;
+		flush_inval_dcache_range(faddr, faddr + 256);
+
+		/* Now check ack */
+		ack = (~cmd->cmd) & 0xff;
+		if (ack != smu->cmd_buf->cmd) {
+			DPRINTK("SMU: incorrect ack, want %x got %x\n",
+				ack, smu->cmd_buf->cmd);
+			rc = -EIO;
+		}
+		reply_len = rc == 0 ? smu->cmd_buf->length : 0;
+		DPRINTK("SMU: reply len: %d\n", reply_len);
+		if (reply_len > cmd->reply_len) {
+			printk(KERN_WARNING "SMU: reply buffer too small,"
+			       "got %d bytes for a %d bytes buffer\n",
+			       reply_len, cmd->reply_len);
+			reply_len = cmd->reply_len;
+		}
+		cmd->reply_len = reply_len;
+		if (cmd->reply_buf && reply_len)
+			memcpy(cmd->reply_buf, smu->cmd_buf->data, reply_len);
+	}
+
+	/* Now complete the command. Write status last in order as we lost
+	 * ownership of the command structure as soon as it's no longer -1
+	 */
+	done = cmd->done;
+	misc = cmd->misc;
+	mb();
+	cmd->status = rc;
+
+	/* Re-enable NAP mode */
+	if (smu->broken_nap)
+		powersave_nap = 1;
+ bail:
+	/* Start next command if any */
+	smu_start_cmd();
+	spin_unlock_irqrestore(&smu->lock, flags);
+
+	/* Call command completion handler if any */
+	if (done)
+		done(cmd, misc);
+
+	/* It's an edge interrupt, nothing to do */
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t smu_msg_intr(int irq, void *arg)
+{
+	/* I don't quite know what to do with this one, we seem to never
+	 * receive it, so I suspect we have to arm it someway in the SMU
+	 * to start getting events that way.
+	 */
+
+	printk(KERN_INFO "SMU: message interrupt !\n");
+
+	/* It's an edge interrupt, nothing to do */
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * Queued command management.
+ *
+ */
+
+int smu_queue_cmd(struct smu_cmd *cmd)
+{
+	unsigned long flags;
+
+	if (smu == NULL)
+		return -ENODEV;
+	if (cmd->data_len > SMU_MAX_DATA ||
+	    cmd->reply_len > SMU_MAX_DATA)
+		return -EINVAL;
+
+	cmd->status = 1;
+	spin_lock_irqsave(&smu->lock, flags);
+	list_add_tail(&cmd->link, &smu->cmd_list);
+	if (smu->cmd_cur == NULL)
+		smu_start_cmd();
+	spin_unlock_irqrestore(&smu->lock, flags);
+
+	/* Workaround for early calls when irq isn't available */
+	if (!smu_irq_inited || !smu->db_irq)
+		smu_spinwait_cmd(cmd);
+
+	return 0;
+}
+EXPORT_SYMBOL(smu_queue_cmd);
+
+
+int smu_queue_simple(struct smu_simple_cmd *scmd, u8 command,
+		     unsigned int data_len,
+		     void (*done)(struct smu_cmd *cmd, void *misc),
+		     void *misc, ...)
+{
+	struct smu_cmd *cmd = &scmd->cmd;
+	va_list list;
+	int i;
+
+	if (data_len > sizeof(scmd->buffer))
+		return -EINVAL;
+
+	memset(scmd, 0, sizeof(*scmd));
+	cmd->cmd = command;
+	cmd->data_len = data_len;
+	cmd->data_buf = scmd->buffer;
+	cmd->reply_len = sizeof(scmd->buffer);
+	cmd->reply_buf = scmd->buffer;
+	cmd->done = done;
+	cmd->misc = misc;
+
+	va_start(list, misc);
+	for (i = 0; i < data_len; ++i)
+		scmd->buffer[i] = (u8)va_arg(list, int);
+	va_end(list);
+
+	return smu_queue_cmd(cmd);
+}
+EXPORT_SYMBOL(smu_queue_simple);
+
+
+void smu_poll(void)
+{
+	u8 gpio;
+
+	if (smu == NULL)
+		return;
+
+	gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell);
+	if ((gpio & 7) == 7)
+		smu_db_intr(smu->db_irq, smu);
+}
+EXPORT_SYMBOL(smu_poll);
+
+
+void smu_done_complete(struct smu_cmd *cmd, void *misc)
+{
+	struct completion *comp = misc;
+
+	complete(comp);
+}
+EXPORT_SYMBOL(smu_done_complete);
+
+
+void smu_spinwait_cmd(struct smu_cmd *cmd)
+{
+	while(cmd->status == 1)
+		smu_poll();
+}
+EXPORT_SYMBOL(smu_spinwait_cmd);
+
+
+/* RTC low level commands */
+static inline int bcd2hex (int n)
+{
+	return (((n & 0xf0) >> 4) * 10) + (n & 0xf);
+}
+
+
+static inline int hex2bcd (int n)
+{
+	return ((n / 10) << 4) + (n % 10);
+}
+
+
+static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf,
+					struct rtc_time *time)
+{
+	cmd_buf->cmd = 0x8e;
+	cmd_buf->length = 8;
+	cmd_buf->data[0] = 0x80;
+	cmd_buf->data[1] = hex2bcd(time->tm_sec);
+	cmd_buf->data[2] = hex2bcd(time->tm_min);
+	cmd_buf->data[3] = hex2bcd(time->tm_hour);
+	cmd_buf->data[4] = time->tm_wday;
+	cmd_buf->data[5] = hex2bcd(time->tm_mday);
+	cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1;
+	cmd_buf->data[7] = hex2bcd(time->tm_year - 100);
+}
+
+
+int smu_get_rtc_time(struct rtc_time *time, int spinwait)
+{
+	struct smu_simple_cmd cmd;
+	int rc;
+
+	if (smu == NULL)
+		return -ENODEV;
+
+	memset(time, 0, sizeof(struct rtc_time));
+	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 1, NULL, NULL,
+			      SMU_CMD_RTC_GET_DATETIME);
+	if (rc)
+		return rc;
+	smu_spinwait_simple(&cmd);
+
+	time->tm_sec = bcd2hex(cmd.buffer[0]);
+	time->tm_min = bcd2hex(cmd.buffer[1]);
+	time->tm_hour = bcd2hex(cmd.buffer[2]);
+	time->tm_wday = bcd2hex(cmd.buffer[3]);
+	time->tm_mday = bcd2hex(cmd.buffer[4]);
+	time->tm_mon = bcd2hex(cmd.buffer[5]) - 1;
+	time->tm_year = bcd2hex(cmd.buffer[6]) + 100;
+
+	return 0;
+}
+
+
+int smu_set_rtc_time(struct rtc_time *time, int spinwait)
+{
+	struct smu_simple_cmd cmd;
+	int rc;
+
+	if (smu == NULL)
+		return -ENODEV;
+
+	rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 8, NULL, NULL,
+			      SMU_CMD_RTC_SET_DATETIME,
+			      hex2bcd(time->tm_sec),
+			      hex2bcd(time->tm_min),
+			      hex2bcd(time->tm_hour),
+			      time->tm_wday,
+			      hex2bcd(time->tm_mday),
+			      hex2bcd(time->tm_mon) + 1,
+			      hex2bcd(time->tm_year - 100));
+	if (rc)
+		return rc;
+	smu_spinwait_simple(&cmd);
+
+	return 0;
+}
+
+
+void smu_shutdown(void)
+{
+	struct smu_simple_cmd cmd;
+
+	if (smu == NULL)
+		return;
+
+	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 9, NULL, NULL,
+			     'S', 'H', 'U', 'T', 'D', 'O', 'W', 'N', 0))
+		return;
+	smu_spinwait_simple(&cmd);
+	for (;;)
+		;
+}
+
+
+void smu_restart(void)
+{
+	struct smu_simple_cmd cmd;
+
+	if (smu == NULL)
+		return;
+
+	if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, NULL, NULL,
+			     'R', 'E', 'S', 'T', 'A', 'R', 'T', 0))
+		return;
+	smu_spinwait_simple(&cmd);
+	for (;;)
+		;
+}
+
+
+int smu_present(void)
+{
+	return smu != NULL;
+}
+EXPORT_SYMBOL(smu_present);
+
+
+int __init smu_init (void)
+{
+	struct device_node *np;
+	const u32 *data;
+	int ret = 0;
+
+        np = of_find_node_by_type(NULL, "smu");
+        if (np == NULL)
+		return -ENODEV;
+
+	printk(KERN_INFO "SMU: Driver %s %s\n", VERSION, AUTHOR);
+
+	/*
+	 * SMU based G5s need some memory below 2Gb. Thankfully this is
+	 * called at a time where memblock is still available.
+	 */
+	smu_cmdbuf_abs = memblock_alloc_base(4096, 4096, 0x80000000UL);
+	if (smu_cmdbuf_abs == 0) {
+		printk(KERN_ERR "SMU: Command buffer allocation failed !\n");
+		ret = -EINVAL;
+		goto fail_np;
+	}
+
+	smu = alloc_bootmem(sizeof(struct smu_device));
+
+	spin_lock_init(&smu->lock);
+	INIT_LIST_HEAD(&smu->cmd_list);
+	INIT_LIST_HEAD(&smu->cmd_i2c_list);
+	smu->of_node = np;
+	smu->db_irq = 0;
+	smu->msg_irq = 0;
+
+	/* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
+	 * 32 bits value safely
+	 */
+	smu->cmd_buf_abs = (u32)smu_cmdbuf_abs;
+	smu->cmd_buf = __va(smu_cmdbuf_abs);
+
+	smu->db_node = of_find_node_by_name(NULL, "smu-doorbell");
+	if (smu->db_node == NULL) {
+		printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n");
+		ret = -ENXIO;
+		goto fail_bootmem;
+	}
+	data = of_get_property(smu->db_node, "reg", NULL);
+	if (data == NULL) {
+		printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n");
+		ret = -ENXIO;
+		goto fail_db_node;
+	}
+
+	/* Current setup has one doorbell GPIO that does both doorbell
+	 * and ack. GPIOs are at 0x50, best would be to find that out
+	 * in the device-tree though.
+	 */
+	smu->doorbell = *data;
+	if (smu->doorbell < 0x50)
+		smu->doorbell += 0x50;
+
+	/* Now look for the smu-interrupt GPIO */
+	do {
+		smu->msg_node = of_find_node_by_name(NULL, "smu-interrupt");
+		if (smu->msg_node == NULL)
+			break;
+		data = of_get_property(smu->msg_node, "reg", NULL);
+		if (data == NULL) {
+			of_node_put(smu->msg_node);
+			smu->msg_node = NULL;
+			break;
+		}
+		smu->msg = *data;
+		if (smu->msg < 0x50)
+			smu->msg += 0x50;
+	} while(0);
+
+	/* Doorbell buffer is currently hard-coded, I didn't find a proper
+	 * device-tree entry giving the address. Best would probably to use
+	 * an offset for K2 base though, but let's do it that way for now.
+	 */
+	smu->db_buf = ioremap(0x8000860c, 0x1000);
+	if (smu->db_buf == NULL) {
+		printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n");
+		ret = -ENXIO;
+		goto fail_msg_node;
+	}
+
+	/* U3 has an issue with NAP mode when issuing SMU commands */
+	smu->broken_nap = pmac_get_uninorth_variant() < 4;
+	if (smu->broken_nap)
+		printk(KERN_INFO "SMU: using NAP mode workaround\n");
+
+	sys_ctrler = SYS_CTRLER_SMU;
+	return 0;
+
+fail_msg_node:
+	of_node_put(smu->msg_node);
+fail_db_node:
+	of_node_put(smu->db_node);
+fail_bootmem:
+	free_bootmem(__pa(smu), sizeof(struct smu_device));
+	smu = NULL;
+fail_np:
+	of_node_put(np);
+	return ret;
+}
+
+
+static int smu_late_init(void)
+{
+	if (!smu)
+		return 0;
+
+	init_timer(&smu->i2c_timer);
+	smu->i2c_timer.function = smu_i2c_retry;
+	smu->i2c_timer.data = (unsigned long)smu;
+
+	if (smu->db_node) {
+		smu->db_irq = irq_of_parse_and_map(smu->db_node, 0);
+		if (!smu->db_irq)
+			printk(KERN_ERR "smu: failed to map irq for node %pOF\n",
+			       smu->db_node);
+	}
+	if (smu->msg_node) {
+		smu->msg_irq = irq_of_parse_and_map(smu->msg_node, 0);
+		if (!smu->msg_irq)
+			printk(KERN_ERR "smu: failed to map irq for node %pOF\n",
+			       smu->msg_node);
+	}
+
+	/*
+	 * Try to request the interrupts
+	 */
+
+	if (smu->db_irq) {
+		if (request_irq(smu->db_irq, smu_db_intr,
+				IRQF_SHARED, "SMU doorbell", smu) < 0) {
+			printk(KERN_WARNING "SMU: can't "
+			       "request interrupt %d\n",
+			       smu->db_irq);
+			smu->db_irq = 0;
+		}
+	}
+
+	if (smu->msg_irq) {
+		if (request_irq(smu->msg_irq, smu_msg_intr,
+				IRQF_SHARED, "SMU message", smu) < 0) {
+			printk(KERN_WARNING "SMU: can't "
+			       "request interrupt %d\n",
+			       smu->msg_irq);
+			smu->msg_irq = 0;
+		}
+	}
+
+	smu_irq_inited = 1;
+	return 0;
+}
+/* This has to be before arch_initcall as the low i2c stuff relies on the
+ * above having been done before we reach arch_initcalls
+ */
+core_initcall(smu_late_init);
+
+/*
+ * sysfs visibility
+ */
+
+static void smu_expose_childs(struct work_struct *unused)
+{
+	struct device_node *np;
+
+	for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;)
+		if (of_device_is_compatible(np, "smu-sensors"))
+			of_platform_device_create(np, "smu-sensors",
+						  &smu->of_dev->dev);
+}
+
+static DECLARE_WORK(smu_expose_childs_work, smu_expose_childs);
+
+static int smu_platform_probe(struct platform_device* dev)
+{
+	if (!smu)
+		return -ENODEV;
+	smu->of_dev = dev;
+
+	/*
+	 * Ok, we are matched, now expose all i2c busses. We have to defer
+	 * that unfortunately or it would deadlock inside the device model
+	 */
+	schedule_work(&smu_expose_childs_work);
+
+	return 0;
+}
+
+static const struct of_device_id smu_platform_match[] =
+{
+	{
+		.type		= "smu",
+	},
+	{},
+};
+
+static struct platform_driver smu_of_platform_driver =
+{
+	.driver = {
+		.name = "smu",
+		.of_match_table = smu_platform_match,
+	},
+	.probe		= smu_platform_probe,
+};
+
+static int __init smu_init_sysfs(void)
+{
+	/*
+	 * For now, we don't power manage machines with an SMU chip,
+	 * I'm a bit too far from figuring out how that works with those
+	 * new chipsets, but that will come back and bite us
+	 */
+	platform_driver_register(&smu_of_platform_driver);
+	return 0;
+}
+
+device_initcall(smu_init_sysfs);
+
+struct platform_device *smu_get_ofdev(void)
+{
+	if (!smu)
+		return NULL;
+	return smu->of_dev;
+}
+
+EXPORT_SYMBOL_GPL(smu_get_ofdev);
+
+/*
+ * i2c interface
+ */
+
+static void smu_i2c_complete_command(struct smu_i2c_cmd *cmd, int fail)
+{
+	void (*done)(struct smu_i2c_cmd *cmd, void *misc) = cmd->done;
+	void *misc = cmd->misc;
+	unsigned long flags;
+
+	/* Check for read case */
+	if (!fail && cmd->read) {
+		if (cmd->pdata[0] < 1)
+			fail = 1;
+		else
+			memcpy(cmd->info.data, &cmd->pdata[1],
+			       cmd->info.datalen);
+	}
+
+	DPRINTK("SMU: completing, success: %d\n", !fail);
+
+	/* Update status and mark no pending i2c command with lock
+	 * held so nobody comes in while we dequeue an eventual
+	 * pending next i2c command
+	 */
+	spin_lock_irqsave(&smu->lock, flags);
+	smu->cmd_i2c_cur = NULL;
+	wmb();
+	cmd->status = fail ? -EIO : 0;
+
+	/* Is there another i2c command waiting ? */
+	if (!list_empty(&smu->cmd_i2c_list)) {
+		struct smu_i2c_cmd *newcmd;
+
+		/* Fetch it, new current, remove from list */
+		newcmd = list_entry(smu->cmd_i2c_list.next,
+				    struct smu_i2c_cmd, link);
+		smu->cmd_i2c_cur = newcmd;
+		list_del(&cmd->link);
+
+		/* Queue with low level smu */
+		list_add_tail(&cmd->scmd.link, &smu->cmd_list);
+		if (smu->cmd_cur == NULL)
+			smu_start_cmd();
+	}
+	spin_unlock_irqrestore(&smu->lock, flags);
+
+	/* Call command completion handler if any */
+	if (done)
+		done(cmd, misc);
+
+}
+
+
+static void smu_i2c_retry(unsigned long data)
+{
+	struct smu_i2c_cmd	*cmd = smu->cmd_i2c_cur;
+
+	DPRINTK("SMU: i2c failure, requeuing...\n");
+
+	/* requeue command simply by resetting reply_len */
+	cmd->pdata[0] = 0xff;
+	cmd->scmd.reply_len = sizeof(cmd->pdata);
+	smu_queue_cmd(&cmd->scmd);
+}
+
+
+static void smu_i2c_low_completion(struct smu_cmd *scmd, void *misc)
+{
+	struct smu_i2c_cmd	*cmd = misc;
+	int			fail = 0;
+
+	DPRINTK("SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x\n",
+		cmd->stage, scmd->status, cmd->pdata[0], scmd->reply_len);
+
+	/* Check for possible status */
+	if (scmd->status < 0)
+		fail = 1;
+	else if (cmd->read) {
+		if (cmd->stage == 0)
+			fail = cmd->pdata[0] != 0;
+		else
+			fail = cmd->pdata[0] >= 0x80;
+	} else {
+		fail = cmd->pdata[0] != 0;
+	}
+
+	/* Handle failures by requeuing command, after 5ms interval
+	 */
+	if (fail && --cmd->retries > 0) {
+		DPRINTK("SMU: i2c failure, starting timer...\n");
+		BUG_ON(cmd != smu->cmd_i2c_cur);
+		if (!smu_irq_inited) {
+			mdelay(5);
+			smu_i2c_retry(0);
+			return;
+		}
+		mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5));
+		return;
+	}
+
+	/* If failure or stage 1, command is complete */
+	if (fail || cmd->stage != 0) {
+		smu_i2c_complete_command(cmd, fail);
+		return;
+	}
+
+	DPRINTK("SMU: going to stage 1\n");
+
+	/* Ok, initial command complete, now poll status */
+	scmd->reply_buf = cmd->pdata;
+	scmd->reply_len = sizeof(cmd->pdata);
+	scmd->data_buf = cmd->pdata;
+	scmd->data_len = 1;
+	cmd->pdata[0] = 0;
+	cmd->stage = 1;
+	cmd->retries = 20;
+	smu_queue_cmd(scmd);
+}
+
+
+int smu_queue_i2c(struct smu_i2c_cmd *cmd)
+{
+	unsigned long flags;
+
+	if (smu == NULL)
+		return -ENODEV;
+
+	/* Fill most fields of scmd */
+	cmd->scmd.cmd = SMU_CMD_I2C_COMMAND;
+	cmd->scmd.done = smu_i2c_low_completion;
+	cmd->scmd.misc = cmd;
+	cmd->scmd.reply_buf = cmd->pdata;
+	cmd->scmd.reply_len = sizeof(cmd->pdata);
+	cmd->scmd.data_buf = (u8 *)(char *)&cmd->info;
+	cmd->scmd.status = 1;
+	cmd->stage = 0;
+	cmd->pdata[0] = 0xff;
+	cmd->retries = 20;
+	cmd->status = 1;
+
+	/* Check transfer type, sanitize some "info" fields
+	 * based on transfer type and do more checking
+	 */
+	cmd->info.caddr = cmd->info.devaddr;
+	cmd->read = cmd->info.devaddr & 0x01;
+	switch(cmd->info.type) {
+	case SMU_I2C_TRANSFER_SIMPLE:
+		memset(&cmd->info.sublen, 0, 4);
+		break;
+	case SMU_I2C_TRANSFER_COMBINED:
+		cmd->info.devaddr &= 0xfe;
+	case SMU_I2C_TRANSFER_STDSUB:
+		if (cmd->info.sublen > 3)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Finish setting up command based on transfer direction
+	 */
+	if (cmd->read) {
+		if (cmd->info.datalen > SMU_I2C_READ_MAX)
+			return -EINVAL;
+		memset(cmd->info.data, 0xff, cmd->info.datalen);
+		cmd->scmd.data_len = 9;
+	} else {
+		if (cmd->info.datalen > SMU_I2C_WRITE_MAX)
+			return -EINVAL;
+		cmd->scmd.data_len = 9 + cmd->info.datalen;
+	}
+
+	DPRINTK("SMU: i2c enqueuing command\n");
+	DPRINTK("SMU:   %s, len=%d bus=%x addr=%x sub0=%x type=%x\n",
+		cmd->read ? "read" : "write", cmd->info.datalen,
+		cmd->info.bus, cmd->info.caddr,
+		cmd->info.subaddr[0], cmd->info.type);
+
+
+	/* Enqueue command in i2c list, and if empty, enqueue also in
+	 * main command list
+	 */
+	spin_lock_irqsave(&smu->lock, flags);
+	if (smu->cmd_i2c_cur == NULL) {
+		smu->cmd_i2c_cur = cmd;
+		list_add_tail(&cmd->scmd.link, &smu->cmd_list);
+		if (smu->cmd_cur == NULL)
+			smu_start_cmd();
+	} else
+		list_add_tail(&cmd->link, &smu->cmd_i2c_list);
+	spin_unlock_irqrestore(&smu->lock, flags);
+
+	return 0;
+}
+
+/*
+ * Handling of "partitions"
+ */
+
+static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len)
+{
+	DECLARE_COMPLETION_ONSTACK(comp);
+	unsigned int chunk;
+	struct smu_cmd cmd;
+	int rc;
+	u8 params[8];
+
+	/* We currently use a chunk size of 0xe. We could check the
+	 * SMU firmware version and use bigger sizes though
+	 */
+	chunk = 0xe;
+
+	while (len) {
+		unsigned int clen = min(len, chunk);
+
+		cmd.cmd = SMU_CMD_MISC_ee_COMMAND;
+		cmd.data_len = 7;
+		cmd.data_buf = params;
+		cmd.reply_len = chunk;
+		cmd.reply_buf = dest;
+		cmd.done = smu_done_complete;
+		cmd.misc = &comp;
+		params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC;
+		params[1] = 0x4;
+		*((u32 *)&params[2]) = addr;
+		params[6] = clen;
+
+		rc = smu_queue_cmd(&cmd);
+		if (rc)
+			return rc;
+		wait_for_completion(&comp);
+		if (cmd.status != 0)
+			return rc;
+		if (cmd.reply_len != clen) {
+			printk(KERN_DEBUG "SMU: short read in "
+			       "smu_read_datablock, got: %d, want: %d\n",
+			       cmd.reply_len, clen);
+			return -EIO;
+		}
+		len -= clen;
+		addr += clen;
+		dest += clen;
+	}
+	return 0;
+}
+
+static struct smu_sdbp_header *smu_create_sdb_partition(int id)
+{
+	DECLARE_COMPLETION_ONSTACK(comp);
+	struct smu_simple_cmd cmd;
+	unsigned int addr, len, tlen;
+	struct smu_sdbp_header *hdr;
+	struct property *prop;
+
+	/* First query the partition info */
+	DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq);
+	smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2,
+			 smu_done_complete, &comp,
+			 SMU_CMD_PARTITION_LATEST, id);
+	wait_for_completion(&comp);
+	DPRINTK("SMU: done, status: %d, reply_len: %d\n",
+		cmd.cmd.status, cmd.cmd.reply_len);
+
+	/* Partition doesn't exist (or other error) */
+	if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6)
+		return NULL;
+
+	/* Fetch address and length from reply */
+	addr = *((u16 *)cmd.buffer);
+	len = cmd.buffer[3] << 2;
+	/* Calucluate total length to allocate, including the 17 bytes
+	 * for "sdb-partition-XX" that we append at the end of the buffer
+	 */
+	tlen = sizeof(struct property) + len + 18;
+
+	prop = kzalloc(tlen, GFP_KERNEL);
+	if (prop == NULL)
+		return NULL;
+	hdr = (struct smu_sdbp_header *)(prop + 1);
+	prop->name = ((char *)prop) + tlen - 18;
+	sprintf(prop->name, "sdb-partition-%02x", id);
+	prop->length = len;
+	prop->value = hdr;
+	prop->next = NULL;
+
+	/* Read the datablock */
+	if (smu_read_datablock((u8 *)hdr, addr, len)) {
+		printk(KERN_DEBUG "SMU: datablock read failed while reading "
+		       "partition %02x !\n", id);
+		goto failure;
+	}
+
+	/* Got it, check a few things and create the property */
+	if (hdr->id != id) {
+		printk(KERN_DEBUG "SMU: Reading partition %02x and got "
+		       "%02x !\n", id, hdr->id);
+		goto failure;
+	}
+	if (of_add_property(smu->of_node, prop)) {
+		printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x "
+		       "property !\n", id);
+		goto failure;
+	}
+
+	return hdr;
+ failure:
+	kfree(prop);
+	return NULL;
+}
+
+/* Note: Only allowed to return error code in pointers (using ERR_PTR)
+ * when interruptible is 1
+ */
+const struct smu_sdbp_header *__smu_get_sdb_partition(int id,
+		unsigned int *size, int interruptible)
+{
+	char pname[32];
+	const struct smu_sdbp_header *part;
+
+	if (!smu)
+		return NULL;
+
+	sprintf(pname, "sdb-partition-%02x", id);
+
+	DPRINTK("smu_get_sdb_partition(%02x)\n", id);
+
+	if (interruptible) {
+		int rc;
+		rc = mutex_lock_interruptible(&smu_part_access);
+		if (rc)
+			return ERR_PTR(rc);
+	} else
+		mutex_lock(&smu_part_access);
+
+	part = of_get_property(smu->of_node, pname, size);
+	if (part == NULL) {
+		DPRINTK("trying to extract from SMU ...\n");
+		part = smu_create_sdb_partition(id);
+		if (part != NULL && size)
+			*size = part->len << 2;
+	}
+	mutex_unlock(&smu_part_access);
+	return part;
+}
+
+const struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size)
+{
+	return __smu_get_sdb_partition(id, size, 0);
+}
+EXPORT_SYMBOL(smu_get_sdb_partition);
+
+
+/*
+ * Userland driver interface
+ */
+
+
+static LIST_HEAD(smu_clist);
+static DEFINE_SPINLOCK(smu_clist_lock);
+
+enum smu_file_mode {
+	smu_file_commands,
+	smu_file_events,
+	smu_file_closing
+};
+
+struct smu_private
+{
+	struct list_head	list;
+	enum smu_file_mode	mode;
+	int			busy;
+	struct smu_cmd		cmd;
+	spinlock_t		lock;
+	wait_queue_head_t	wait;
+	u8			buffer[SMU_MAX_DATA];
+};
+
+
+static int smu_open(struct inode *inode, struct file *file)
+{
+	struct smu_private *pp;
+	unsigned long flags;
+
+	pp = kzalloc(sizeof(struct smu_private), GFP_KERNEL);
+	if (pp == 0)
+		return -ENOMEM;
+	spin_lock_init(&pp->lock);
+	pp->mode = smu_file_commands;
+	init_waitqueue_head(&pp->wait);
+
+	mutex_lock(&smu_mutex);
+	spin_lock_irqsave(&smu_clist_lock, flags);
+	list_add(&pp->list, &smu_clist);
+	spin_unlock_irqrestore(&smu_clist_lock, flags);
+	file->private_data = pp;
+	mutex_unlock(&smu_mutex);
+
+	return 0;
+}
+
+
+static void smu_user_cmd_done(struct smu_cmd *cmd, void *misc)
+{
+	struct smu_private *pp = misc;
+
+	wake_up_all(&pp->wait);
+}
+
+
+static ssize_t smu_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	struct smu_private *pp = file->private_data;
+	unsigned long flags;
+	struct smu_user_cmd_hdr hdr;
+	int rc = 0;
+
+	if (pp->busy)
+		return -EBUSY;
+	else if (copy_from_user(&hdr, buf, sizeof(hdr)))
+		return -EFAULT;
+	else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) {
+		pp->mode = smu_file_events;
+		return 0;
+	} else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) {
+		const struct smu_sdbp_header *part;
+		part = __smu_get_sdb_partition(hdr.cmd, NULL, 1);
+		if (part == NULL)
+			return -EINVAL;
+		else if (IS_ERR(part))
+			return PTR_ERR(part);
+		return 0;
+	} else if (hdr.cmdtype != SMU_CMDTYPE_SMU)
+		return -EINVAL;
+	else if (pp->mode != smu_file_commands)
+		return -EBADFD;
+	else if (hdr.data_len > SMU_MAX_DATA)
+		return -EINVAL;
+
+	spin_lock_irqsave(&pp->lock, flags);
+	if (pp->busy) {
+		spin_unlock_irqrestore(&pp->lock, flags);
+		return -EBUSY;
+	}
+	pp->busy = 1;
+	pp->cmd.status = 1;
+	spin_unlock_irqrestore(&pp->lock, flags);
+
+	if (copy_from_user(pp->buffer, buf + sizeof(hdr), hdr.data_len)) {
+		pp->busy = 0;
+		return -EFAULT;
+	}
+
+	pp->cmd.cmd = hdr.cmd;
+	pp->cmd.data_len = hdr.data_len;
+	pp->cmd.reply_len = SMU_MAX_DATA;
+	pp->cmd.data_buf = pp->buffer;
+	pp->cmd.reply_buf = pp->buffer;
+	pp->cmd.done = smu_user_cmd_done;
+	pp->cmd.misc = pp;
+	rc = smu_queue_cmd(&pp->cmd);
+	if (rc < 0)
+		return rc;
+	return count;
+}
+
+
+static ssize_t smu_read_command(struct file *file, struct smu_private *pp,
+				char __user *buf, size_t count)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct smu_user_reply_hdr hdr;
+	unsigned long flags;
+	int size, rc = 0;
+
+	if (!pp->busy)
+		return 0;
+	if (count < sizeof(struct smu_user_reply_hdr))
+		return -EOVERFLOW;
+	spin_lock_irqsave(&pp->lock, flags);
+	if (pp->cmd.status == 1) {
+		if (file->f_flags & O_NONBLOCK) {
+			spin_unlock_irqrestore(&pp->lock, flags);
+			return -EAGAIN;
+		}
+		add_wait_queue(&pp->wait, &wait);
+		for (;;) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			rc = 0;
+			if (pp->cmd.status != 1)
+				break;
+			rc = -ERESTARTSYS;
+			if (signal_pending(current))
+				break;
+			spin_unlock_irqrestore(&pp->lock, flags);
+			schedule();
+			spin_lock_irqsave(&pp->lock, flags);
+		}
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&pp->wait, &wait);
+	}
+	spin_unlock_irqrestore(&pp->lock, flags);
+	if (rc)
+		return rc;
+	if (pp->cmd.status != 0)
+		pp->cmd.reply_len = 0;
+	size = sizeof(hdr) + pp->cmd.reply_len;
+	if (count < size)
+		size = count;
+	rc = size;
+	hdr.status = pp->cmd.status;
+	hdr.reply_len = pp->cmd.reply_len;
+	if (copy_to_user(buf, &hdr, sizeof(hdr)))
+		return -EFAULT;
+	size -= sizeof(hdr);
+	if (size && copy_to_user(buf + sizeof(hdr), pp->buffer, size))
+		return -EFAULT;
+	pp->busy = 0;
+
+	return rc;
+}
+
+
+static ssize_t smu_read_events(struct file *file, struct smu_private *pp,
+			       char __user *buf, size_t count)
+{
+	/* Not implemented */
+	msleep_interruptible(1000);
+	return 0;
+}
+
+
+static ssize_t smu_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct smu_private *pp = file->private_data;
+
+	if (pp->mode == smu_file_commands)
+		return smu_read_command(file, pp, buf, count);
+	if (pp->mode == smu_file_events)
+		return smu_read_events(file, pp, buf, count);
+
+	return -EBADFD;
+}
+
+static unsigned int smu_fpoll(struct file *file, poll_table *wait)
+{
+	struct smu_private *pp = file->private_data;
+	unsigned int mask = 0;
+	unsigned long flags;
+
+	if (pp == 0)
+		return 0;
+
+	if (pp->mode == smu_file_commands) {
+		poll_wait(file, &pp->wait, wait);
+
+		spin_lock_irqsave(&pp->lock, flags);
+		if (pp->busy && pp->cmd.status != 1)
+			mask |= POLLIN;
+		spin_unlock_irqrestore(&pp->lock, flags);
+	}
+	if (pp->mode == smu_file_events) {
+		/* Not yet implemented */
+	}
+	return mask;
+}
+
+static int smu_release(struct inode *inode, struct file *file)
+{
+	struct smu_private *pp = file->private_data;
+	unsigned long flags;
+	unsigned int busy;
+
+	if (pp == 0)
+		return 0;
+
+	file->private_data = NULL;
+
+	/* Mark file as closing to avoid races with new request */
+	spin_lock_irqsave(&pp->lock, flags);
+	pp->mode = smu_file_closing;
+	busy = pp->busy;
+
+	/* Wait for any pending request to complete */
+	if (busy && pp->cmd.status == 1) {
+		DECLARE_WAITQUEUE(wait, current);
+
+		add_wait_queue(&pp->wait, &wait);
+		for (;;) {
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			if (pp->cmd.status != 1)
+				break;
+			spin_unlock_irqrestore(&pp->lock, flags);
+			schedule();
+			spin_lock_irqsave(&pp->lock, flags);
+		}
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&pp->wait, &wait);
+	}
+	spin_unlock_irqrestore(&pp->lock, flags);
+
+	spin_lock_irqsave(&smu_clist_lock, flags);
+	list_del(&pp->list);
+	spin_unlock_irqrestore(&smu_clist_lock, flags);
+	kfree(pp);
+
+	return 0;
+}
+
+
+static const struct file_operations smu_device_fops = {
+	.llseek		= no_llseek,
+	.read		= smu_read,
+	.write		= smu_write,
+	.poll		= smu_fpoll,
+	.open		= smu_open,
+	.release	= smu_release,
+};
+
+static struct miscdevice pmu_device = {
+	MISC_DYNAMIC_MINOR, "smu", &smu_device_fops
+};
+
+static int smu_device_init(void)
+{
+	if (!smu)
+		return -ENODEV;
+	if (misc_register(&pmu_device) < 0)
+		printk(KERN_ERR "via-pmu: cannot register misc device.\n");
+	return 0;
+}
+device_initcall(smu_device_init);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/therm_adt746x.c b/src/kernel/linux/v4.14/drivers/macintosh/therm_adt746x.c
new file mode 100644
index 0000000..f433521
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/therm_adt746x.c
@@ -0,0 +1,623 @@
+/*
+ * Device driver for the i2c thermostat found on the iBook G4, Albook G4
+ *
+ * Copyright (C) 2003, 2004 Colin Leroy, Rasmus Rohde, Benjamin Herrenschmidt
+ *
+ * Documentation from 115254175ADT7467_pra.pdf and 3686221171167ADT7460_b.pdf
+ * http://www.onsemi.com/PowerSolutions/product.do?id=ADT7467
+ * http://www.onsemi.com/PowerSolutions/product.do?id=ADT7460
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/suspend.h>
+#include <linux/kthread.h>
+#include <linux/moduleparam.h>
+#include <linux/freezer.h>
+#include <linux/of_platform.h>
+
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+
+#undef DEBUG
+
+#define CONFIG_REG   0x40
+#define MANUAL_MASK  0xe0
+#define AUTO_MASK    0x20
+#define INVERT_MASK  0x10
+
+static u8 TEMP_REG[3]    = {0x26, 0x25, 0x27}; /* local, sensor1, sensor2 */
+static u8 LIMIT_REG[3]   = {0x6b, 0x6a, 0x6c}; /* local, sensor1, sensor2 */
+static u8 MANUAL_MODE[2] = {0x5c, 0x5d};       
+static u8 REM_CONTROL[2] = {0x00, 0x40};
+static u8 FAN_SPEED[2]   = {0x28, 0x2a};
+static u8 FAN_SPD_SET[2] = {0x30, 0x31};
+
+static u8 default_limits_local[3] = {70, 50, 70};    /* local, sensor1, sensor2 */
+static u8 default_limits_chip[3] = {80, 65, 80};    /* local, sensor1, sensor2 */
+static const char *sensor_location[3] = { "?", "?", "?" };
+
+static int limit_adjust;
+static int fan_speed = -1;
+static bool verbose;
+
+MODULE_AUTHOR("Colin Leroy <colin@colino.net>");
+MODULE_DESCRIPTION("Driver for ADT746x thermostat in iBook G4 and "
+		   "Powerbook G4 Alu");
+MODULE_LICENSE("GPL");
+
+module_param(limit_adjust, int, 0644);
+MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50 sensor1, 70 sensor2) "
+		 "by N degrees.");
+
+module_param(fan_speed, int, 0644);
+MODULE_PARM_DESC(fan_speed,"Specify starting fan speed (0-255) "
+		 "(default 64)");
+
+module_param(verbose, bool, 0);
+MODULE_PARM_DESC(verbose,"Verbose log operations "
+		 "(default 0)");
+
+struct thermostat {
+	struct i2c_client	*clt;
+	u8			temps[3];
+	u8			cached_temp[3];
+	u8			initial_limits[3];
+	u8			limits[3];
+	int			last_speed[2];
+	int			last_var[2];
+	int			pwm_inv[2];
+	struct task_struct	*thread;
+	struct platform_device	*pdev;
+	enum {
+		ADT7460,
+		ADT7467
+	}			type;
+};
+
+static void write_both_fan_speed(struct thermostat *th, int speed);
+static void write_fan_speed(struct thermostat *th, int speed, int fan);
+
+static int
+write_reg(struct thermostat* th, int reg, u8 data)
+{
+	u8 tmp[2];
+	int rc;
+	
+	tmp[0] = reg;
+	tmp[1] = data;
+	rc = i2c_master_send(th->clt, (const char *)tmp, 2);
+	if (rc < 0)
+		return rc;
+	if (rc != 2)
+		return -ENODEV;
+	return 0;
+}
+
+static int
+read_reg(struct thermostat* th, int reg)
+{
+	u8 reg_addr, data;
+	int rc;
+
+	reg_addr = (u8)reg;
+	rc = i2c_master_send(th->clt, &reg_addr, 1);
+	if (rc < 0)
+		return rc;
+	if (rc != 1)
+		return -ENODEV;
+	rc = i2c_master_recv(th->clt, (char *)&data, 1);
+	if (rc < 0)
+		return rc;
+	return data;
+}
+
+static int read_fan_speed(struct thermostat *th, u8 addr)
+{
+	u8 tmp[2];
+	u16 res;
+	
+	/* should start with low byte */
+	tmp[1] = read_reg(th, addr);
+	tmp[0] = read_reg(th, addr + 1);
+	
+	res = tmp[1] + (tmp[0] << 8);
+	/* "a value of 0xffff means that the fan has stopped" */
+	return (res == 0xffff ? 0 : (90000*60)/res);
+}
+
+static void write_both_fan_speed(struct thermostat *th, int speed)
+{
+	write_fan_speed(th, speed, 0);
+	if (th->type == ADT7460)
+		write_fan_speed(th, speed, 1);
+}
+
+static void write_fan_speed(struct thermostat *th, int speed, int fan)
+{
+	u8 manual;
+	
+	if (speed > 0xff) 
+		speed = 0xff;
+	else if (speed < -1) 
+		speed = 0;
+	
+	if (th->type == ADT7467 && fan == 1)
+		return;
+	
+	if (th->last_speed[fan] != speed) {
+		if (verbose) {
+			if (speed == -1)
+				printk(KERN_DEBUG "adt746x: Setting speed to automatic "
+					"for %s fan.\n", sensor_location[fan+1]);
+			else
+				printk(KERN_DEBUG "adt746x: Setting speed to %d "
+					"for %s fan.\n", speed, sensor_location[fan+1]);
+		}
+	} else
+		return;
+	
+	if (speed >= 0) {
+		manual = read_reg(th, MANUAL_MODE[fan]);
+		manual &= ~INVERT_MASK;
+		write_reg(th, MANUAL_MODE[fan],
+			manual | MANUAL_MASK | th->pwm_inv[fan]);
+		write_reg(th, FAN_SPD_SET[fan], speed);
+	} else {
+		/* back to automatic */
+		if(th->type == ADT7460) {
+			manual = read_reg(th,
+				MANUAL_MODE[fan]) & (~MANUAL_MASK);
+			manual &= ~INVERT_MASK;
+			manual |= th->pwm_inv[fan];
+			write_reg(th,
+				MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
+		} else {
+			manual = read_reg(th, MANUAL_MODE[fan]);
+			manual &= ~INVERT_MASK;
+			manual |= th->pwm_inv[fan];
+			write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK));
+		}
+	}
+	
+	th->last_speed[fan] = speed;			
+}
+
+static void read_sensors(struct thermostat *th)
+{
+	int i = 0;
+
+	for (i = 0; i < 3; i++)
+		th->temps[i]  = read_reg(th, TEMP_REG[i]);
+}
+
+#ifdef DEBUG
+static void display_stats(struct thermostat *th)
+{
+	if (th->temps[0] != th->cached_temp[0]
+	||  th->temps[1] != th->cached_temp[1]
+	||  th->temps[2] != th->cached_temp[2]) {
+		printk(KERN_INFO "adt746x: Temperature infos:"
+				 " thermostats: %d,%d,%d;"
+				 " limits: %d,%d,%d;"
+				 " fan speed: %d RPM\n",
+				 th->temps[0], th->temps[1], th->temps[2],
+				 th->limits[0],  th->limits[1],  th->limits[2],
+				 read_fan_speed(th, FAN_SPEED[0]));
+	}
+	th->cached_temp[0] = th->temps[0];
+	th->cached_temp[1] = th->temps[1];
+	th->cached_temp[2] = th->temps[2];
+}
+#endif
+
+static void update_fans_speed (struct thermostat *th)
+{
+	int lastvar = 0; /* last variation, for iBook */
+	int i = 0;
+
+	/* we don't care about local sensor, so we start at sensor 1 */
+	for (i = 1; i < 3; i++) {
+		int started = 0;
+		int fan_number = (th->type == ADT7460 && i == 2);
+		int var = th->temps[i] - th->limits[i];
+
+		if (var > -1) {
+			int step = (255 - fan_speed) / 7;
+			int new_speed = 0;
+
+			/* hysteresis : change fan speed only if variation is
+			 * more than two degrees */
+			if (abs(var - th->last_var[fan_number]) < 2)
+				continue;
+
+			started = 1;
+			new_speed = fan_speed + ((var-1)*step);
+
+			if (new_speed < fan_speed)
+				new_speed = fan_speed;
+			if (new_speed > 255)
+				new_speed = 255;
+
+			if (verbose)
+				printk(KERN_DEBUG "adt746x: Setting fans speed to %d "
+						 "(limit exceeded by %d on %s)\n",
+						new_speed, var,
+						sensor_location[fan_number+1]);
+			write_both_fan_speed(th, new_speed);
+			th->last_var[fan_number] = var;
+		} else if (var < -2) {
+			/* don't stop fan if sensor2 is cold and sensor1 is not
+			 * so cold (lastvar >= -1) */
+			if (i == 2 && lastvar < -1) {
+				if (th->last_speed[fan_number] != 0)
+					if (verbose)
+						printk(KERN_DEBUG "adt746x: Stopping "
+							"fans.\n");
+				write_both_fan_speed(th, 0);
+			}
+		}
+
+		lastvar = var;
+
+		if (started)
+			return; /* we don't want to re-stop the fan
+				* if sensor1 is heating and sensor2 is not */
+	}
+}
+
+static int monitor_task(void *arg)
+{
+	struct thermostat* th = arg;
+
+	set_freezable();
+	while(!kthread_should_stop()) {
+		try_to_freeze();
+		msleep_interruptible(2000);
+
+#ifndef DEBUG
+		if (fan_speed != -1)
+			read_sensors(th);
+#else
+		read_sensors(th);
+#endif		
+
+		if (fan_speed != -1)
+			update_fans_speed(th);
+
+#ifdef DEBUG
+		display_stats(th);
+#endif
+
+	}
+
+	return 0;
+}
+
+static void set_limit(struct thermostat *th, int i)
+{
+	/* Set sensor1 limit higher to avoid powerdowns */
+	th->limits[i] = default_limits_chip[i] + limit_adjust;
+	write_reg(th, LIMIT_REG[i], th->limits[i]);
+		
+	/* set our limits to normal */
+	th->limits[i] = default_limits_local[i] + limit_adjust;
+}
+
+#define BUILD_SHOW_FUNC_INT(name, data)				\
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\
+{								\
+	struct thermostat *th = dev_get_drvdata(dev);		\
+	return sprintf(buf, "%d\n", data);			\
+}
+
+#define BUILD_SHOW_FUNC_INT_LITE(name, data)				\
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\
+{								\
+	return sprintf(buf, "%d\n", data);			\
+}
+
+#define BUILD_SHOW_FUNC_STR(name, data)				\
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)       \
+{								\
+	return sprintf(buf, "%s\n", data);			\
+}
+
+#define BUILD_SHOW_FUNC_FAN(name, data)				\
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)       \
+{								\
+	struct thermostat *th = dev_get_drvdata(dev);		\
+	return sprintf(buf, "%d (%d rpm)\n", 			\
+		th->last_speed[data],				\
+		read_fan_speed(th, FAN_SPEED[data])		\
+		);						\
+}
+
+#define BUILD_STORE_FUNC_DEG(name, data)			\
+static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
+{								\
+	struct thermostat *th = dev_get_drvdata(dev);		\
+	int val;						\
+	int i;							\
+	val = simple_strtol(buf, NULL, 10);			\
+	printk(KERN_INFO "Adjusting limits by %d degrees\n", val);	\
+	limit_adjust = val;					\
+	for (i=0; i < 3; i++)					\
+		set_limit(th, i);				\
+	return n;						\
+}
+
+#define BUILD_STORE_FUNC_INT(name, data)			\
+static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
+{								\
+	int val;						\
+	val = simple_strtol(buf, NULL, 10);			\
+	if (val < 0 || val > 255)				\
+		return -EINVAL;					\
+	printk(KERN_INFO "Setting specified fan speed to %d\n", val);	\
+	data = val;						\
+	return n;						\
+}
+
+BUILD_SHOW_FUNC_INT(sensor1_temperature,	 (read_reg(th, TEMP_REG[1])))
+BUILD_SHOW_FUNC_INT(sensor2_temperature,	 (read_reg(th, TEMP_REG[2])))
+BUILD_SHOW_FUNC_INT(sensor1_limit,		 th->limits[1])
+BUILD_SHOW_FUNC_INT(sensor2_limit,		 th->limits[2])
+BUILD_SHOW_FUNC_STR(sensor1_location,		 sensor_location[1])
+BUILD_SHOW_FUNC_STR(sensor2_location,		 sensor_location[2])
+
+BUILD_SHOW_FUNC_INT_LITE(specified_fan_speed, fan_speed)
+BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed)
+
+BUILD_SHOW_FUNC_FAN(sensor1_fan_speed,	 0)
+BUILD_SHOW_FUNC_FAN(sensor2_fan_speed,	 1)
+
+BUILD_SHOW_FUNC_INT_LITE(limit_adjust,	 limit_adjust)
+BUILD_STORE_FUNC_DEG(limit_adjust,	 th)
+		
+static DEVICE_ATTR(sensor1_temperature,	S_IRUGO,
+		   show_sensor1_temperature,NULL);
+static DEVICE_ATTR(sensor2_temperature,	S_IRUGO,
+		   show_sensor2_temperature,NULL);
+static DEVICE_ATTR(sensor1_limit, S_IRUGO,
+		   show_sensor1_limit,	NULL);
+static DEVICE_ATTR(sensor2_limit, S_IRUGO,
+		   show_sensor2_limit,	NULL);
+static DEVICE_ATTR(sensor1_location, S_IRUGO,
+		   show_sensor1_location, NULL);
+static DEVICE_ATTR(sensor2_location, S_IRUGO,
+		   show_sensor2_location, NULL);
+
+static DEVICE_ATTR(specified_fan_speed,	S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
+		   show_specified_fan_speed,store_specified_fan_speed);
+
+static DEVICE_ATTR(sensor1_fan_speed,	S_IRUGO,
+		   show_sensor1_fan_speed,	NULL);
+static DEVICE_ATTR(sensor2_fan_speed,	S_IRUGO,
+		   show_sensor2_fan_speed,	NULL);
+
+static DEVICE_ATTR(limit_adjust,	S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
+		   show_limit_adjust,	store_limit_adjust);
+
+static void thermostat_create_files(struct thermostat *th)
+{
+	struct device_node *np = th->clt->dev.of_node;
+	struct device *dev;
+	int err;
+
+	/* To maintain ABI compatibility with userspace, create
+	 * the old style platform driver and attach the attributes
+	 * to it here
+	 */
+	th->pdev = of_platform_device_create(np, "temperatures", NULL);
+	if (!th->pdev)
+		return;
+	dev = &th->pdev->dev;
+	dev_set_drvdata(dev, th);
+	err = device_create_file(dev, &dev_attr_sensor1_temperature);
+	err |= device_create_file(dev, &dev_attr_sensor2_temperature);
+	err |= device_create_file(dev, &dev_attr_sensor1_limit);
+	err |= device_create_file(dev, &dev_attr_sensor2_limit);
+	err |= device_create_file(dev, &dev_attr_sensor1_location);
+	err |= device_create_file(dev, &dev_attr_sensor2_location);
+	err |= device_create_file(dev, &dev_attr_limit_adjust);
+	err |= device_create_file(dev, &dev_attr_specified_fan_speed);
+	err |= device_create_file(dev, &dev_attr_sensor1_fan_speed);
+	if(th->type == ADT7460)
+		err |= device_create_file(dev, &dev_attr_sensor2_fan_speed);
+	if (err)
+		printk(KERN_WARNING
+			"Failed to create temperature attribute file(s).\n");
+}
+
+static void thermostat_remove_files(struct thermostat *th)
+{
+	struct device *dev;
+
+	if (!th->pdev)
+		return;
+	dev = &th->pdev->dev;
+	device_remove_file(dev, &dev_attr_sensor1_temperature);
+	device_remove_file(dev, &dev_attr_sensor2_temperature);
+	device_remove_file(dev, &dev_attr_sensor1_limit);
+	device_remove_file(dev, &dev_attr_sensor2_limit);
+	device_remove_file(dev, &dev_attr_sensor1_location);
+	device_remove_file(dev, &dev_attr_sensor2_location);
+	device_remove_file(dev, &dev_attr_limit_adjust);
+	device_remove_file(dev, &dev_attr_specified_fan_speed);
+	device_remove_file(dev, &dev_attr_sensor1_fan_speed);	
+	if (th->type == ADT7460)
+		device_remove_file(dev, &dev_attr_sensor2_fan_speed);
+	of_device_unregister(th->pdev);
+
+}
+
+static int probe_thermostat(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct device_node *np = client->dev.of_node;
+	struct thermostat* th;
+	const __be32 *prop;
+	int i, rc, vers, offset = 0;
+
+	if (!np)
+		return -ENXIO;
+	prop = of_get_property(np, "hwsensor-params-version", NULL);
+	if (!prop)
+		return -ENXIO;
+	vers = be32_to_cpup(prop);
+	printk(KERN_INFO "adt746x: version %d (%ssupported)\n",
+	       vers, vers == 1 ? "" : "un");
+	if (vers != 1)
+		return -ENXIO;
+
+	if (of_get_property(np, "hwsensor-location", NULL)) {
+		for (i = 0; i < 3; i++) {
+			sensor_location[i] = of_get_property(np,
+					"hwsensor-location", NULL) + offset;
+
+			if (sensor_location[i] == NULL)
+				sensor_location[i] = "";
+
+			printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]);
+			offset += strlen(sensor_location[i]) + 1;
+		}
+	}
+
+	th = kzalloc(sizeof(struct thermostat), GFP_KERNEL);
+	if (!th)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, th);
+	th->clt = client;
+	th->type = id->driver_data;
+
+	rc = read_reg(th, CONFIG_REG);
+	if (rc < 0) {
+		dev_err(&client->dev, "Thermostat failed to read config!\n");
+		kfree(th);
+		return -ENODEV;
+	}
+
+	/* force manual control to start the fan quieter */
+	if (fan_speed == -1)
+		fan_speed = 64;
+	
+	if (th->type == ADT7460) {
+		printk(KERN_INFO "adt746x: ADT7460 initializing\n");
+		/* The 7460 needs to be started explicitly */
+		write_reg(th, CONFIG_REG, 1);
+	} else
+		printk(KERN_INFO "adt746x: ADT7467 initializing\n");
+
+	for (i = 0; i < 3; i++) {
+		th->initial_limits[i] = read_reg(th, LIMIT_REG[i]);
+		set_limit(th, i);
+	}
+
+	printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d"
+			 " to %d, %d, %d\n",
+			 th->initial_limits[0], th->initial_limits[1],
+			 th->initial_limits[2], th->limits[0], th->limits[1],
+			 th->limits[2]);
+
+	/* record invert bit status because fw can corrupt it after suspend */
+	th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK;
+	th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK;
+
+	/* be sure to really write fan speed the first time */
+	th->last_speed[0] = -2;
+	th->last_speed[1] = -2;
+	th->last_var[0] = -80;
+	th->last_var[1] = -80;
+
+	if (fan_speed != -1) {
+		/* manual mode, stop fans */
+		write_both_fan_speed(th, 0);
+	} else {
+		/* automatic mode */
+		write_both_fan_speed(th, -1);
+	}
+	
+	th->thread = kthread_run(monitor_task, th, "kfand");
+	if (th->thread == ERR_PTR(-ENOMEM)) {
+		printk(KERN_INFO "adt746x: Kthread creation failed\n");
+		th->thread = NULL;
+		return -ENOMEM;
+	}
+
+	thermostat_create_files(th);
+
+	return 0;
+}
+
+static int remove_thermostat(struct i2c_client *client)
+{
+	struct thermostat *th = i2c_get_clientdata(client);
+	int i;
+	
+	thermostat_remove_files(th);
+
+	if (th->thread != NULL)
+		kthread_stop(th->thread);
+
+	printk(KERN_INFO "adt746x: Putting max temperatures back from "
+			 "%d, %d, %d to %d, %d, %d\n",
+		th->limits[0], th->limits[1], th->limits[2],
+		th->initial_limits[0], th->initial_limits[1],
+		th->initial_limits[2]);
+
+	for (i = 0; i < 3; i++)
+		write_reg(th, LIMIT_REG[i], th->initial_limits[i]);
+
+	write_both_fan_speed(th, -1);
+
+	kfree(th);
+
+	return 0;
+}
+
+static const struct i2c_device_id therm_adt746x_id[] = {
+	{ "MAC,adt7460", ADT7460 },
+	{ "MAC,adt7467", ADT7467 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, therm_adt746x_id);
+
+static struct i2c_driver thermostat_driver = {
+	.driver = {
+		.name	= "therm_adt746x",
+	},
+	.probe = probe_thermostat,
+	.remove = remove_thermostat,
+	.id_table = therm_adt746x_id,
+};
+
+static int __init thermostat_init(void)
+{
+#ifndef CONFIG_I2C_POWERMAC
+	request_module("i2c-powermac");
+#endif
+
+	return i2c_add_driver(&thermostat_driver);
+}
+
+static void __exit thermostat_exit(void)
+{
+	i2c_del_driver(&thermostat_driver);
+}
+
+module_init(thermostat_init);
+module_exit(thermostat_exit);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/therm_windtunnel.c b/src/kernel/linux/v4.14/drivers/macintosh/therm_windtunnel.c
new file mode 100644
index 0000000..68dcbcb
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/therm_windtunnel.c
@@ -0,0 +1,529 @@
+/* 
+ *   Creation Date: <2003/03/14 20:54:13 samuel>
+ *   Time-stamp: <2004/03/20 14:20:59 samuel>
+ *   
+ *	<therm_windtunnel.c>
+ *	
+ *	The G4 "windtunnel" has a single fan controlled by an
+ *	ADM1030 fan controller and a DS1775 thermostat.
+ *
+ *	The fan controller is equipped with a temperature sensor
+ *	which measures the case temperature. The DS1775 sensor
+ *	measures the CPU temperature. This driver tunes the
+ *	behavior of the fan. It is based upon empirical observations
+ *	of the 'AppleFan' driver under Mac OS X.
+ *
+ *	WARNING: This driver has only been testen on Apple's
+ *	1.25 MHz Dual G4 (March 03). It is tuned for a CPU
+ *	temperature around 57 C.
+ *
+ *   Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se)
+ *
+ *   Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt
+ *   
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation
+ *   
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/of_platform.h>
+
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/macio.h>
+
+#define LOG_TEMP		0			/* continuously log temperature */
+
+static struct {
+	volatile int		running;
+	struct task_struct	*poll_task;
+	
+	struct mutex	 	lock;
+	struct platform_device	*of_dev;
+	
+	struct i2c_client	*thermostat;
+	struct i2c_client	*fan;
+
+	int			overheat_temp;		/* 100% fan at this temp */
+	int			overheat_hyst;
+	int			temp;
+	int			casetemp;
+	int			fan_level;		/* active fan_table setting */
+
+	int			downind;
+	int			upind;
+
+	int			r0, r1, r20, r23, r25;	/* saved register */
+} x;
+
+#define T(x,y)			(((x)<<8) | (y)*0x100/10 )
+
+static struct {
+	int			fan_down_setting;
+	int			temp;
+	int			fan_up_setting;
+} fan_table[] = {
+	{ 11, T(0,0),  11 },	/* min fan */
+	{ 11, T(55,0), 11 },
+	{  6, T(55,3), 11 },
+	{  7, T(56,0), 11 },
+	{  8, T(57,0), 8 },
+	{  7, T(58,3), 7 },
+	{  6, T(58,8), 6 },
+	{  5, T(59,2), 5 },
+	{  4, T(59,6), 4 },
+	{  3, T(59,9), 3 },
+	{  2, T(60,1), 2 },
+	{  1, 0xfffff, 1 }	/* on fire */
+};
+
+static void
+print_temp( const char *s, int temp )
+{
+	printk("%s%d.%d C", s ? s : "", temp>>8, (temp & 255)*10/256 );
+}
+
+static ssize_t
+show_cpu_temperature( struct device *dev, struct device_attribute *attr, char *buf )
+{
+	return sprintf(buf, "%d.%d\n", x.temp>>8, (x.temp & 255)*10/256 );
+}
+
+static ssize_t
+show_case_temperature( struct device *dev, struct device_attribute *attr, char *buf )
+{
+	return sprintf(buf, "%d.%d\n", x.casetemp>>8, (x.casetemp & 255)*10/256 );
+}
+
+static DEVICE_ATTR(cpu_temperature, S_IRUGO, show_cpu_temperature, NULL );
+static DEVICE_ATTR(case_temperature, S_IRUGO, show_case_temperature, NULL );
+
+
+
+/************************************************************************/
+/*	controller thread						*/
+/************************************************************************/
+
+static int
+write_reg( struct i2c_client *cl, int reg, int data, int len )
+{
+	u8 tmp[3];
+
+	if( len < 1 || len > 2 || data < 0 )
+		return -EINVAL;
+
+	tmp[0] = reg;
+	tmp[1] = (len == 1) ? data : (data >> 8);
+	tmp[2] = data;
+	len++;
+	
+	if( i2c_master_send(cl, tmp, len) != len )
+		return -ENODEV;
+	return 0;
+}
+
+static int
+read_reg( struct i2c_client *cl, int reg, int len )
+{
+	u8 buf[2];
+
+	if( len != 1 && len != 2 )
+		return -EINVAL;
+	buf[0] = reg;
+	if( i2c_master_send(cl, buf, 1) != 1 )
+		return -ENODEV;
+	if( i2c_master_recv(cl, buf, len) != len )
+		return -ENODEV;
+	return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0];
+}
+
+static void
+tune_fan( int fan_setting )
+{
+	int val = (fan_setting << 3) | 7;
+
+	/* write_reg( x.fan, 0x24, val, 1 ); */
+	write_reg( x.fan, 0x25, val, 1 );
+	write_reg( x.fan, 0x20, 0, 1 );
+	print_temp("CPU-temp: ", x.temp );
+	if( x.casetemp )
+		print_temp(", Case: ", x.casetemp );
+	printk(",  Fan: %d (tuned %+d)\n", 11-fan_setting, x.fan_level-fan_setting );
+
+	x.fan_level = fan_setting;
+}
+
+static void
+poll_temp( void )
+{
+	int temp, i, level, casetemp;
+
+	temp = read_reg( x.thermostat, 0, 2 );
+
+	/* this actually occurs when the computer is loaded */
+	if( temp < 0 )
+		return;
+
+	casetemp = read_reg(x.fan, 0x0b, 1) << 8;
+	casetemp |= (read_reg(x.fan, 0x06, 1) & 0x7) << 5;
+
+	if( LOG_TEMP && x.temp != temp ) {
+		print_temp("CPU-temp: ", temp );
+		print_temp(", Case: ", casetemp );
+		printk(",  Fan: %d\n", 11-x.fan_level );
+	}
+	x.temp = temp;
+	x.casetemp = casetemp;
+
+	level = -1;
+	for( i=0; (temp & 0xffff) > fan_table[i].temp ; i++ )
+		;
+	if( i < x.downind )
+		level = fan_table[i].fan_down_setting;
+	x.downind = i;
+
+	for( i=0; (temp & 0xffff) >= fan_table[i+1].temp ; i++ )
+		;
+	if( x.upind < i )
+		level = fan_table[i].fan_up_setting;
+	x.upind = i;
+
+	if( level >= 0 )
+		tune_fan( level );
+}
+
+
+static void
+setup_hardware( void )
+{
+	int val;
+	int err;
+
+	/* save registers (if we unload the module) */
+	x.r0 = read_reg( x.fan, 0x00, 1 );
+	x.r1 = read_reg( x.fan, 0x01, 1 );
+	x.r20 = read_reg( x.fan, 0x20, 1 );
+	x.r23 = read_reg( x.fan, 0x23, 1 );
+	x.r25 = read_reg( x.fan, 0x25, 1 );
+
+	/* improve measurement resolution (convergence time 1.5s) */
+	if( (val=read_reg(x.thermostat, 1, 1)) >= 0 ) {
+		val |= 0x60;
+		if( write_reg( x.thermostat, 1, val, 1 ) )
+			printk("Failed writing config register\n");
+	}
+	/* disable interrupts and TAC input */
+	write_reg( x.fan, 0x01, 0x01, 1 );
+	/* enable filter */
+	write_reg( x.fan, 0x23, 0x91, 1 );
+	/* remote temp. controls fan */
+	write_reg( x.fan, 0x00, 0x95, 1 );
+
+	/* The thermostat (which besides measureing temperature controls
+	 * has a THERM output which puts the fan on 100%) is usually
+	 * set to kick in at 80 C (chip default). We reduce this a bit
+	 * to be on the safe side (OSX doesn't)...
+	 */
+	if( x.overheat_temp == (80 << 8) ) {
+		x.overheat_temp = 75 << 8;
+		x.overheat_hyst = 70 << 8;
+		write_reg( x.thermostat, 2, x.overheat_hyst, 2 );
+		write_reg( x.thermostat, 3, x.overheat_temp, 2 );
+
+		print_temp("Reducing overheating limit to ", x.overheat_temp );
+		print_temp(" (Hyst: ", x.overheat_hyst );
+		printk(")\n");
+	}
+
+	/* set an initial fan setting */
+	x.downind = 0xffff;
+	x.upind = -1;
+	/* tune_fan( fan_up_table[x.upind].fan_setting ); */
+
+	err = device_create_file( &x.of_dev->dev, &dev_attr_cpu_temperature );
+	err |= device_create_file( &x.of_dev->dev, &dev_attr_case_temperature );
+	if (err)
+		printk(KERN_WARNING
+			"Failed to create temperature attribute file(s).\n");
+}
+
+static void
+restore_regs( void )
+{
+	device_remove_file( &x.of_dev->dev, &dev_attr_cpu_temperature );
+	device_remove_file( &x.of_dev->dev, &dev_attr_case_temperature );
+
+	write_reg( x.fan, 0x01, x.r1, 1 );
+	write_reg( x.fan, 0x20, x.r20, 1 );
+	write_reg( x.fan, 0x23, x.r23, 1 );
+	write_reg( x.fan, 0x25, x.r25, 1 );
+	write_reg( x.fan, 0x00, x.r0, 1 );
+}
+
+static int control_loop(void *dummy)
+{
+	mutex_lock(&x.lock);
+	setup_hardware();
+	mutex_unlock(&x.lock);
+
+	for (;;) {
+		msleep_interruptible(8000);
+		if (kthread_should_stop())
+			break;
+
+		mutex_lock(&x.lock);
+		poll_temp();
+		mutex_unlock(&x.lock);
+	}
+
+	mutex_lock(&x.lock);
+	restore_regs();
+	mutex_unlock(&x.lock);
+
+	return 0;
+}
+
+
+/************************************************************************/
+/*	i2c probing and setup						*/
+/************************************************************************/
+
+static int
+do_attach( struct i2c_adapter *adapter )
+{
+	/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */
+	static const unsigned short scan_ds1775[] = {
+		0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+		I2C_CLIENT_END
+	};
+	static const unsigned short scan_adm1030[] = {
+		0x2c, 0x2d, 0x2e, 0x2f,
+		I2C_CLIENT_END
+	};
+
+	if( strncmp(adapter->name, "uni-n", 5) )
+		return 0;
+
+	if( !x.running ) {
+		struct i2c_board_info info;
+
+		memset(&info, 0, sizeof(struct i2c_board_info));
+		strlcpy(info.type, "therm_ds1775", I2C_NAME_SIZE);
+		i2c_new_probed_device(adapter, &info, scan_ds1775, NULL);
+
+		strlcpy(info.type, "therm_adm1030", I2C_NAME_SIZE);
+		i2c_new_probed_device(adapter, &info, scan_adm1030, NULL);
+
+		if( x.thermostat && x.fan ) {
+			x.running = 1;
+			x.poll_task = kthread_run(control_loop, NULL, "g4fand");
+		}
+	}
+	return 0;
+}
+
+static int
+do_remove(struct i2c_client *client)
+{
+	if (x.running) {
+		x.running = 0;
+		kthread_stop(x.poll_task);
+		x.poll_task = NULL;
+	}
+	if (client == x.thermostat)
+		x.thermostat = NULL;
+	else if (client == x.fan)
+		x.fan = NULL;
+	else
+		printk(KERN_ERR "g4fan: bad client\n");
+
+	return 0;
+}
+
+static int
+attach_fan( struct i2c_client *cl )
+{
+	if( x.fan )
+		goto out;
+
+	/* check that this is an ADM1030 */
+	if( read_reg(cl, 0x3d, 1) != 0x30 || read_reg(cl, 0x3e, 1) != 0x41 )
+		goto out;
+	printk("ADM1030 fan controller [@%02x]\n", cl->addr );
+
+	x.fan = cl;
+ out:
+	return 0;
+}
+
+static int
+attach_thermostat( struct i2c_client *cl ) 
+{
+	int hyst_temp, os_temp, temp;
+
+	if( x.thermostat )
+		goto out;
+
+	if( (temp=read_reg(cl, 0, 2)) < 0 )
+		goto out;
+	
+	/* temperature sanity check */
+	if( temp < 0x1600 || temp > 0x3c00 )
+		goto out;
+	hyst_temp = read_reg(cl, 2, 2);
+	os_temp = read_reg(cl, 3, 2);
+	if( hyst_temp < 0 || os_temp < 0 )
+		goto out;
+
+	printk("DS1775 digital thermometer [@%02x]\n", cl->addr );
+	print_temp("Temp: ", temp );
+	print_temp("  Hyst: ", hyst_temp );
+	print_temp("  OS: ", os_temp );
+	printk("\n");
+
+	x.temp = temp;
+	x.overheat_temp = os_temp;
+	x.overheat_hyst = hyst_temp;
+	x.thermostat = cl;
+out:
+	return 0;
+}
+
+enum chip { ds1775, adm1030 };
+
+static const struct i2c_device_id therm_windtunnel_id[] = {
+	{ "therm_ds1775", ds1775 },
+	{ "therm_adm1030", adm1030 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, therm_windtunnel_id);
+
+static int
+do_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = cl->adapter;
+
+	if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA
+				     | I2C_FUNC_SMBUS_WRITE_BYTE) )
+		return 0;
+
+	switch (id->driver_data) {
+	case adm1030:
+		return attach_fan( cl );
+	case ds1775:
+		return attach_thermostat(cl);
+	}
+	return 0;
+}
+
+static struct i2c_driver g4fan_driver = {
+	.driver = {
+		.name	= "therm_windtunnel",
+	},
+	.attach_adapter = do_attach,
+	.probe		= do_probe,
+	.remove		= do_remove,
+	.id_table	= therm_windtunnel_id,
+};
+
+
+/************************************************************************/
+/*	initialization / cleanup					*/
+/************************************************************************/
+
+static int therm_of_probe(struct platform_device *dev)
+{
+	return i2c_add_driver( &g4fan_driver );
+}
+
+static int
+therm_of_remove( struct platform_device *dev )
+{
+	i2c_del_driver( &g4fan_driver );
+	return 0;
+}
+
+static const struct of_device_id therm_of_match[] = {{
+	.name		= "fan",
+	.compatible	= "adm1030"
+    }, {}
+};
+MODULE_DEVICE_TABLE(of, therm_of_match);
+
+static struct platform_driver therm_of_driver = {
+	.driver = {
+		.name = "temperature",
+		.of_match_table = therm_of_match,
+	},
+	.probe		= therm_of_probe,
+	.remove		= therm_of_remove,
+};
+
+struct apple_thermal_info {
+	u8		id;			/* implementation ID */
+	u8		fan_count;		/* number of fans */
+	u8		thermostat_count;	/* number of thermostats */
+	u8		unused;
+};
+
+static int __init
+g4fan_init( void )
+{
+	const struct apple_thermal_info *info;
+	struct device_node *np;
+
+	mutex_init(&x.lock);
+
+	if( !(np=of_find_node_by_name(NULL, "power-mgt")) )
+		return -ENODEV;
+	info = of_get_property(np, "thermal-info", NULL);
+	of_node_put(np);
+
+	if( !info || !of_machine_is_compatible("PowerMac3,6") )
+		return -ENODEV;
+
+	if( info->id != 3 ) {
+		printk(KERN_ERR "therm_windtunnel: unsupported thermal design %d\n", info->id );
+		return -ENODEV;
+	}
+	if( !(np=of_find_node_by_name(NULL, "fan")) )
+		return -ENODEV;
+	x.of_dev = of_platform_device_create(np, "temperature", NULL);
+	of_node_put( np );
+
+	if( !x.of_dev ) {
+		printk(KERN_ERR "Can't register fan controller!\n");
+		return -ENODEV;
+	}
+
+	platform_driver_register( &therm_of_driver );
+	return 0;
+}
+
+static void __exit
+g4fan_exit( void )
+{
+	platform_driver_unregister( &therm_of_driver );
+
+	if( x.of_dev )
+		of_device_unregister( x.of_dev );
+}
+
+module_init(g4fan_init);
+module_exit(g4fan_exit);
+
+MODULE_AUTHOR("Samuel Rydh <samuel@ibrium.se>");
+MODULE_DESCRIPTION("Apple G4 (windtunnel) fan controller");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-cuda.c b/src/kernel/linux/v4.14/drivers/macintosh/via-cuda.c
new file mode 100644
index 0000000..98dd702
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-cuda.c
@@ -0,0 +1,768 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for the Cuda and Egret system controllers found on PowerMacs
+ * and 68k Macs.
+ *
+ * The Cuda or Egret is a 6805 microcontroller interfaced to the 6522 VIA.
+ * This MCU controls system power, Parameter RAM, Real Time Clock and the
+ * Apple Desktop Bus (ADB) that connects to the keyboard and mouse.
+ *
+ * Copyright (C) 1996 Paul Mackerras.
+ */
+#include <stdarg.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/adb.h>
+#include <linux/cuda.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#ifdef CONFIG_PPC
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#else
+#include <asm/macintosh.h>
+#include <asm/macints.h>
+#include <asm/mac_via.h>
+#endif
+#include <asm/io.h>
+#include <linux/init.h>
+
+static volatile unsigned char __iomem *via;
+static DEFINE_SPINLOCK(cuda_lock);
+
+/* VIA registers - spaced 0x200 bytes apart */
+#define RS		0x200		/* skip between registers */
+#define B		0		/* B-side data */
+#define A		RS		/* A-side data */
+#define DIRB		(2*RS)		/* B-side direction (1=output) */
+#define DIRA		(3*RS)		/* A-side direction (1=output) */
+#define T1CL		(4*RS)		/* Timer 1 ctr/latch (low 8 bits) */
+#define T1CH		(5*RS)		/* Timer 1 counter (high 8 bits) */
+#define T1LL		(6*RS)		/* Timer 1 latch (low 8 bits) */
+#define T1LH		(7*RS)		/* Timer 1 latch (high 8 bits) */
+#define T2CL		(8*RS)		/* Timer 2 ctr/latch (low 8 bits) */
+#define T2CH		(9*RS)		/* Timer 2 counter (high 8 bits) */
+#define SR		(10*RS)		/* Shift register */
+#define ACR		(11*RS)		/* Auxiliary control register */
+#define PCR		(12*RS)		/* Peripheral control register */
+#define IFR		(13*RS)		/* Interrupt flag register */
+#define IER		(14*RS)		/* Interrupt enable register */
+#define ANH		(15*RS)		/* A-side data, no handshake */
+
+/*
+ * When the Cuda design replaced the Egret, some signal names and
+ * logic sense changed. They all serve the same purposes, however.
+ *
+ *   VIA pin       |  Egret pin
+ * ----------------+------------------------------------------
+ *   PB3 (input)   |  Transceiver session   (active low)
+ *   PB4 (output)  |  VIA full              (active high)
+ *   PB5 (output)  |  System session        (active high)
+ *
+ *   VIA pin       |  Cuda pin
+ * ----------------+------------------------------------------
+ *   PB3 (input)   |  Transfer request      (active low)
+ *   PB4 (output)  |  Byte acknowledge      (active low)
+ *   PB5 (output)  |  Transfer in progress  (active low)
+ */
+
+/* Bits in Port B data register */
+#define TREQ		0x08		/* Transfer request */
+#define TACK		0x10		/* Transfer acknowledge */
+#define TIP		0x20		/* Transfer in progress */
+
+/* Bits in ACR */
+#define SR_CTRL		0x1c		/* Shift register control bits */
+#define SR_EXT		0x0c		/* Shift on external clock */
+#define SR_OUT		0x10		/* Shift out if 1 */
+
+/* Bits in IFR and IER */
+#define IER_SET		0x80		/* set bits in IER */
+#define IER_CLR		0		/* clear bits in IER */
+#define SR_INT		0x04		/* Shift register full/empty */
+
+/* Duration of byte acknowledgement pulse (us) */
+#define EGRET_TACK_ASSERTED_DELAY	300
+#define EGRET_TACK_NEGATED_DELAY	400
+
+/* Interval from interrupt to start of session (us) */
+#define EGRET_SESSION_DELAY		450
+
+#ifdef CONFIG_PPC
+#define mcu_is_egret	false
+#else
+static bool mcu_is_egret;
+#endif
+
+static inline bool TREQ_asserted(u8 portb)
+{
+	return !(portb & TREQ);
+}
+
+static inline void assert_TIP(void)
+{
+	if (mcu_is_egret) {
+		udelay(EGRET_SESSION_DELAY);
+		out_8(&via[B], in_8(&via[B]) | TIP);
+	} else
+		out_8(&via[B], in_8(&via[B]) & ~TIP);
+}
+
+static inline void assert_TIP_and_TACK(void)
+{
+	if (mcu_is_egret) {
+		udelay(EGRET_SESSION_DELAY);
+		out_8(&via[B], in_8(&via[B]) | TIP | TACK);
+	} else
+		out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK));
+}
+
+static inline void assert_TACK(void)
+{
+	if (mcu_is_egret) {
+		udelay(EGRET_TACK_NEGATED_DELAY);
+		out_8(&via[B], in_8(&via[B]) | TACK);
+	} else
+		out_8(&via[B], in_8(&via[B]) & ~TACK);
+}
+
+static inline void toggle_TACK(void)
+{
+	out_8(&via[B], in_8(&via[B]) ^ TACK);
+}
+
+static inline void negate_TACK(void)
+{
+	if (mcu_is_egret) {
+		udelay(EGRET_TACK_ASSERTED_DELAY);
+		out_8(&via[B], in_8(&via[B]) & ~TACK);
+	} else
+		out_8(&via[B], in_8(&via[B]) | TACK);
+}
+
+static inline void negate_TIP_and_TACK(void)
+{
+	if (mcu_is_egret) {
+		udelay(EGRET_TACK_ASSERTED_DELAY);
+		out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK));
+	} else
+		out_8(&via[B], in_8(&via[B]) | TIP | TACK);
+}
+
+static enum cuda_state {
+    idle,
+    sent_first_byte,
+    sending,
+    reading,
+    read_done,
+    awaiting_reply
+} cuda_state;
+
+static struct adb_request *current_req;
+static struct adb_request *last_req;
+static unsigned char cuda_rbuf[16];
+static unsigned char *reply_ptr;
+static int reading_reply;
+static int data_index;
+static int cuda_irq;
+#ifdef CONFIG_PPC
+static struct device_node *vias;
+#endif
+static int cuda_fully_inited;
+
+#ifdef CONFIG_ADB
+static int cuda_probe(void);
+static int cuda_send_request(struct adb_request *req, int sync);
+static int cuda_adb_autopoll(int devs);
+static int cuda_reset_adb_bus(void);
+#endif /* CONFIG_ADB */
+
+static int cuda_init_via(void);
+static void cuda_start(void);
+static irqreturn_t cuda_interrupt(int irq, void *arg);
+static void cuda_input(unsigned char *buf, int nb);
+void cuda_poll(void);
+static int cuda_write(struct adb_request *req);
+
+int cuda_request(struct adb_request *req,
+		 void (*done)(struct adb_request *), int nbytes, ...);
+
+#ifdef CONFIG_ADB
+struct adb_driver via_cuda_driver = {
+	.name         = "CUDA",
+	.probe        = cuda_probe,
+	.send_request = cuda_send_request,
+	.autopoll     = cuda_adb_autopoll,
+	.poll         = cuda_poll,
+	.reset_bus    = cuda_reset_adb_bus,
+};
+#endif /* CONFIG_ADB */
+
+#ifdef CONFIG_MAC
+int __init find_via_cuda(void)
+{
+    struct adb_request req;
+    int err;
+
+    if (macintosh_config->adb_type != MAC_ADB_CUDA &&
+        macintosh_config->adb_type != MAC_ADB_EGRET)
+	return 0;
+
+    via = via1;
+    cuda_state = idle;
+    mcu_is_egret = macintosh_config->adb_type == MAC_ADB_EGRET;
+
+    err = cuda_init_via();
+    if (err) {
+	printk(KERN_ERR "cuda_init_via() failed\n");
+	via = NULL;
+	return 0;
+    }
+
+    /* enable autopoll */
+    cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, 1);
+    while (!req.complete)
+	cuda_poll();
+
+    return 1;
+}
+#else
+int __init find_via_cuda(void)
+{
+    struct adb_request req;
+    phys_addr_t taddr;
+    const u32 *reg;
+    int err;
+
+    if (vias != 0)
+	return 1;
+    vias = of_find_node_by_name(NULL, "via-cuda");
+    if (vias == 0)
+	return 0;
+
+    reg = of_get_property(vias, "reg", NULL);
+    if (reg == NULL) {
+	    printk(KERN_ERR "via-cuda: No \"reg\" property !\n");
+	    goto fail;
+    }
+    taddr = of_translate_address(vias, reg);
+    if (taddr == 0) {
+	    printk(KERN_ERR "via-cuda: Can't translate address !\n");
+	    goto fail;
+    }
+    via = ioremap(taddr, 0x2000);
+    if (via == NULL) {
+	    printk(KERN_ERR "via-cuda: Can't map address !\n");
+	    goto fail;
+    }
+
+    cuda_state = idle;
+    sys_ctrler = SYS_CTRLER_CUDA;
+
+    err = cuda_init_via();
+    if (err) {
+	printk(KERN_ERR "cuda_init_via() failed\n");
+	via = NULL;
+	return 0;
+    }
+
+    /* Clear and enable interrupts, but only on PPC. On 68K it's done  */
+    /* for us by the main VIA driver in arch/m68k/mac/via.c        */
+
+    out_8(&via[IFR], 0x7f);	/* clear interrupts by writing 1s */
+    out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */
+
+    /* enable autopoll */
+    cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, 1);
+    while (!req.complete)
+	cuda_poll();
+
+    return 1;
+
+ fail:
+    of_node_put(vias);
+    vias = NULL;
+    return 0;
+}
+#endif /* !defined CONFIG_MAC */
+
+static int __init via_cuda_start(void)
+{
+    if (via == NULL)
+	return -ENODEV;
+
+#ifdef CONFIG_MAC
+    cuda_irq = IRQ_MAC_ADB;
+#else
+    cuda_irq = irq_of_parse_and_map(vias, 0);
+    if (!cuda_irq) {
+	printk(KERN_ERR "via-cuda: can't map interrupts for %pOF\n",
+	       vias);
+	return -ENODEV;
+    }
+#endif
+
+    if (request_irq(cuda_irq, cuda_interrupt, 0, "ADB", cuda_interrupt)) {
+	printk(KERN_ERR "via-cuda: can't request irq %d\n", cuda_irq);
+	return -EAGAIN;
+    }
+
+    pr_info("Macintosh Cuda and Egret driver.\n");
+
+    cuda_fully_inited = 1;
+    return 0;
+}
+
+device_initcall(via_cuda_start);
+
+#ifdef CONFIG_ADB
+static int
+cuda_probe(void)
+{
+#ifdef CONFIG_PPC
+    if (sys_ctrler != SYS_CTRLER_CUDA)
+	return -ENODEV;
+#else
+    if (macintosh_config->adb_type != MAC_ADB_CUDA &&
+        macintosh_config->adb_type != MAC_ADB_EGRET)
+	return -ENODEV;
+#endif
+    if (via == NULL)
+	return -ENODEV;
+    return 0;
+}
+#endif /* CONFIG_ADB */
+
+static int __init sync_egret(void)
+{
+	if (TREQ_asserted(in_8(&via[B]))) {
+		/* Complete the inbound transfer */
+		assert_TIP_and_TACK();
+		while (1) {
+			negate_TACK();
+			mdelay(1);
+			(void)in_8(&via[SR]);
+			assert_TACK();
+			if (!TREQ_asserted(in_8(&via[B])))
+				break;
+		}
+		negate_TIP_and_TACK();
+	} else if (in_8(&via[B]) & TIP) {
+		/* Terminate the outbound transfer */
+		negate_TACK();
+		assert_TACK();
+		mdelay(1);
+		negate_TIP_and_TACK();
+	}
+	/* Clear shift register interrupt */
+	if (in_8(&via[IFR]) & SR_INT)
+		(void)in_8(&via[SR]);
+	return 0;
+}
+
+#define WAIT_FOR(cond, what)					\
+    do {                                                        \
+    	int x;							\
+	for (x = 1000; !(cond); --x) {				\
+	    if (x == 0) {					\
+		pr_err("Timeout waiting for " what "\n");	\
+		return -ENXIO;					\
+	    }							\
+	    udelay(100);					\
+	}							\
+    } while (0)
+
+static int
+__init cuda_init_via(void)
+{
+#ifdef CONFIG_PPC
+    out_8(&via[IER], 0x7f);					/* disable interrupts from VIA */
+    (void)in_8(&via[IER]);
+#else
+    out_8(&via[IER], SR_INT);					/* disable SR interrupt from VIA */
+#endif
+
+    out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ);	/* TACK & TIP out */
+    out_8(&via[ACR], (in_8(&via[ACR]) & ~SR_CTRL) | SR_EXT);	/* SR data in */
+    (void)in_8(&via[SR]);					/* clear any left-over data */
+
+    if (mcu_is_egret)
+	return sync_egret();
+
+    negate_TIP_and_TACK();
+
+    /* delay 4ms and then clear any pending interrupt */
+    mdelay(4);
+    (void)in_8(&via[SR]);
+    out_8(&via[IFR], SR_INT);
+
+    /* sync with the CUDA - assert TACK without TIP */
+    assert_TACK();
+
+    /* wait for the CUDA to assert TREQ in response */
+    WAIT_FOR(TREQ_asserted(in_8(&via[B])), "CUDA response to sync");
+
+    /* wait for the interrupt and then clear it */
+    WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (2)");
+    (void)in_8(&via[SR]);
+    out_8(&via[IFR], SR_INT);
+
+    /* finish the sync by negating TACK */
+    negate_TACK();
+
+    /* wait for the CUDA to negate TREQ and the corresponding interrupt */
+    WAIT_FOR(!TREQ_asserted(in_8(&via[B])), "CUDA response to sync (3)");
+    WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (4)");
+    (void)in_8(&via[SR]);
+    out_8(&via[IFR], SR_INT);
+
+    return 0;
+}
+
+#ifdef CONFIG_ADB
+/* Send an ADB command */
+static int
+cuda_send_request(struct adb_request *req, int sync)
+{
+    int i;
+
+    if ((via == NULL) || !cuda_fully_inited) {
+	req->complete = 1;
+	return -ENXIO;
+    }
+  
+    req->reply_expected = 1;
+
+    i = cuda_write(req);
+    if (i)
+	return i;
+
+    if (sync) {
+	while (!req->complete)
+	    cuda_poll();
+    }
+    return 0;
+}
+
+
+/* Enable/disable autopolling */
+static int
+cuda_adb_autopoll(int devs)
+{
+    struct adb_request req;
+
+    if ((via == NULL) || !cuda_fully_inited)
+	return -ENXIO;
+
+    cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, (devs? 1: 0));
+    while (!req.complete)
+	cuda_poll();
+    return 0;
+}
+
+/* Reset adb bus - how do we do this?? */
+static int
+cuda_reset_adb_bus(void)
+{
+    struct adb_request req;
+
+    if ((via == NULL) || !cuda_fully_inited)
+	return -ENXIO;
+
+    cuda_request(&req, NULL, 2, ADB_PACKET, 0);		/* maybe? */
+    while (!req.complete)
+	cuda_poll();
+    return 0;
+}
+#endif /* CONFIG_ADB */
+
+/* Construct and send a cuda request */
+int
+cuda_request(struct adb_request *req, void (*done)(struct adb_request *),
+	     int nbytes, ...)
+{
+    va_list list;
+    int i;
+
+    if (via == NULL) {
+	req->complete = 1;
+	return -ENXIO;
+    }
+
+    req->nbytes = nbytes;
+    req->done = done;
+    va_start(list, nbytes);
+    for (i = 0; i < nbytes; ++i)
+	req->data[i] = va_arg(list, int);
+    va_end(list);
+    req->reply_expected = 1;
+    return cuda_write(req);
+}
+EXPORT_SYMBOL(cuda_request);
+
+static int
+cuda_write(struct adb_request *req)
+{
+    unsigned long flags;
+
+    if (req->nbytes < 2 || req->data[0] > CUDA_PACKET) {
+	req->complete = 1;
+	return -EINVAL;
+    }
+    req->next = NULL;
+    req->sent = 0;
+    req->complete = 0;
+    req->reply_len = 0;
+
+    spin_lock_irqsave(&cuda_lock, flags);
+    if (current_req != 0) {
+	last_req->next = req;
+	last_req = req;
+    } else {
+	current_req = req;
+	last_req = req;
+	if (cuda_state == idle)
+	    cuda_start();
+    }
+    spin_unlock_irqrestore(&cuda_lock, flags);
+
+    return 0;
+}
+
+static void
+cuda_start(void)
+{
+    /* assert cuda_state == idle */
+    if (current_req == NULL)
+	return;
+    data_index = 0;
+    if (TREQ_asserted(in_8(&via[B])))
+	return;			/* a byte is coming in from the CUDA */
+
+    /* set the shift register to shift out and send a byte */
+    out_8(&via[ACR], in_8(&via[ACR]) | SR_OUT);
+    out_8(&via[SR], current_req->data[data_index++]);
+    if (mcu_is_egret)
+	assert_TIP_and_TACK();
+    else
+	assert_TIP();
+    cuda_state = sent_first_byte;
+}
+
+void
+cuda_poll(void)
+{
+	cuda_interrupt(0, NULL);
+}
+EXPORT_SYMBOL(cuda_poll);
+
+#define ARRAY_FULL(a, p)	((p) - (a) == ARRAY_SIZE(a))
+
+static irqreturn_t
+cuda_interrupt(int irq, void *arg)
+{
+    unsigned long flags;
+    u8 status;
+    struct adb_request *req = NULL;
+    unsigned char ibuf[16];
+    int ibuf_len = 0;
+    int complete = 0;
+    
+    spin_lock_irqsave(&cuda_lock, flags);
+
+    /* On powermacs, this handler is registered for the VIA IRQ. But they use
+     * just the shift register IRQ -- other VIA interrupt sources are disabled.
+     * On m68k macs, the VIA IRQ sources are dispatched individually. Unless
+     * we are polling, the shift register IRQ flag has already been cleared.
+     */
+
+#ifdef CONFIG_MAC
+    if (!arg)
+#endif
+    {
+        if ((in_8(&via[IFR]) & SR_INT) == 0) {
+            spin_unlock_irqrestore(&cuda_lock, flags);
+            return IRQ_NONE;
+        } else {
+            out_8(&via[IFR], SR_INT);
+        }
+    }
+
+    status = in_8(&via[B]) & (TIP | TACK | TREQ);
+
+    switch (cuda_state) {
+    case idle:
+	/* System controller has unsolicited data for us */
+	(void)in_8(&via[SR]);
+idle_state:
+	assert_TIP();
+	cuda_state = reading;
+	reply_ptr = cuda_rbuf;
+	reading_reply = 0;
+	break;
+
+    case awaiting_reply:
+	/* System controller has reply data for us */
+	(void)in_8(&via[SR]);
+	assert_TIP();
+	cuda_state = reading;
+	reply_ptr = current_req->reply;
+	reading_reply = 1;
+	break;
+
+    case sent_first_byte:
+	if (TREQ_asserted(status)) {
+	    /* collision */
+	    out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT);
+	    (void)in_8(&via[SR]);
+	    negate_TIP_and_TACK();
+	    cuda_state = idle;
+	    /* Egret does not raise an "aborted" interrupt */
+	    if (mcu_is_egret)
+		goto idle_state;
+	} else {
+	    out_8(&via[SR], current_req->data[data_index++]);
+	    toggle_TACK();
+	    if (mcu_is_egret)
+		assert_TACK();
+	    cuda_state = sending;
+	}
+	break;
+
+    case sending:
+	req = current_req;
+	if (data_index >= req->nbytes) {
+	    out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT);
+	    (void)in_8(&via[SR]);
+	    negate_TIP_and_TACK();
+	    req->sent = 1;
+	    if (req->reply_expected) {
+		cuda_state = awaiting_reply;
+	    } else {
+		current_req = req->next;
+		complete = 1;
+		/* not sure about this */
+		cuda_state = idle;
+		cuda_start();
+	    }
+	} else {
+	    out_8(&via[SR], req->data[data_index++]);
+	    toggle_TACK();
+	    if (mcu_is_egret)
+		assert_TACK();
+	}
+	break;
+
+    case reading:
+	if (reading_reply ? ARRAY_FULL(current_req->reply, reply_ptr)
+	                  : ARRAY_FULL(cuda_rbuf, reply_ptr))
+	    (void)in_8(&via[SR]);
+	else
+	    *reply_ptr++ = in_8(&via[SR]);
+	if (!TREQ_asserted(status)) {
+	    if (mcu_is_egret)
+		assert_TACK();
+	    /* that's all folks */
+	    negate_TIP_and_TACK();
+	    cuda_state = read_done;
+	    /* Egret does not raise a "read done" interrupt */
+	    if (mcu_is_egret)
+		goto read_done_state;
+	} else {
+	    toggle_TACK();
+	    if (mcu_is_egret)
+		negate_TACK();
+	}
+	break;
+
+    case read_done:
+	(void)in_8(&via[SR]);
+read_done_state:
+	if (reading_reply) {
+	    req = current_req;
+	    req->reply_len = reply_ptr - req->reply;
+	    if (req->data[0] == ADB_PACKET) {
+		/* Have to adjust the reply from ADB commands */
+		if (req->reply_len <= 2 || (req->reply[1] & 2) != 0) {
+		    /* the 0x2 bit indicates no response */
+		    req->reply_len = 0;
+		} else {
+		    /* leave just the command and result bytes in the reply */
+		    req->reply_len -= 2;
+		    memmove(req->reply, req->reply + 2, req->reply_len);
+		}
+	    }
+	    current_req = req->next;
+	    complete = 1;
+	    reading_reply = 0;
+	} else {
+	    /* This is tricky. We must break the spinlock to call
+	     * cuda_input. However, doing so means we might get
+	     * re-entered from another CPU getting an interrupt
+	     * or calling cuda_poll(). I ended up using the stack
+	     * (it's only for 16 bytes) and moving the actual
+	     * call to cuda_input to outside of the lock.
+	     */
+	    ibuf_len = reply_ptr - cuda_rbuf;
+	    memcpy(ibuf, cuda_rbuf, ibuf_len);
+	}
+	reply_ptr = cuda_rbuf;
+	cuda_state = idle;
+	cuda_start();
+	if (cuda_state == idle && TREQ_asserted(in_8(&via[B]))) {
+	    assert_TIP();
+	    cuda_state = reading;
+	}
+	break;
+
+    default:
+	pr_err("cuda_interrupt: unknown cuda_state %d?\n", cuda_state);
+    }
+    spin_unlock_irqrestore(&cuda_lock, flags);
+    if (complete && req) {
+    	void (*done)(struct adb_request *) = req->done;
+    	mb();
+    	req->complete = 1;
+    	/* Here, we assume that if the request has a done member, the
+    	 * struct request will survive to setting req->complete to 1
+    	 */
+    	if (done)
+		(*done)(req);
+    }
+    if (ibuf_len)
+	cuda_input(ibuf, ibuf_len);
+    return IRQ_HANDLED;
+}
+
+static void
+cuda_input(unsigned char *buf, int nb)
+{
+    switch (buf[0]) {
+    case ADB_PACKET:
+#ifdef CONFIG_XMON
+	if (nb == 5 && buf[2] == 0x2c) {
+	    extern int xmon_wants_key, xmon_adb_keycode;
+	    if (xmon_wants_key) {
+		xmon_adb_keycode = buf[3];
+		return;
+	    }
+	}
+#endif /* CONFIG_XMON */
+#ifdef CONFIG_ADB
+	adb_input(buf+2, nb-2, buf[1] & 0x40);
+#endif /* CONFIG_ADB */
+	break;
+
+    case TIMER_PACKET:
+	/* Egret sends these periodically. Might be useful as a 'heartbeat'
+	 * to trigger a recovery for the VIA shift register errata.
+	 */
+	break;
+
+    default:
+	print_hex_dump(KERN_INFO, "cuda_input: ", DUMP_PREFIX_NONE, 32, 1,
+	               buf, nb, false);
+    }
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-macii.c b/src/kernel/linux/v4.14/drivers/macintosh/via-macii.c
new file mode 100644
index 0000000..4ba06a1
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-macii.c
@@ -0,0 +1,550 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for the via ADB on (many) Mac II-class machines
+ *
+ * Based on the original ADB keyboard handler Copyright (c) 1997 Alan Cox
+ * Also derived from code Copyright (C) 1996 Paul Mackerras.
+ *
+ * With various updates provided over the years by Michael Schmitz,
+ * Guideo Koerber and others.
+ *
+ * Rewrite for Unified ADB by Joshua M. Thompson (funaho@jurai.org)
+ *
+ * 1999-08-02 (jmt) - Initial rewrite for Unified ADB.
+ * 2000-03-29 Tony Mantler <tonym@mac.linux-m68k.org>
+ * 				- Big overhaul, should actually work now.
+ * 2006-12-31 Finn Thain - Another overhaul.
+ *
+ * Suggested reading:
+ *   Inside Macintosh, ch. 5 ADB Manager
+ *   Guide to the Macinstosh Family Hardware, ch. 8 Apple Desktop Bus
+ *   Rockwell R6522 VIA datasheet
+ *
+ * Apple's "ADB Analyzer" bus sniffer is invaluable:
+ *   ftp://ftp.apple.com/developer/Tool_Chest/Devices_-_Hardware/Apple_Desktop_Bus/
+ */
+ 
+#include <stdarg.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/adb.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <asm/macintosh.h>
+#include <asm/macints.h>
+#include <asm/mac_via.h>
+
+static volatile unsigned char *via;
+
+/* VIA registers - spaced 0x200 bytes apart */
+#define RS		0x200		/* skip between registers */
+#define B		0		/* B-side data */
+#define A		RS		/* A-side data */
+#define DIRB		(2*RS)		/* B-side direction (1=output) */
+#define DIRA		(3*RS)		/* A-side direction (1=output) */
+#define T1CL		(4*RS)		/* Timer 1 ctr/latch (low 8 bits) */
+#define T1CH		(5*RS)		/* Timer 1 counter (high 8 bits) */
+#define T1LL		(6*RS)		/* Timer 1 latch (low 8 bits) */
+#define T1LH		(7*RS)		/* Timer 1 latch (high 8 bits) */
+#define T2CL		(8*RS)		/* Timer 2 ctr/latch (low 8 bits) */
+#define T2CH		(9*RS)		/* Timer 2 counter (high 8 bits) */
+#define SR		(10*RS)		/* Shift register */
+#define ACR		(11*RS)		/* Auxiliary control register */
+#define PCR		(12*RS)		/* Peripheral control register */
+#define IFR		(13*RS)		/* Interrupt flag register */
+#define IER		(14*RS)		/* Interrupt enable register */
+#define ANH		(15*RS)		/* A-side data, no handshake */
+
+/* Bits in B data register: all active low */
+#define CTLR_IRQ	0x08		/* Controller rcv status (input) */
+#define ST_MASK		0x30		/* mask for selecting ADB state bits */
+
+/* Bits in ACR */
+#define SR_CTRL		0x1c		/* Shift register control bits */
+#define SR_EXT		0x0c		/* Shift on external clock */
+#define SR_OUT		0x10		/* Shift out if 1 */
+
+/* Bits in IFR and IER */
+#define IER_SET		0x80		/* set bits in IER */
+#define IER_CLR		0		/* clear bits in IER */
+#define SR_INT		0x04		/* Shift register full/empty */
+
+/* ADB transaction states according to GMHW */
+#define ST_CMD		0x00		/* ADB state: command byte */
+#define ST_EVEN		0x10		/* ADB state: even data byte */
+#define ST_ODD		0x20		/* ADB state: odd data byte */
+#define ST_IDLE		0x30		/* ADB state: idle, nothing to send */
+
+static int  macii_init_via(void);
+static void macii_start(void);
+static irqreturn_t macii_interrupt(int irq, void *arg);
+static void macii_queue_poll(void);
+
+static int macii_probe(void);
+static int macii_init(void);
+static int macii_send_request(struct adb_request *req, int sync);
+static int macii_write(struct adb_request *req);
+static int macii_autopoll(int devs);
+static void macii_poll(void);
+static int macii_reset_bus(void);
+
+struct adb_driver via_macii_driver = {
+	"Mac II",
+	macii_probe,
+	macii_init,
+	macii_send_request,
+	macii_autopoll,
+	macii_poll,
+	macii_reset_bus
+};
+
+static enum macii_state {
+	idle,
+	sending,
+	reading,
+	read_done,
+} macii_state;
+
+static struct adb_request *current_req; /* first request struct in the queue */
+static struct adb_request *last_req;     /* last request struct in the queue */
+static unsigned char reply_buf[16];        /* storage for autopolled replies */
+static unsigned char *reply_ptr;     /* next byte in reply_buf or req->reply */
+static int reading_reply;        /* store reply in reply_buf else req->reply */
+static int data_index;      /* index of the next byte to send from req->data */
+static int reply_len; /* number of bytes received in reply_buf or req->reply */
+static int status;          /* VIA's ADB status bits captured upon interrupt */
+static int last_status;              /* status bits as at previous interrupt */
+static int srq_asserted;     /* have to poll for the device that asserted it */
+static int command_byte;         /* the most recent command byte transmitted */
+static int autopoll_devs;      /* bits set are device addresses to be polled */
+
+/* Sanity check for request queue. Doesn't check for cycles. */
+static int request_is_queued(struct adb_request *req) {
+	struct adb_request *cur;
+	unsigned long flags;
+	local_irq_save(flags);
+	cur = current_req;
+	while (cur) {
+		if (cur == req) {
+			local_irq_restore(flags);
+			return 1;
+		}
+		cur = cur->next;
+	}
+	local_irq_restore(flags);
+	return 0;
+}
+
+/* Check for MacII style ADB */
+static int macii_probe(void)
+{
+	if (macintosh_config->adb_type != MAC_ADB_II) return -ENODEV;
+
+	via = via1;
+
+	printk("adb: Mac II ADB Driver v1.0 for Unified ADB\n");
+	return 0;
+}
+
+/* Initialize the driver */
+int macii_init(void)
+{
+	unsigned long flags;
+	int err;
+	
+	local_irq_save(flags);
+	
+	err = macii_init_via();
+	if (err) goto out;
+
+	err = request_irq(IRQ_MAC_ADB, macii_interrupt, 0, "ADB",
+			  macii_interrupt);
+	if (err) goto out;
+
+	macii_state = idle;
+out:
+	local_irq_restore(flags);
+	return err;
+}
+
+/* initialize the hardware */	
+static int macii_init_via(void)
+{
+	unsigned char x;
+
+	/* We want CTLR_IRQ as input and ST_EVEN | ST_ODD as output lines. */
+	via[DIRB] = (via[DIRB] | ST_EVEN | ST_ODD) & ~CTLR_IRQ;
+
+	/* Set up state: idle */
+	via[B] |= ST_IDLE;
+	last_status = via[B] & (ST_MASK|CTLR_IRQ);
+
+	/* Shift register on input */
+	via[ACR] = (via[ACR] & ~SR_CTRL) | SR_EXT;
+
+	/* Wipe any pending data and int */
+	x = via[SR];
+
+	return 0;
+}
+
+/* Send an ADB poll (Talk Register 0 command prepended to the request queue) */
+static void macii_queue_poll(void)
+{
+	/* No point polling the active device as it will never assert SRQ, so
+	 * poll the next device in the autopoll list. This could leave us
+	 * stuck in a polling loop if an unprobed device is asserting SRQ.
+	 * In theory, that could only happen if a device was plugged in after
+	 * probing started. Unplugging it again will break the cycle.
+	 * (Simply polling the next higher device often ends up polling almost
+	 * every device (after wrapping around), which takes too long.)
+	 */
+	int device_mask;
+	int next_device;
+	static struct adb_request req;
+
+	if (!autopoll_devs) return;
+
+	device_mask = (1 << (((command_byte & 0xF0) >> 4) + 1)) - 1;
+	if (autopoll_devs & ~device_mask)
+		next_device = ffs(autopoll_devs & ~device_mask) - 1;
+	else
+		next_device = ffs(autopoll_devs) - 1;
+
+	BUG_ON(request_is_queued(&req));
+
+	adb_request(&req, NULL, ADBREQ_NOSEND, 1,
+	            ADB_READREG(next_device, 0));
+
+	req.sent = 0;
+	req.complete = 0;
+	req.reply_len = 0;
+	req.next = current_req;
+
+	if (current_req != NULL) {
+		current_req = &req;
+	} else {
+		current_req = &req;
+		last_req = &req;
+	}
+}
+
+/* Send an ADB request; if sync, poll out the reply 'till it's done */
+static int macii_send_request(struct adb_request *req, int sync)
+{
+	int err;
+	unsigned long flags;
+
+	BUG_ON(request_is_queued(req));
+
+	local_irq_save(flags);
+	err = macii_write(req);
+	local_irq_restore(flags);
+
+	if (!err && sync) {
+		while (!req->complete) {
+			macii_poll();
+		}
+		BUG_ON(request_is_queued(req));
+	}
+
+	return err;
+}
+
+/* Send an ADB request (append to request queue) */
+static int macii_write(struct adb_request *req)
+{
+	if (req->nbytes < 2 || req->data[0] != ADB_PACKET || req->nbytes > 15) {
+		req->complete = 1;
+		return -EINVAL;
+	}
+	
+	req->next = NULL;
+	req->sent = 0;
+	req->complete = 0;
+	req->reply_len = 0;
+
+	if (current_req != NULL) {
+		last_req->next = req;
+		last_req = req;
+	} else {
+		current_req = req;
+		last_req = req;
+		if (macii_state == idle) macii_start();
+	}
+	return 0;
+}
+
+/* Start auto-polling */
+static int macii_autopoll(int devs)
+{
+	static struct adb_request req;
+	unsigned long flags;
+	int err = 0;
+
+	/* bit 1 == device 1, and so on. */
+	autopoll_devs = devs & 0xFFFE;
+
+	if (!autopoll_devs) return 0;
+
+	local_irq_save(flags);
+
+	if (current_req == NULL) {
+		/* Send a Talk Reg 0. The controller will repeatedly transmit
+		 * this as long as it is idle.
+		 */
+		adb_request(&req, NULL, ADBREQ_NOSEND, 1,
+		            ADB_READREG(ffs(autopoll_devs) - 1, 0));
+		err = macii_write(&req);
+	}
+
+	local_irq_restore(flags);
+	return err;
+}
+
+static inline int need_autopoll(void) {
+	/* Was the last command Talk Reg 0
+	 * and is the target on the autopoll list?
+	 */
+	if ((command_byte & 0x0F) == 0x0C &&
+	    ((1 << ((command_byte & 0xF0) >> 4)) & autopoll_devs))
+		return 0;
+	return 1;
+}
+
+/* Prod the chip without interrupts */
+static void macii_poll(void)
+{
+	disable_irq(IRQ_MAC_ADB);
+	macii_interrupt(0, NULL);
+	enable_irq(IRQ_MAC_ADB);
+}
+
+/* Reset the bus */
+static int macii_reset_bus(void)
+{
+	static struct adb_request req;
+	
+	if (request_is_queued(&req))
+		return 0;
+
+	/* Command = 0, Address = ignored */
+	adb_request(&req, NULL, 0, 1, ADB_BUSRESET);
+
+	/* Don't want any more requests during the Global Reset low time. */
+	udelay(3000);
+
+	return 0;
+}
+
+/* Start sending ADB packet */
+static void macii_start(void)
+{
+	struct adb_request *req;
+
+	req = current_req;
+
+	BUG_ON(req == NULL);
+
+	BUG_ON(macii_state != idle);
+
+	/* Now send it. Be careful though, that first byte of the request
+	 * is actually ADB_PACKET; the real data begins at index 1!
+	 * And req->nbytes is the number of bytes of real data plus one.
+	 */
+
+	/* store command byte */
+	command_byte = req->data[1];
+	/* Output mode */
+	via[ACR] |= SR_OUT;
+	/* Load data */
+	via[SR] = req->data[1];
+	/* set ADB state to 'command' */
+	via[B] = (via[B] & ~ST_MASK) | ST_CMD;
+
+	macii_state = sending;
+	data_index = 2;
+}
+
+/*
+ * The notorious ADB interrupt handler - does all of the protocol handling.
+ * Relies on the ADB controller sending and receiving data, thereby
+ * generating shift register interrupts (SR_INT) for us. This means there has
+ * to be activity on the ADB bus. The chip will poll to achieve this.
+ *
+ * The basic ADB state machine was left unchanged from the original MacII code
+ * by Alan Cox, which was based on the CUDA driver for PowerMac. 
+ * The syntax of the ADB status lines is totally different on MacII,
+ * though. MacII uses the states Command -> Even -> Odd -> Even ->...-> Idle
+ * for sending and Idle -> Even -> Odd -> Even ->...-> Idle for receiving.
+ * Start and end of a receive packet are signalled by asserting /IRQ on the
+ * interrupt line (/IRQ means the CTLR_IRQ bit in port B; not to be confused
+ * with the VIA shift register interrupt. /IRQ never actually interrupts the
+ * processor, it's just an ordinary input.)
+ */
+static irqreturn_t macii_interrupt(int irq, void *arg)
+{
+	int x;
+	static int entered;
+	struct adb_request *req;
+
+	if (!arg) {
+		/* Clear the SR IRQ flag when polling. */
+		if (via[IFR] & SR_INT)
+			via[IFR] = SR_INT;
+		else
+			return IRQ_NONE;
+	}
+
+	BUG_ON(entered++);
+
+	last_status = status;
+	status = via[B] & (ST_MASK|CTLR_IRQ);
+
+	switch (macii_state) {
+		case idle:
+			if (reading_reply) {
+				reply_ptr = current_req->reply;
+			} else {
+				BUG_ON(current_req != NULL);
+				reply_ptr = reply_buf;
+			}
+
+			x = via[SR];
+
+			if ((status & CTLR_IRQ) && (x == 0xFF)) {
+				/* Bus timeout without SRQ sequence:
+				 *     data is "FF" while CTLR_IRQ is "H"
+				 */
+				reply_len = 0;
+				srq_asserted = 0;
+				macii_state = read_done;
+			} else {
+				macii_state = reading;
+				*reply_ptr = x;
+				reply_len = 1;
+			}
+
+			/* set ADB state = even for first data byte */
+			via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
+			break;
+
+		case sending:
+			req = current_req;
+			if (data_index >= req->nbytes) {
+				req->sent = 1;
+				macii_state = idle;
+
+				if (req->reply_expected) {
+					reading_reply = 1;
+				} else {
+					req->complete = 1;
+					current_req = req->next;
+					if (req->done) (*req->done)(req);
+
+					if (current_req)
+						macii_start();
+					else
+						if (need_autopoll())
+							macii_autopoll(autopoll_devs);
+				}
+
+				if (macii_state == idle) {
+					/* reset to shift in */
+					via[ACR] &= ~SR_OUT;
+					x = via[SR];
+					/* set ADB state idle - might get SRQ */
+					via[B] = (via[B] & ~ST_MASK) | ST_IDLE;
+				}
+			} else {
+				via[SR] = req->data[data_index++];
+
+				if ( (via[B] & ST_MASK) == ST_CMD ) {
+					/* just sent the command byte, set to EVEN */
+					via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
+				} else {
+					/* invert state bits, toggle ODD/EVEN */
+					via[B] ^= ST_MASK;
+				}
+			}
+			break;
+
+		case reading:
+			x = via[SR];
+			BUG_ON((status & ST_MASK) == ST_CMD ||
+			       (status & ST_MASK) == ST_IDLE);
+
+			/* Bus timeout with SRQ sequence:
+			 *     data is "XX FF"      while CTLR_IRQ is "L L"
+			 * End of packet without SRQ sequence:
+			 *     data is "XX...YY 00" while CTLR_IRQ is "L...H L"
+			 * End of packet SRQ sequence:
+			 *     data is "XX...YY 00" while CTLR_IRQ is "L...L L"
+			 * (where XX is the first response byte and
+			 * YY is the last byte of valid response data.)
+			 */
+
+			srq_asserted = 0;
+			if (!(status & CTLR_IRQ)) {
+				if (x == 0xFF) {
+					if (!(last_status & CTLR_IRQ)) {
+						macii_state = read_done;
+						reply_len = 0;
+						srq_asserted = 1;
+					}
+				} else if (x == 0x00) {
+					macii_state = read_done;
+					if (!(last_status & CTLR_IRQ))
+						srq_asserted = 1;
+				}
+			}
+
+			if (macii_state == reading) {
+				BUG_ON(reply_len > 15);
+				reply_ptr++;
+				*reply_ptr = x;
+				reply_len++;
+			}
+
+			/* invert state bits, toggle ODD/EVEN */
+			via[B] ^= ST_MASK;
+			break;
+
+		case read_done:
+			x = via[SR];
+
+			if (reading_reply) {
+				reading_reply = 0;
+				req = current_req;
+				req->reply_len = reply_len;
+				req->complete = 1;
+				current_req = req->next;
+				if (req->done) (*req->done)(req);
+			} else if (reply_len && autopoll_devs)
+				adb_input(reply_buf, reply_len, 0);
+
+			macii_state = idle;
+
+			/* SRQ seen before, initiate poll now */
+			if (srq_asserted)
+				macii_queue_poll();
+
+			if (current_req)
+				macii_start();
+			else
+				if (need_autopoll())
+					macii_autopoll(autopoll_devs);
+
+			if (macii_state == idle)
+				via[B] = (via[B] & ~ST_MASK) | ST_IDLE;
+			break;
+
+		default:
+		break;
+	}
+
+	entered--;
+	return IRQ_HANDLED;
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-backlight.c b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-backlight.c
new file mode 100644
index 0000000..89ed515
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-backlight.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Backlight code for via-pmu
+ *
+ * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
+ * Copyright (C) 2001-2002 Benjamin Herrenschmidt
+ * Copyright (C) 2006      Michael Hanselmann <linux-kernel@hansmi.ch>
+ *
+ */
+
+#include <asm/ptrace.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <asm/backlight.h>
+#include <asm/prom.h>
+
+#define MAX_PMU_LEVEL 0xFF
+
+static const struct backlight_ops pmu_backlight_data;
+static DEFINE_SPINLOCK(pmu_backlight_lock);
+static int sleeping, uses_pmu_bl;
+static u8 bl_curve[FB_BACKLIGHT_LEVELS];
+
+static void pmu_backlight_init_curve(u8 off, u8 min, u8 max)
+{
+	int i, flat, count, range = (max - min);
+
+	bl_curve[0] = off;
+
+	for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
+		bl_curve[flat] = min;
+
+	count = FB_BACKLIGHT_LEVELS * 15 / 16;
+	for (i = 0; i < count; ++i)
+		bl_curve[flat + i] = min + (range * (i + 1) / count);
+}
+
+static int pmu_backlight_curve_lookup(int value)
+{
+	int level = (FB_BACKLIGHT_LEVELS - 1);
+	int i, max = 0;
+
+	/* Look for biggest value */
+	for (i = 0; i < FB_BACKLIGHT_LEVELS; i++)
+		max = max((int)bl_curve[i], max);
+
+	/* Look for nearest value */
+	for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) {
+		int diff = abs(bl_curve[i] - value);
+		if (diff < max) {
+			max = diff;
+			level = i;
+		}
+	}
+	return level;
+}
+
+static int pmu_backlight_get_level_brightness(int level)
+{
+	int pmulevel;
+
+	/* Get and convert the value */
+	pmulevel = bl_curve[level] * FB_BACKLIGHT_MAX / MAX_PMU_LEVEL;
+	if (pmulevel < 0)
+		pmulevel = 0;
+	else if (pmulevel > MAX_PMU_LEVEL)
+		pmulevel = MAX_PMU_LEVEL;
+
+	return pmulevel;
+}
+
+static int __pmu_backlight_update_status(struct backlight_device *bd)
+{
+	struct adb_request req;
+	int level = bd->props.brightness;
+
+
+	if (bd->props.power != FB_BLANK_UNBLANK ||
+	    bd->props.fb_blank != FB_BLANK_UNBLANK)
+		level = 0;
+
+	if (level > 0) {
+		int pmulevel = pmu_backlight_get_level_brightness(level);
+
+		pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, pmulevel);
+		pmu_wait_complete(&req);
+
+		pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+			PMU_POW_BACKLIGHT | PMU_POW_ON);
+		pmu_wait_complete(&req);
+	} else {
+		pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+			PMU_POW_BACKLIGHT | PMU_POW_OFF);
+		pmu_wait_complete(&req);
+	}
+
+	return 0;
+}
+
+static int pmu_backlight_update_status(struct backlight_device *bd)
+{
+	unsigned long flags;
+	int rc = 0;
+
+	spin_lock_irqsave(&pmu_backlight_lock, flags);
+	/* Don't update brightness when sleeping */
+	if (!sleeping)
+		rc = __pmu_backlight_update_status(bd);
+	spin_unlock_irqrestore(&pmu_backlight_lock, flags);
+	return rc;
+}
+
+
+static const struct backlight_ops pmu_backlight_data = {
+	.update_status	= pmu_backlight_update_status,
+
+};
+
+#ifdef CONFIG_PM
+void pmu_backlight_set_sleep(int sleep)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pmu_backlight_lock, flags);
+	sleeping = sleep;
+	if (pmac_backlight && uses_pmu_bl) {
+		if (sleep) {
+			struct adb_request req;
+
+			pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+				    PMU_POW_BACKLIGHT | PMU_POW_OFF);
+			pmu_wait_complete(&req);
+		} else
+			__pmu_backlight_update_status(pmac_backlight);
+	}
+	spin_unlock_irqrestore(&pmu_backlight_lock, flags);
+}
+#endif /* CONFIG_PM */
+
+void __init pmu_backlight_init()
+{
+	struct backlight_properties props;
+	struct backlight_device *bd;
+	char name[10];
+	int level, autosave;
+
+	/* Special case for the old PowerBook since I can't test on it */
+	autosave =
+		of_machine_is_compatible("AAPL,3400/2400") ||
+		of_machine_is_compatible("AAPL,3500");
+
+	if (!autosave &&
+	    !pmac_has_backlight_type("pmu") &&
+	    !of_machine_is_compatible("AAPL,PowerBook1998") &&
+	    !of_machine_is_compatible("PowerBook1,1"))
+		return;
+
+	snprintf(name, sizeof(name), "pmubl");
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.max_brightness = FB_BACKLIGHT_LEVELS - 1;
+	bd = backlight_device_register(name, NULL, NULL, &pmu_backlight_data,
+				       &props);
+	if (IS_ERR(bd)) {
+		printk(KERN_ERR "PMU Backlight registration failed\n");
+		return;
+	}
+	uses_pmu_bl = 1;
+	pmu_backlight_init_curve(0x7F, 0x46, 0x0E);
+
+	level = bd->props.max_brightness;
+
+	if (autosave) {
+		/* read autosaved value if available */
+		struct adb_request req;
+		pmu_request(&req, NULL, 2, 0xd9, 0);
+		pmu_wait_complete(&req);
+
+		level = pmu_backlight_curve_lookup(
+				(req.reply[0] >> 4) *
+				bd->props.max_brightness / 15);
+	}
+
+	bd->props.brightness = level;
+	bd->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(bd);
+
+	printk(KERN_INFO "PMU Backlight initialized (%s)\n", name);
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.c b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.c
new file mode 100644
index 0000000..25cd565
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.c
@@ -0,0 +1,80 @@
+/*
+ * via-pmu event device for reporting some events that come through the PMU
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ */
+
+#include <linux/input.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include "via-pmu-event.h"
+
+static struct input_dev *pmu_input_dev;
+
+static int __init via_pmu_event_init(void)
+{
+	int err;
+
+	/* do other models report button/lid status? */
+	if (pmu_get_model() != PMU_KEYLARGO_BASED)
+		return -ENODEV;
+
+	pmu_input_dev = input_allocate_device();
+	if (!pmu_input_dev)
+		return -ENOMEM;
+
+	pmu_input_dev->name = "PMU";
+	pmu_input_dev->id.bustype = BUS_HOST;
+	pmu_input_dev->id.vendor = 0x0001;
+	pmu_input_dev->id.product = 0x0001;
+	pmu_input_dev->id.version = 0x0100;
+
+	set_bit(EV_KEY, pmu_input_dev->evbit);
+	set_bit(EV_SW, pmu_input_dev->evbit);
+	set_bit(KEY_POWER, pmu_input_dev->keybit);
+	set_bit(SW_LID, pmu_input_dev->swbit);
+
+	err = input_register_device(pmu_input_dev);
+	if (err)
+		input_free_device(pmu_input_dev);
+	return err;
+}
+
+void via_pmu_event(int key, int down)
+{
+
+	if (unlikely(!pmu_input_dev))
+		return;
+
+	switch (key) {
+	case PMU_EVT_POWER:
+		input_report_key(pmu_input_dev, KEY_POWER, down);
+		break;
+	case PMU_EVT_LID:
+		input_report_switch(pmu_input_dev, SW_LID, down);
+		break;
+	default:
+		/* no such key handled */
+		return;
+	}
+
+	input_sync(pmu_input_dev);
+}
+
+late_initcall(via_pmu_event_init);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.h b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.h
new file mode 100644
index 0000000..5e52109
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-event.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __VIA_PMU_EVENT_H
+#define __VIA_PMU_EVENT_H
+
+#define PMU_EVT_POWER	0
+#define PMU_EVT_LID	1
+extern void via_pmu_event(int key, int down);
+
+#endif /* __VIA_PMU_EVENT_H */
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-led.c b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-led.c
new file mode 100644
index 0000000..ae067ab
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu-led.c
@@ -0,0 +1,117 @@
+/*
+ * via-pmu LED class device
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <asm/prom.h>
+
+static spinlock_t pmu_blink_lock;
+static struct adb_request pmu_blink_req;
+/* -1: no change, 0: request off, 1: request on */
+static int requested_change;
+
+static void pmu_req_done(struct adb_request * req)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pmu_blink_lock, flags);
+	/* if someone requested a change in the meantime
+	 * (we only see the last one which is fine)
+	 * then apply it now */
+	if (requested_change != -1 && !pmu_sys_suspended)
+		pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change);
+	/* reset requested change */
+	requested_change = -1;
+	spin_unlock_irqrestore(&pmu_blink_lock, flags);
+}
+
+static void pmu_led_set(struct led_classdev *led_cdev,
+			enum led_brightness brightness)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pmu_blink_lock, flags);
+	switch (brightness) {
+	case LED_OFF:
+		requested_change = 0;
+		break;
+	case LED_FULL:
+		requested_change = 1;
+		break;
+	default:
+		goto out;
+		break;
+	}
+	/* if request isn't done, then don't do anything */
+	if (pmu_blink_req.complete && !pmu_sys_suspended)
+		pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change);
+ out:
+ 	spin_unlock_irqrestore(&pmu_blink_lock, flags);
+}
+
+static struct led_classdev pmu_led = {
+	.name = "pmu-led::front",
+#ifdef CONFIG_ADB_PMU_LED_DISK
+	.default_trigger = "disk-activity",
+#endif
+	.brightness_set = pmu_led_set,
+};
+
+static int __init via_pmu_led_init(void)
+{
+	struct device_node *dt;
+	const char *model;
+
+	/* only do this on keylargo based models */
+	if (pmu_get_model() != PMU_KEYLARGO_BASED)
+		return -ENODEV;
+
+	dt = of_find_node_by_path("/");
+	if (dt == NULL)
+		return -ENODEV;
+	model = of_get_property(dt, "model", NULL);
+	if (model == NULL) {
+		of_node_put(dt);
+		return -ENODEV;
+	}
+	if (strncmp(model, "PowerBook", strlen("PowerBook")) != 0 &&
+	    strncmp(model, "iBook", strlen("iBook")) != 0 &&
+	    strcmp(model, "PowerMac7,2") != 0 &&
+	    strcmp(model, "PowerMac7,3") != 0) {
+		of_node_put(dt);
+		/* ignore */
+		return -ENODEV;
+	}
+	of_node_put(dt);
+
+	spin_lock_init(&pmu_blink_lock);
+	/* no outstanding req */
+	pmu_blink_req.complete = 1;
+	pmu_blink_req.done = pmu_req_done;
+
+	return led_classdev_register(NULL, &pmu_led);
+}
+
+late_initcall(via_pmu_led_init);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu.c b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu.c
new file mode 100644
index 0000000..f6e040f
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu.c
@@ -0,0 +1,2600 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for the via-pmu on Apple Powermacs.
+ *
+ * The VIA (versatile interface adapter) interfaces to the PMU,
+ * a 6805 microprocessor core whose primary function is to control
+ * battery charging and system power on the PowerBook 3400 and 2400.
+ * The PMU also controls the ADB (Apple Desktop Bus) which connects
+ * to the keyboard and mouse, as well as the non-volatile RAM
+ * and the RTC (real time clock) chip.
+ *
+ * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
+ * Copyright (C) 2001-2002 Benjamin Herrenschmidt
+ * Copyright (C) 2006-2007 Johannes Berg
+ *
+ * THIS DRIVER IS BECOMING A TOTAL MESS !
+ *  - Cleanup atomically disabling reply to PMU events after
+ *    a sleep or a freq. switch
+ *
+ */
+#include <stdarg.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched/signal.h>
+#include <linux/miscdevice.h>
+#include <linux/blkdev.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <linux/cuda.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/pm.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/syscore_ops.h>
+#include <linux/freezer.h>
+#include <linux/syscalls.h>
+#include <linux/suspend.h>
+#include <linux/cpu.h>
+#include <linux/compat.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/sections.h>
+#include <asm/irq.h>
+#include <asm/pmac_feature.h>
+#include <asm/pmac_pfunc.h>
+#include <asm/pmac_low_i2c.h>
+#include <linux/uaccess.h>
+#include <asm/mmu_context.h>
+#include <asm/cputable.h>
+#include <asm/time.h>
+#include <asm/backlight.h>
+
+#include "via-pmu-event.h"
+
+/* Some compile options */
+#undef DEBUG_SLEEP
+
+/* Misc minor number allocated for /dev/pmu */
+#define PMU_MINOR		154
+
+/* How many iterations between battery polls */
+#define BATTERY_POLLING_COUNT	2
+
+static DEFINE_MUTEX(pmu_info_proc_mutex);
+static volatile unsigned char __iomem *via;
+
+/* VIA registers - spaced 0x200 bytes apart */
+#define RS		0x200		/* skip between registers */
+#define B		0		/* B-side data */
+#define A		RS		/* A-side data */
+#define DIRB		(2*RS)		/* B-side direction (1=output) */
+#define DIRA		(3*RS)		/* A-side direction (1=output) */
+#define T1CL		(4*RS)		/* Timer 1 ctr/latch (low 8 bits) */
+#define T1CH		(5*RS)		/* Timer 1 counter (high 8 bits) */
+#define T1LL		(6*RS)		/* Timer 1 latch (low 8 bits) */
+#define T1LH		(7*RS)		/* Timer 1 latch (high 8 bits) */
+#define T2CL		(8*RS)		/* Timer 2 ctr/latch (low 8 bits) */
+#define T2CH		(9*RS)		/* Timer 2 counter (high 8 bits) */
+#define SR		(10*RS)		/* Shift register */
+#define ACR		(11*RS)		/* Auxiliary control register */
+#define PCR		(12*RS)		/* Peripheral control register */
+#define IFR		(13*RS)		/* Interrupt flag register */
+#define IER		(14*RS)		/* Interrupt enable register */
+#define ANH		(15*RS)		/* A-side data, no handshake */
+
+/* Bits in B data register: both active low */
+#define TACK		0x08		/* Transfer acknowledge (input) */
+#define TREQ		0x10		/* Transfer request (output) */
+
+/* Bits in ACR */
+#define SR_CTRL		0x1c		/* Shift register control bits */
+#define SR_EXT		0x0c		/* Shift on external clock */
+#define SR_OUT		0x10		/* Shift out if 1 */
+
+/* Bits in IFR and IER */
+#define IER_SET		0x80		/* set bits in IER */
+#define IER_CLR		0		/* clear bits in IER */
+#define SR_INT		0x04		/* Shift register full/empty */
+#define CB2_INT		0x08
+#define CB1_INT		0x10		/* transition on CB1 input */
+
+static volatile enum pmu_state {
+	idle,
+	sending,
+	intack,
+	reading,
+	reading_intr,
+	locked,
+} pmu_state;
+
+static volatile enum int_data_state {
+	int_data_empty,
+	int_data_fill,
+	int_data_ready,
+	int_data_flush
+} int_data_state[2] = { int_data_empty, int_data_empty };
+
+static struct adb_request *current_req;
+static struct adb_request *last_req;
+static struct adb_request *req_awaiting_reply;
+static unsigned char interrupt_data[2][32];
+static int interrupt_data_len[2];
+static int int_data_last;
+static unsigned char *reply_ptr;
+static int data_index;
+static int data_len;
+static volatile int adb_int_pending;
+static volatile int disable_poll;
+static struct device_node *vias;
+static int pmu_kind = PMU_UNKNOWN;
+static int pmu_fully_inited;
+static int pmu_has_adb;
+static struct device_node *gpio_node;
+static unsigned char __iomem *gpio_reg;
+static int gpio_irq = 0;
+static int gpio_irq_enabled = -1;
+static volatile int pmu_suspended;
+static spinlock_t pmu_lock;
+static u8 pmu_intr_mask;
+static int pmu_version;
+static int drop_interrupts;
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+static int option_lid_wakeup = 1;
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+static unsigned long async_req_locks;
+static unsigned int pmu_irq_stats[11];
+
+static struct proc_dir_entry *proc_pmu_root;
+static struct proc_dir_entry *proc_pmu_info;
+static struct proc_dir_entry *proc_pmu_irqstats;
+static struct proc_dir_entry *proc_pmu_options;
+static int option_server_mode;
+
+int pmu_battery_count;
+int pmu_cur_battery;
+unsigned int pmu_power_flags = PMU_PWR_AC_PRESENT;
+struct pmu_battery_info pmu_batteries[PMU_MAX_BATTERIES];
+static int query_batt_timer = BATTERY_POLLING_COUNT;
+static struct adb_request batt_req;
+static struct proc_dir_entry *proc_pmu_batt[PMU_MAX_BATTERIES];
+
+int __fake_sleep;
+int asleep;
+
+#ifdef CONFIG_ADB
+static int adb_dev_map;
+static int pmu_adb_flags;
+
+static int pmu_probe(void);
+static int pmu_init(void);
+static int pmu_send_request(struct adb_request *req, int sync);
+static int pmu_adb_autopoll(int devs);
+static int pmu_adb_reset_bus(void);
+#endif /* CONFIG_ADB */
+
+static int init_pmu(void);
+static void pmu_start(void);
+static irqreturn_t via_pmu_interrupt(int irq, void *arg);
+static irqreturn_t gpio1_interrupt(int irq, void *arg);
+static const struct file_operations pmu_info_proc_fops;
+static const struct file_operations pmu_irqstats_proc_fops;
+static void pmu_pass_intr(unsigned char *data, int len);
+static const struct file_operations pmu_battery_proc_fops;
+static const struct file_operations pmu_options_proc_fops;
+
+#ifdef CONFIG_ADB
+struct adb_driver via_pmu_driver = {
+	"PMU",
+	pmu_probe,
+	pmu_init,
+	pmu_send_request,
+	pmu_adb_autopoll,
+	pmu_poll_adb,
+	pmu_adb_reset_bus
+};
+#endif /* CONFIG_ADB */
+
+extern void low_sleep_handler(void);
+extern void enable_kernel_altivec(void);
+extern void enable_kernel_fp(void);
+
+#ifdef DEBUG_SLEEP
+int pmu_polled_request(struct adb_request *req);
+void pmu_blink(int n);
+#endif
+
+/*
+ * This table indicates for each PMU opcode:
+ * - the number of data bytes to be sent with the command, or -1
+ *   if a length byte should be sent,
+ * - the number of response bytes which the PMU will return, or
+ *   -1 if it will send a length byte.
+ */
+static const s8 pmu_data_len[256][2] = {
+/*	   0	   1	   2	   3	   4	   5	   6	   7  */
+/*00*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*08*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*10*/	{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*18*/	{ 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0},
+/*20*/	{-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*28*/	{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1},
+/*30*/	{ 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*38*/	{ 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0},
+/*40*/	{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*48*/	{ 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1},
+/*50*/	{ 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0},
+/*58*/	{ 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},
+/*60*/	{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*68*/	{ 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},
+/*70*/	{ 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*78*/	{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1},
+/*80*/	{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*88*/	{ 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*90*/	{ 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*98*/	{ 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*a0*/	{ 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},
+/*a8*/	{ 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*b0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*b8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*c0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*c8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*d0*/	{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*d8*/	{ 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1},
+/*e0*/	{-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0},
+/*e8*/	{ 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0},
+/*f0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*f8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+};
+
+static char *pbook_type[] = {
+	"Unknown PowerBook",
+	"PowerBook 2400/3400/3500(G3)",
+	"PowerBook G3 Series",
+	"1999 PowerBook G3",
+	"Core99"
+};
+
+int __init find_via_pmu(void)
+{
+	u64 taddr;
+	const u32 *reg;
+
+	if (via != 0)
+		return 1;
+	vias = of_find_node_by_name(NULL, "via-pmu");
+	if (vias == NULL)
+		return 0;
+
+	reg = of_get_property(vias, "reg", NULL);
+	if (reg == NULL) {
+		printk(KERN_ERR "via-pmu: No \"reg\" property !\n");
+		goto fail;
+	}
+	taddr = of_translate_address(vias, reg);
+	if (taddr == OF_BAD_ADDR) {
+		printk(KERN_ERR "via-pmu: Can't translate address !\n");
+		goto fail;
+	}
+
+	spin_lock_init(&pmu_lock);
+
+	pmu_has_adb = 1;
+
+	pmu_intr_mask =	PMU_INT_PCEJECT |
+			PMU_INT_SNDBRT |
+			PMU_INT_ADB |
+			PMU_INT_TICK;
+	
+	if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0)
+	    || of_device_is_compatible(vias->parent, "ohare")))
+		pmu_kind = PMU_OHARE_BASED;
+	else if (of_device_is_compatible(vias->parent, "paddington"))
+		pmu_kind = PMU_PADDINGTON_BASED;
+	else if (of_device_is_compatible(vias->parent, "heathrow"))
+		pmu_kind = PMU_HEATHROW_BASED;
+	else if (of_device_is_compatible(vias->parent, "Keylargo")
+		 || of_device_is_compatible(vias->parent, "K2-Keylargo")) {
+		struct device_node *gpiop;
+		struct device_node *adbp;
+		u64 gaddr = OF_BAD_ADDR;
+
+		pmu_kind = PMU_KEYLARGO_BASED;
+		adbp = of_find_node_by_type(NULL, "adb");
+		pmu_has_adb = (adbp != NULL);
+		of_node_put(adbp);
+		pmu_intr_mask =	PMU_INT_PCEJECT |
+				PMU_INT_SNDBRT |
+				PMU_INT_ADB |
+				PMU_INT_TICK |
+				PMU_INT_ENVIRONMENT;
+		
+		gpiop = of_find_node_by_name(NULL, "gpio");
+		if (gpiop) {
+			reg = of_get_property(gpiop, "reg", NULL);
+			if (reg)
+				gaddr = of_translate_address(gpiop, reg);
+			if (gaddr != OF_BAD_ADDR)
+				gpio_reg = ioremap(gaddr, 0x10);
+			of_node_put(gpiop);
+		}
+		if (gpio_reg == NULL) {
+			printk(KERN_ERR "via-pmu: Can't find GPIO reg !\n");
+			goto fail;
+		}
+	} else
+		pmu_kind = PMU_UNKNOWN;
+
+	via = ioremap(taddr, 0x2000);
+	if (via == NULL) {
+		printk(KERN_ERR "via-pmu: Can't map address !\n");
+		goto fail_via_remap;
+	}
+	
+	out_8(&via[IER], IER_CLR | 0x7f);	/* disable all intrs */
+	out_8(&via[IFR], 0x7f);			/* clear IFR */
+
+	pmu_state = idle;
+
+	if (!init_pmu())
+		goto fail_init;
+
+	printk(KERN_INFO "PMU driver v%d initialized for %s, firmware: %02x\n",
+	       PMU_DRIVER_VERSION, pbook_type[pmu_kind], pmu_version);
+	       
+	sys_ctrler = SYS_CTRLER_PMU;
+	
+	return 1;
+
+ fail_init:
+	iounmap(via);
+	via = NULL;
+ fail_via_remap:
+	iounmap(gpio_reg);
+	gpio_reg = NULL;
+ fail:
+	of_node_put(vias);
+	vias = NULL;
+	return 0;
+}
+
+#ifdef CONFIG_ADB
+static int pmu_probe(void)
+{
+	return vias == NULL? -ENODEV: 0;
+}
+
+static int __init pmu_init(void)
+{
+	if (vias == NULL)
+		return -ENODEV;
+	return 0;
+}
+#endif /* CONFIG_ADB */
+
+/*
+ * We can't wait until pmu_init gets called, that happens too late.
+ * It happens after IDE and SCSI initialization, which can take a few
+ * seconds, and by that time the PMU could have given up on us and
+ * turned us off.
+ * Thus this is called with arch_initcall rather than device_initcall.
+ */
+static int __init via_pmu_start(void)
+{
+	unsigned int irq;
+
+	if (vias == NULL)
+		return -ENODEV;
+
+	batt_req.complete = 1;
+
+	irq = irq_of_parse_and_map(vias, 0);
+	if (!irq) {
+		printk(KERN_ERR "via-pmu: can't map interrupt\n");
+		return -ENODEV;
+	}
+	/* We set IRQF_NO_SUSPEND because we don't want the interrupt
+	 * to be disabled between the 2 passes of driver suspend, we
+	 * control our own disabling for that one
+	 */
+	if (request_irq(irq, via_pmu_interrupt, IRQF_NO_SUSPEND,
+			"VIA-PMU", (void *)0)) {
+		printk(KERN_ERR "via-pmu: can't request irq %d\n", irq);
+		return -ENODEV;
+	}
+
+	if (pmu_kind == PMU_KEYLARGO_BASED) {
+		gpio_node = of_find_node_by_name(NULL, "extint-gpio1");
+		if (gpio_node == NULL)
+			gpio_node = of_find_node_by_name(NULL,
+							 "pmu-interrupt");
+		if (gpio_node)
+			gpio_irq = irq_of_parse_and_map(gpio_node, 0);
+
+		if (gpio_irq) {
+			if (request_irq(gpio_irq, gpio1_interrupt,
+					IRQF_NO_SUSPEND, "GPIO1 ADB",
+					(void *)0))
+				printk(KERN_ERR "pmu: can't get irq %d"
+				       " (GPIO1)\n", gpio_irq);
+			else
+				gpio_irq_enabled = 1;
+		}
+	}
+
+	/* Enable interrupts */
+	out_8(&via[IER], IER_SET | SR_INT | CB1_INT);
+
+	pmu_fully_inited = 1;
+
+	/* Make sure PMU settle down before continuing. This is _very_ important
+	 * since the IDE probe may shut interrupts down for quite a bit of time. If
+	 * a PMU communication is pending while this happens, the PMU may timeout
+	 * Not that on Core99 machines, the PMU keeps sending us environement
+	 * messages, we should find a way to either fix IDE or make it call
+	 * pmu_suspend() before masking interrupts. This can also happens while
+	 * scolling with some fbdevs.
+	 */
+	do {
+		pmu_poll();
+	} while (pmu_state != idle);
+
+	return 0;
+}
+
+arch_initcall(via_pmu_start);
+
+/*
+ * This has to be done after pci_init, which is a subsys_initcall.
+ */
+static int __init via_pmu_dev_init(void)
+{
+	if (vias == NULL)
+		return -ENODEV;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	/* Initialize backlight */
+	pmu_backlight_init();
+#endif
+
+#ifdef CONFIG_PPC32
+  	if (of_machine_is_compatible("AAPL,3400/2400") ||
+  		of_machine_is_compatible("AAPL,3500")) {
+		int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO,
+			NULL, PMAC_MB_INFO_MODEL, 0);
+		pmu_battery_count = 1;
+		if (mb == PMAC_TYPE_COMET)
+			pmu_batteries[0].flags |= PMU_BATT_TYPE_COMET;
+		else
+			pmu_batteries[0].flags |= PMU_BATT_TYPE_HOOPER;
+	} else if (of_machine_is_compatible("AAPL,PowerBook1998") ||
+		of_machine_is_compatible("PowerBook1,1")) {
+		pmu_battery_count = 2;
+		pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART;
+		pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART;
+	} else {
+		struct device_node* prim =
+			of_find_node_by_name(NULL, "power-mgt");
+		const u32 *prim_info = NULL;
+		if (prim)
+			prim_info = of_get_property(prim, "prim-info", NULL);
+		if (prim_info) {
+			/* Other stuffs here yet unknown */
+			pmu_battery_count = (prim_info[6] >> 16) & 0xff;
+			pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART;
+			if (pmu_battery_count > 1)
+				pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART;
+		}
+		of_node_put(prim);
+	}
+#endif /* CONFIG_PPC32 */
+
+	/* Create /proc/pmu */
+	proc_pmu_root = proc_mkdir("pmu", NULL);
+	if (proc_pmu_root) {
+		long i;
+
+		for (i=0; i<pmu_battery_count; i++) {
+			char title[16];
+			sprintf(title, "battery_%ld", i);
+			proc_pmu_batt[i] = proc_create_data(title, 0, proc_pmu_root,
+					&pmu_battery_proc_fops, (void *)i);
+		}
+
+		proc_pmu_info = proc_create("info", 0, proc_pmu_root, &pmu_info_proc_fops);
+		proc_pmu_irqstats = proc_create("interrupts", 0, proc_pmu_root,
+						&pmu_irqstats_proc_fops);
+		proc_pmu_options = proc_create("options", 0600, proc_pmu_root,
+						&pmu_options_proc_fops);
+	}
+	return 0;
+}
+
+device_initcall(via_pmu_dev_init);
+
+static int
+init_pmu(void)
+{
+	int timeout;
+	struct adb_request req;
+
+	/* Negate TREQ. Set TACK to input and TREQ to output. */
+	out_8(&via[B], in_8(&via[B]) | TREQ);
+	out_8(&via[DIRB], (in_8(&via[DIRB]) | TREQ) & ~TACK);
+
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask);
+	timeout =  100000;
+	while (!req.complete) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "init_pmu: no response from PMU\n");
+			return 0;
+		}
+		udelay(10);
+		pmu_poll();
+	}
+
+	/* ack all pending interrupts */
+	timeout = 100000;
+	interrupt_data[0][0] = 1;
+	while (interrupt_data[0][0] || pmu_state != idle) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "init_pmu: timed out acking intrs\n");
+			return 0;
+		}
+		if (pmu_state == idle)
+			adb_int_pending = 1;
+		via_pmu_interrupt(0, NULL);
+		udelay(10);
+	}
+
+	/* Tell PMU we are ready.  */
+	if (pmu_kind == PMU_KEYLARGO_BASED) {
+		pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2);
+		while (!req.complete)
+			pmu_poll();
+	}
+
+	/* Read PMU version */
+	pmu_request(&req, NULL, 1, PMU_GET_VERSION);
+	pmu_wait_complete(&req);
+	if (req.reply_len > 0)
+		pmu_version = req.reply[0];
+	
+	/* Read server mode setting */
+	if (pmu_kind == PMU_KEYLARGO_BASED) {
+		pmu_request(&req, NULL, 2, PMU_POWER_EVENTS,
+			    PMU_PWR_GET_POWERUP_EVENTS);
+		pmu_wait_complete(&req);
+		if (req.reply_len == 2) {
+			if (req.reply[1] & PMU_PWR_WAKEUP_AC_INSERT)
+				option_server_mode = 1;
+			printk(KERN_INFO "via-pmu: Server Mode is %s\n",
+			       option_server_mode ? "enabled" : "disabled");
+		}
+	}
+	return 1;
+}
+
+int
+pmu_get_model(void)
+{
+	return pmu_kind;
+}
+
+static void pmu_set_server_mode(int server_mode)
+{
+	struct adb_request req;
+
+	if (pmu_kind != PMU_KEYLARGO_BASED)
+		return;
+
+	option_server_mode = server_mode;
+	pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, PMU_PWR_GET_POWERUP_EVENTS);
+	pmu_wait_complete(&req);
+	if (req.reply_len < 2)
+		return;
+	if (server_mode)
+		pmu_request(&req, NULL, 4, PMU_POWER_EVENTS,
+			    PMU_PWR_SET_POWERUP_EVENTS,
+			    req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); 
+	else
+		pmu_request(&req, NULL, 4, PMU_POWER_EVENTS,
+			    PMU_PWR_CLR_POWERUP_EVENTS,
+			    req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); 
+	pmu_wait_complete(&req);
+}
+
+/* This new version of the code for 2400/3400/3500 powerbooks
+ * is inspired from the implementation in gkrellm-pmu
+ */
+static void
+done_battery_state_ohare(struct adb_request* req)
+{
+	/* format:
+	 *  [0]    :  flags
+	 *    0x01 :  AC indicator
+	 *    0x02 :  charging
+	 *    0x04 :  battery exist
+	 *    0x08 :  
+	 *    0x10 :  
+	 *    0x20 :  full charged
+	 *    0x40 :  pcharge reset
+	 *    0x80 :  battery exist
+	 *
+	 *  [1][2] :  battery voltage
+	 *  [3]    :  CPU temperature
+	 *  [4]    :  battery temperature
+	 *  [5]    :  current
+	 *  [6][7] :  pcharge
+	 *              --tkoba
+	 */
+	unsigned int bat_flags = PMU_BATT_TYPE_HOOPER;
+	long pcharge, charge, vb, vmax, lmax;
+	long vmax_charging, vmax_charged;
+	long amperage, voltage, time, max;
+	int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO,
+			NULL, PMAC_MB_INFO_MODEL, 0);
+
+	if (req->reply[0] & 0x01)
+		pmu_power_flags |= PMU_PWR_AC_PRESENT;
+	else
+		pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
+	
+	if (mb == PMAC_TYPE_COMET) {
+		vmax_charged = 189;
+		vmax_charging = 213;
+		lmax = 6500;
+	} else {
+		vmax_charged = 330;
+		vmax_charging = 330;
+		lmax = 6500;
+	}
+	vmax = vmax_charged;
+
+	/* If battery installed */
+	if (req->reply[0] & 0x04) {
+		bat_flags |= PMU_BATT_PRESENT;
+		if (req->reply[0] & 0x02)
+			bat_flags |= PMU_BATT_CHARGING;
+		vb = (req->reply[1] << 8) | req->reply[2];
+		voltage = (vb * 265 + 72665) / 10;
+		amperage = req->reply[5];
+		if ((req->reply[0] & 0x01) == 0) {
+			if (amperage > 200)
+				vb += ((amperage - 200) * 15)/100;
+		} else if (req->reply[0] & 0x02) {
+			vb = (vb * 97) / 100;
+			vmax = vmax_charging;
+		}
+		charge = (100 * vb) / vmax;
+		if (req->reply[0] & 0x40) {
+			pcharge = (req->reply[6] << 8) + req->reply[7];
+			if (pcharge > lmax)
+				pcharge = lmax;
+			pcharge *= 100;
+			pcharge = 100 - pcharge / lmax;
+			if (pcharge < charge)
+				charge = pcharge;
+		}
+		if (amperage > 0)
+			time = (charge * 16440) / amperage;
+		else
+			time = 0;
+		max = 100;
+		amperage = -amperage;
+	} else
+		charge = max = amperage = voltage = time = 0;
+
+	pmu_batteries[pmu_cur_battery].flags = bat_flags;
+	pmu_batteries[pmu_cur_battery].charge = charge;
+	pmu_batteries[pmu_cur_battery].max_charge = max;
+	pmu_batteries[pmu_cur_battery].amperage = amperage;
+	pmu_batteries[pmu_cur_battery].voltage = voltage;
+	pmu_batteries[pmu_cur_battery].time_remaining = time;
+
+	clear_bit(0, &async_req_locks);
+}
+
+static void
+done_battery_state_smart(struct adb_request* req)
+{
+	/* format:
+	 *  [0] : format of this structure (known: 3,4,5)
+	 *  [1] : flags
+	 *  
+	 *  format 3 & 4:
+	 *  
+	 *  [2] : charge
+	 *  [3] : max charge
+	 *  [4] : current
+	 *  [5] : voltage
+	 *  
+	 *  format 5:
+	 *  
+	 *  [2][3] : charge
+	 *  [4][5] : max charge
+	 *  [6][7] : current
+	 *  [8][9] : voltage
+	 */
+	 
+	unsigned int bat_flags = PMU_BATT_TYPE_SMART;
+	int amperage;
+	unsigned int capa, max, voltage;
+	
+	if (req->reply[1] & 0x01)
+		pmu_power_flags |= PMU_PWR_AC_PRESENT;
+	else
+		pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
+
+
+	capa = max = amperage = voltage = 0;
+	
+	if (req->reply[1] & 0x04) {
+		bat_flags |= PMU_BATT_PRESENT;
+		switch(req->reply[0]) {
+			case 3:
+			case 4: capa = req->reply[2];
+				max = req->reply[3];
+				amperage = *((signed char *)&req->reply[4]);
+				voltage = req->reply[5];
+				break;
+			case 5: capa = (req->reply[2] << 8) | req->reply[3];
+				max = (req->reply[4] << 8) | req->reply[5];
+				amperage = *((signed short *)&req->reply[6]);
+				voltage = (req->reply[8] << 8) | req->reply[9];
+				break;
+			default:
+				pr_warn("pmu.c: unrecognized battery info, "
+					"len: %d, %4ph\n", req->reply_len,
+							   req->reply);
+				break;
+		}
+	}
+
+	if ((req->reply[1] & 0x01) && (amperage > 0))
+		bat_flags |= PMU_BATT_CHARGING;
+
+	pmu_batteries[pmu_cur_battery].flags = bat_flags;
+	pmu_batteries[pmu_cur_battery].charge = capa;
+	pmu_batteries[pmu_cur_battery].max_charge = max;
+	pmu_batteries[pmu_cur_battery].amperage = amperage;
+	pmu_batteries[pmu_cur_battery].voltage = voltage;
+	if (amperage) {
+		if ((req->reply[1] & 0x01) && (amperage > 0))
+			pmu_batteries[pmu_cur_battery].time_remaining
+				= ((max-capa) * 3600) / amperage;
+		else
+			pmu_batteries[pmu_cur_battery].time_remaining
+				= (capa * 3600) / (-amperage);
+	} else
+		pmu_batteries[pmu_cur_battery].time_remaining = 0;
+
+	pmu_cur_battery = (pmu_cur_battery + 1) % pmu_battery_count;
+
+	clear_bit(0, &async_req_locks);
+}
+
+static void
+query_battery_state(void)
+{
+	if (test_and_set_bit(0, &async_req_locks))
+		return;
+	if (pmu_kind == PMU_OHARE_BASED)
+		pmu_request(&batt_req, done_battery_state_ohare,
+			1, PMU_BATTERY_STATE);
+	else
+		pmu_request(&batt_req, done_battery_state_smart,
+			2, PMU_SMART_BATTERY_STATE, pmu_cur_battery+1);
+}
+
+static int pmu_info_proc_show(struct seq_file *m, void *v)
+{
+	seq_printf(m, "PMU driver version     : %d\n", PMU_DRIVER_VERSION);
+	seq_printf(m, "PMU firmware version   : %02x\n", pmu_version);
+	seq_printf(m, "AC Power               : %d\n",
+		((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0) || pmu_battery_count == 0);
+	seq_printf(m, "Battery count          : %d\n", pmu_battery_count);
+
+	return 0;
+}
+
+static int pmu_info_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, pmu_info_proc_show, NULL);
+}
+
+static const struct file_operations pmu_info_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= pmu_info_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int pmu_irqstats_proc_show(struct seq_file *m, void *v)
+{
+	int i;
+	static const char *irq_names[] = {
+		"Total CB1 triggered events",
+		"Total GPIO1 triggered events",
+		"PC-Card eject button",
+		"Sound/Brightness button",
+		"ADB message",
+		"Battery state change",
+		"Environment interrupt",
+		"Tick timer",
+		"Ghost interrupt (zero len)",
+		"Empty interrupt (empty mask)",
+		"Max irqs in a row"
+        };
+
+	for (i=0; i<11; i++) {
+		seq_printf(m, " %2u: %10u (%s)\n",
+			     i, pmu_irq_stats[i], irq_names[i]);
+	}
+	return 0;
+}
+
+static int pmu_irqstats_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, pmu_irqstats_proc_show, NULL);
+}
+
+static const struct file_operations pmu_irqstats_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= pmu_irqstats_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int pmu_battery_proc_show(struct seq_file *m, void *v)
+{
+	long batnum = (long)m->private;
+	
+	seq_putc(m, '\n');
+	seq_printf(m, "flags      : %08x\n", pmu_batteries[batnum].flags);
+	seq_printf(m, "charge     : %d\n", pmu_batteries[batnum].charge);
+	seq_printf(m, "max_charge : %d\n", pmu_batteries[batnum].max_charge);
+	seq_printf(m, "current    : %d\n", pmu_batteries[batnum].amperage);
+	seq_printf(m, "voltage    : %d\n", pmu_batteries[batnum].voltage);
+	seq_printf(m, "time rem.  : %d\n", pmu_batteries[batnum].time_remaining);
+	return 0;
+}
+
+static int pmu_battery_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, pmu_battery_proc_show, PDE_DATA(inode));
+}
+
+static const struct file_operations pmu_battery_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= pmu_battery_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int pmu_options_proc_show(struct seq_file *m, void *v)
+{
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+	if (pmu_kind == PMU_KEYLARGO_BASED &&
+	    pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0)
+		seq_printf(m, "lid_wakeup=%d\n", option_lid_wakeup);
+#endif
+	if (pmu_kind == PMU_KEYLARGO_BASED)
+		seq_printf(m, "server_mode=%d\n", option_server_mode);
+
+	return 0;
+}
+
+static int pmu_options_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, pmu_options_proc_show, NULL);
+}
+
+static ssize_t pmu_options_proc_write(struct file *file,
+		const char __user *buffer, size_t count, loff_t *pos)
+{
+	char tmp[33];
+	char *label, *val;
+	size_t fcount = count;
+	
+	if (!count)
+		return -EINVAL;
+	if (count > 32)
+		count = 32;
+	if (copy_from_user(tmp, buffer, count))
+		return -EFAULT;
+	tmp[count] = 0;
+
+	label = tmp;
+	while(*label == ' ')
+		label++;
+	val = label;
+	while(*val && (*val != '=')) {
+		if (*val == ' ')
+			*val = 0;
+		val++;
+	}
+	if ((*val) == 0)
+		return -EINVAL;
+	*(val++) = 0;
+	while(*val == ' ')
+		val++;
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+	if (pmu_kind == PMU_KEYLARGO_BASED &&
+	    pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0)
+		if (!strcmp(label, "lid_wakeup"))
+			option_lid_wakeup = ((*val) == '1');
+#endif
+	if (pmu_kind == PMU_KEYLARGO_BASED && !strcmp(label, "server_mode")) {
+		int new_value;
+		new_value = ((*val) == '1');
+		if (new_value != option_server_mode)
+			pmu_set_server_mode(new_value);
+	}
+	return fcount;
+}
+
+static const struct file_operations pmu_options_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= pmu_options_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= pmu_options_proc_write,
+};
+
+#ifdef CONFIG_ADB
+/* Send an ADB command */
+static int pmu_send_request(struct adb_request *req, int sync)
+{
+	int i, ret;
+
+	if ((vias == NULL) || (!pmu_fully_inited)) {
+		req->complete = 1;
+		return -ENXIO;
+	}
+
+	ret = -EINVAL;
+
+	switch (req->data[0]) {
+	case PMU_PACKET:
+		for (i = 0; i < req->nbytes - 1; ++i)
+			req->data[i] = req->data[i+1];
+		--req->nbytes;
+		if (pmu_data_len[req->data[0]][1] != 0) {
+			req->reply[0] = ADB_RET_OK;
+			req->reply_len = 1;
+		} else
+			req->reply_len = 0;
+		ret = pmu_queue_request(req);
+		break;
+	case CUDA_PACKET:
+		switch (req->data[1]) {
+		case CUDA_GET_TIME:
+			if (req->nbytes != 2)
+				break;
+			req->data[0] = PMU_READ_RTC;
+			req->nbytes = 1;
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_GET_TIME;
+			ret = pmu_queue_request(req);
+			break;
+		case CUDA_SET_TIME:
+			if (req->nbytes != 6)
+				break;
+			req->data[0] = PMU_SET_RTC;
+			req->nbytes = 5;
+			for (i = 1; i <= 4; ++i)
+				req->data[i] = req->data[i+1];
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_SET_TIME;
+			ret = pmu_queue_request(req);
+			break;
+		}
+		break;
+	case ADB_PACKET:
+	    	if (!pmu_has_adb)
+    			return -ENXIO;
+		for (i = req->nbytes - 1; i > 1; --i)
+			req->data[i+2] = req->data[i];
+		req->data[3] = req->nbytes - 2;
+		req->data[2] = pmu_adb_flags;
+		/*req->data[1] = req->data[1];*/
+		req->data[0] = PMU_ADB_CMD;
+		req->nbytes += 2;
+		req->reply_expected = 1;
+		req->reply_len = 0;
+		ret = pmu_queue_request(req);
+		break;
+	}
+	if (ret) {
+		req->complete = 1;
+		return ret;
+	}
+
+	if (sync)
+		while (!req->complete)
+			pmu_poll();
+
+	return 0;
+}
+
+/* Enable/disable autopolling */
+static int __pmu_adb_autopoll(int devs)
+{
+	struct adb_request req;
+
+	if (devs) {
+		pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86,
+			    adb_dev_map >> 8, adb_dev_map);
+		pmu_adb_flags = 2;
+	} else {
+		pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF);
+		pmu_adb_flags = 0;
+	}
+	while (!req.complete)
+		pmu_poll();
+	return 0;
+}
+
+static int pmu_adb_autopoll(int devs)
+{
+	if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb)
+		return -ENXIO;
+
+	adb_dev_map = devs;
+	return __pmu_adb_autopoll(devs);
+}
+
+/* Reset the ADB bus */
+static int pmu_adb_reset_bus(void)
+{
+	struct adb_request req;
+	int save_autopoll = adb_dev_map;
+
+	if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb)
+		return -ENXIO;
+
+	/* anyone got a better idea?? */
+	__pmu_adb_autopoll(0);
+
+	req.nbytes = 4;
+	req.done = NULL;
+	req.data[0] = PMU_ADB_CMD;
+	req.data[1] = ADB_BUSRESET;
+	req.data[2] = 0;
+	req.data[3] = 0;
+	req.data[4] = 0;
+	req.reply_len = 0;
+	req.reply_expected = 1;
+	if (pmu_queue_request(&req) != 0) {
+		printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n");
+		return -EIO;
+	}
+	pmu_wait_complete(&req);
+
+	if (save_autopoll != 0)
+		__pmu_adb_autopoll(save_autopoll);
+
+	return 0;
+}
+#endif /* CONFIG_ADB */
+
+/* Construct and send a pmu request */
+int
+pmu_request(struct adb_request *req, void (*done)(struct adb_request *),
+	    int nbytes, ...)
+{
+	va_list list;
+	int i;
+
+	if (vias == NULL)
+		return -ENXIO;
+
+	if (nbytes < 0 || nbytes > 32) {
+		printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes);
+		req->complete = 1;
+		return -EINVAL;
+	}
+	req->nbytes = nbytes;
+	req->done = done;
+	va_start(list, nbytes);
+	for (i = 0; i < nbytes; ++i)
+		req->data[i] = va_arg(list, int);
+	va_end(list);
+	req->reply_len = 0;
+	req->reply_expected = 0;
+	return pmu_queue_request(req);
+}
+
+int
+pmu_queue_request(struct adb_request *req)
+{
+	unsigned long flags;
+	int nsend;
+
+	if (via == NULL) {
+		req->complete = 1;
+		return -ENXIO;
+	}
+	if (req->nbytes <= 0) {
+		req->complete = 1;
+		return 0;
+	}
+	nsend = pmu_data_len[req->data[0]][0];
+	if (nsend >= 0 && req->nbytes != nsend + 1) {
+		req->complete = 1;
+		return -EINVAL;
+	}
+
+	req->next = NULL;
+	req->sent = 0;
+	req->complete = 0;
+
+	spin_lock_irqsave(&pmu_lock, flags);
+	if (current_req != 0) {
+		last_req->next = req;
+		last_req = req;
+	} else {
+		current_req = req;
+		last_req = req;
+		if (pmu_state == idle)
+			pmu_start();
+	}
+	spin_unlock_irqrestore(&pmu_lock, flags);
+
+	return 0;
+}
+
+static inline void
+wait_for_ack(void)
+{
+	/* Sightly increased the delay, I had one occurrence of the message
+	 * reported
+	 */
+	int timeout = 4000;
+	while ((in_8(&via[B]) & TACK) == 0) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "PMU not responding (!ack)\n");
+			return;
+		}
+		udelay(10);
+	}
+}
+
+/* New PMU seems to be very sensitive to those timings, so we make sure
+ * PCI is flushed immediately */
+static inline void
+send_byte(int x)
+{
+	volatile unsigned char __iomem *v = via;
+
+	out_8(&v[ACR], in_8(&v[ACR]) | SR_OUT | SR_EXT);
+	out_8(&v[SR], x);
+	out_8(&v[B], in_8(&v[B]) & ~TREQ);		/* assert TREQ */
+	(void)in_8(&v[B]);
+}
+
+static inline void
+recv_byte(void)
+{
+	volatile unsigned char __iomem *v = via;
+
+	out_8(&v[ACR], (in_8(&v[ACR]) & ~SR_OUT) | SR_EXT);
+	in_8(&v[SR]);		/* resets SR */
+	out_8(&v[B], in_8(&v[B]) & ~TREQ);
+	(void)in_8(&v[B]);
+}
+
+static inline void
+pmu_done(struct adb_request *req)
+{
+	void (*done)(struct adb_request *) = req->done;
+	mb();
+	req->complete = 1;
+    	/* Here, we assume that if the request has a done member, the
+    	 * struct request will survive to setting req->complete to 1
+    	 */
+	if (done)
+		(*done)(req);
+}
+
+static void
+pmu_start(void)
+{
+	struct adb_request *req;
+
+	/* assert pmu_state == idle */
+	/* get the packet to send */
+	req = current_req;
+	if (req == 0 || pmu_state != idle
+	    || (/*req->reply_expected && */req_awaiting_reply))
+		return;
+
+	pmu_state = sending;
+	data_index = 1;
+	data_len = pmu_data_len[req->data[0]][0];
+
+	/* Sounds safer to make sure ACK is high before writing. This helped
+	 * kill a problem with ADB and some iBooks
+	 */
+	wait_for_ack();
+	/* set the shift register to shift out and send a byte */
+	send_byte(req->data[0]);
+}
+
+void
+pmu_poll(void)
+{
+	if (!via)
+		return;
+	if (disable_poll)
+		return;
+	via_pmu_interrupt(0, NULL);
+}
+
+void
+pmu_poll_adb(void)
+{
+	if (!via)
+		return;
+	if (disable_poll)
+		return;
+	/* Kicks ADB read when PMU is suspended */
+	adb_int_pending = 1;
+	do {
+		via_pmu_interrupt(0, NULL);
+	} while (pmu_suspended && (adb_int_pending || pmu_state != idle
+		|| req_awaiting_reply));
+}
+
+void
+pmu_wait_complete(struct adb_request *req)
+{
+	if (!via)
+		return;
+	while((pmu_state != idle && pmu_state != locked) || !req->complete)
+		via_pmu_interrupt(0, NULL);
+}
+
+/* This function loops until the PMU is idle and prevents it from
+ * anwsering to ADB interrupts. pmu_request can still be called.
+ * This is done to avoid spurrious shutdowns when we know we'll have
+ * interrupts switched off for a long time
+ */
+void
+pmu_suspend(void)
+{
+	unsigned long flags;
+
+	if (!via)
+		return;
+	
+	spin_lock_irqsave(&pmu_lock, flags);
+	pmu_suspended++;
+	if (pmu_suspended > 1) {
+		spin_unlock_irqrestore(&pmu_lock, flags);
+		return;
+	}
+
+	do {
+		spin_unlock_irqrestore(&pmu_lock, flags);
+		if (req_awaiting_reply)
+			adb_int_pending = 1;
+		via_pmu_interrupt(0, NULL);
+		spin_lock_irqsave(&pmu_lock, flags);
+		if (!adb_int_pending && pmu_state == idle && !req_awaiting_reply) {
+			if (gpio_irq >= 0)
+				disable_irq_nosync(gpio_irq);
+			out_8(&via[IER], CB1_INT | IER_CLR);
+			spin_unlock_irqrestore(&pmu_lock, flags);
+			break;
+		}
+	} while (1);
+}
+
+void
+pmu_resume(void)
+{
+	unsigned long flags;
+
+	if (!via || (pmu_suspended < 1))
+		return;
+
+	spin_lock_irqsave(&pmu_lock, flags);
+	pmu_suspended--;
+	if (pmu_suspended > 0) {
+		spin_unlock_irqrestore(&pmu_lock, flags);
+		return;
+	}
+	adb_int_pending = 1;
+	if (gpio_irq >= 0)
+		enable_irq(gpio_irq);
+	out_8(&via[IER], CB1_INT | IER_SET);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+	pmu_poll();
+}
+
+/* Interrupt data could be the result data from an ADB cmd */
+static void
+pmu_handle_data(unsigned char *data, int len)
+{
+	unsigned char ints, pirq;
+	int i = 0;
+
+	asleep = 0;
+	if (drop_interrupts || len < 1) {
+		adb_int_pending = 0;
+		pmu_irq_stats[8]++;
+		return;
+	}
+
+	/* Get PMU interrupt mask */
+	ints = data[0];
+
+	/* Record zero interrupts for stats */
+	if (ints == 0)
+		pmu_irq_stats[9]++;
+
+	/* Hack to deal with ADB autopoll flag */
+	if (ints & PMU_INT_ADB)
+		ints &= ~(PMU_INT_ADB_AUTO | PMU_INT_AUTO_SRQ_POLL);
+
+next:
+
+	if (ints == 0) {
+		if (i > pmu_irq_stats[10])
+			pmu_irq_stats[10] = i;
+		return;
+	}
+
+	for (pirq = 0; pirq < 8; pirq++)
+		if (ints & (1 << pirq))
+			break;
+	pmu_irq_stats[pirq]++;
+	i++;
+	ints &= ~(1 << pirq);
+
+	/* Note: for some reason, we get an interrupt with len=1,
+	 * data[0]==0 after each normal ADB interrupt, at least
+	 * on the Pismo. Still investigating...  --BenH
+	 */
+	if ((1 << pirq) & PMU_INT_ADB) {
+		if ((data[0] & PMU_INT_ADB_AUTO) == 0) {
+			struct adb_request *req = req_awaiting_reply;
+			if (req == 0) {
+				printk(KERN_ERR "PMU: extra ADB reply\n");
+				return;
+			}
+			req_awaiting_reply = NULL;
+			if (len <= 2)
+				req->reply_len = 0;
+			else {
+				memcpy(req->reply, data + 1, len - 1);
+				req->reply_len = len - 1;
+			}
+			pmu_done(req);
+		} else {
+			if (len == 4 && data[1] == 0x2c) {
+				extern int xmon_wants_key, xmon_adb_keycode;
+				if (xmon_wants_key) {
+					xmon_adb_keycode = data[2];
+					return;
+				}
+			}
+#ifdef CONFIG_ADB
+			/*
+			 * XXX On the [23]400 the PMU gives us an up
+			 * event for keycodes 0x74 or 0x75 when the PC
+			 * card eject buttons are released, so we
+			 * ignore those events.
+			 */
+			if (!(pmu_kind == PMU_OHARE_BASED && len == 4
+			      && data[1] == 0x2c && data[3] == 0xff
+			      && (data[2] & ~1) == 0xf4))
+				adb_input(data+1, len-1, 1);
+#endif /* CONFIG_ADB */		
+		}
+	}
+	/* Sound/brightness button pressed */
+	else if ((1 << pirq) & PMU_INT_SNDBRT) {
+#ifdef CONFIG_PMAC_BACKLIGHT
+		if (len == 3)
+			pmac_backlight_set_legacy_brightness_pmu(data[1] >> 4);
+#endif
+	}
+	/* Tick interrupt */
+	else if ((1 << pirq) & PMU_INT_TICK) {
+		/* Environement or tick interrupt, query batteries */
+		if (pmu_battery_count) {
+			if ((--query_batt_timer) == 0) {
+				query_battery_state();
+				query_batt_timer = BATTERY_POLLING_COUNT;
+			}
+		}
+        }
+	else if ((1 << pirq) & PMU_INT_ENVIRONMENT) {
+		if (pmu_battery_count)
+			query_battery_state();
+		pmu_pass_intr(data, len);
+		/* len == 6 is probably a bad check. But how do I
+		 * know what PMU versions send what events here? */
+		if (len == 6) {
+			via_pmu_event(PMU_EVT_POWER, !!(data[1]&8));
+			via_pmu_event(PMU_EVT_LID, data[1]&1);
+		}
+	} else {
+	       pmu_pass_intr(data, len);
+	}
+	goto next;
+}
+
+static struct adb_request*
+pmu_sr_intr(void)
+{
+	struct adb_request *req;
+	int bite = 0;
+
+	if (in_8(&via[B]) & TREQ) {
+		printk(KERN_ERR "PMU: spurious SR intr (%x)\n", in_8(&via[B]));
+		out_8(&via[IFR], SR_INT);
+		return NULL;
+	}
+	/* The ack may not yet be low when we get the interrupt */
+	while ((in_8(&via[B]) & TACK) != 0)
+			;
+
+	/* if reading grab the byte, and reset the interrupt */
+	if (pmu_state == reading || pmu_state == reading_intr)
+		bite = in_8(&via[SR]);
+
+	/* reset TREQ and wait for TACK to go high */
+	out_8(&via[B], in_8(&via[B]) | TREQ);
+	wait_for_ack();
+
+	switch (pmu_state) {
+	case sending:
+		req = current_req;
+		if (data_len < 0) {
+			data_len = req->nbytes - 1;
+			send_byte(data_len);
+			break;
+		}
+		if (data_index <= data_len) {
+			send_byte(req->data[data_index++]);
+			break;
+		}
+		req->sent = 1;
+		data_len = pmu_data_len[req->data[0]][1];
+		if (data_len == 0) {
+			pmu_state = idle;
+			current_req = req->next;
+			if (req->reply_expected)
+				req_awaiting_reply = req;
+			else
+				return req;
+		} else {
+			pmu_state = reading;
+			data_index = 0;
+			reply_ptr = req->reply + req->reply_len;
+			recv_byte();
+		}
+		break;
+
+	case intack:
+		data_index = 0;
+		data_len = -1;
+		pmu_state = reading_intr;
+		reply_ptr = interrupt_data[int_data_last];
+		recv_byte();
+		if (gpio_irq >= 0 && !gpio_irq_enabled) {
+			enable_irq(gpio_irq);
+			gpio_irq_enabled = 1;
+		}
+		break;
+
+	case reading:
+	case reading_intr:
+		if (data_len == -1) {
+			data_len = bite;
+			if (bite > 32)
+				printk(KERN_ERR "PMU: bad reply len %d\n", bite);
+		} else if (data_index < 32) {
+			reply_ptr[data_index++] = bite;
+		}
+		if (data_index < data_len) {
+			recv_byte();
+			break;
+		}
+
+		if (pmu_state == reading_intr) {
+			pmu_state = idle;
+			int_data_state[int_data_last] = int_data_ready;
+			interrupt_data_len[int_data_last] = data_len;
+		} else {
+			req = current_req;
+			/* 
+			 * For PMU sleep and freq change requests, we lock the
+			 * PMU until it's explicitly unlocked. This avoids any
+			 * spurrious event polling getting in
+			 */
+			current_req = req->next;
+			req->reply_len += data_index;
+			if (req->data[0] == PMU_SLEEP || req->data[0] == PMU_CPU_SPEED)
+				pmu_state = locked;
+			else
+				pmu_state = idle;
+			return req;
+		}
+		break;
+
+	default:
+		printk(KERN_ERR "via_pmu_interrupt: unknown state %d?\n",
+		       pmu_state);
+	}
+	return NULL;
+}
+
+static irqreturn_t
+via_pmu_interrupt(int irq, void *arg)
+{
+	unsigned long flags;
+	int intr;
+	int nloop = 0;
+	int int_data = -1;
+	struct adb_request *req = NULL;
+	int handled = 0;
+
+	/* This is a bit brutal, we can probably do better */
+	spin_lock_irqsave(&pmu_lock, flags);
+	++disable_poll;
+	
+	for (;;) {
+		intr = in_8(&via[IFR]) & (SR_INT | CB1_INT);
+		if (intr == 0)
+			break;
+		handled = 1;
+		if (++nloop > 1000) {
+			printk(KERN_DEBUG "PMU: stuck in intr loop, "
+			       "intr=%x, ier=%x pmu_state=%d\n",
+			       intr, in_8(&via[IER]), pmu_state);
+			break;
+		}
+		out_8(&via[IFR], intr);
+		if (intr & CB1_INT) {
+			adb_int_pending = 1;
+			pmu_irq_stats[0]++;
+		}
+		if (intr & SR_INT) {
+			req = pmu_sr_intr();
+			if (req)
+				break;
+		}
+	}
+
+recheck:
+	if (pmu_state == idle) {
+		if (adb_int_pending) {
+			if (int_data_state[0] == int_data_empty)
+				int_data_last = 0;
+			else if (int_data_state[1] == int_data_empty)
+				int_data_last = 1;
+			else
+				goto no_free_slot;
+			pmu_state = intack;
+			int_data_state[int_data_last] = int_data_fill;
+			/* Sounds safer to make sure ACK is high before writing.
+			 * This helped kill a problem with ADB and some iBooks
+			 */
+			wait_for_ack();
+			send_byte(PMU_INT_ACK);
+			adb_int_pending = 0;
+		} else if (current_req)
+			pmu_start();
+	}
+no_free_slot:			
+	/* Mark the oldest buffer for flushing */
+	if (int_data_state[!int_data_last] == int_data_ready) {
+		int_data_state[!int_data_last] = int_data_flush;
+		int_data = !int_data_last;
+	} else if (int_data_state[int_data_last] == int_data_ready) {
+		int_data_state[int_data_last] = int_data_flush;
+		int_data = int_data_last;
+	}
+	--disable_poll;
+	spin_unlock_irqrestore(&pmu_lock, flags);
+
+	/* Deal with completed PMU requests outside of the lock */
+	if (req) {
+		pmu_done(req);
+		req = NULL;
+	}
+		
+	/* Deal with interrupt datas outside of the lock */
+	if (int_data >= 0) {
+		pmu_handle_data(interrupt_data[int_data], interrupt_data_len[int_data]);
+		spin_lock_irqsave(&pmu_lock, flags);
+		++disable_poll;
+		int_data_state[int_data] = int_data_empty;
+		int_data = -1;
+		goto recheck;
+	}
+
+	return IRQ_RETVAL(handled);
+}
+
+void
+pmu_unlock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pmu_lock, flags);
+	if (pmu_state == locked)
+		pmu_state = idle;
+	adb_int_pending = 1;
+	spin_unlock_irqrestore(&pmu_lock, flags);
+}
+
+
+static irqreturn_t
+gpio1_interrupt(int irq, void *arg)
+{
+	unsigned long flags;
+
+	if ((in_8(gpio_reg + 0x9) & 0x02) == 0) {
+		spin_lock_irqsave(&pmu_lock, flags);
+		if (gpio_irq_enabled > 0) {
+			disable_irq_nosync(gpio_irq);
+			gpio_irq_enabled = 0;
+		}
+		pmu_irq_stats[1]++;
+		adb_int_pending = 1;
+		spin_unlock_irqrestore(&pmu_lock, flags);
+		via_pmu_interrupt(0, NULL);
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+void
+pmu_enable_irled(int on)
+{
+	struct adb_request req;
+
+	if (vias == NULL)
+		return ;
+	if (pmu_kind == PMU_KEYLARGO_BASED)
+		return ;
+
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL, PMU_POW_IRLED |
+	    (on ? PMU_POW_ON : PMU_POW_OFF));
+	pmu_wait_complete(&req);
+}
+
+void
+pmu_restart(void)
+{
+	struct adb_request req;
+
+	if (via == NULL)
+		return;
+
+	local_irq_disable();
+
+	drop_interrupts = 1;
+	
+	if (pmu_kind != PMU_KEYLARGO_BASED) {
+		pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
+						PMU_INT_TICK );
+		while(!req.complete)
+			pmu_poll();
+	}
+
+	pmu_request(&req, NULL, 1, PMU_RESET);
+	pmu_wait_complete(&req);
+	for (;;)
+		;
+}
+
+void
+pmu_shutdown(void)
+{
+	struct adb_request req;
+
+	if (via == NULL)
+		return;
+
+	local_irq_disable();
+
+	drop_interrupts = 1;
+
+	if (pmu_kind != PMU_KEYLARGO_BASED) {
+		pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
+						PMU_INT_TICK );
+		pmu_wait_complete(&req);
+	} else {
+		/* Disable server mode on shutdown or we'll just
+		 * wake up again
+		 */
+		pmu_set_server_mode(0);
+	}
+
+	pmu_request(&req, NULL, 5, PMU_SHUTDOWN,
+		    'M', 'A', 'T', 'T');
+	pmu_wait_complete(&req);
+	for (;;)
+		;
+}
+
+int
+pmu_present(void)
+{
+	return via != 0;
+}
+
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+/*
+ * Put the powerbook to sleep.
+ */
+ 
+static u32 save_via[8];
+
+static void
+save_via_state(void)
+{
+	save_via[0] = in_8(&via[ANH]);
+	save_via[1] = in_8(&via[DIRA]);
+	save_via[2] = in_8(&via[B]);
+	save_via[3] = in_8(&via[DIRB]);
+	save_via[4] = in_8(&via[PCR]);
+	save_via[5] = in_8(&via[ACR]);
+	save_via[6] = in_8(&via[T1CL]);
+	save_via[7] = in_8(&via[T1CH]);
+}
+static void
+restore_via_state(void)
+{
+	out_8(&via[ANH], save_via[0]);
+	out_8(&via[DIRA], save_via[1]);
+	out_8(&via[B], save_via[2]);
+	out_8(&via[DIRB], save_via[3]);
+	out_8(&via[PCR], save_via[4]);
+	out_8(&via[ACR], save_via[5]);
+	out_8(&via[T1CL], save_via[6]);
+	out_8(&via[T1CH], save_via[7]);
+	out_8(&via[IER], IER_CLR | 0x7f);	/* disable all intrs */
+	out_8(&via[IFR], 0x7f);				/* clear IFR */
+	out_8(&via[IER], IER_SET | SR_INT | CB1_INT);
+}
+
+#define	GRACKLE_PM	(1<<7)
+#define GRACKLE_DOZE	(1<<5)
+#define	GRACKLE_NAP	(1<<4)
+#define	GRACKLE_SLEEP	(1<<3)
+
+static int powerbook_sleep_grackle(void)
+{
+	unsigned long save_l2cr;
+	unsigned short pmcr1;
+	struct adb_request req;
+	struct pci_dev *grackle;
+
+	grackle = pci_get_bus_and_slot(0, 0);
+	if (!grackle)
+		return -ENODEV;
+
+	/* Turn off various things. Darwin does some retry tests here... */
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL0, PMU_POW0_OFF|PMU_POW0_HARD_DRIVE);
+	pmu_wait_complete(&req);
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+		PMU_POW_OFF|PMU_POW_BACKLIGHT|PMU_POW_IRLED|PMU_POW_MEDIABAY);
+	pmu_wait_complete(&req);
+
+	/* For 750, save backside cache setting and disable it */
+	save_l2cr = _get_L2CR();	/* (returns -1 if not available) */
+
+	if (!__fake_sleep) {
+		/* Ask the PMU to put us to sleep */
+		pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+		pmu_wait_complete(&req);
+	}
+
+	/* The VIA is supposed not to be restored correctly*/
+	save_via_state();
+	/* We shut down some HW */
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
+
+	pci_read_config_word(grackle, 0x70, &pmcr1);
+	/* Apparently, MacOS uses NAP mode for Grackle ??? */
+	pmcr1 &= ~(GRACKLE_DOZE|GRACKLE_SLEEP); 
+	pmcr1 |= GRACKLE_PM|GRACKLE_NAP;
+	pci_write_config_word(grackle, 0x70, pmcr1);
+
+	/* Call low-level ASM sleep handler */
+	if (__fake_sleep)
+		mdelay(5000);
+	else
+		low_sleep_handler();
+
+	/* We're awake again, stop grackle PM */
+	pci_read_config_word(grackle, 0x70, &pmcr1);
+	pmcr1 &= ~(GRACKLE_PM|GRACKLE_DOZE|GRACKLE_SLEEP|GRACKLE_NAP); 
+	pci_write_config_word(grackle, 0x70, pmcr1);
+
+	pci_dev_put(grackle);
+
+	/* Make sure the PMU is idle */
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
+	restore_via_state();
+	
+	/* Restore L2 cache */
+	if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
+ 		_set_L2CR(save_l2cr);
+	
+	/* Restore userland MMU context */
+	switch_mmu_context(NULL, current->active_mm, NULL);
+
+	/* Power things up */
+	pmu_unlock();
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask);
+	pmu_wait_complete(&req);
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL0,
+			PMU_POW0_ON|PMU_POW0_HARD_DRIVE);
+	pmu_wait_complete(&req);
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+			PMU_POW_ON|PMU_POW_BACKLIGHT|PMU_POW_CHARGER|PMU_POW_IRLED|PMU_POW_MEDIABAY);
+	pmu_wait_complete(&req);
+
+	return 0;
+}
+
+static int
+powerbook_sleep_Core99(void)
+{
+	unsigned long save_l2cr;
+	unsigned long save_l3cr;
+	struct adb_request req;
+	
+	if (pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) < 0) {
+		printk(KERN_ERR "Sleep mode not supported on this machine\n");
+		return -ENOSYS;
+	}
+
+	if (num_online_cpus() > 1 || cpu_is_offline(0))
+		return -EAGAIN;
+
+	/* Stop environment and ADB interrupts */
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0);
+	pmu_wait_complete(&req);
+
+	/* Tell PMU what events will wake us up */
+	pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_CLR_WAKEUP_EVENTS,
+		0xff, 0xff);
+	pmu_wait_complete(&req);
+	pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_SET_WAKEUP_EVENTS,
+		0, PMU_PWR_WAKEUP_KEY |
+		(option_lid_wakeup ? PMU_PWR_WAKEUP_LID_OPEN : 0));
+	pmu_wait_complete(&req);
+
+	/* Save the state of the L2 and L3 caches */
+	save_l3cr = _get_L3CR();	/* (returns -1 if not available) */
+	save_l2cr = _get_L2CR();	/* (returns -1 if not available) */
+
+	if (!__fake_sleep) {
+		/* Ask the PMU to put us to sleep */
+		pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+		pmu_wait_complete(&req);
+	}
+
+	/* The VIA is supposed not to be restored correctly*/
+	save_via_state();
+
+	/* Shut down various ASICs. There's a chance that we can no longer
+	 * talk to the PMU after this, so I moved it to _after_ sending the
+	 * sleep command to it. Still need to be checked.
+	 */
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 1);
+
+	/* Call low-level ASM sleep handler */
+	if (__fake_sleep)
+		mdelay(5000);
+	else
+		low_sleep_handler();
+
+	/* Restore Apple core ASICs state */
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 0);
+
+	/* Restore VIA */
+	restore_via_state();
+
+	/* tweak LPJ before cpufreq is there */
+	loops_per_jiffy *= 2;
+
+	/* Restore video */
+	pmac_call_early_video_resume();
+
+	/* Restore L2 cache */
+	if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
+ 		_set_L2CR(save_l2cr);
+	/* Restore L3 cache */
+	if (save_l3cr != 0xffffffff && (save_l3cr & L3CR_L3E) != 0)
+ 		_set_L3CR(save_l3cr);
+	
+	/* Restore userland MMU context */
+	switch_mmu_context(NULL, current->active_mm, NULL);
+
+	/* Tell PMU we are ready */
+	pmu_unlock();
+	pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2);
+	pmu_wait_complete(&req);
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask);
+	pmu_wait_complete(&req);
+
+	/* Restore LPJ, cpufreq will adjust the cpu frequency */
+	loops_per_jiffy /= 2;
+
+	return 0;
+}
+
+#define PB3400_MEM_CTRL		0xf8000000
+#define PB3400_MEM_CTRL_SLEEP	0x70
+
+static void __iomem *pb3400_mem_ctrl;
+
+static void powerbook_sleep_init_3400(void)
+{
+	/* map in the memory controller registers */
+	pb3400_mem_ctrl = ioremap(PB3400_MEM_CTRL, 0x100);
+	if (pb3400_mem_ctrl == NULL)
+		printk(KERN_WARNING "ioremap failed: sleep won't be possible");
+}
+
+static int powerbook_sleep_3400(void)
+{
+	int i, x;
+	unsigned int hid0;
+	unsigned long msr;
+	struct adb_request sleep_req;
+	unsigned int __iomem *mem_ctrl_sleep;
+
+	if (pb3400_mem_ctrl == NULL)
+		return -ENOMEM;
+	mem_ctrl_sleep = pb3400_mem_ctrl + PB3400_MEM_CTRL_SLEEP;
+
+	/* Set the memory controller to keep the memory refreshed
+	   while we're asleep */
+	for (i = 0x403f; i >= 0x4000; --i) {
+		out_be32(mem_ctrl_sleep, i);
+		do {
+			x = (in_be32(mem_ctrl_sleep) >> 16) & 0x3ff;
+		} while (x == 0);
+		if (x >= 0x100)
+			break;
+	}
+
+	/* Ask the PMU to put us to sleep */
+	pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+	pmu_wait_complete(&sleep_req);
+	pmu_unlock();
+
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 1);
+
+	asleep = 1;
+
+	/* Put the CPU into sleep mode */
+	hid0 = mfspr(SPRN_HID0);
+	hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP;
+	mtspr(SPRN_HID0, hid0);
+	local_irq_enable();
+	msr = mfmsr() | MSR_POW;
+	while (asleep) {
+		mb();
+		mtmsr(msr);
+		isync();
+	}
+	local_irq_disable();
+
+	/* OK, we're awake again, start restoring things */
+	out_be32(mem_ctrl_sleep, 0x3f);
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 0);
+
+	return 0;
+}
+
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+
+/*
+ * Support for /dev/pmu device
+ */
+#define RB_SIZE		0x10
+struct pmu_private {
+	struct list_head list;
+	int	rb_get;
+	int	rb_put;
+	struct rb_entry {
+		unsigned short len;
+		unsigned char data[16];
+	}	rb_buf[RB_SIZE];
+	wait_queue_head_t wait;
+	spinlock_t lock;
+#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
+	int	backlight_locker;
+#endif
+};
+
+static LIST_HEAD(all_pmu_pvt);
+static DEFINE_SPINLOCK(all_pvt_lock);
+
+static void
+pmu_pass_intr(unsigned char *data, int len)
+{
+	struct pmu_private *pp;
+	struct list_head *list;
+	int i;
+	unsigned long flags;
+
+	if (len > sizeof(pp->rb_buf[0].data))
+		len = sizeof(pp->rb_buf[0].data);
+	spin_lock_irqsave(&all_pvt_lock, flags);
+	for (list = &all_pmu_pvt; (list = list->next) != &all_pmu_pvt; ) {
+		pp = list_entry(list, struct pmu_private, list);
+		spin_lock(&pp->lock);
+		i = pp->rb_put + 1;
+		if (i >= RB_SIZE)
+			i = 0;
+		if (i != pp->rb_get) {
+			struct rb_entry *rp = &pp->rb_buf[pp->rb_put];
+			rp->len = len;
+			memcpy(rp->data, data, len);
+			pp->rb_put = i;
+			wake_up_interruptible(&pp->wait);
+		}
+		spin_unlock(&pp->lock);
+	}
+	spin_unlock_irqrestore(&all_pvt_lock, flags);
+}
+
+static int
+pmu_open(struct inode *inode, struct file *file)
+{
+	struct pmu_private *pp;
+	unsigned long flags;
+
+	pp = kmalloc(sizeof(struct pmu_private), GFP_KERNEL);
+	if (pp == 0)
+		return -ENOMEM;
+	pp->rb_get = pp->rb_put = 0;
+	spin_lock_init(&pp->lock);
+	init_waitqueue_head(&pp->wait);
+	mutex_lock(&pmu_info_proc_mutex);
+	spin_lock_irqsave(&all_pvt_lock, flags);
+#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
+	pp->backlight_locker = 0;
+#endif
+	list_add(&pp->list, &all_pmu_pvt);
+	spin_unlock_irqrestore(&all_pvt_lock, flags);
+	file->private_data = pp;
+	mutex_unlock(&pmu_info_proc_mutex);
+	return 0;
+}
+
+static ssize_t 
+pmu_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct pmu_private *pp = file->private_data;
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+	int ret = 0;
+
+	if (count < 1 || pp == 0)
+		return -EINVAL;
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+
+	spin_lock_irqsave(&pp->lock, flags);
+	add_wait_queue(&pp->wait, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+
+	for (;;) {
+		ret = -EAGAIN;
+		if (pp->rb_get != pp->rb_put) {
+			int i = pp->rb_get;
+			struct rb_entry *rp = &pp->rb_buf[i];
+			ret = rp->len;
+			spin_unlock_irqrestore(&pp->lock, flags);
+			if (ret > count)
+				ret = count;
+			if (ret > 0 && copy_to_user(buf, rp->data, ret))
+				ret = -EFAULT;
+			if (++i >= RB_SIZE)
+				i = 0;
+			spin_lock_irqsave(&pp->lock, flags);
+			pp->rb_get = i;
+		}
+		if (ret >= 0)
+			break;
+		if (file->f_flags & O_NONBLOCK)
+			break;
+		ret = -ERESTARTSYS;
+		if (signal_pending(current))
+			break;
+		spin_unlock_irqrestore(&pp->lock, flags);
+		schedule();
+		spin_lock_irqsave(&pp->lock, flags);
+	}
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&pp->wait, &wait);
+	spin_unlock_irqrestore(&pp->lock, flags);
+	
+	return ret;
+}
+
+static ssize_t
+pmu_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	return 0;
+}
+
+static unsigned int
+pmu_fpoll(struct file *filp, poll_table *wait)
+{
+	struct pmu_private *pp = filp->private_data;
+	unsigned int mask = 0;
+	unsigned long flags;
+	
+	if (pp == 0)
+		return 0;
+	poll_wait(filp, &pp->wait, wait);
+	spin_lock_irqsave(&pp->lock, flags);
+	if (pp->rb_get != pp->rb_put)
+		mask |= POLLIN;
+	spin_unlock_irqrestore(&pp->lock, flags);
+	return mask;
+}
+
+static int
+pmu_release(struct inode *inode, struct file *file)
+{
+	struct pmu_private *pp = file->private_data;
+	unsigned long flags;
+
+	if (pp != 0) {
+		file->private_data = NULL;
+		spin_lock_irqsave(&all_pvt_lock, flags);
+		list_del(&pp->list);
+		spin_unlock_irqrestore(&all_pvt_lock, flags);
+
+#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT)
+		if (pp->backlight_locker)
+			pmac_backlight_enable();
+#endif
+
+		kfree(pp);
+	}
+	return 0;
+}
+
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+static void pmac_suspend_disable_irqs(void)
+{
+	/* Call platform functions marked "on sleep" */
+	pmac_pfunc_i2c_suspend();
+	pmac_pfunc_base_suspend();
+}
+
+static int powerbook_sleep(suspend_state_t state)
+{
+	int error = 0;
+
+	/* Wait for completion of async requests */
+	while (!batt_req.complete)
+		pmu_poll();
+
+	/* Giveup the lazy FPU & vec so we don't have to back them
+	 * up from the low level code
+	 */
+	enable_kernel_fp();
+
+#ifdef CONFIG_ALTIVEC
+	if (cpu_has_feature(CPU_FTR_ALTIVEC))
+		enable_kernel_altivec();
+#endif /* CONFIG_ALTIVEC */
+
+	switch (pmu_kind) {
+	case PMU_OHARE_BASED:
+		error = powerbook_sleep_3400();
+		break;
+	case PMU_HEATHROW_BASED:
+	case PMU_PADDINGTON_BASED:
+		error = powerbook_sleep_grackle();
+		break;
+	case PMU_KEYLARGO_BASED:
+		error = powerbook_sleep_Core99();
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	if (error)
+		return error;
+
+	mdelay(100);
+
+	return 0;
+}
+
+static void pmac_suspend_enable_irqs(void)
+{
+	/* Force a poll of ADB interrupts */
+	adb_int_pending = 1;
+	via_pmu_interrupt(0, NULL);
+
+	mdelay(10);
+
+	/* Call platform functions marked "on wake" */
+	pmac_pfunc_base_resume();
+	pmac_pfunc_i2c_resume();
+}
+
+static int pmu_sleep_valid(suspend_state_t state)
+{
+	return state == PM_SUSPEND_MEM
+		&& (pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, -1) >= 0);
+}
+
+static const struct platform_suspend_ops pmu_pm_ops = {
+	.enter = powerbook_sleep,
+	.valid = pmu_sleep_valid,
+};
+
+static int register_pmu_pm_ops(void)
+{
+	if (pmu_kind == PMU_OHARE_BASED)
+		powerbook_sleep_init_3400();
+	ppc_md.suspend_disable_irqs = pmac_suspend_disable_irqs;
+	ppc_md.suspend_enable_irqs = pmac_suspend_enable_irqs;
+	suspend_set_ops(&pmu_pm_ops);
+
+	return 0;
+}
+
+device_initcall(register_pmu_pm_ops);
+#endif
+
+static int pmu_ioctl(struct file *filp,
+		     u_int cmd, u_long arg)
+{
+	__u32 __user *argp = (__u32 __user *)arg;
+	int error = -EINVAL;
+
+	switch (cmd) {
+	case PMU_IOC_SLEEP:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+		return pm_suspend(PM_SUSPEND_MEM);
+	case PMU_IOC_CAN_SLEEP:
+		if (pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, -1) < 0)
+			return put_user(0, argp);
+		else
+			return put_user(1, argp);
+
+#ifdef CONFIG_PMAC_BACKLIGHT_LEGACY
+	/* Compatibility ioctl's for backlight */
+	case PMU_IOC_GET_BACKLIGHT:
+	{
+		int brightness;
+
+		brightness = pmac_backlight_get_legacy_brightness();
+		if (brightness < 0)
+			return brightness;
+		else
+			return put_user(brightness, argp);
+
+	}
+	case PMU_IOC_SET_BACKLIGHT:
+	{
+		int brightness;
+
+		error = get_user(brightness, argp);
+		if (error)
+			return error;
+
+		return pmac_backlight_set_legacy_brightness(brightness);
+	}
+#ifdef CONFIG_INPUT_ADBHID
+	case PMU_IOC_GRAB_BACKLIGHT: {
+		struct pmu_private *pp = filp->private_data;
+
+		if (pp->backlight_locker)
+			return 0;
+
+		pp->backlight_locker = 1;
+		pmac_backlight_disable();
+
+		return 0;
+	}
+#endif /* CONFIG_INPUT_ADBHID */
+#endif /* CONFIG_PMAC_BACKLIGHT_LEGACY */
+
+	case PMU_IOC_GET_MODEL:
+	    	return put_user(pmu_kind, argp);
+	case PMU_IOC_HAS_ADB:
+		return put_user(pmu_has_adb, argp);
+	}
+	return error;
+}
+
+static long pmu_unlocked_ioctl(struct file *filp,
+			       u_int cmd, u_long arg)
+{
+	int ret;
+
+	mutex_lock(&pmu_info_proc_mutex);
+	ret = pmu_ioctl(filp, cmd, arg);
+	mutex_unlock(&pmu_info_proc_mutex);
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+#define PMU_IOC_GET_BACKLIGHT32	_IOR('B', 1, compat_size_t)
+#define PMU_IOC_SET_BACKLIGHT32	_IOW('B', 2, compat_size_t)
+#define PMU_IOC_GET_MODEL32	_IOR('B', 3, compat_size_t)
+#define PMU_IOC_HAS_ADB32	_IOR('B', 4, compat_size_t)
+#define PMU_IOC_CAN_SLEEP32	_IOR('B', 5, compat_size_t)
+#define PMU_IOC_GRAB_BACKLIGHT32 _IOR('B', 6, compat_size_t)
+
+static long compat_pmu_ioctl (struct file *filp, u_int cmd, u_long arg)
+{
+	switch (cmd) {
+	case PMU_IOC_SLEEP:
+		break;
+	case PMU_IOC_GET_BACKLIGHT32:
+		cmd = PMU_IOC_GET_BACKLIGHT;
+		break;
+	case PMU_IOC_SET_BACKLIGHT32:
+		cmd = PMU_IOC_SET_BACKLIGHT;
+		break;
+	case PMU_IOC_GET_MODEL32:
+		cmd = PMU_IOC_GET_MODEL;
+		break;
+	case PMU_IOC_HAS_ADB32:
+		cmd = PMU_IOC_HAS_ADB;
+		break;
+	case PMU_IOC_CAN_SLEEP32:
+		cmd = PMU_IOC_CAN_SLEEP;
+		break;
+	case PMU_IOC_GRAB_BACKLIGHT32:
+		cmd = PMU_IOC_GRAB_BACKLIGHT;
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return pmu_unlocked_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations pmu_device_fops = {
+	.read		= pmu_read,
+	.write		= pmu_write,
+	.poll		= pmu_fpoll,
+	.unlocked_ioctl	= pmu_unlocked_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= compat_pmu_ioctl,
+#endif
+	.open		= pmu_open,
+	.release	= pmu_release,
+	.llseek		= noop_llseek,
+};
+
+static struct miscdevice pmu_device = {
+	PMU_MINOR, "pmu", &pmu_device_fops
+};
+
+static int pmu_device_init(void)
+{
+	if (!via)
+		return 0;
+	if (misc_register(&pmu_device) < 0)
+		printk(KERN_ERR "via-pmu: cannot register misc device.\n");
+	return 0;
+}
+device_initcall(pmu_device_init);
+
+
+#ifdef DEBUG_SLEEP
+static inline void 
+polled_handshake(volatile unsigned char __iomem *via)
+{
+	via[B] &= ~TREQ; eieio();
+	while ((via[B] & TACK) != 0)
+		;
+	via[B] |= TREQ; eieio();
+	while ((via[B] & TACK) == 0)
+		;
+}
+
+static inline void 
+polled_send_byte(volatile unsigned char __iomem *via, int x)
+{
+	via[ACR] |= SR_OUT | SR_EXT; eieio();
+	via[SR] = x; eieio();
+	polled_handshake(via);
+}
+
+static inline int
+polled_recv_byte(volatile unsigned char __iomem *via)
+{
+	int x;
+
+	via[ACR] = (via[ACR] & ~SR_OUT) | SR_EXT; eieio();
+	x = via[SR]; eieio();
+	polled_handshake(via);
+	x = via[SR]; eieio();
+	return x;
+}
+
+int
+pmu_polled_request(struct adb_request *req)
+{
+	unsigned long flags;
+	int i, l, c;
+	volatile unsigned char __iomem *v = via;
+
+	req->complete = 1;
+	c = req->data[0];
+	l = pmu_data_len[c][0];
+	if (l >= 0 && req->nbytes != l + 1)
+		return -EINVAL;
+
+	local_irq_save(flags);
+	while (pmu_state != idle)
+		pmu_poll();
+
+	while ((via[B] & TACK) == 0)
+		;
+	polled_send_byte(v, c);
+	if (l < 0) {
+		l = req->nbytes - 1;
+		polled_send_byte(v, l);
+	}
+	for (i = 1; i <= l; ++i)
+		polled_send_byte(v, req->data[i]);
+
+	l = pmu_data_len[c][1];
+	if (l < 0)
+		l = polled_recv_byte(v);
+	for (i = 0; i < l; ++i)
+		req->reply[i + req->reply_len] = polled_recv_byte(v);
+
+	if (req->done)
+		(*req->done)(req);
+
+	local_irq_restore(flags);
+	return 0;
+}
+
+/* N.B. This doesn't work on the 3400 */
+void pmu_blink(int n)
+{
+	struct adb_request req;
+
+	memset(&req, 0, sizeof(req));
+
+	for (; n > 0; --n) {
+		req.nbytes = 4;
+		req.done = NULL;
+		req.data[0] = 0xee;
+		req.data[1] = 4;
+		req.data[2] = 0;
+		req.data[3] = 1;
+		req.reply[0] = ADB_RET_OK;
+		req.reply_len = 1;
+		req.reply_expected = 0;
+		pmu_polled_request(&req);
+		mdelay(50);
+		req.nbytes = 4;
+		req.done = NULL;
+		req.data[0] = 0xee;
+		req.data[1] = 4;
+		req.data[2] = 0;
+		req.data[3] = 0;
+		req.reply[0] = ADB_RET_OK;
+		req.reply_len = 1;
+		req.reply_expected = 0;
+		pmu_polled_request(&req);
+		mdelay(50);
+	}
+	mdelay(50);
+}
+#endif /* DEBUG_SLEEP */
+
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+int pmu_sys_suspended;
+
+static int pmu_syscore_suspend(void)
+{
+	/* Suspend PMU event interrupts */
+	pmu_suspend();
+	pmu_sys_suspended = 1;
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	/* Tell backlight code not to muck around with the chip anymore */
+	pmu_backlight_set_sleep(1);
+#endif
+
+	return 0;
+}
+
+static void pmu_syscore_resume(void)
+{
+	struct adb_request req;
+
+	if (!pmu_sys_suspended)
+		return;
+
+	/* Tell PMU we are ready */
+	pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2);
+	pmu_wait_complete(&req);
+
+#ifdef CONFIG_PMAC_BACKLIGHT
+	/* Tell backlight code it can use the chip again */
+	pmu_backlight_set_sleep(0);
+#endif
+	/* Resume PMU event interrupts */
+	pmu_resume();
+	pmu_sys_suspended = 0;
+}
+
+static struct syscore_ops pmu_syscore_ops = {
+	.suspend = pmu_syscore_suspend,
+	.resume = pmu_syscore_resume,
+};
+
+static int pmu_syscore_register(void)
+{
+	register_syscore_ops(&pmu_syscore_ops);
+
+	return 0;
+}
+subsys_initcall(pmu_syscore_register);
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+
+EXPORT_SYMBOL(pmu_request);
+EXPORT_SYMBOL(pmu_queue_request);
+EXPORT_SYMBOL(pmu_poll);
+EXPORT_SYMBOL(pmu_poll_adb);
+EXPORT_SYMBOL(pmu_wait_complete);
+EXPORT_SYMBOL(pmu_suspend);
+EXPORT_SYMBOL(pmu_resume);
+EXPORT_SYMBOL(pmu_unlock);
+#if defined(CONFIG_PPC32)
+EXPORT_SYMBOL(pmu_enable_irled);
+EXPORT_SYMBOL(pmu_battery_count);
+EXPORT_SYMBOL(pmu_batteries);
+EXPORT_SYMBOL(pmu_power_flags);
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/via-pmu68k.c b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu68k.c
new file mode 100644
index 0000000..25465fb
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/via-pmu68k.c
@@ -0,0 +1,817 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for the PMU on 68K-based Apple PowerBooks
+ *
+ * The VIA (versatile interface adapter) interfaces to the PMU,
+ * a 6805 microprocessor core whose primary function is to control
+ * battery charging and system power on the PowerBooks.
+ * The PMU also controls the ADB (Apple Desktop Bus) which connects
+ * to the keyboard and mouse, as well as the non-volatile RAM
+ * and the RTC (real time clock) chip.
+ *
+ * Adapted for 68K PMU by Joshua M. Thompson
+ *
+ * Based largely on the PowerMac PMU code by Paul Mackerras and
+ * Fabio Riccardi.
+ *
+ * Also based on the PMU driver from MkLinux by Apple Computer, Inc.
+ * and the Open Software Foundation, Inc.
+ */
+
+#include <stdarg.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/blkdev.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <linux/cuda.h>
+
+#include <asm/macintosh.h>
+#include <asm/macints.h>
+#include <asm/mac_via.h>
+
+#include <asm/pgtable.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+/* Misc minor number allocated for /dev/pmu */
+#define PMU_MINOR	154
+
+/* VIA registers - spaced 0x200 bytes apart */
+#define RS		0x200		/* skip between registers */
+#define B		0		/* B-side data */
+#define A		RS		/* A-side data */
+#define DIRB		(2*RS)		/* B-side direction (1=output) */
+#define DIRA		(3*RS)		/* A-side direction (1=output) */
+#define T1CL		(4*RS)		/* Timer 1 ctr/latch (low 8 bits) */
+#define T1CH		(5*RS)		/* Timer 1 counter (high 8 bits) */
+#define T1LL		(6*RS)		/* Timer 1 latch (low 8 bits) */
+#define T1LH		(7*RS)		/* Timer 1 latch (high 8 bits) */
+#define T2CL		(8*RS)		/* Timer 2 ctr/latch (low 8 bits) */
+#define T2CH		(9*RS)		/* Timer 2 counter (high 8 bits) */
+#define SR		(10*RS)		/* Shift register */
+#define ACR		(11*RS)		/* Auxiliary control register */
+#define PCR		(12*RS)		/* Peripheral control register */
+#define IFR		(13*RS)		/* Interrupt flag register */
+#define IER		(14*RS)		/* Interrupt enable register */
+#define ANH		(15*RS)		/* A-side data, no handshake */
+
+/* Bits in B data register: both active low */
+#define TACK		0x02		/* Transfer acknowledge (input) */
+#define TREQ		0x04		/* Transfer request (output) */
+
+/* Bits in ACR */
+#define SR_CTRL		0x1c		/* Shift register control bits */
+#define SR_EXT		0x0c		/* Shift on external clock */
+#define SR_OUT		0x10		/* Shift out if 1 */
+
+/* Bits in IFR and IER */
+#define SR_INT		0x04		/* Shift register full/empty */
+#define CB1_INT		0x10		/* transition on CB1 input */
+
+static enum pmu_state {
+	idle,
+	sending,
+	intack,
+	reading,
+	reading_intr,
+} pmu_state;
+
+static struct adb_request *current_req;
+static struct adb_request *last_req;
+static struct adb_request *req_awaiting_reply;
+static unsigned char interrupt_data[32];
+static unsigned char *reply_ptr;
+static int data_index;
+static int data_len;
+static int adb_int_pending;
+static int pmu_adb_flags;
+static int adb_dev_map;
+static struct adb_request bright_req_1, bright_req_2, bright_req_3;
+static int pmu_kind = PMU_UNKNOWN;
+static int pmu_fully_inited;
+
+int asleep;
+
+static int pmu_probe(void);
+static int pmu_init(void);
+static void pmu_start(void);
+static irqreturn_t pmu_interrupt(int irq, void *arg);
+static int pmu_send_request(struct adb_request *req, int sync);
+static int pmu_autopoll(int devs);
+void pmu_poll(void);
+static int pmu_reset_bus(void);
+
+static void pmu_start(void);
+static void send_byte(int x);
+static void recv_byte(void);
+static void pmu_done(struct adb_request *req);
+static void pmu_handle_data(unsigned char *data, int len);
+static void set_volume(int level);
+static void pmu_enable_backlight(int on);
+static void pmu_set_brightness(int level);
+
+struct adb_driver via_pmu_driver = {
+	"68K PMU",
+	pmu_probe,
+	pmu_init,
+	pmu_send_request,
+	pmu_autopoll,
+	pmu_poll,
+	pmu_reset_bus
+};
+
+/*
+ * This table indicates for each PMU opcode:
+ * - the number of data bytes to be sent with the command, or -1
+ *   if a length byte should be sent,
+ * - the number of response bytes which the PMU will return, or
+ *   -1 if it will send a length byte.
+ */
+static s8 pmu_data_len[256][2] = {
+/*	   0	   1	   2	   3	   4	   5	   6	   7  */
+/*00*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*08*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*10*/	{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*18*/	{ 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0},
+/*20*/	{-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*28*/	{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1},
+/*30*/	{ 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*38*/	{ 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0},
+/*40*/	{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*48*/	{ 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1},
+/*50*/	{ 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0},
+/*58*/	{ 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},
+/*60*/	{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*68*/	{ 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},
+/*70*/	{ 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*78*/	{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1},
+/*80*/	{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*88*/	{ 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*90*/	{ 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*98*/	{ 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*a0*/	{ 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},
+/*a8*/	{ 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*b0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*b8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*c0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*c8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+/*d0*/	{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*d8*/	{ 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1},
+/*e0*/	{-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0},
+/*e8*/	{ 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0},
+/*f0*/	{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
+/*f8*/	{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
+};
+
+int pmu_probe(void)
+{
+	if (macintosh_config->adb_type == MAC_ADB_PB1) {
+		pmu_kind = PMU_68K_V1;
+	} else if (macintosh_config->adb_type == MAC_ADB_PB2) {
+		pmu_kind = PMU_68K_V2;
+	} else {
+		return -ENODEV;
+	}
+
+	pmu_state = idle;
+
+	return 0;
+}
+
+static int 
+pmu_init(void)
+{
+	int timeout;
+	volatile struct adb_request req;
+
+	via2[B] |= TREQ;				/* negate TREQ */
+	via2[DIRB] = (via2[DIRB] | TREQ) & ~TACK;	/* TACK in, TREQ out */
+
+	pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB);
+	timeout =  100000;
+	while (!req.complete) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "pmu_init: no response from PMU\n");
+			return -EAGAIN;
+		}
+		udelay(10);
+		pmu_poll();
+	}
+
+	/* ack all pending interrupts */
+	timeout = 100000;
+	interrupt_data[0] = 1;
+	while (interrupt_data[0] || pmu_state != idle) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "pmu_init: timed out acking intrs\n");
+			return -EAGAIN;
+		}
+		if (pmu_state == idle) {
+			adb_int_pending = 1;
+			pmu_interrupt(0, NULL);
+		}
+		pmu_poll();
+		udelay(10);
+	}
+
+	pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK,
+			PMU_INT_ADB_AUTO|PMU_INT_SNDBRT|PMU_INT_ADB);
+	timeout =  100000;
+	while (!req.complete) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "pmu_init: no response from PMU\n");
+			return -EAGAIN;
+		}
+		udelay(10);
+		pmu_poll();
+	}
+
+	bright_req_1.complete = 1;
+	bright_req_2.complete = 1;
+	bright_req_3.complete = 1;
+
+	if (request_irq(IRQ_MAC_ADB_SR, pmu_interrupt, 0, "pmu-shift",
+			pmu_interrupt)) {
+		printk(KERN_ERR "pmu_init: can't get irq %d\n",
+			IRQ_MAC_ADB_SR);
+		return -EAGAIN;
+	}
+	if (request_irq(IRQ_MAC_ADB_CL, pmu_interrupt, 0, "pmu-clock",
+			pmu_interrupt)) {
+		printk(KERN_ERR "pmu_init: can't get irq %d\n",
+			IRQ_MAC_ADB_CL);
+		free_irq(IRQ_MAC_ADB_SR, pmu_interrupt);
+		return -EAGAIN;
+	}
+
+	pmu_fully_inited = 1;
+	
+	/* Enable backlight */
+	pmu_enable_backlight(1);
+
+	printk("adb: PMU 68K driver v0.5 for Unified ADB.\n");
+
+	return 0;
+}
+
+int
+pmu_get_model(void)
+{
+	return pmu_kind;
+}
+
+/* Send an ADB command */
+static int 
+pmu_send_request(struct adb_request *req, int sync)
+{
+    int i, ret;
+
+    if (!pmu_fully_inited)
+    {
+ 	req->complete = 1;
+   	return -ENXIO;
+   }
+
+    ret = -EINVAL;
+	
+    switch (req->data[0]) {
+    case PMU_PACKET:
+		for (i = 0; i < req->nbytes - 1; ++i)
+			req->data[i] = req->data[i+1];
+		--req->nbytes;
+		if (pmu_data_len[req->data[0]][1] != 0) {
+			req->reply[0] = ADB_RET_OK;
+			req->reply_len = 1;
+		} else
+			req->reply_len = 0;
+		ret = pmu_queue_request(req);
+		break;
+    case CUDA_PACKET:
+		switch (req->data[1]) {
+		case CUDA_GET_TIME:
+			if (req->nbytes != 2)
+				break;
+			req->data[0] = PMU_READ_RTC;
+			req->nbytes = 1;
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_GET_TIME;
+			ret = pmu_queue_request(req);
+			break;
+		case CUDA_SET_TIME:
+			if (req->nbytes != 6)
+				break;
+			req->data[0] = PMU_SET_RTC;
+			req->nbytes = 5;
+			for (i = 1; i <= 4; ++i)
+				req->data[i] = req->data[i+1];
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_SET_TIME;
+			ret = pmu_queue_request(req);
+			break;
+		case CUDA_GET_PRAM:
+			if (req->nbytes != 4)
+				break;
+			req->data[0] = PMU_READ_NVRAM;
+			req->data[1] = req->data[2];
+			req->data[2] = req->data[3];
+			req->nbytes = 3;
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_GET_PRAM;
+			ret = pmu_queue_request(req);
+			break;
+		case CUDA_SET_PRAM:
+			if (req->nbytes != 5)
+				break;
+			req->data[0] = PMU_WRITE_NVRAM;
+			req->data[1] = req->data[2];
+			req->data[2] = req->data[3];
+			req->data[3] = req->data[4];
+			req->nbytes = 4;
+			req->reply_len = 3;
+			req->reply[0] = CUDA_PACKET;
+			req->reply[1] = 0;
+			req->reply[2] = CUDA_SET_PRAM;
+			ret = pmu_queue_request(req);
+			break;
+		}
+		break;
+    case ADB_PACKET:
+		for (i = req->nbytes - 1; i > 1; --i)
+			req->data[i+2] = req->data[i];
+		req->data[3] = req->nbytes - 2;
+		req->data[2] = pmu_adb_flags;
+		/*req->data[1] = req->data[1];*/
+		req->data[0] = PMU_ADB_CMD;
+		req->nbytes += 2;
+		req->reply_expected = 1;
+		req->reply_len = 0;
+		ret = pmu_queue_request(req);
+		break;
+    }
+    if (ret)
+    {
+    	req->complete = 1;
+    	return ret;
+    }
+    	
+    if (sync) {
+	while (!req->complete)
+		pmu_poll();
+    }
+
+    return 0;
+}
+
+/* Enable/disable autopolling */
+static int 
+pmu_autopoll(int devs)
+{
+	struct adb_request req;
+
+	if (!pmu_fully_inited) return -ENXIO;
+
+	if (devs) {
+		adb_dev_map = devs;
+		pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86,
+			    adb_dev_map >> 8, adb_dev_map);
+		pmu_adb_flags = 2;
+	} else {
+		pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF);
+		pmu_adb_flags = 0;
+	}
+	while (!req.complete)
+		pmu_poll();
+	return 0;
+}
+
+/* Reset the ADB bus */
+static int 
+pmu_reset_bus(void)
+{
+	struct adb_request req;
+	long timeout;
+	int save_autopoll = adb_dev_map;
+
+	if (!pmu_fully_inited) return -ENXIO;
+
+	/* anyone got a better idea?? */
+	pmu_autopoll(0);
+
+	req.nbytes = 5;
+	req.done = NULL;
+	req.data[0] = PMU_ADB_CMD;
+	req.data[1] = 0;
+	req.data[2] = 3; /* ADB_BUSRESET ??? */
+	req.data[3] = 0;
+	req.data[4] = 0;
+	req.reply_len = 0;
+	req.reply_expected = 1;
+	if (pmu_queue_request(&req) != 0)
+	{
+		printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n");
+		return -EIO;
+	}
+	while (!req.complete)
+		pmu_poll();
+	timeout = 100000;
+	while (!req.complete) {
+		if (--timeout < 0) {
+			printk(KERN_ERR "pmu_adb_reset_bus (reset): no response from PMU\n");
+			return -EIO;
+		}
+		udelay(10);
+		pmu_poll();
+	}
+
+	if (save_autopoll != 0)
+		pmu_autopoll(save_autopoll);
+		
+	return 0;
+}
+
+/* Construct and send a pmu request */
+int 
+pmu_request(struct adb_request *req, void (*done)(struct adb_request *),
+	    int nbytes, ...)
+{
+	va_list list;
+	int i;
+
+	if (nbytes < 0 || nbytes > 32) {
+		printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes);
+		req->complete = 1;
+		return -EINVAL;
+	}
+	req->nbytes = nbytes;
+	req->done = done;
+	va_start(list, nbytes);
+	for (i = 0; i < nbytes; ++i)
+		req->data[i] = va_arg(list, int);
+	va_end(list);
+	if (pmu_data_len[req->data[0]][1] != 0) {
+		req->reply[0] = ADB_RET_OK;
+		req->reply_len = 1;
+	} else
+		req->reply_len = 0;
+	req->reply_expected = 0;
+	return pmu_queue_request(req);
+}
+
+int
+pmu_queue_request(struct adb_request *req)
+{
+	unsigned long flags;
+	int nsend;
+
+	if (req->nbytes <= 0) {
+		req->complete = 1;
+		return 0;
+	}
+	nsend = pmu_data_len[req->data[0]][0];
+	if (nsend >= 0 && req->nbytes != nsend + 1) {
+		req->complete = 1;
+		return -EINVAL;
+	}
+
+	req->next = NULL;
+	req->sent = 0;
+	req->complete = 0;
+	local_irq_save(flags);
+
+	if (current_req != 0) {
+		last_req->next = req;
+		last_req = req;
+	} else {
+		current_req = req;
+		last_req = req;
+		if (pmu_state == idle)
+			pmu_start();
+	}
+
+	local_irq_restore(flags);
+	return 0;
+}
+
+static void 
+send_byte(int x)
+{
+	via1[ACR] |= SR_CTRL;
+	via1[SR] = x;
+	via2[B] &= ~TREQ;		/* assert TREQ */
+}
+
+static void 
+recv_byte(void)
+{
+	char c;
+
+	via1[ACR] = (via1[ACR] | SR_EXT) & ~SR_OUT;
+	c = via1[SR];		/* resets SR */
+	via2[B] &= ~TREQ;
+}
+
+static void 
+pmu_start(void)
+{
+	unsigned long flags;
+	struct adb_request *req;
+
+	/* assert pmu_state == idle */
+	/* get the packet to send */
+	local_irq_save(flags);
+	req = current_req;
+	if (req == 0 || pmu_state != idle
+	    || (req->reply_expected && req_awaiting_reply))
+		goto out;
+
+	pmu_state = sending;
+	data_index = 1;
+	data_len = pmu_data_len[req->data[0]][0];
+
+	/* set the shift register to shift out and send a byte */
+	send_byte(req->data[0]);
+
+out:
+	local_irq_restore(flags);
+}
+
+void 
+pmu_poll(void)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+	if (via1[IFR] & SR_INT) {
+		via1[IFR] = SR_INT;
+		pmu_interrupt(IRQ_MAC_ADB_SR, NULL);
+	}
+	if (via1[IFR] & CB1_INT) {
+		via1[IFR] = CB1_INT;
+		pmu_interrupt(IRQ_MAC_ADB_CL, NULL);
+	}
+	local_irq_restore(flags);
+}
+
+static irqreturn_t
+pmu_interrupt(int irq, void *dev_id)
+{
+	struct adb_request *req;
+	int timeout, bite = 0;	/* to prevent compiler warning */
+
+#if 0
+	printk("pmu_interrupt: irq %d state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n",
+		irq, pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending);
+#endif
+
+	if (irq == IRQ_MAC_ADB_CL) {		/* CB1 interrupt */
+		adb_int_pending = 1;
+	} else if (irq == IRQ_MAC_ADB_SR) {	/* SR interrupt  */
+		if (via2[B] & TACK) {
+			printk(KERN_DEBUG "PMU: SR_INT but ack still high! (%x)\n", via2[B]);
+		}
+
+		/* if reading grab the byte */
+		if ((via1[ACR] & SR_OUT) == 0) bite = via1[SR];
+
+		/* reset TREQ and wait for TACK to go high */
+		via2[B] |= TREQ;
+		timeout = 3200;
+		while (!(via2[B] & TACK)) {
+			if (--timeout < 0) {
+				printk(KERN_ERR "PMU not responding (!ack)\n");
+				goto finish;
+			}
+			udelay(10);
+		}
+
+		switch (pmu_state) {
+		case sending:
+			req = current_req;
+			if (data_len < 0) {
+				data_len = req->nbytes - 1;
+				send_byte(data_len);
+				break;
+			}
+			if (data_index <= data_len) {
+				send_byte(req->data[data_index++]);
+				break;
+			}
+			req->sent = 1;
+			data_len = pmu_data_len[req->data[0]][1];
+			if (data_len == 0) {
+				pmu_state = idle;
+				current_req = req->next;
+				if (req->reply_expected)
+					req_awaiting_reply = req;
+				else
+					pmu_done(req);
+			} else {
+				pmu_state = reading;
+				data_index = 0;
+				reply_ptr = req->reply + req->reply_len;
+				recv_byte();
+			}
+			break;
+
+		case intack:
+			data_index = 0;
+			data_len = -1;
+			pmu_state = reading_intr;
+			reply_ptr = interrupt_data;
+			recv_byte();
+			break;
+
+		case reading:
+		case reading_intr:
+			if (data_len == -1) {
+				data_len = bite;
+				if (bite > 32)
+					printk(KERN_ERR "PMU: bad reply len %d\n",
+					       bite);
+			} else {
+				reply_ptr[data_index++] = bite;
+			}
+			if (data_index < data_len) {
+				recv_byte();
+				break;
+			}
+
+			if (pmu_state == reading_intr) {
+				pmu_handle_data(interrupt_data, data_index);
+			} else {
+				req = current_req;
+				current_req = req->next;
+				req->reply_len += data_index;
+				pmu_done(req);
+			}
+			pmu_state = idle;
+
+			break;
+
+		default:
+			printk(KERN_ERR "pmu_interrupt: unknown state %d?\n",
+			       pmu_state);
+		}
+	}
+finish:
+	if (pmu_state == idle) {
+		if (adb_int_pending) {
+			pmu_state = intack;
+			send_byte(PMU_INT_ACK);
+			adb_int_pending = 0;
+		} else if (current_req) {
+			pmu_start();
+		}
+	}
+
+#if 0
+	printk("pmu_interrupt: exit state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n",
+		pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending);
+#endif
+	return IRQ_HANDLED;
+}
+
+static void 
+pmu_done(struct adb_request *req)
+{
+	req->complete = 1;
+	if (req->done)
+		(*req->done)(req);
+}
+
+/* Interrupt data could be the result data from an ADB cmd */
+static void 
+pmu_handle_data(unsigned char *data, int len)
+{
+	static int show_pmu_ints = 1;
+
+	asleep = 0;
+	if (len < 1) {
+		adb_int_pending = 0;
+		return;
+	}
+	if (data[0] & PMU_INT_ADB) {
+		if ((data[0] & PMU_INT_ADB_AUTO) == 0) {
+			struct adb_request *req = req_awaiting_reply;
+			if (req == 0) {
+				printk(KERN_ERR "PMU: extra ADB reply\n");
+				return;
+			}
+			req_awaiting_reply = NULL;
+			if (len <= 2)
+				req->reply_len = 0;
+			else {
+				memcpy(req->reply, data + 1, len - 1);
+				req->reply_len = len - 1;
+			}
+			pmu_done(req);
+		} else {
+			adb_input(data+1, len-1, 1);
+		}
+	} else {
+		if (data[0] == 0x08 && len == 3) {
+			/* sound/brightness buttons pressed */
+			pmu_set_brightness(data[1] >> 3);
+			set_volume(data[2]);
+		} else if (show_pmu_ints
+			   && !(data[0] == PMU_INT_TICK && len == 1)) {
+			int i;
+			printk(KERN_DEBUG "pmu intr");
+			for (i = 0; i < len; ++i)
+				printk(" %.2x", data[i]);
+			printk("\n");
+		}
+	}
+}
+
+static int backlight_level = -1;
+static int backlight_enabled = 0;
+
+#define LEVEL_TO_BRIGHT(lev)	((lev) < 1? 0x7f: 0x4a - ((lev) << 1))
+
+static void 
+pmu_enable_backlight(int on)
+{
+	struct adb_request req;
+
+	if (on) {
+	    /* first call: get current backlight value */
+	    if (backlight_level < 0) {
+		switch(pmu_kind) {
+		    case PMU_68K_V1:
+		    case PMU_68K_V2:
+			pmu_request(&req, NULL, 3, PMU_READ_NVRAM, 0x14, 0xe);
+			while (!req.complete)
+				pmu_poll();
+			printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", (int)req.reply[1]);
+			backlight_level = req.reply[1];
+			break;
+		    default:
+		        backlight_enabled = 0;
+		        return;
+		}
+	    }
+	    pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
+	    	LEVEL_TO_BRIGHT(backlight_level));
+	    while (!req.complete)
+		pmu_poll();
+	}
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+	    PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
+	while (!req.complete)
+		pmu_poll();
+	backlight_enabled = on;
+}
+
+static void 
+pmu_set_brightness(int level)
+{
+	int bright;
+
+	backlight_level = level;
+	bright = LEVEL_TO_BRIGHT(level);
+	if (!backlight_enabled)
+		return;
+	if (bright_req_1.complete)
+		pmu_request(&bright_req_1, NULL, 2, PMU_BACKLIGHT_BRIGHT,
+		    bright);
+	if (bright_req_2.complete)
+		pmu_request(&bright_req_2, NULL, 2, PMU_POWER_CTRL,
+		    PMU_POW_BACKLIGHT | (bright < 0x7f ? PMU_POW_ON : PMU_POW_OFF));
+}
+
+void 
+pmu_enable_irled(int on)
+{
+	struct adb_request req;
+
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL, PMU_POW_IRLED |
+	    (on ? PMU_POW_ON : PMU_POW_OFF));
+	while (!req.complete)
+		pmu_poll();
+}
+
+static void 
+set_volume(int level)
+{
+}
+
+int
+pmu_present(void)
+{
+	return (pmu_kind != PMU_UNKNOWN);
+}
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm.h b/src/kernel/linux/v4.14/drivers/macintosh/windfarm.h
new file mode 100644
index 0000000..901c42f
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm.h
@@ -0,0 +1,157 @@
+/*
+ * Windfarm PowerMac thermal control.
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#ifndef __WINDFARM_H__
+#define __WINDFARM_H__
+
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/device.h>
+
+/* Display a 16.16 fixed point value */
+#define FIX32TOPRINT(f)	(((s32)(f)) >> 16),(((((s32)(f)) & 0xffff) * 1000) >> 16)
+
+/*
+ * Control objects
+ */
+
+struct wf_control;
+
+struct wf_control_ops {
+	int			(*set_value)(struct wf_control *ct, s32 val);
+	int			(*get_value)(struct wf_control *ct, s32 *val);
+	s32			(*get_min)(struct wf_control *ct);
+	s32			(*get_max)(struct wf_control *ct);
+	void			(*release)(struct wf_control *ct);
+	struct module		*owner;
+};
+
+struct wf_control {
+	struct list_head		link;
+	const struct wf_control_ops	*ops;
+	const char			*name;
+	int				type;
+	struct kref			ref;
+	struct device_attribute		attr;
+	void				*priv;
+};
+
+#define WF_CONTROL_TYPE_GENERIC		0
+#define WF_CONTROL_RPM_FAN		1
+#define WF_CONTROL_PWM_FAN		2
+
+
+/* Note about lifetime rules: wf_register_control() will initialize
+ * the kref and wf_unregister_control will decrement it, thus the
+ * object creating/disposing a given control shouldn't assume it
+ * still exists after wf_unregister_control has been called.
+ */
+extern int wf_register_control(struct wf_control *ct);
+extern void wf_unregister_control(struct wf_control *ct);
+extern int wf_get_control(struct wf_control *ct);
+extern void wf_put_control(struct wf_control *ct);
+
+static inline int wf_control_set_max(struct wf_control *ct)
+{
+	s32 vmax = ct->ops->get_max(ct);
+	return ct->ops->set_value(ct, vmax);
+}
+
+static inline int wf_control_set_min(struct wf_control *ct)
+{
+	s32 vmin = ct->ops->get_min(ct);
+	return ct->ops->set_value(ct, vmin);
+}
+
+static inline int wf_control_set(struct wf_control *ct, s32 val)
+{
+	return ct->ops->set_value(ct, val);
+}
+
+static inline int wf_control_get(struct wf_control *ct, s32 *val)
+{
+	return ct->ops->get_value(ct, val);
+}
+
+static inline s32 wf_control_get_min(struct wf_control *ct)
+{
+	return ct->ops->get_min(ct);
+}
+
+static inline s32 wf_control_get_max(struct wf_control *ct)
+{
+	return ct->ops->get_max(ct);
+}
+
+/*
+ * Sensor objects
+ */
+
+struct wf_sensor;
+
+struct wf_sensor_ops {
+	int			(*get_value)(struct wf_sensor *sr, s32 *val);
+	void			(*release)(struct wf_sensor *sr);
+	struct module		*owner;
+};
+
+struct wf_sensor {
+	struct list_head		link;
+	const struct wf_sensor_ops	*ops;
+	const char			*name;
+	struct kref			ref;
+	struct device_attribute		attr;
+	void				*priv;
+};
+
+/* Same lifetime rules as controls */
+extern int wf_register_sensor(struct wf_sensor *sr);
+extern void wf_unregister_sensor(struct wf_sensor *sr);
+extern int wf_get_sensor(struct wf_sensor *sr);
+extern void wf_put_sensor(struct wf_sensor *sr);
+
+static inline int wf_sensor_get(struct wf_sensor *sr, s32 *val)
+{
+	return sr->ops->get_value(sr, val);
+}
+
+/* For use by clients. Note that we are a bit racy here since
+ * notifier_block doesn't have a module owner field. I may fix
+ * it one day ...
+ *
+ * LOCKING NOTE !
+ *
+ * All "events" except WF_EVENT_TICK are called with an internal mutex
+ * held which will deadlock if you call basically any core routine.
+ * So don't ! Just take note of the event and do your actual operations
+ * from the ticker.
+ *
+ */
+extern int wf_register_client(struct notifier_block *nb);
+extern int wf_unregister_client(struct notifier_block *nb);
+
+/* Overtemp conditions. Those are refcounted */
+extern void wf_set_overtemp(void);
+extern void wf_clear_overtemp(void);
+
+#define WF_EVENT_NEW_CONTROL	0 /* param is wf_control * */
+#define WF_EVENT_NEW_SENSOR	1 /* param is wf_sensor * */
+#define WF_EVENT_OVERTEMP	2 /* no param */
+#define WF_EVENT_NORMALTEMP	3 /* overtemp condition cleared */
+#define WF_EVENT_TICK		4 /* 1 second tick */
+
+/* Note: If that driver gets more broad use, we could replace the
+ * simplistic overtemp bits with "environmental conditions". That
+ * could then be used to also notify of things like fan failure,
+ * case open, battery conditions, ...
+ */
+
+#endif /* __WINDFARM_H__ */
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_ad7417_sensor.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_ad7417_sensor.c
new file mode 100644
index 0000000..7c28b71
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_ad7417_sensor.c
@@ -0,0 +1,347 @@
+/*
+ * Windfarm PowerMac thermal control. AD7417 sensors
+ *
+ * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+
+#include "windfarm.h"
+#include "windfarm_mpu.h"
+
+#define VERSION "1.0"
+
+struct wf_ad7417_priv {
+	struct kref		ref;
+	struct i2c_client	*i2c;
+	u8			config;
+	u8			cpu;
+	const struct mpu_data	*mpu;
+	struct wf_sensor	sensors[5];
+	struct mutex		lock;
+};
+
+static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_ad7417_priv *pv = sr->priv;
+	u8 buf[2];
+	s16 raw;
+	int rc;
+
+	*value = 0;
+	mutex_lock(&pv->lock);
+
+	/* Read temp register */
+	buf[0] = 0;
+	rc = i2c_master_send(pv->i2c, buf, 1);
+	if (rc < 0)
+		goto error;
+	rc = i2c_master_recv(pv->i2c, buf, 2);
+	if (rc < 0)
+		goto error;
+
+	/* Read a a 16-bit signed value */
+	raw = be16_to_cpup((__le16 *)buf);
+
+	/* Convert 8.8-bit to 16.16 fixed point */
+	*value = ((s32)raw) << 8;
+
+	mutex_unlock(&pv->lock);
+	return 0;
+
+error:
+	mutex_unlock(&pv->lock);
+	return -1;
+}
+
+/*
+ * Scaling factors for the AD7417 ADC converters (except
+ * for the CPU diode which is obtained from the EEPROM).
+ * Those values are obtained from the property list of
+ * the darwin driver
+ */
+#define ADC_12V_CURRENT_SCALE	0x0320	/* _AD2 */
+#define ADC_CPU_VOLTAGE_SCALE	0x00a0	/* _AD3 */
+#define ADC_CPU_CURRENT_SCALE	0x1f40	/* _AD4 */
+
+static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv,
+				  int chan, s32 raw, s32 *value)
+{
+	switch(chan) {
+	case 1: /* Diode */
+		*value = (raw * (s32)pv->mpu->mdiode +
+			((s32)pv->mpu->bdiode << 12)) >> 2;
+		break;
+	case 2: /* 12v current */
+		*value = raw * ADC_12V_CURRENT_SCALE;
+		break;
+	case 3: /* core voltage */
+		*value = raw * ADC_CPU_VOLTAGE_SCALE;
+		break;
+	case 4: /* core current */
+		*value = raw * ADC_CPU_CURRENT_SCALE;
+		break;
+	}
+}
+
+static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_ad7417_priv *pv = sr->priv;
+	int chan = sr - pv->sensors;
+	int i, rc;
+	u8 buf[2];
+	u16 raw;
+
+	*value = 0;
+	mutex_lock(&pv->lock);
+	for (i = 0; i < 10; i++) {
+		/* Set channel */
+		buf[0] = 1;
+		buf[1] = (pv->config & 0x1f) | (chan << 5);
+		rc = i2c_master_send(pv->i2c, buf, 2);
+		if (rc < 0)
+			goto error;
+
+		/* Wait for conversion */
+		msleep(1);
+
+		/* Switch to data register */
+		buf[0] = 4;
+		rc = i2c_master_send(pv->i2c, buf, 1);
+		if (rc < 0)
+			goto error;
+
+		/* Read result */
+		rc = i2c_master_recv(pv->i2c, buf, 2);
+		if (rc < 0)
+			goto error;
+
+		/* Read a a 16-bit signed value */
+		raw = be16_to_cpup((__le16 *)buf) >> 6;
+		wf_ad7417_adc_convert(pv, chan, raw, value);
+
+		dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]"
+			 " raw value: 0x%x, conv to: 0x%08x\n",
+			 chan, sr->name, raw, *value);
+
+		mutex_unlock(&pv->lock);
+		return 0;
+
+	error:
+		dev_dbg(&pv->i2c->dev,
+			  "Error reading ADC, try %d...\n", i);
+		if (i < 9)
+			msleep(10);
+	}
+	mutex_unlock(&pv->lock);
+	return -1;
+}
+
+static void wf_ad7417_release(struct kref *ref)
+{
+	struct wf_ad7417_priv *pv = container_of(ref,
+						 struct wf_ad7417_priv, ref);
+	kfree(pv);
+}
+
+static void wf_ad7417_sensor_release(struct wf_sensor *sr)
+{
+	struct wf_ad7417_priv *pv = sr->priv;
+
+	kfree(sr->name);
+	kref_put(&pv->ref, wf_ad7417_release);
+}
+
+static const struct wf_sensor_ops wf_ad7417_temp_ops = {
+	.get_value	= wf_ad7417_temp_get,
+	.release	= wf_ad7417_sensor_release,
+	.owner		= THIS_MODULE,
+};
+
+static const struct wf_sensor_ops wf_ad7417_adc_ops = {
+	.get_value	= wf_ad7417_adc_get,
+	.release	= wf_ad7417_sensor_release,
+	.owner		= THIS_MODULE,
+};
+
+static void wf_ad7417_add_sensor(struct wf_ad7417_priv *pv,
+				 int index, const char *name,
+				 const struct wf_sensor_ops *ops)
+{
+	pv->sensors[index].name = kasprintf(GFP_KERNEL, "%s-%d", name, pv->cpu);
+	pv->sensors[index].priv = pv;
+	pv->sensors[index].ops = ops;
+	if (!wf_register_sensor(&pv->sensors[index]))
+		kref_get(&pv->ref);
+}
+
+static void wf_ad7417_init_chip(struct wf_ad7417_priv *pv)
+{
+	int rc;
+	u8 buf[2];
+	u8 config = 0;
+
+	/*
+	 * Read ADC the configuration register and cache it. We
+	 * also make sure Config2 contains proper values, I've seen
+	 * cases where we got stale grabage in there, thus preventing
+	 * proper reading of conv. values
+	 */
+
+	/* Clear Config2 */
+	buf[0] = 5;
+	buf[1] = 0;
+	i2c_master_send(pv->i2c, buf, 2);
+
+	/* Read & cache Config1 */
+	buf[0] = 1;
+	rc = i2c_master_send(pv->i2c, buf, 1);
+	if (rc > 0) {
+		rc = i2c_master_recv(pv->i2c, buf, 1);
+		if (rc > 0) {
+			config = buf[0];
+
+			dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n",
+				config);
+
+			/* Disable shutdown mode */
+			config &= 0xfe;
+			buf[0] = 1;
+			buf[1] = config;
+			rc = i2c_master_send(pv->i2c, buf, 2);
+		}
+	}
+	if (rc <= 0)
+		dev_err(&pv->i2c->dev, "Error reading ADC config\n");
+
+	pv->config = config;
+}
+
+static int wf_ad7417_probe(struct i2c_client *client,
+			   const struct i2c_device_id *id)
+{
+	struct wf_ad7417_priv *pv;
+	const struct mpu_data *mpu;
+	const char *loc;
+	int cpu_nr;
+
+	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL);
+	if (!loc) {
+		dev_warn(&client->dev, "Missing hwsensor-location property!\n");
+		return -ENXIO;
+	}
+
+	/*
+	 * Identify which CPU we belong to by looking at the first entry
+	 * in the hwsensor-location list
+	 */
+	if (!strncmp(loc, "CPU A", 5))
+		cpu_nr = 0;
+	else if (!strncmp(loc, "CPU B", 5))
+		cpu_nr = 1;
+	else {
+		pr_err("wf_ad7417: Can't identify location %s\n", loc);
+		return -ENXIO;
+	}
+	mpu = wf_get_mpu(cpu_nr);
+	if (!mpu) {
+		dev_err(&client->dev, "Failed to retrieve MPU data\n");
+		return -ENXIO;
+	}
+
+	pv = kzalloc(sizeof(struct wf_ad7417_priv), GFP_KERNEL);
+	if (pv == NULL)
+		return -ENODEV;
+
+	kref_init(&pv->ref);
+	mutex_init(&pv->lock);
+	pv->i2c = client;
+	pv->cpu = cpu_nr;
+	pv->mpu = mpu;
+	dev_set_drvdata(&client->dev, pv);
+
+	/* Initialize the chip */
+	wf_ad7417_init_chip(pv);
+
+	/*
+	 * We cannot rely on Apple device-tree giving us child
+	 * node with the names of the individual sensors so we
+	 * just hard code what we know about them
+	 */
+	wf_ad7417_add_sensor(pv, 0, "cpu-amb-temp", &wf_ad7417_temp_ops);
+	wf_ad7417_add_sensor(pv, 1, "cpu-diode-temp", &wf_ad7417_adc_ops);
+	wf_ad7417_add_sensor(pv, 2, "cpu-12v-current", &wf_ad7417_adc_ops);
+	wf_ad7417_add_sensor(pv, 3, "cpu-voltage", &wf_ad7417_adc_ops);
+	wf_ad7417_add_sensor(pv, 4, "cpu-current", &wf_ad7417_adc_ops);
+
+	return 0;
+}
+
+static int wf_ad7417_remove(struct i2c_client *client)
+{
+	struct wf_ad7417_priv *pv = dev_get_drvdata(&client->dev);
+	int i;
+
+	/* Mark client detached */
+	pv->i2c = NULL;
+
+	/* Release sensor */
+	for (i = 0; i < 5; i++)
+		wf_unregister_sensor(&pv->sensors[i]);
+
+	kref_put(&pv->ref, wf_ad7417_release);
+
+	return 0;
+}
+
+static const struct i2c_device_id wf_ad7417_id[] = {
+	{ "MAC,ad7417", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_ad7417_id);
+
+static struct i2c_driver wf_ad7417_driver = {
+	.driver = {
+		.name	= "wf_ad7417",
+	},
+	.probe		= wf_ad7417_probe,
+	.remove		= wf_ad7417_remove,
+	.id_table	= wf_ad7417_id,
+};
+
+static int wf_ad7417_init(void)
+{
+	/* This is only supported on these machines */
+	if (!of_machine_is_compatible("PowerMac7,2") &&
+	    !of_machine_is_compatible("PowerMac7,3") &&
+	    !of_machine_is_compatible("RackMac3,1"))
+		return -ENODEV;
+
+	return i2c_add_driver(&wf_ad7417_driver);
+}
+
+static void wf_ad7417_exit(void)
+{
+	i2c_del_driver(&wf_ad7417_driver);
+}
+
+module_init(wf_ad7417_init);
+module_exit(wf_ad7417_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_core.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_core.c
new file mode 100644
index 0000000..5e013d7
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_core.c
@@ -0,0 +1,464 @@
+/*
+ * Windfarm PowerMac thermal control. Core
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * This core code tracks the list of sensors & controls, register
+ * clients, and holds the kernel thread used for control.
+ *
+ * TODO:
+ *
+ * Add some information about sensor/control type and data format to
+ * sensors/controls, and have the sysfs attribute stuff be moved
+ * generically here instead of hard coded in the platform specific
+ * driver as it us currently
+ *
+ * This however requires solving some annoying lifetime issues with
+ * sysfs which doesn't seem to have lifetime rules for struct attribute,
+ * I may have to create full features kobjects for every sensor/control
+ * instead which is a bit of an overkill imho
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/kthread.h>
+#include <linux/jiffies.h>
+#include <linux/reboot.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/freezer.h>
+
+#include <asm/prom.h>
+
+#include "windfarm.h"
+
+#define VERSION "0.2"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+static LIST_HEAD(wf_controls);
+static LIST_HEAD(wf_sensors);
+static DEFINE_MUTEX(wf_lock);
+static BLOCKING_NOTIFIER_HEAD(wf_client_list);
+static int wf_client_count;
+static unsigned int wf_overtemp;
+static unsigned int wf_overtemp_counter;
+struct task_struct *wf_thread;
+
+static struct platform_device wf_platform_device = {
+	.name	= "windfarm",
+};
+
+/*
+ * Utilities & tick thread
+ */
+
+static inline void wf_notify(int event, void *param)
+{
+	blocking_notifier_call_chain(&wf_client_list, event, param);
+}
+
+static int wf_critical_overtemp(void)
+{
+	static char const critical_overtemp_path[] = "/sbin/critical_overtemp";
+	char *argv[] = { (char *)critical_overtemp_path, NULL };
+	static char *envp[] = { "HOME=/",
+				"TERM=linux",
+				"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
+				NULL };
+
+	return call_usermodehelper(critical_overtemp_path,
+				   argv, envp, UMH_WAIT_EXEC);
+}
+
+static int wf_thread_func(void *data)
+{
+	unsigned long next, delay;
+
+	next = jiffies;
+
+	DBG("wf: thread started\n");
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+
+		if (time_after_eq(jiffies, next)) {
+			wf_notify(WF_EVENT_TICK, NULL);
+			if (wf_overtemp) {
+				wf_overtemp_counter++;
+				/* 10 seconds overtemp, notify userland */
+				if (wf_overtemp_counter > 10)
+					wf_critical_overtemp();
+				/* 30 seconds, shutdown */
+				if (wf_overtemp_counter > 30) {
+					printk(KERN_ERR "windfarm: Overtemp "
+					       "for more than 30"
+					       " seconds, shutting down\n");
+					machine_power_off();
+				}
+			}
+			next += HZ;
+		}
+
+		delay = next - jiffies;
+		if (delay <= HZ)
+			schedule_timeout_interruptible(delay);
+	}
+
+	DBG("wf: thread stopped\n");
+
+	return 0;
+}
+
+static void wf_start_thread(void)
+{
+	wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm");
+	if (IS_ERR(wf_thread)) {
+		printk(KERN_ERR "windfarm: failed to create thread,err %ld\n",
+		       PTR_ERR(wf_thread));
+		wf_thread = NULL;
+	}
+}
+
+
+static void wf_stop_thread(void)
+{
+	if (wf_thread)
+		kthread_stop(wf_thread);
+	wf_thread = NULL;
+}
+
+/*
+ * Controls
+ */
+
+static void wf_control_release(struct kref *kref)
+{
+	struct wf_control *ct = container_of(kref, struct wf_control, ref);
+
+	DBG("wf: Deleting control %s\n", ct->name);
+
+	if (ct->ops && ct->ops->release)
+		ct->ops->release(ct);
+	else
+		kfree(ct);
+}
+
+static ssize_t wf_show_control(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
+	const char *typestr;
+	s32 val = 0;
+	int err;
+
+	err = ctrl->ops->get_value(ctrl, &val);
+	if (err < 0) {
+		if (err == -EFAULT)
+			return sprintf(buf, "<HW FAULT>\n");
+		return err;
+	}
+	switch(ctrl->type) {
+	case WF_CONTROL_RPM_FAN:
+		typestr = " RPM";
+		break;
+	case WF_CONTROL_PWM_FAN:
+		typestr = " %";
+		break;
+	default:
+		typestr = "";
+	}
+	return sprintf(buf, "%d%s\n", val, typestr);
+}
+
+/* This is really only for debugging... */
+static ssize_t wf_store_control(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
+	int val;
+	int err;
+	char *endp;
+
+	val = simple_strtoul(buf, &endp, 0);
+	while (endp < buf + count && (*endp == ' ' || *endp == '\n'))
+		++endp;
+	if (endp - buf < count)
+		return -EINVAL;
+	err = ctrl->ops->set_value(ctrl, val);
+	if (err < 0)
+		return err;
+	return count;
+}
+
+int wf_register_control(struct wf_control *new_ct)
+{
+	struct wf_control *ct;
+
+	mutex_lock(&wf_lock);
+	list_for_each_entry(ct, &wf_controls, link) {
+		if (!strcmp(ct->name, new_ct->name)) {
+			printk(KERN_WARNING "windfarm: trying to register"
+			       " duplicate control %s\n", ct->name);
+			mutex_unlock(&wf_lock);
+			return -EEXIST;
+		}
+	}
+	kref_init(&new_ct->ref);
+	list_add(&new_ct->link, &wf_controls);
+
+	sysfs_attr_init(&new_ct->attr.attr);
+	new_ct->attr.attr.name = new_ct->name;
+	new_ct->attr.attr.mode = 0644;
+	new_ct->attr.show = wf_show_control;
+	new_ct->attr.store = wf_store_control;
+	if (device_create_file(&wf_platform_device.dev, &new_ct->attr))
+		printk(KERN_WARNING "windfarm: device_create_file failed"
+			" for %s\n", new_ct->name);
+		/* the subsystem still does useful work without the file */
+
+	DBG("wf: Registered control %s\n", new_ct->name);
+
+	wf_notify(WF_EVENT_NEW_CONTROL, new_ct);
+	mutex_unlock(&wf_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wf_register_control);
+
+void wf_unregister_control(struct wf_control *ct)
+{
+	mutex_lock(&wf_lock);
+	list_del(&ct->link);
+	mutex_unlock(&wf_lock);
+
+	DBG("wf: Unregistered control %s\n", ct->name);
+
+	kref_put(&ct->ref, wf_control_release);
+}
+EXPORT_SYMBOL_GPL(wf_unregister_control);
+
+int wf_get_control(struct wf_control *ct)
+{
+	if (!try_module_get(ct->ops->owner))
+		return -ENODEV;
+	kref_get(&ct->ref);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wf_get_control);
+
+void wf_put_control(struct wf_control *ct)
+{
+	struct module *mod = ct->ops->owner;
+	kref_put(&ct->ref, wf_control_release);
+	module_put(mod);
+}
+EXPORT_SYMBOL_GPL(wf_put_control);
+
+
+/*
+ * Sensors
+ */
+
+
+static void wf_sensor_release(struct kref *kref)
+{
+	struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref);
+
+	DBG("wf: Deleting sensor %s\n", sr->name);
+
+	if (sr->ops && sr->ops->release)
+		sr->ops->release(sr);
+	else
+		kfree(sr);
+}
+
+static ssize_t wf_show_sensor(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr);
+	s32 val = 0;
+	int err;
+
+	err = sens->ops->get_value(sens, &val);
+	if (err < 0)
+		return err;
+	return sprintf(buf, "%d.%03d\n", FIX32TOPRINT(val));
+}
+
+int wf_register_sensor(struct wf_sensor *new_sr)
+{
+	struct wf_sensor *sr;
+
+	mutex_lock(&wf_lock);
+	list_for_each_entry(sr, &wf_sensors, link) {
+		if (!strcmp(sr->name, new_sr->name)) {
+			printk(KERN_WARNING "windfarm: trying to register"
+			       " duplicate sensor %s\n", sr->name);
+			mutex_unlock(&wf_lock);
+			return -EEXIST;
+		}
+	}
+	kref_init(&new_sr->ref);
+	list_add(&new_sr->link, &wf_sensors);
+
+	sysfs_attr_init(&new_sr->attr.attr);
+	new_sr->attr.attr.name = new_sr->name;
+	new_sr->attr.attr.mode = 0444;
+	new_sr->attr.show = wf_show_sensor;
+	new_sr->attr.store = NULL;
+	if (device_create_file(&wf_platform_device.dev, &new_sr->attr))
+		printk(KERN_WARNING "windfarm: device_create_file failed"
+			" for %s\n", new_sr->name);
+		/* the subsystem still does useful work without the file */
+
+	DBG("wf: Registered sensor %s\n", new_sr->name);
+
+	wf_notify(WF_EVENT_NEW_SENSOR, new_sr);
+	mutex_unlock(&wf_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wf_register_sensor);
+
+void wf_unregister_sensor(struct wf_sensor *sr)
+{
+	mutex_lock(&wf_lock);
+	list_del(&sr->link);
+	mutex_unlock(&wf_lock);
+
+	DBG("wf: Unregistered sensor %s\n", sr->name);
+
+	wf_put_sensor(sr);
+}
+EXPORT_SYMBOL_GPL(wf_unregister_sensor);
+
+int wf_get_sensor(struct wf_sensor *sr)
+{
+	if (!try_module_get(sr->ops->owner))
+		return -ENODEV;
+	kref_get(&sr->ref);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wf_get_sensor);
+
+void wf_put_sensor(struct wf_sensor *sr)
+{
+	struct module *mod = sr->ops->owner;
+	kref_put(&sr->ref, wf_sensor_release);
+	module_put(mod);
+}
+EXPORT_SYMBOL_GPL(wf_put_sensor);
+
+
+/*
+ * Client & notification
+ */
+
+int wf_register_client(struct notifier_block *nb)
+{
+	int rc;
+	struct wf_control *ct;
+	struct wf_sensor *sr;
+
+	mutex_lock(&wf_lock);
+	rc = blocking_notifier_chain_register(&wf_client_list, nb);
+	if (rc != 0)
+		goto bail;
+	wf_client_count++;
+	list_for_each_entry(ct, &wf_controls, link)
+		wf_notify(WF_EVENT_NEW_CONTROL, ct);
+	list_for_each_entry(sr, &wf_sensors, link)
+		wf_notify(WF_EVENT_NEW_SENSOR, sr);
+	if (wf_client_count == 1)
+		wf_start_thread();
+ bail:
+	mutex_unlock(&wf_lock);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(wf_register_client);
+
+int wf_unregister_client(struct notifier_block *nb)
+{
+	mutex_lock(&wf_lock);
+	blocking_notifier_chain_unregister(&wf_client_list, nb);
+	wf_client_count--;
+	if (wf_client_count == 0)
+		wf_stop_thread();
+	mutex_unlock(&wf_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wf_unregister_client);
+
+void wf_set_overtemp(void)
+{
+	mutex_lock(&wf_lock);
+	wf_overtemp++;
+	if (wf_overtemp == 1) {
+		printk(KERN_WARNING "windfarm: Overtemp condition detected !\n");
+		wf_overtemp_counter = 0;
+		wf_notify(WF_EVENT_OVERTEMP, NULL);
+	}
+	mutex_unlock(&wf_lock);
+}
+EXPORT_SYMBOL_GPL(wf_set_overtemp);
+
+void wf_clear_overtemp(void)
+{
+	mutex_lock(&wf_lock);
+	WARN_ON(wf_overtemp == 0);
+	if (wf_overtemp == 0) {
+		mutex_unlock(&wf_lock);
+		return;
+	}
+	wf_overtemp--;
+	if (wf_overtemp == 0) {
+		printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n");
+		wf_notify(WF_EVENT_NORMALTEMP, NULL);
+	}
+	mutex_unlock(&wf_lock);
+}
+EXPORT_SYMBOL_GPL(wf_clear_overtemp);
+
+static int __init windfarm_core_init(void)
+{
+	DBG("wf: core loaded\n");
+
+	platform_device_register(&wf_platform_device);
+	return 0;
+}
+
+static void __exit windfarm_core_exit(void)
+{
+	BUG_ON(wf_client_count != 0);
+
+	DBG("wf: core unloaded\n");
+
+	platform_device_unregister(&wf_platform_device);
+}
+
+
+module_init(windfarm_core_init);
+module_exit(windfarm_core_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Core component of PowerMac thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_cpufreq_clamp.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_cpufreq_clamp.c
new file mode 100644
index 0000000..2626990
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_cpufreq_clamp.c
@@ -0,0 +1,106 @@
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/cpufreq.h>
+
+#include <asm/prom.h>
+
+#include "windfarm.h"
+
+#define VERSION "0.3"
+
+static int clamped;
+static struct wf_control *clamp_control;
+
+static int clamp_notifier_call(struct notifier_block *self,
+			       unsigned long event, void *data)
+{
+	struct cpufreq_policy *p = data;
+	unsigned long max_freq;
+
+	if (event != CPUFREQ_ADJUST)
+		return 0;
+
+	max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq);
+	cpufreq_verify_within_limits(p, 0, max_freq);
+
+	return 0;
+}
+
+static struct notifier_block clamp_notifier = {
+	.notifier_call = clamp_notifier_call,
+};
+
+static int clamp_set(struct wf_control *ct, s32 value)
+{
+	if (value)
+		printk(KERN_INFO "windfarm: Clamping CPU frequency to "
+		       "minimum !\n");
+	else
+		printk(KERN_INFO "windfarm: CPU frequency unclamped !\n");
+	clamped = value;
+	cpufreq_update_policy(0);
+	return 0;
+}
+
+static int clamp_get(struct wf_control *ct, s32 *value)
+{
+	*value = clamped;
+	return 0;
+}
+
+static s32 clamp_min(struct wf_control *ct)
+{
+	return 0;
+}
+
+static s32 clamp_max(struct wf_control *ct)
+{
+	return 1;
+}
+
+static const struct wf_control_ops clamp_ops = {
+	.set_value	= clamp_set,
+	.get_value	= clamp_get,
+	.get_min	= clamp_min,
+	.get_max	= clamp_max,
+	.owner		= THIS_MODULE,
+};
+
+static int __init wf_cpufreq_clamp_init(void)
+{
+	struct wf_control *clamp;
+
+	clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL);
+	if (clamp == NULL)
+		return -ENOMEM;
+	cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER);
+	clamp->ops = &clamp_ops;
+	clamp->name = "cpufreq-clamp";
+	if (wf_register_control(clamp))
+		goto fail;
+	clamp_control = clamp;
+	return 0;
+ fail:
+	kfree(clamp);
+	return -ENODEV;
+}
+
+static void __exit wf_cpufreq_clamp_exit(void)
+{
+	if (clamp_control)
+		wf_unregister_control(clamp_control);
+}
+
+
+module_init(wf_cpufreq_clamp_init);
+module_exit(wf_cpufreq_clamp_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_fcu_controls.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_fcu_controls.c
new file mode 100644
index 0000000..fab7a21
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_fcu_controls.c
@@ -0,0 +1,600 @@
+/*
+ * Windfarm PowerMac thermal control. FCU fan control
+ *
+ * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+#undef DEBUG
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+
+#include "windfarm.h"
+#include "windfarm_mpu.h"
+
+#define VERSION "1.0"
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+/*
+ * This option is "weird" :) Basically, if you define this to 1
+ * the control loop for the RPMs fans (not PWMs) will apply the
+ * correction factor obtained from the PID to the actual RPM
+ * speed read from the FCU.
+ *
+ * If you define the below constant to 0, then it will be
+ * applied to the setpoint RPM speed, that is basically the
+ * speed we proviously "asked" for.
+ *
+ * I'm using 0 for now which is what therm_pm72 used to do and
+ * what Darwin -apparently- does based on observed behaviour.
+ */
+#define RPM_PID_USE_ACTUAL_SPEED	0
+
+/* Default min/max for pumps */
+#define CPU_PUMP_OUTPUT_MAX		3200
+#define CPU_PUMP_OUTPUT_MIN		1250
+
+#define FCU_FAN_RPM		0
+#define FCU_FAN_PWM		1
+
+struct wf_fcu_priv {
+	struct kref		ref;
+	struct i2c_client	*i2c;
+	struct mutex		lock;
+	struct list_head	fan_list;
+	int			rpm_shift;
+};
+
+struct wf_fcu_fan {
+	struct list_head	link;
+	int			id;
+	s32			min, max, target;
+	struct wf_fcu_priv	*fcu_priv;
+	struct wf_control	ctrl;
+};
+
+static void wf_fcu_release(struct kref *ref)
+{
+	struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref);
+
+	kfree(pv);
+}
+
+static void wf_fcu_fan_release(struct wf_control *ct)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+
+	kref_put(&fan->fcu_priv->ref, wf_fcu_release);
+	kfree(fan);
+}
+
+static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg,
+			   unsigned char *buf, int nb)
+{
+	int tries, nr, nw;
+
+	mutex_lock(&pv->lock);
+
+	buf[0] = reg;
+	tries = 0;
+	for (;;) {
+		nw = i2c_master_send(pv->i2c, buf, 1);
+		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
+			break;
+		msleep(10);
+		++tries;
+	}
+	if (nw <= 0) {
+		pr_err("Failure writing address to FCU: %d", nw);
+		nr = nw;
+		goto bail;
+	}
+	tries = 0;
+	for (;;) {
+		nr = i2c_master_recv(pv->i2c, buf, nb);
+		if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100)
+			break;
+		msleep(10);
+		++tries;
+	}
+	if (nr <= 0)
+		pr_err("wf_fcu: Failure reading data from FCU: %d", nw);
+ bail:
+	mutex_unlock(&pv->lock);
+	return nr;
+}
+
+static int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg,
+			    const unsigned char *ptr, int nb)
+{
+	int tries, nw;
+	unsigned char buf[16];
+
+	buf[0] = reg;
+	memcpy(buf+1, ptr, nb);
+	++nb;
+	tries = 0;
+	for (;;) {
+		nw = i2c_master_send(pv->i2c, buf, nb);
+		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
+			break;
+		msleep(10);
+		++tries;
+	}
+	if (nw < 0)
+		pr_err("wf_fcu: Failure writing to FCU: %d", nw);
+	return nw;
+}
+
+static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+	struct wf_fcu_priv *pv = fan->fcu_priv;
+	int rc, shift = pv->rpm_shift;
+	unsigned char buf[2];
+
+	if (value < fan->min)
+		value = fan->min;
+	if (value > fan->max)
+		value = fan->max;
+
+	fan->target = value;
+
+	buf[0] = value >> (8 - shift);
+	buf[1] = value << shift;
+	rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2);
+	if (rc < 0)
+		return -EIO;
+	return 0;
+}
+
+static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+	struct wf_fcu_priv *pv = fan->fcu_priv;
+	int rc, reg_base, shift = pv->rpm_shift;
+	unsigned char failure;
+	unsigned char active;
+	unsigned char buf[2];
+
+	rc = wf_fcu_read_reg(pv, 0xb, &failure, 1);
+	if (rc != 1)
+		return -EIO;
+	if ((failure & (1 << fan->id)) != 0)
+		return -EFAULT;
+	rc = wf_fcu_read_reg(pv, 0xd, &active, 1);
+	if (rc != 1)
+		return -EIO;
+	if ((active & (1 << fan->id)) == 0)
+		return -ENXIO;
+
+	/* Programmed value or real current speed */
+#if RPM_PID_USE_ACTUAL_SPEED
+	reg_base = 0x11;
+#else
+	reg_base = 0x10;
+#endif
+	rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2);
+	if (rc != 2)
+		return -EIO;
+
+	*value = (buf[0] << (8 - shift)) | buf[1] >> shift;
+
+	return 0;
+}
+
+static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+	struct wf_fcu_priv *pv = fan->fcu_priv;
+	unsigned char buf[2];
+	int rc;
+
+	if (value < fan->min)
+		value = fan->min;
+	if (value > fan->max)
+		value = fan->max;
+
+	fan->target = value;
+
+	value = (value * 2559) / 1000;
+	buf[0] = value;
+	rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1);
+	if (rc < 0)
+		return -EIO;
+	return 0;
+}
+
+static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+	struct wf_fcu_priv *pv = fan->fcu_priv;
+	unsigned char failure;
+	unsigned char active;
+	unsigned char buf[2];
+	int rc;
+
+	rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1);
+	if (rc != 1)
+		return -EIO;
+	if ((failure & (1 << fan->id)) != 0)
+		return -EFAULT;
+	rc = wf_fcu_read_reg(pv, 0x2d, &active, 1);
+	if (rc != 1)
+		return -EIO;
+	if ((active & (1 << fan->id)) == 0)
+		return -ENXIO;
+
+	rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1);
+	if (rc != 1)
+		return -EIO;
+
+	*value = (((s32)buf[0]) * 1000) / 2559;
+
+	return 0;
+}
+
+static s32 wf_fcu_fan_min(struct wf_control *ct)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+
+	return fan->min;
+}
+
+static s32 wf_fcu_fan_max(struct wf_control *ct)
+{
+	struct wf_fcu_fan *fan = ct->priv;
+
+	return fan->max;
+}
+
+static const struct wf_control_ops wf_fcu_fan_rpm_ops = {
+	.set_value	= wf_fcu_fan_set_rpm,
+	.get_value	= wf_fcu_fan_get_rpm,
+	.get_min	= wf_fcu_fan_min,
+	.get_max	= wf_fcu_fan_max,
+	.release	= wf_fcu_fan_release,
+	.owner		= THIS_MODULE,
+};
+
+static const struct wf_control_ops wf_fcu_fan_pwm_ops = {
+	.set_value	= wf_fcu_fan_set_pwm,
+	.get_value	= wf_fcu_fan_get_pwm,
+	.get_min	= wf_fcu_fan_min,
+	.get_max	= wf_fcu_fan_max,
+	.release	= wf_fcu_fan_release,
+	.owner		= THIS_MODULE,
+};
+
+static void wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan)
+{
+	const struct mpu_data *mpu = wf_get_mpu(0);
+	u16 pump_min = 0, pump_max = 0xffff;
+	u16 tmp[4];
+
+	/* Try to fetch pumps min/max infos from eeprom */
+	if (mpu) {
+		memcpy(&tmp, mpu->processor_part_num, 8);
+		if (tmp[0] != 0xffff && tmp[1] != 0xffff) {
+			pump_min = max(pump_min, tmp[0]);
+			pump_max = min(pump_max, tmp[1]);
+		}
+		if (tmp[2] != 0xffff && tmp[3] != 0xffff) {
+			pump_min = max(pump_min, tmp[2]);
+			pump_max = min(pump_max, tmp[3]);
+		}
+	}
+
+	/* Double check the values, this _IS_ needed as the EEPROM on
+	 * some dual 2.5Ghz G5s seem, at least, to have both min & max
+	 * same to the same value ... (grrrr)
+	 */
+	if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) {
+		pump_min = CPU_PUMP_OUTPUT_MIN;
+		pump_max = CPU_PUMP_OUTPUT_MAX;
+	}
+
+	fan->min = pump_min;
+	fan->max = pump_max;
+
+	DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n",
+	    fan->ctrl.name, pump_min, pump_max);
+}
+
+static void wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan)
+{
+	struct wf_fcu_priv *pv = fan->fcu_priv;
+	const struct mpu_data *mpu0 = wf_get_mpu(0);
+	const struct mpu_data *mpu1 = wf_get_mpu(1);
+
+	/* Default */
+	fan->min = 2400 >> pv->rpm_shift;
+	fan->max = 56000 >> pv->rpm_shift;
+
+	/* CPU fans have min/max in MPU */
+	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) {
+		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan);
+		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan);
+		goto bail;
+	}
+	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) {
+		fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan);
+		fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan);
+		goto bail;
+	}
+	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) {
+		fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan);
+		fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan);
+		goto bail;
+	}
+	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) {
+		fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan);
+		fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan);
+		goto bail;
+	}
+	/* Rackmac variants, we just use mpu0 intake */
+	if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) {
+		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan);
+		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan);
+		goto bail;
+	}
+ bail:
+	DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n",
+	    fan->ctrl.name, fan->min, fan->max);
+}
+
+static void wf_fcu_add_fan(struct wf_fcu_priv *pv, const char *name,
+			   int type, int id)
+{
+	struct wf_fcu_fan *fan;
+
+	fan = kzalloc(sizeof(*fan), GFP_KERNEL);
+	if (!fan)
+		return;
+	fan->fcu_priv = pv;
+	fan->id = id;
+	fan->ctrl.name = name;
+	fan->ctrl.priv = fan;
+
+	/* min/max is oddball but the code comes from
+	 * therm_pm72 which seems to work so ...
+	 */
+	if (type == FCU_FAN_RPM) {
+		if (!strncmp(name, "cpu-pump", strlen("cpu-pump")))
+			wf_fcu_get_pump_minmax(fan);
+		else
+			wf_fcu_get_rpmfan_minmax(fan);
+		fan->ctrl.type = WF_CONTROL_RPM_FAN;
+		fan->ctrl.ops = &wf_fcu_fan_rpm_ops;
+	} else {
+		fan->min = 10;
+		fan->max = 100;
+		fan->ctrl.type = WF_CONTROL_PWM_FAN;
+		fan->ctrl.ops = &wf_fcu_fan_pwm_ops;
+	}
+
+	if (wf_register_control(&fan->ctrl)) {
+		pr_err("wf_fcu: Failed to register fan %s\n", name);
+		kfree(fan);
+		return;
+	}
+	list_add(&fan->link, &pv->fan_list);
+	kref_get(&pv->ref);
+}
+
+static void wf_fcu_lookup_fans(struct wf_fcu_priv *pv)
+{
+	/* Translation of device-tree location properties to
+	 * windfarm fan names
+	 */
+	static const struct {
+		const char *dt_name;	/* Device-tree name */
+		const char *ct_name;	/* Control name */
+	} loc_trans[] = {
+		{ "BACKSIDE",		"backside-fan",		},
+		{ "SYS CTRLR FAN",	"backside-fan",		},
+		{ "DRIVE BAY",		"drive-bay-fan",	},
+		{ "SLOT",		"slots-fan",		},
+		{ "PCI FAN",		"slots-fan",		},
+		{ "CPU A INTAKE",	"cpu-front-fan-0",	},
+		{ "CPU A EXHAUST",	"cpu-rear-fan-0",	},
+		{ "CPU B INTAKE",	"cpu-front-fan-1",	},
+		{ "CPU B EXHAUST",	"cpu-rear-fan-1",	},
+		{ "CPU A PUMP",		"cpu-pump-0",		},
+		{ "CPU B PUMP",		"cpu-pump-1",		},
+		{ "CPU A 1",		"cpu-fan-a-0",		},
+		{ "CPU A 2",		"cpu-fan-b-0",		},
+		{ "CPU A 3",		"cpu-fan-c-0",		},
+		{ "CPU B 1",		"cpu-fan-a-1",		},
+		{ "CPU B 2",		"cpu-fan-b-1",		},
+		{ "CPU B 3",		"cpu-fan-c-1",		},
+	};
+	struct device_node *np = NULL, *fcu = pv->i2c->dev.of_node;
+	int i;
+
+	DBG("Looking up FCU controls in device-tree...\n");
+
+	while ((np = of_get_next_child(fcu, np)) != NULL) {
+		int id, type = -1;
+		const char *loc;
+		const char *name;
+		const u32 *reg;
+
+		DBG(" control: %s, type: %s\n", np->name, np->type);
+
+		/* Detect control type */
+		if (!strcmp(np->type, "fan-rpm-control") ||
+		    !strcmp(np->type, "fan-rpm"))
+			type = FCU_FAN_RPM;
+		if (!strcmp(np->type, "fan-pwm-control") ||
+		    !strcmp(np->type, "fan-pwm"))
+			type = FCU_FAN_PWM;
+		/* Only care about fans for now */
+		if (type == -1)
+			continue;
+
+		/* Lookup for a matching location */
+		loc = of_get_property(np, "location", NULL);
+		reg = of_get_property(np, "reg", NULL);
+		if (loc == NULL || reg == NULL)
+			continue;
+		DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg);
+
+		for (i = 0; i < ARRAY_SIZE(loc_trans); i++) {
+			if (strncmp(loc, loc_trans[i].dt_name,
+				    strlen(loc_trans[i].dt_name)))
+				continue;
+			name = loc_trans[i].ct_name;
+
+			DBG(" location match, name: %s\n", name);
+
+			if (type == FCU_FAN_RPM)
+				id = ((*reg) - 0x10) / 2;
+			else
+				id = ((*reg) - 0x30) / 2;
+			if (id > 7) {
+				pr_warning("wf_fcu: Can't parse "
+				       "fan ID in device-tree for %pOF\n",
+					   np);
+				break;
+			}
+			wf_fcu_add_fan(pv, name, type, id);
+			break;
+		}
+	}
+}
+
+static void wf_fcu_default_fans(struct wf_fcu_priv *pv)
+{
+	/* We only support the default fans for PowerMac7,2 */
+	if (!of_machine_is_compatible("PowerMac7,2"))
+		return;
+
+	wf_fcu_add_fan(pv, "backside-fan",	FCU_FAN_PWM, 1);
+	wf_fcu_add_fan(pv, "drive-bay-fan",	FCU_FAN_RPM, 2);
+	wf_fcu_add_fan(pv, "slots-fan",		FCU_FAN_PWM, 2);
+	wf_fcu_add_fan(pv, "cpu-front-fan-0",	FCU_FAN_RPM, 3);
+	wf_fcu_add_fan(pv, "cpu-rear-fan-0",	FCU_FAN_RPM, 4);
+	wf_fcu_add_fan(pv, "cpu-front-fan-1",	FCU_FAN_RPM, 5);
+	wf_fcu_add_fan(pv, "cpu-rear-fan-1",	FCU_FAN_RPM, 6);
+}
+
+static int wf_fcu_init_chip(struct wf_fcu_priv *pv)
+{
+	unsigned char buf = 0xff;
+	int rc;
+
+	rc = wf_fcu_write_reg(pv, 0xe, &buf, 1);
+	if (rc < 0)
+		return -EIO;
+	rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1);
+	if (rc < 0)
+		return -EIO;
+	rc = wf_fcu_read_reg(pv, 0, &buf, 1);
+	if (rc < 0)
+		return -EIO;
+	pv->rpm_shift = (buf == 1) ? 2 : 3;
+
+	pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n",
+		 pv->rpm_shift);
+
+	return 0;
+}
+
+static int wf_fcu_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct wf_fcu_priv *pv;
+
+	pv = kzalloc(sizeof(*pv), GFP_KERNEL);
+	if (!pv)
+		return -ENOMEM;
+
+	kref_init(&pv->ref);
+	mutex_init(&pv->lock);
+	INIT_LIST_HEAD(&pv->fan_list);
+	pv->i2c = client;
+
+	/*
+	 * First we must start the FCU which will query the
+	 * shift value to apply to RPMs
+	 */
+	if (wf_fcu_init_chip(pv)) {
+		pr_err("wf_fcu: Initialization failed !\n");
+		kfree(pv);
+		return -ENXIO;
+	}
+
+	/* First lookup fans in the device-tree */
+	wf_fcu_lookup_fans(pv);
+
+	/*
+	 * Older machines don't have the device-tree entries
+	 * we are looking for, just hard code the list
+	 */
+	if (list_empty(&pv->fan_list))
+		wf_fcu_default_fans(pv);
+
+	/* Still no fans ? FAIL */
+	if (list_empty(&pv->fan_list)) {
+		pr_err("wf_fcu: Failed to find fans for your machine\n");
+		kfree(pv);
+		return -ENODEV;
+	}
+
+	dev_set_drvdata(&client->dev, pv);
+
+	return 0;
+}
+
+static int wf_fcu_remove(struct i2c_client *client)
+{
+	struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev);
+	struct wf_fcu_fan *fan;
+
+	while (!list_empty(&pv->fan_list)) {
+		fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link);
+		list_del(&fan->link);
+		wf_unregister_control(&fan->ctrl);
+	}
+	kref_put(&pv->ref, wf_fcu_release);
+	return 0;
+}
+
+static const struct i2c_device_id wf_fcu_id[] = {
+	{ "MAC,fcu", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_fcu_id);
+
+static struct i2c_driver wf_fcu_driver = {
+	.driver = {
+		.name	= "wf_fcu",
+	},
+	.probe		= wf_fcu_probe,
+	.remove		= wf_fcu_remove,
+	.id_table	= wf_fcu_id,
+};
+
+module_i2c_driver(wf_fcu_driver);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm75_sensor.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm75_sensor.c
new file mode 100644
index 0000000..6cdfe71
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm75_sensor.c
@@ -0,0 +1,182 @@
+/*
+ * Windfarm PowerMac thermal control. LM75 sensor
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/pmac_low_i2c.h>
+
+#include "windfarm.h"
+
+#define VERSION "1.0"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+struct wf_lm75_sensor {
+	int			ds1775 : 1;
+	int			inited : 1;
+	struct i2c_client	*i2c;
+	struct wf_sensor	sens;
+};
+#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens)
+
+static int wf_lm75_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_lm75_sensor *lm = wf_to_lm75(sr);
+	s32 data;
+
+	if (lm->i2c == NULL)
+		return -ENODEV;
+
+	/* Init chip if necessary */
+	if (!lm->inited) {
+		u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(lm->i2c, 1);
+
+		DBG("wf_lm75: Initializing %s, cfg was: %02x\n",
+		    sr->name, cfg);
+
+		/* clear shutdown bit, keep other settings as left by
+		 * the firmware for now
+		 */
+		cfg_new = cfg & ~0x01;
+		i2c_smbus_write_byte_data(lm->i2c, 1, cfg_new);
+		lm->inited = 1;
+
+		/* If we just powered it up, let's wait 200 ms */
+		msleep(200);
+	}
+
+	/* Read temperature register */
+	data = (s32)le16_to_cpu(i2c_smbus_read_word_data(lm->i2c, 0));
+	data <<= 8;
+	*value = data;
+
+	return 0;
+}
+
+static void wf_lm75_release(struct wf_sensor *sr)
+{
+	struct wf_lm75_sensor *lm = wf_to_lm75(sr);
+
+	kfree(lm);
+}
+
+static const struct wf_sensor_ops wf_lm75_ops = {
+	.get_value	= wf_lm75_get,
+	.release	= wf_lm75_release,
+	.owner		= THIS_MODULE,
+};
+
+static int wf_lm75_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{	
+	struct wf_lm75_sensor *lm;
+	int rc, ds1775 = id->driver_data;
+	const char *name, *loc;
+
+	DBG("wf_lm75: creating  %s device at address 0x%02x\n",
+	    ds1775 ? "ds1775" : "lm75", client->addr);
+
+	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL);
+	if (!loc) {
+		dev_warn(&client->dev, "Missing hwsensor-location property!\n");
+		return -ENXIO;
+	}
+
+	/* Usual rant about sensor names not beeing very consistent in
+	 * the device-tree, oh well ...
+	 * Add more entries below as you deal with more setups
+	 */
+	if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY"))
+		name = "hd-temp";
+	else if (!strcmp(loc, "Incoming Air Temp"))
+		name = "incoming-air-temp";
+	else if (!strcmp(loc, "ODD Temp"))
+		name = "optical-drive-temp";
+	else if (!strcmp(loc, "HD Temp"))
+		name = "hard-drive-temp";
+	else if (!strcmp(loc, "PCI SLOTS"))
+		name = "slots-temp";
+	else if (!strcmp(loc, "CPU A INLET"))
+		name = "cpu-inlet-temp-0";
+	else if (!strcmp(loc, "CPU B INLET"))
+		name = "cpu-inlet-temp-1";
+	else
+		return -ENXIO;
+ 	
+
+	lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
+	if (lm == NULL)
+		return -ENODEV;
+
+	lm->inited = 0;
+	lm->ds1775 = ds1775;
+	lm->i2c = client;
+	lm->sens.name = name;
+	lm->sens.ops = &wf_lm75_ops;
+	i2c_set_clientdata(client, lm);
+
+	rc = wf_register_sensor(&lm->sens);
+	if (rc)
+		kfree(lm);
+	return rc;
+}
+
+static int wf_lm75_remove(struct i2c_client *client)
+{
+	struct wf_lm75_sensor *lm = i2c_get_clientdata(client);
+
+	DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
+
+	/* Mark client detached */
+	lm->i2c = NULL;
+
+	/* release sensor */
+	wf_unregister_sensor(&lm->sens);
+
+	return 0;
+}
+
+static const struct i2c_device_id wf_lm75_id[] = {
+	{ "MAC,lm75", 0 },
+	{ "MAC,ds1775", 1 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_lm75_id);
+
+static struct i2c_driver wf_lm75_driver = {
+	.driver = {
+		.name	= "wf_lm75",
+	},
+	.probe		= wf_lm75_probe,
+	.remove		= wf_lm75_remove,
+	.id_table	= wf_lm75_id,
+};
+
+module_i2c_driver(wf_lm75_driver);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm87_sensor.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm87_sensor.c
new file mode 100644
index 0000000..35aa571
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_lm87_sensor.c
@@ -0,0 +1,201 @@
+/*
+ * Windfarm PowerMac thermal control. LM87 sensor
+ *
+ * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/pmac_low_i2c.h>
+
+#include "windfarm.h"
+
+#define VERSION "1.0"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+struct wf_lm87_sensor {
+	struct i2c_client	*i2c;
+	struct wf_sensor	sens;
+};
+#define wf_to_lm87(c) container_of(c, struct wf_lm87_sensor, sens)
+
+
+static int wf_lm87_read_reg(struct i2c_client *chip, int reg)
+{
+	int rc, tries = 0;
+	u8 buf;
+
+	for (;;) {
+		/* Set address */
+		buf = (u8)reg;
+		rc = i2c_master_send(chip, &buf, 1);
+		if (rc <= 0)
+			goto error;
+		rc = i2c_master_recv(chip, &buf, 1);
+		if (rc <= 0)
+			goto error;
+		return (int)buf;
+	error:
+		DBG("wf_lm87: Error reading LM87, retrying...\n");
+		if (++tries > 10) {
+			printk(KERN_ERR "wf_lm87: Error reading LM87 !\n");
+			return -EIO;
+		}
+		msleep(10);
+	}
+}
+
+static int wf_lm87_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_lm87_sensor *lm = sr->priv;
+	s32 temp;
+
+	if (lm->i2c == NULL)
+		return -ENODEV;
+
+#define LM87_INT_TEMP		0x27
+
+	/* Read temperature register */
+	temp = wf_lm87_read_reg(lm->i2c, LM87_INT_TEMP);
+	if (temp < 0)
+		return temp;
+	*value = temp << 16;
+
+	return 0;
+}
+
+static void wf_lm87_release(struct wf_sensor *sr)
+{
+	struct wf_lm87_sensor *lm = wf_to_lm87(sr);
+
+	kfree(lm);
+}
+
+static const struct wf_sensor_ops wf_lm87_ops = {
+	.get_value	= wf_lm87_get,
+	.release	= wf_lm87_release,
+	.owner		= THIS_MODULE,
+};
+
+static int wf_lm87_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{	
+	struct wf_lm87_sensor *lm;
+	const char *name = NULL, *loc;
+	struct device_node *np = NULL;
+	int rc;
+
+	/*
+	 * The lm87 contains a whole pile of sensors, additionally,
+	 * the Xserve G5 has several lm87's. However, for now we only
+	 * care about the internal temperature sensor
+	 */
+	while ((np = of_get_next_child(client->dev.of_node, np)) != NULL) {
+		if (strcmp(np->name, "int-temp"))
+			continue;
+		loc = of_get_property(np, "location", NULL);
+		if (!loc)
+			continue;
+		if (strstr(loc, "DIMM"))
+			name = "dimms-temp";
+		else if (strstr(loc, "Processors"))
+			name = "between-cpus-temp";
+		if (name) {
+			of_node_put(np);
+			break;
+		}
+	}
+	if (!name) {
+		pr_warning("wf_lm87: Unsupported sensor %pOF\n",
+			   client->dev.of_node);
+		return -ENODEV;
+	}
+
+	lm = kzalloc(sizeof(struct wf_lm87_sensor), GFP_KERNEL);
+	if (lm == NULL)
+		return -ENODEV;
+
+	lm->i2c = client;
+	lm->sens.name = name;
+	lm->sens.ops = &wf_lm87_ops;
+	lm->sens.priv = lm;
+	i2c_set_clientdata(client, lm);
+
+	rc = wf_register_sensor(&lm->sens);
+	if (rc)
+		kfree(lm);
+	return rc;
+}
+
+static int wf_lm87_remove(struct i2c_client *client)
+{
+	struct wf_lm87_sensor *lm = i2c_get_clientdata(client);
+
+	DBG("wf_lm87: i2c detatch called for %s\n", lm->sens.name);
+
+	/* Mark client detached */
+	lm->i2c = NULL;
+
+	/* release sensor */
+	wf_unregister_sensor(&lm->sens);
+
+	return 0;
+}
+
+static const struct i2c_device_id wf_lm87_id[] = {
+	{ "MAC,lm87cimt", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_lm87_id);
+
+static struct i2c_driver wf_lm87_driver = {
+	.driver = {
+		.name	= "wf_lm87",
+	},
+	.probe		= wf_lm87_probe,
+	.remove		= wf_lm87_remove,
+	.id_table	= wf_lm87_id,
+};
+
+static int __init wf_lm87_sensor_init(void)
+{
+	/* We only support this on the Xserve */
+	if (!of_machine_is_compatible("RackMac3,1"))
+		return -ENODEV;
+
+	return i2c_add_driver(&wf_lm87_driver);
+}
+
+static void __exit wf_lm87_sensor_exit(void)
+{
+	i2c_del_driver(&wf_lm87_driver);
+}
+
+
+module_init(wf_lm87_sensor_init);
+module_exit(wf_lm87_sensor_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("LM87 sensor objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_max6690_sensor.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_max6690_sensor.c
new file mode 100644
index 0000000..6ad035e
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_max6690_sensor.c
@@ -0,0 +1,137 @@
+/*
+ * Windfarm PowerMac thermal control.  MAX6690 sensor.
+ *
+ * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
+ *
+ * Use and redistribute under the terms of the GNU GPL v2.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/pmac_low_i2c.h>
+
+#include "windfarm.h"
+
+#define VERSION "1.0"
+
+/* This currently only exports the external temperature sensor,
+   since that's all the control loops need. */
+
+/* Some MAX6690 register numbers */
+#define MAX6690_INTERNAL_TEMP	0
+#define MAX6690_EXTERNAL_TEMP	1
+
+struct wf_6690_sensor {
+	struct i2c_client	*i2c;
+	struct wf_sensor	sens;
+};
+
+#define wf_to_6690(x)	container_of((x), struct wf_6690_sensor, sens)
+
+static int wf_max6690_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_6690_sensor *max = wf_to_6690(sr);
+	s32 data;
+
+	if (max->i2c == NULL)
+		return -ENODEV;
+
+	/* chip gets initialized by firmware */
+	data = i2c_smbus_read_byte_data(max->i2c, MAX6690_EXTERNAL_TEMP);
+	if (data < 0)
+		return data;
+	*value = data << 16;
+	return 0;
+}
+
+static void wf_max6690_release(struct wf_sensor *sr)
+{
+	struct wf_6690_sensor *max = wf_to_6690(sr);
+
+	kfree(max);
+}
+
+static const struct wf_sensor_ops wf_max6690_ops = {
+	.get_value	= wf_max6690_get,
+	.release	= wf_max6690_release,
+	.owner		= THIS_MODULE,
+};
+
+static int wf_max6690_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	const char *name, *loc;
+	struct wf_6690_sensor *max;
+	int rc;
+
+	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL);
+	if (!loc) {
+		dev_warn(&client->dev, "Missing hwsensor-location property!\n");
+		return -ENXIO;
+	}
+
+	/*
+	 * We only expose the external temperature register for
+	 * now as this is all we need for our control loops
+	 */
+	if (!strcmp(loc, "BACKSIDE") || !strcmp(loc, "SYS CTRLR AMBIENT"))
+		name = "backside-temp";
+	else if (!strcmp(loc, "NB Ambient"))
+		name = "north-bridge-temp";
+	else if (!strcmp(loc, "GPU Ambient"))
+		name = "gpu-temp";
+	else
+		return -ENXIO;
+
+	max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL);
+	if (max == NULL) {
+		printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor: "
+		       "no memory\n");
+		return -ENOMEM;
+	}
+
+	max->i2c = client;
+	max->sens.name = name;
+	max->sens.ops = &wf_max6690_ops;
+	i2c_set_clientdata(client, max);
+
+	rc = wf_register_sensor(&max->sens);
+	if (rc)
+		kfree(max);
+	return rc;
+}
+
+static int wf_max6690_remove(struct i2c_client *client)
+{
+	struct wf_6690_sensor *max = i2c_get_clientdata(client);
+
+	max->i2c = NULL;
+	wf_unregister_sensor(&max->sens);
+
+	return 0;
+}
+
+static const struct i2c_device_id wf_max6690_id[] = {
+	{ "MAC,max6690", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_max6690_id);
+
+static struct i2c_driver wf_max6690_driver = {
+	.driver = {
+		.name		= "wf_max6690",
+	},
+	.probe		= wf_max6690_probe,
+	.remove		= wf_max6690_remove,
+	.id_table	= wf_max6690_id,
+};
+
+module_i2c_driver(wf_max6690_driver);
+
+MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
+MODULE_DESCRIPTION("MAX6690 sensor objects for PowerMac thermal control");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_mpu.h b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_mpu.h
new file mode 100644
index 0000000..046edc8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_mpu.h
@@ -0,0 +1,105 @@
+/*
+ * Windfarm PowerMac thermal control
+ *
+ * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#ifndef __WINDFARM_MPU_H
+#define __WINDFARM_MPU_H
+
+typedef unsigned short fu16;
+typedef int fs32;
+typedef short fs16;
+
+/* Definition of the MPU data structure which contains per CPU
+ * calibration information (among others) for the G5 machines
+ */
+struct mpu_data
+{
+	u8	signature;		/* 0x00 - EEPROM sig. */
+	u8	bytes_used;		/* 0x01 - Bytes used in eeprom (160 ?) */
+	u8	size;			/* 0x02 - EEPROM size (256 ?) */
+	u8	version;		/* 0x03 - EEPROM version */
+	u32	data_revision;		/* 0x04 - Dataset revision */
+	u8	processor_bin_code[3];	/* 0x08 - Processor BIN code */
+	u8	bin_code_expansion;	/* 0x0b - ??? (padding ?) */
+	u8	processor_num;		/* 0x0c - Number of CPUs on this MPU */
+	u8	input_mul_bus_div;	/* 0x0d - Clock input multiplier/bus divider */
+	u8	reserved1[2];		/* 0x0e - */
+	u32	input_clk_freq_high;	/* 0x10 - Input clock frequency high */
+	u8	cpu_nb_target_cycles;	/* 0x14 - ??? */
+	u8	cpu_statlat;		/* 0x15 - ??? */
+	u8	cpu_snooplat;		/* 0x16 - ??? */
+	u8	cpu_snoopacc;		/* 0x17 - ??? */
+	u8	nb_paamwin;		/* 0x18 - ??? */
+	u8	nb_statlat;		/* 0x19 - ??? */
+	u8	nb_snooplat;		/* 0x1a - ??? */
+	u8	nb_snoopwin;		/* 0x1b - ??? */
+	u8	api_bus_mode;		/* 0x1c - ??? */
+	u8	reserved2[3];		/* 0x1d - */
+	u32	input_clk_freq_low;	/* 0x20 - Input clock frequency low */
+	u8	processor_card_slot;	/* 0x24 - Processor card slot number */
+	u8	reserved3[2];		/* 0x25 - */
+	u8	padjmax;       		/* 0x27 - Max power adjustment (Not in OF!) */
+	u8	ttarget;		/* 0x28 - Target temperature */
+	u8	tmax;			/* 0x29 - Max temperature */
+	u8	pmaxh;			/* 0x2a - Max power */
+	u8	tguardband;		/* 0x2b - Guardband temp ??? Hist. len in OSX */
+	fs32	pid_gp;			/* 0x2c - PID proportional gain */
+	fs32	pid_gr;			/* 0x30 - PID reset gain */
+	fs32	pid_gd;			/* 0x34 - PID derivative gain */
+	fu16	voph;			/* 0x38 - Vop High */
+	fu16	vopl;			/* 0x3a - Vop Low */
+	fs16	nactual_die;		/* 0x3c - nActual Die */
+	fs16	nactual_heatsink;	/* 0x3e - nActual Heatsink */
+	fs16	nactual_system;		/* 0x40 - nActual System */
+	u16	calibration_flags;	/* 0x42 - Calibration flags */
+	fu16	mdiode;			/* 0x44 - Diode M value (scaling factor) */
+	fs16	bdiode;			/* 0x46 - Diode B value (offset) */
+	fs32	theta_heat_sink;	/* 0x48 - Theta heat sink */
+	u16	rminn_intake_fan;	/* 0x4c - Intake fan min RPM */
+	u16	rmaxn_intake_fan;	/* 0x4e - Intake fan max RPM */
+	u16	rminn_exhaust_fan;	/* 0x50 - Exhaust fan min RPM */
+	u16	rmaxn_exhaust_fan;	/* 0x52 - Exhaust fan max RPM */
+	u8	processor_part_num[8];	/* 0x54 - Processor part number XX pumps min/max */
+	u32	processor_lot_num;	/* 0x5c - Processor lot number */
+	u8	orig_card_sernum[0x10];	/* 0x60 - Card original serial number */
+	u8	curr_card_sernum[0x10];	/* 0x70 - Card current serial number */
+	u8	mlb_sernum[0x18];	/* 0x80 - MLB serial number */
+	u32	checksum1;		/* 0x98 - */
+	u32	checksum2;		/* 0x9c - */	
+}; /* Total size = 0xa0 */
+
+static inline const struct mpu_data *wf_get_mpu(int cpu)
+{
+	struct device_node *np;
+	char nodename[64];
+	const void *data;
+	int len;
+
+	/*
+	 * prom.c routine for finding a node by path is a bit brain dead
+	 * and requires exact @xxx unit numbers. This is a bit ugly but
+	 * will work for these machines
+	 */
+	sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0);
+	np = of_find_node_by_path(nodename);
+	if (!np)
+		return NULL;
+	data = of_get_property(np, "cpuid", &len);	
+	of_node_put(np);
+	if (!data)
+		return NULL;
+
+	/*
+	 * We are naughty, we have dropped the reference to the device
+	 * node and still return a pointer to the content. We know we
+	 * can do that though as this is only ever called on PowerMac
+	 * which cannot remove those nodes
+	 */
+	return data;
+}
+
+#endif /*  __WINDFARM_MPU_H */
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.c
new file mode 100644
index 0000000..f10efb2
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.c
@@ -0,0 +1,149 @@
+/*
+ * Windfarm PowerMac thermal control. Generic PID helpers
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/module.h>
+
+#include "windfarm_pid.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param)
+{
+	memset(st, 0, sizeof(struct wf_pid_state));
+	st->param = *param;
+	st->first = 1;
+}
+EXPORT_SYMBOL_GPL(wf_pid_init);
+
+s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample)
+{
+	s64	error, integ, deriv;
+	s32	target;
+	int	i, hlen = st->param.history_len;
+
+	/* Calculate error term */
+	error = new_sample - st->param.itarget;
+
+	/* Get samples into our history buffer */
+	if (st->first) {
+		for (i = 0; i < hlen; i++) {
+			st->samples[i] = new_sample;
+			st->errors[i] = error;
+		}
+		st->first = 0;
+		st->index = 0;
+	} else {
+		st->index = (st->index + 1) % hlen;
+		st->samples[st->index] = new_sample;
+		st->errors[st->index] = error;
+	}
+
+	/* Calculate integral term */
+	for (i = 0, integ = 0; i < hlen; i++)
+		integ += st->errors[(st->index + hlen - i) % hlen];
+	integ *= st->param.interval;
+
+	/* Calculate derivative term */
+	deriv = st->errors[st->index] -
+		st->errors[(st->index + hlen - 1) % hlen];
+	deriv /= st->param.interval;
+
+	/* Calculate target */
+	target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd +
+		  error * (s64)st->param.gp) >> 36);
+	if (st->param.additive)
+		target += st->target;
+	target = max(target, st->param.min);
+	target = min(target, st->param.max);
+	st->target = target;
+
+	return st->target;
+}
+EXPORT_SYMBOL_GPL(wf_pid_run);
+
+void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
+		     struct wf_cpu_pid_param *param)
+{
+	memset(st, 0, sizeof(struct wf_cpu_pid_state));
+	st->param = *param;
+	st->first = 1;
+}
+EXPORT_SYMBOL_GPL(wf_cpu_pid_init);
+
+s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp)
+{
+	s64	integ, deriv, prop;
+	s32	error, target, sval, adj;
+	int	i, hlen = st->param.history_len;
+
+	/* Calculate error term */
+	error = st->param.pmaxadj - new_power;
+
+	/* Get samples into our history buffer */
+	if (st->first) {
+		for (i = 0; i < hlen; i++) {
+			st->powers[i] = new_power;
+			st->errors[i] = error;
+		}
+		st->temps[0] = st->temps[1] = new_temp;
+		st->first = 0;
+		st->index = st->tindex = 0;
+	} else {
+		st->index = (st->index + 1) % hlen;
+		st->powers[st->index] = new_power;
+		st->errors[st->index] = error;
+		st->tindex = (st->tindex + 1) % 2;
+		st->temps[st->tindex] = new_temp;
+	}
+
+	/* Calculate integral term */
+	for (i = 0, integ = 0; i < hlen; i++)
+		integ += st->errors[(st->index + hlen - i) % hlen];
+	integ *= st->param.interval;
+	integ *= st->param.gr;
+	sval = st->param.tmax - (s32)(integ >> 20);
+	adj = min(st->param.ttarget, sval);
+
+	DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj);
+
+	/* Calculate derivative term */
+	deriv = st->temps[st->tindex] -
+		st->temps[(st->tindex + 2 - 1) % 2];
+	deriv /= st->param.interval;
+	deriv *= st->param.gd;
+
+	/* Calculate proportional term */
+	prop = st->last_delta = (new_temp - adj);
+	prop *= st->param.gp;
+
+	DBG("deriv: %lx, prop: %lx\n", deriv, prop);
+
+	/* Calculate target */
+	target = st->target + (s32)((deriv + prop) >> 36);
+	target = max(target, st->param.min);
+	target = min(target, st->param.max);
+	st->target = target;
+
+	return st->target;
+}
+EXPORT_SYMBOL_GPL(wf_cpu_pid_run);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("PID algorithm for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.h b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.h
new file mode 100644
index 0000000..bbccc22
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pid.h
@@ -0,0 +1,85 @@
+/*
+ * Windfarm PowerMac thermal control. Generic PID helpers
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * This is a pair of generic PID helpers that can be used by
+ * control loops. One is the basic PID implementation, the
+ * other one is more specifically tailored to the loops used
+ * for CPU control with 2 input sample types (temp and power)
+ */
+
+/*
+ * *** Simple PID ***
+ */
+
+#define WF_PID_MAX_HISTORY	32
+
+/* This parameter array is passed to the PID algorithm. Currently,
+ * we don't support changing parameters on the fly as it's not needed
+ * but could be implemented (with necessary adjustment of the history
+ * buffer
+ */
+struct wf_pid_param {
+	int	interval;	/* Interval between samples in seconds */
+	int	history_len;	/* Size of history buffer */
+	int	additive;	/* 1: target relative to previous value */
+	s32	gd, gp, gr;	/* PID gains */
+	s32	itarget;	/* PID input target */
+	s32	min,max;	/* min and max target values */
+};
+
+struct wf_pid_state {
+	int	first;				/* first run of the loop */
+	int	index; 				/* index of current sample */
+	s32	target;				/* current target value */
+	s32	samples[WF_PID_MAX_HISTORY];	/* samples history buffer */
+	s32	errors[WF_PID_MAX_HISTORY];	/* error history buffer */
+
+	struct wf_pid_param param;
+};
+
+extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param);
+extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample);
+
+
+/*
+ * *** CPU PID ***
+ */
+
+#define WF_CPU_PID_MAX_HISTORY	32
+
+/* This parameter array is passed to the CPU PID algorithm. Currently,
+ * we don't support changing parameters on the fly as it's not needed
+ * but could be implemented (with necessary adjustment of the history
+ * buffer
+ */
+struct wf_cpu_pid_param {
+	int	interval;	/* Interval between samples in seconds */
+	int	history_len;	/* Size of history buffer */
+	s32	gd, gp, gr;	/* PID gains */
+	s32	pmaxadj;	/* PID max power adjust */
+	s32	ttarget;	/* PID input target */
+	s32	tmax;		/* PID input max */
+	s32	min,max;	/* min and max target values */
+};
+
+struct wf_cpu_pid_state {
+	int	first;				/* first run of the loop */
+	int	index; 				/* index of current power */
+	int	tindex; 			/* index of current temp */
+	s32	target;				/* current target value */
+	s32	last_delta;			/* last Tactual - Ttarget */
+	s32	powers[WF_PID_MAX_HISTORY];	/* power history buffer */
+	s32	errors[WF_PID_MAX_HISTORY];	/* error history buffer */
+	s32	temps[2];			/* temp. history buffer */
+
+	struct wf_cpu_pid_param param;
+};
+
+extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
+			    struct wf_cpu_pid_param *param);
+extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp);
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm112.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm112.c
new file mode 100644
index 0000000..088ca17
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm112.c
@@ -0,0 +1,718 @@
+/*
+ * Windfarm PowerMac thermal control.
+ * Control loops for machines with SMU and PPC970MP processors.
+ *
+ * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
+ * Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Use and redistribute under the terms of the GNU GPL v2.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <asm/prom.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.2"
+
+#define DEBUG
+#undef LOTSA_DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+#ifdef LOTSA_DEBUG
+#define DBG_LOTS(args...)	printk(args)
+#else
+#define DBG_LOTS(args...)	do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 60 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+/* We currently only handle 2 chips, 4 cores... */
+#define NR_CHIPS	2
+#define NR_CORES	4
+#define NR_CPU_FANS	3 * NR_CHIPS
+
+/* Controls and sensors */
+static struct wf_sensor *sens_cpu_temp[NR_CORES];
+static struct wf_sensor *sens_cpu_power[NR_CORES];
+static struct wf_sensor *hd_temp;
+static struct wf_sensor *slots_power;
+static struct wf_sensor *u4_temp;
+
+static struct wf_control *cpu_fans[NR_CPU_FANS];
+static char *cpu_fan_names[NR_CPU_FANS] = {
+	"cpu-rear-fan-0",
+	"cpu-rear-fan-1",
+	"cpu-front-fan-0",
+	"cpu-front-fan-1",
+	"cpu-pump-0",
+	"cpu-pump-1",
+};
+static struct wf_control *cpufreq_clamp;
+
+/* Second pump isn't required (and isn't actually present) */
+#define CPU_FANS_REQD		(NR_CPU_FANS - 2)
+#define FIRST_PUMP		4
+#define LAST_PUMP		5
+
+/* We keep a temperature history for average calculation of 180s */
+#define CPU_TEMP_HIST_SIZE	180
+
+/* Scale factor for fan speed, *100 */
+static int cpu_fan_scale[NR_CPU_FANS] = {
+	100,
+	100,
+	97,		/* inlet fans run at 97% of exhaust fan */
+	97,
+	100,		/* updated later */
+	100,		/* updated later */
+};
+
+static struct wf_control *backside_fan;
+static struct wf_control *slots_fan;
+static struct wf_control *drive_bay_fan;
+
+/* PID loop state */
+static struct wf_cpu_pid_state cpu_pid[NR_CORES];
+static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
+static int cpu_thist_pt;
+static s64 cpu_thist_total;
+static s32 cpu_all_tmax = 100 << 16;
+static int cpu_last_target;
+static struct wf_pid_state backside_pid;
+static int backside_tick;
+static struct wf_pid_state slots_pid;
+static int slots_started;
+static struct wf_pid_state drive_bay_pid;
+static int drive_bay_tick;
+
+static int nr_cores;
+static int have_all_controls;
+static int have_all_sensors;
+static int started;
+
+static int failure_state;
+#define FAILURE_SENSOR		1
+#define FAILURE_FAN		2
+#define FAILURE_PERM		4
+#define FAILURE_LOW_OVERTEMP	8
+#define FAILURE_HIGH_OVERTEMP	16
+
+/* Overtemp values */
+#define LOW_OVER_AVERAGE	0
+#define LOW_OVER_IMMEDIATE	(10 << 16)
+#define LOW_OVER_CLEAR		((-10) << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+#define HIGH_OVER_AVERAGE	(10 << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+
+
+/* Implementation... */
+static int create_cpu_loop(int cpu)
+{
+	int chip = cpu / 2;
+	int core = cpu & 1;
+	struct smu_sdbp_header *hdr;
+	struct smu_sdbp_cpupiddata *piddata;
+	struct wf_cpu_pid_param pid;
+	struct wf_control *main_fan = cpu_fans[0];
+	s32 tmax;
+	int fmin;
+
+	/* Get FVT params to get Tmax; if not found, assume default */
+	hdr = smu_sat_get_sdb_partition(chip, 0xC4 + core, NULL);
+	if (hdr) {
+		struct smu_sdbp_fvt *fvt = (struct smu_sdbp_fvt *)&hdr[1];
+		tmax = fvt->maxtemp << 16;
+	} else
+		tmax = 95 << 16;	/* default to 95 degrees C */
+
+	/* We keep a global tmax for overtemp calculations */
+	if (tmax < cpu_all_tmax)
+		cpu_all_tmax = tmax;
+
+	kfree(hdr);
+
+	/* Get PID params from the appropriate SAT */
+	hdr = smu_sat_get_sdb_partition(chip, 0xC8 + core, NULL);
+	if (hdr == NULL) {
+		printk(KERN_WARNING"windfarm: can't get CPU PID fan config\n");
+		return -EINVAL;
+	}
+	piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+	/*
+	 * Darwin has a minimum fan speed of 1000 rpm for the 4-way and
+	 * 515 for the 2-way.  That appears to be overkill, so for now,
+	 * impose a minimum of 750 or 515.
+	 */
+	fmin = (nr_cores > 2) ? 750 : 515;
+
+	/* Initialize PID loop */
+	pid.interval = 1;	/* seconds */
+	pid.history_len = piddata->history_len;
+	pid.gd = piddata->gd;
+	pid.gp = piddata->gp;
+	pid.gr = piddata->gr / piddata->history_len;
+	pid.pmaxadj = (piddata->max_power << 16) - (piddata->power_adj << 8);
+	pid.ttarget = tmax - (piddata->target_temp_delta << 16);
+	pid.tmax = tmax;
+	pid.min = main_fan->ops->get_min(main_fan);
+	pid.max = main_fan->ops->get_max(main_fan);
+	if (pid.min < fmin)
+		pid.min = fmin;
+
+	wf_cpu_pid_init(&cpu_pid[cpu], &pid);
+
+	kfree(hdr);
+
+	return 0;
+}
+
+static void cpu_max_all_fans(void)
+{
+	int i;
+
+	/* We max all CPU fans in case of a sensor error. We also do the
+	 * cpufreq clamping now, even if it's supposedly done later by the
+	 * generic code anyway, we do it earlier here to react faster
+	 */
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	for (i = 0; i < NR_CPU_FANS; ++i)
+		if (cpu_fans[i])
+			wf_control_set_max(cpu_fans[i]);
+}
+
+static int cpu_check_overtemp(s32 temp)
+{
+	int new_state = 0;
+	s32 t_avg, t_old;
+
+	/* First check for immediate overtemps */
+	if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
+			       " temperature !\n");
+	}
+	if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " immediate CPU temperature !\n");
+	}
+
+	/* We calculate a history of max temperatures and use that for the
+	 * overtemp management
+	 */
+	t_old = cpu_thist[cpu_thist_pt];
+	cpu_thist[cpu_thist_pt] = temp;
+	cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
+	cpu_thist_total -= t_old;
+	cpu_thist_total += temp;
+	t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;
+
+	DBG_LOTS("t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
+		 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));
+
+	/* Now check for average overtemps */
+	if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to average CPU"
+			       " temperature !\n");
+	}
+	if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " average CPU temperature !\n");
+	}
+
+	/* Now handle overtemp conditions. We don't currently use the windfarm
+	 * overtemp handling core as it's not fully suited to the needs of those
+	 * new machine. This will be fixed later.
+	 */
+	if (new_state) {
+		/* High overtemp -> immediate shutdown */
+		if (new_state & FAILURE_HIGH_OVERTEMP)
+			machine_power_off();
+		if ((failure_state & new_state) != new_state)
+			cpu_max_all_fans();
+		failure_state |= new_state;
+	} else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
+		   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
+		printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
+		failure_state &= ~FAILURE_LOW_OVERTEMP;
+	}
+
+	return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
+}
+
+static void cpu_fans_tick(void)
+{
+	int err, cpu;
+	s32 greatest_delta = 0;
+	s32 temp, power, t_max = 0;
+	int i, t, target = 0;
+	struct wf_sensor *sr;
+	struct wf_control *ct;
+	struct wf_cpu_pid_state *sp;
+
+	DBG_LOTS(KERN_DEBUG);
+	for (cpu = 0; cpu < nr_cores; ++cpu) {
+		/* Get CPU core temperature */
+		sr = sens_cpu_temp[cpu];
+		err = sr->ops->get_value(sr, &temp);
+		if (err) {
+			DBG("\n");
+			printk(KERN_WARNING "windfarm: CPU %d temperature "
+			       "sensor error %d\n", cpu, err);
+			failure_state |= FAILURE_SENSOR;
+			cpu_max_all_fans();
+			return;
+		}
+
+		/* Keep track of highest temp */
+		t_max = max(t_max, temp);
+
+		/* Get CPU power */
+		sr = sens_cpu_power[cpu];
+		err = sr->ops->get_value(sr, &power);
+		if (err) {
+			DBG("\n");
+			printk(KERN_WARNING "windfarm: CPU %d power "
+			       "sensor error %d\n", cpu, err);
+			failure_state |= FAILURE_SENSOR;
+			cpu_max_all_fans();
+			return;
+		}
+
+		/* Run PID */
+		sp = &cpu_pid[cpu];
+		t = wf_cpu_pid_run(sp, power, temp);
+
+		if (cpu == 0 || sp->last_delta > greatest_delta) {
+			greatest_delta = sp->last_delta;
+			target = t;
+		}
+		DBG_LOTS("[%d] P=%d.%.3d T=%d.%.3d ",
+		    cpu, FIX32TOPRINT(power), FIX32TOPRINT(temp));
+	}
+	DBG_LOTS("fans = %d, t_max = %d.%03d\n", target, FIX32TOPRINT(t_max));
+
+	/* Darwin limits decrease to 20 per iteration */
+	if (target < (cpu_last_target - 20))
+		target = cpu_last_target - 20;
+	cpu_last_target = target;
+	for (cpu = 0; cpu < nr_cores; ++cpu)
+		cpu_pid[cpu].target = target;
+
+	/* Handle possible overtemps */
+	if (cpu_check_overtemp(t_max))
+		return;
+
+	/* Set fans */
+	for (i = 0; i < NR_CPU_FANS; ++i) {
+		ct = cpu_fans[i];
+		if (ct == NULL)
+			continue;
+		err = ct->ops->set_value(ct, target * cpu_fan_scale[i] / 100);
+		if (err) {
+			printk(KERN_WARNING "windfarm: fan %s reports "
+			       "error %d\n", ct->name, err);
+			failure_state |= FAILURE_FAN;
+			break;
+		}
+	}
+}
+
+/* Backside/U4 fan */
+static struct wf_pid_param backside_param = {
+	.interval	= 5,
+	.history_len	= 2,
+	.gd		= 48 << 20,
+	.gp		= 5 << 20,
+	.gr		= 0,
+	.itarget	= 64 << 16,
+	.additive	= 1,
+};
+
+static void backside_fan_tick(void)
+{
+	s32 temp;
+	int speed;
+	int err;
+
+	if (!backside_fan || !u4_temp)
+		return;
+	if (!backside_tick) {
+		/* first time; initialize things */
+		printk(KERN_INFO "windfarm: Backside control loop started.\n");
+		backside_param.min = backside_fan->ops->get_min(backside_fan);
+		backside_param.max = backside_fan->ops->get_max(backside_fan);
+		wf_pid_init(&backside_pid, &backside_param);
+		backside_tick = 1;
+	}
+	if (--backside_tick > 0)
+		return;
+	backside_tick = backside_pid.param.interval;
+
+	err = u4_temp->ops->get_value(u4_temp, &temp);
+	if (err) {
+		printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n",
+		       err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(backside_fan);
+		return;
+	}
+	speed = wf_pid_run(&backside_pid, temp);
+	DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	err = backside_fan->ops->set_value(backside_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+/* Drive bay fan */
+static struct wf_pid_param drive_bay_prm = {
+	.interval	= 5,
+	.history_len	= 2,
+	.gd		= 30 << 20,
+	.gp		= 5 << 20,
+	.gr		= 0,
+	.itarget	= 40 << 16,
+	.additive	= 1,
+};
+
+static void drive_bay_fan_tick(void)
+{
+	s32 temp;
+	int speed;
+	int err;
+
+	if (!drive_bay_fan || !hd_temp)
+		return;
+	if (!drive_bay_tick) {
+		/* first time; initialize things */
+		printk(KERN_INFO "windfarm: Drive bay control loop started.\n");
+		drive_bay_prm.min = drive_bay_fan->ops->get_min(drive_bay_fan);
+		drive_bay_prm.max = drive_bay_fan->ops->get_max(drive_bay_fan);
+		wf_pid_init(&drive_bay_pid, &drive_bay_prm);
+		drive_bay_tick = 1;
+	}
+	if (--drive_bay_tick > 0)
+		return;
+	drive_bay_tick = drive_bay_pid.param.interval;
+
+	err = hd_temp->ops->get_value(hd_temp, &temp);
+	if (err) {
+		printk(KERN_WARNING "windfarm: drive bay temp sensor "
+		       "error %d\n", err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(drive_bay_fan);
+		return;
+	}
+	speed = wf_pid_run(&drive_bay_pid, temp);
+	DBG_LOTS("drive_bay PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	err = drive_bay_fan->ops->set_value(drive_bay_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+/* PCI slots area fan */
+/* This makes the fan speed proportional to the power consumed */
+static struct wf_pid_param slots_param = {
+	.interval	= 1,
+	.history_len	= 2,
+	.gd		= 0,
+	.gp		= 0,
+	.gr		= 0x1277952,
+	.itarget	= 0,
+	.min		= 1560,
+	.max		= 3510,
+};
+
+static void slots_fan_tick(void)
+{
+	s32 power;
+	int speed;
+	int err;
+
+	if (!slots_fan || !slots_power)
+		return;
+	if (!slots_started) {
+		/* first time; initialize things */
+		printk(KERN_INFO "windfarm: Slots control loop started.\n");
+		wf_pid_init(&slots_pid, &slots_param);
+		slots_started = 1;
+	}
+
+	err = slots_power->ops->get_value(slots_power, &power);
+	if (err) {
+		printk(KERN_WARNING "windfarm: slots power sensor error %d\n",
+		       err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(slots_fan);
+		return;
+	}
+	speed = wf_pid_run(&slots_pid, power);
+	DBG_LOTS("slots PID power=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(power), speed);
+
+	err = slots_fan->ops->set_value(slots_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: slots fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+static void set_fail_state(void)
+{
+	int i;
+
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	for (i = 0; i < NR_CPU_FANS; ++i)
+		if (cpu_fans[i])
+			wf_control_set_max(cpu_fans[i]);
+	if (backside_fan)
+		wf_control_set_max(backside_fan);
+	if (slots_fan)
+		wf_control_set_max(slots_fan);
+	if (drive_bay_fan)
+		wf_control_set_max(drive_bay_fan);
+}
+
+static void pm112_tick(void)
+{
+	int i, last_failure;
+
+	if (!started) {
+		started = 1;
+		printk(KERN_INFO "windfarm: CPUs control loops started.\n");
+		for (i = 0; i < nr_cores; ++i) {
+			if (create_cpu_loop(i) < 0) {
+				failure_state = FAILURE_PERM;
+				set_fail_state();
+				break;
+			}
+		}
+		DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));
+
+#ifdef HACKED_OVERTEMP
+		cpu_all_tmax = 60 << 16;
+#endif
+	}
+
+	/* Permanent failure, bail out */
+	if (failure_state & FAILURE_PERM)
+		return;
+	/* Clear all failure bits except low overtemp which will be eventually
+	 * cleared by the control loop itself
+	 */
+	last_failure = failure_state;
+	failure_state &= FAILURE_LOW_OVERTEMP;
+	cpu_fans_tick();
+	backside_fan_tick();
+	slots_fan_tick();
+	drive_bay_fan_tick();
+
+	DBG_LOTS("last_failure: 0x%x, failure_state: %x\n",
+		 last_failure, failure_state);
+
+	/* Check for failures. Any failure causes cpufreq clamping */
+	if (failure_state && last_failure == 0 && cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	if (failure_state == 0 && last_failure && cpufreq_clamp)
+		wf_control_set_min(cpufreq_clamp);
+
+	/* That's it for now, we might want to deal with other failures
+	 * differently in the future though
+	 */
+}
+
+static void pm112_new_control(struct wf_control *ct)
+{
+	int i, max_exhaust;
+
+	if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
+		if (wf_get_control(ct) == 0)
+			cpufreq_clamp = ct;
+	}
+
+	for (i = 0; i < NR_CPU_FANS; ++i) {
+		if (!strcmp(ct->name, cpu_fan_names[i])) {
+			if (cpu_fans[i] == NULL && wf_get_control(ct) == 0)
+				cpu_fans[i] = ct;
+			break;
+		}
+	}
+	if (i >= NR_CPU_FANS) {
+		/* not a CPU fan, try the others */
+		if (!strcmp(ct->name, "backside-fan")) {
+			if (backside_fan == NULL && wf_get_control(ct) == 0)
+				backside_fan = ct;
+		} else if (!strcmp(ct->name, "slots-fan")) {
+			if (slots_fan == NULL && wf_get_control(ct) == 0)
+				slots_fan = ct;
+		} else if (!strcmp(ct->name, "drive-bay-fan")) {
+			if (drive_bay_fan == NULL && wf_get_control(ct) == 0)
+				drive_bay_fan = ct;
+		}
+		return;
+	}
+
+	for (i = 0; i < CPU_FANS_REQD; ++i)
+		if (cpu_fans[i] == NULL)
+			return;
+
+	/* work out pump scaling factors */
+	max_exhaust = cpu_fans[0]->ops->get_max(cpu_fans[0]);
+	for (i = FIRST_PUMP; i <= LAST_PUMP; ++i)
+		if ((ct = cpu_fans[i]) != NULL)
+			cpu_fan_scale[i] =
+				ct->ops->get_max(ct) * 100 / max_exhaust;
+
+	have_all_controls = 1;
+}
+
+static void pm112_new_sensor(struct wf_sensor *sr)
+{
+	unsigned int i;
+
+	if (!strncmp(sr->name, "cpu-temp-", 9)) {
+		i = sr->name[9] - '0';
+		if (sr->name[10] == 0 && i < NR_CORES &&
+		    sens_cpu_temp[i] == NULL && wf_get_sensor(sr) == 0)
+			sens_cpu_temp[i] = sr;
+
+	} else if (!strncmp(sr->name, "cpu-power-", 10)) {
+		i = sr->name[10] - '0';
+		if (sr->name[11] == 0 && i < NR_CORES &&
+		    sens_cpu_power[i] == NULL && wf_get_sensor(sr) == 0)
+			sens_cpu_power[i] = sr;
+	} else if (!strcmp(sr->name, "hd-temp")) {
+		if (hd_temp == NULL && wf_get_sensor(sr) == 0)
+			hd_temp = sr;
+	} else if (!strcmp(sr->name, "slots-power")) {
+		if (slots_power == NULL && wf_get_sensor(sr) == 0)
+			slots_power = sr;
+	} else if (!strcmp(sr->name, "backside-temp")) {
+		if (u4_temp == NULL && wf_get_sensor(sr) == 0)
+			u4_temp = sr;
+	} else
+		return;
+
+	/* check if we have all the sensors we need */
+	for (i = 0; i < nr_cores; ++i)
+		if (sens_cpu_temp[i] == NULL || sens_cpu_power[i] == NULL)
+			return;
+
+	have_all_sensors = 1;
+}
+
+static int pm112_wf_notify(struct notifier_block *self,
+			   unsigned long event, void *data)
+{
+	switch (event) {
+	case WF_EVENT_NEW_SENSOR:
+		pm112_new_sensor(data);
+		break;
+	case WF_EVENT_NEW_CONTROL:
+		pm112_new_control(data);
+		break;
+	case WF_EVENT_TICK:
+		if (have_all_controls && have_all_sensors)
+			pm112_tick();
+	}
+	return 0;
+}
+
+static struct notifier_block pm112_events = {
+	.notifier_call = pm112_wf_notify,
+};
+
+static int wf_pm112_probe(struct platform_device *dev)
+{
+	wf_register_client(&pm112_events);
+	return 0;
+}
+
+static int wf_pm112_remove(struct platform_device *dev)
+{
+	wf_unregister_client(&pm112_events);
+	/* should release all sensors and controls */
+	return 0;
+}
+
+static struct platform_driver wf_pm112_driver = {
+	.probe = wf_pm112_probe,
+	.remove = wf_pm112_remove,
+	.driver = {
+		.name = "windfarm",
+	},
+};
+
+static int __init wf_pm112_init(void)
+{
+	struct device_node *cpu;
+
+	if (!of_machine_is_compatible("PowerMac11,2"))
+		return -ENODEV;
+
+	/* Count the number of CPU cores */
+	nr_cores = 0;
+	for_each_node_by_type(cpu, "cpu")
+		++nr_cores;
+
+	printk(KERN_INFO "windfarm: initializing for dual-core desktop G5\n");
+
+#ifdef MODULE
+	request_module("windfarm_smu_controls");
+	request_module("windfarm_smu_sensors");
+	request_module("windfarm_smu_sat");
+	request_module("windfarm_lm75_sensor");
+	request_module("windfarm_max6690_sensor");
+	request_module("windfarm_cpufreq_clamp");
+
+#endif /* MODULE */
+
+	platform_driver_register(&wf_pm112_driver);
+	return 0;
+}
+
+static void __exit wf_pm112_exit(void)
+{
+	platform_driver_unregister(&wf_pm112_driver);
+}
+
+module_init(wf_pm112_init);
+module_exit(wf_pm112_exit);
+
+MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
+MODULE_DESCRIPTION("Thermal control for PowerMac11,2");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:windfarm");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm121.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm121.c
new file mode 100644
index 0000000..b350fb8
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm121.c
@@ -0,0 +1,1053 @@
+/*
+ * Windfarm PowerMac thermal control. iMac G5 iSight
+ *
+ * (c) Copyright 2007 Étienne Bersac <bersace@gmail.com>
+ *
+ * Bits & pieces from windfarm_pm81.c by (c) Copyright 2005 Benjamin
+ * Herrenschmidt, IBM Corp. <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ *
+ *
+ * PowerMac12,1
+ * ============
+ *
+ *
+ * The algorithm used is the PID control algorithm, used the same way
+ * the published Darwin code does, using the same values that are
+ * present in the Darwin 8.10 snapshot property lists (note however
+ * that none of the code has been re-used, it's a complete
+ * re-implementation
+ *
+ * There is two models using PowerMac12,1. Model 2 is iMac G5 iSight
+ * 17" while Model 3 is iMac G5 20". They do have both the same
+ * controls with a tiny difference. The control-ids of hard-drive-fan
+ * and cpu-fan is swapped.
+ *
+ *
+ * Target Correction :
+ *
+ * controls have a target correction calculated as :
+ *
+ * new_min = ((((average_power * slope) >> 16) + offset) >> 16) + min_value
+ * new_value = max(new_value, max(new_min, 0))
+ *
+ * OD Fan control correction.
+ *
+ * # model_id: 2
+ *   offset		: -19563152
+ *   slope		:  1956315
+ *
+ * # model_id: 3
+ *   offset		: -15650652
+ *   slope		:  1565065
+ *
+ * HD Fan control correction.
+ *
+ * # model_id: 2
+ *   offset		: -15650652
+ *   slope		:  1565065
+ *
+ * # model_id: 3
+ *   offset		: -19563152
+ *   slope		:  1956315
+ *
+ * CPU Fan control correction.
+ *
+ * # model_id: 2
+ *   offset		: -25431900
+ *   slope		:  2543190
+ *
+ * # model_id: 3
+ *   offset		: -15650652
+ *   slope		:  1565065
+ *
+ *
+ * Target rubber-banding :
+ *
+ * Some controls have a target correction which depends on another
+ * control value. The correction is computed in the following way :
+ *
+ * new_min = ref_value * slope + offset
+ *
+ * ref_value is the value of the reference control. If new_min is
+ * greater than 0, then we correct the target value using :
+ *
+ * new_target = max (new_target, new_min >> 16)
+ *
+ *
+ * # model_id : 2
+ *   control	: cpu-fan
+ *   ref	: optical-drive-fan
+ *   offset	: -15650652
+ *   slope	: 1565065
+ *
+ * # model_id : 3
+ *   control	: optical-drive-fan
+ *   ref	: hard-drive-fan
+ *   offset	: -32768000
+ *   slope	: 65536
+ *
+ *
+ * In order to have the moste efficient correction with those
+ * dependencies, we must trigger HD loop before OD loop before CPU
+ * loop.
+ *
+ *
+ * The various control loops found in Darwin config file are:
+ *
+ * HD Fan control loop.
+ *
+ * # model_id: 2
+ *   control        : hard-drive-fan
+ *   sensor         : hard-drive-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x002D70A3
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x370000
+ *                    Interval = 5s
+ *
+ * # model_id: 3
+ *   control        : hard-drive-fan
+ *   sensor         : hard-drive-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x002170A3
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x370000
+ *                    Interval = 5s
+ *
+ * OD Fan control loop.
+ *
+ * # model_id: 2
+ *   control        : optical-drive-fan
+ *   sensor         : optical-drive-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x001FAE14
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x320000
+ *                    Interval = 5s
+ *
+ * # model_id: 3
+ *   control        : optical-drive-fan
+ *   sensor         : optical-drive-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x001FAE14
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x320000
+ *                    Interval = 5s
+ *
+ * GPU Fan control loop.
+ *
+ * # model_id: 2
+ *   control        : hard-drive-fan
+ *   sensor         : gpu-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x002A6666
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x5A0000
+ *                    Interval = 5s
+ *
+ * # model_id: 3
+ *   control        : cpu-fan
+ *   sensor         : gpu-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x0010CCCC
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x500000
+ *                    Interval = 5s
+ *
+ * KODIAK (aka northbridge) Fan control loop.
+ *
+ * # model_id: 2
+ *   control        : optical-drive-fan
+ *   sensor         : north-bridge-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x003BD70A
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x550000
+ *                    Interval = 5s
+ *
+ * # model_id: 3
+ *   control        : hard-drive-fan
+ *   sensor         : north-bridge-temp
+ *   PID params     : G_d = 0x00000000
+ *                    G_p = 0x0030F5C2
+ *                    G_r = 0x00019999
+ *                    History = 2 entries
+ *                    Input target = 0x550000
+ *                    Interval = 5s
+ *
+ * CPU Fan control loop.
+ *
+ *   control        : cpu-fan
+ *   sensors        : cpu-temp, cpu-power
+ *   PID params     : from SDB partition
+ *
+ *
+ * CPU Slew control loop.
+ *
+ *   control        : cpufreq-clamp
+ *   sensor         : cpu-temp
+ *
+ */
+
+#undef	DEBUG
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kmod.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.3"
+
+static int pm121_mach_model;	/* machine model id */
+
+/* Controls & sensors */
+static struct wf_sensor	*sensor_cpu_power;
+static struct wf_sensor	*sensor_cpu_temp;
+static struct wf_sensor	*sensor_cpu_voltage;
+static struct wf_sensor	*sensor_cpu_current;
+static struct wf_sensor	*sensor_gpu_temp;
+static struct wf_sensor	*sensor_north_bridge_temp;
+static struct wf_sensor	*sensor_hard_drive_temp;
+static struct wf_sensor	*sensor_optical_drive_temp;
+static struct wf_sensor	*sensor_incoming_air_temp; /* unused ! */
+
+enum {
+	FAN_CPU,
+	FAN_HD,
+	FAN_OD,
+	CPUFREQ,
+	N_CONTROLS
+};
+static struct wf_control *controls[N_CONTROLS] = {};
+
+/* Set to kick the control loop into life */
+static int pm121_all_controls_ok, pm121_all_sensors_ok, pm121_started;
+
+enum {
+	FAILURE_FAN		= 1 << 0,
+	FAILURE_SENSOR		= 1 << 1,
+	FAILURE_OVERTEMP	= 1 << 2
+};
+
+/* All sys loops. Note the HD before the OD loop in order to have it
+   run before. */
+enum {
+	LOOP_GPU,		/* control = hd or cpu, but luckily,
+				   it doesn't matter */
+	LOOP_HD,		/* control = hd */
+	LOOP_KODIAK,		/* control = hd or od */
+	LOOP_OD,		/* control = od */
+	N_LOOPS
+};
+
+static const char *loop_names[N_LOOPS] = {
+	"GPU",
+	"HD",
+	"KODIAK",
+	"OD",
+};
+
+#define	PM121_NUM_CONFIGS	2
+
+static unsigned int pm121_failure_state;
+static int pm121_readjust, pm121_skipping;
+static bool pm121_overtemp;
+static s32 average_power;
+
+struct pm121_correction {
+	int	offset;
+	int	slope;
+};
+
+static struct pm121_correction corrections[N_CONTROLS][PM121_NUM_CONFIGS] = {
+	/* FAN_OD */
+	{
+		/* MODEL 2 */
+		{ .offset	= -19563152,
+		  .slope	=  1956315
+		},
+		/* MODEL 3 */
+		{ .offset	= -15650652,
+		  .slope	=  1565065
+		},
+	},
+	/* FAN_HD */
+	{
+		/* MODEL 2 */
+		{ .offset	= -15650652,
+		  .slope	=  1565065
+		},
+		/* MODEL 3 */
+		{ .offset	= -19563152,
+		  .slope	=  1956315
+		},
+	},
+	/* FAN_CPU */
+	{
+		/* MODEL 2 */
+		{ .offset	= -25431900,
+		  .slope	=  2543190
+		},
+		/* MODEL 3 */
+		{ .offset	= -15650652,
+		  .slope	=  1565065
+		},
+	},
+	/* CPUFREQ has no correction (and is not implemented at all) */
+};
+
+struct pm121_connection {
+	unsigned int	control_id;
+	unsigned int	ref_id;
+	struct pm121_correction	correction;
+};
+
+static struct pm121_connection pm121_connections[] = {
+	/* MODEL 2 */
+	{ .control_id	= FAN_CPU,
+	  .ref_id	= FAN_OD,
+	  { .offset	= -32768000,
+	    .slope	=  65536
+	  }
+	},
+	/* MODEL 3 */
+	{ .control_id	= FAN_OD,
+	  .ref_id	= FAN_HD,
+	  { .offset	= -32768000,
+	    .slope	=  65536
+	  }
+	},
+};
+
+/* pointer to the current model connection */
+static struct pm121_connection *pm121_connection;
+
+/*
+ * ****** System Fans Control Loop ******
+ *
+ */
+
+/* Since each loop handles only one control and we want to avoid
+ * writing virtual control, we store the control correction with the
+ * loop params. Some data are not set, there are common to all loop
+ * and thus, hardcoded.
+ */
+struct pm121_sys_param {
+	/* purely informative since we use mach_model-2 as index */
+	int			model_id;
+	struct wf_sensor	**sensor; /* use sensor_id instead ? */
+	s32			gp, itarget;
+	unsigned int		control_id;
+};
+
+static struct pm121_sys_param
+pm121_sys_all_params[N_LOOPS][PM121_NUM_CONFIGS] = {
+	/* GPU Fan control loop */
+	{
+		{ .model_id	= 2,
+		  .sensor	= &sensor_gpu_temp,
+		  .gp		= 0x002A6666,
+		  .itarget	= 0x5A0000,
+		  .control_id	= FAN_HD,
+		},
+		{ .model_id	= 3,
+		  .sensor	= &sensor_gpu_temp,
+		  .gp		= 0x0010CCCC,
+		  .itarget	= 0x500000,
+		  .control_id	= FAN_CPU,
+		},
+	},
+	/* HD Fan control loop */
+	{
+		{ .model_id	= 2,
+		  .sensor	= &sensor_hard_drive_temp,
+		  .gp		= 0x002D70A3,
+		  .itarget	= 0x370000,
+		  .control_id	= FAN_HD,
+		},
+		{ .model_id	= 3,
+		  .sensor	= &sensor_hard_drive_temp,
+		  .gp		= 0x002170A3,
+		  .itarget	= 0x370000,
+		  .control_id	= FAN_HD,
+		},
+	},
+	/* KODIAK Fan control loop */
+	{
+		{ .model_id	= 2,
+		  .sensor	= &sensor_north_bridge_temp,
+		  .gp		= 0x003BD70A,
+		  .itarget	= 0x550000,
+		  .control_id	= FAN_OD,
+		},
+		{ .model_id	= 3,
+		  .sensor	= &sensor_north_bridge_temp,
+		  .gp		= 0x0030F5C2,
+		  .itarget	= 0x550000,
+		  .control_id	= FAN_HD,
+		},
+	},
+	/* OD Fan control loop */
+	{
+		{ .model_id	= 2,
+		  .sensor	= &sensor_optical_drive_temp,
+		  .gp		= 0x001FAE14,
+		  .itarget	= 0x320000,
+		  .control_id	= FAN_OD,
+		},
+		{ .model_id	= 3,
+		  .sensor	= &sensor_optical_drive_temp,
+		  .gp		= 0x001FAE14,
+		  .itarget	= 0x320000,
+		  .control_id	= FAN_OD,
+		},
+	},
+};
+
+/* the hardcoded values */
+#define	PM121_SYS_GD		0x00000000
+#define	PM121_SYS_GR		0x00019999
+#define	PM121_SYS_HISTORY_SIZE	2
+#define	PM121_SYS_INTERVAL	5
+
+/* State data used by the system fans control loop
+ */
+struct pm121_sys_state {
+	int			ticks;
+	s32			setpoint;
+	struct wf_pid_state	pid;
+};
+
+struct pm121_sys_state *pm121_sys_state[N_LOOPS] = {};
+
+/*
+ * ****** CPU Fans Control Loop ******
+ *
+ */
+
+#define PM121_CPU_INTERVAL	1
+
+/* State data used by the cpu fans control loop
+ */
+struct pm121_cpu_state {
+	int			ticks;
+	s32			setpoint;
+	struct wf_cpu_pid_state	pid;
+};
+
+static struct pm121_cpu_state *pm121_cpu_state;
+
+
+
+/*
+ * ***** Implementation *****
+ *
+ */
+
+/* correction the value using the output-low-bound correction algo */
+static s32 pm121_correct(s32 new_setpoint,
+			 unsigned int control_id,
+			 s32 min)
+{
+	s32 new_min;
+	struct pm121_correction *correction;
+	correction = &corrections[control_id][pm121_mach_model - 2];
+
+	new_min = (average_power * correction->slope) >> 16;
+	new_min += correction->offset;
+	new_min = (new_min >> 16) + min;
+
+	return max3(new_setpoint, new_min, 0);
+}
+
+static s32 pm121_connect(unsigned int control_id, s32 setpoint)
+{
+	s32 new_min, value, new_setpoint;
+
+	if (pm121_connection->control_id == control_id) {
+		controls[control_id]->ops->get_value(controls[control_id],
+						     &value);
+		new_min = value * pm121_connection->correction.slope;
+		new_min += pm121_connection->correction.offset;
+		if (new_min > 0) {
+			new_setpoint = max(setpoint, (new_min >> 16));
+			if (new_setpoint != setpoint) {
+				pr_debug("pm121: %s depending on %s, "
+					 "corrected from %d to %d RPM\n",
+					 controls[control_id]->name,
+					 controls[pm121_connection->ref_id]->name,
+					 (int) setpoint, (int) new_setpoint);
+			}
+		} else
+			new_setpoint = setpoint;
+	}
+	/* no connection */
+	else
+		new_setpoint = setpoint;
+
+	return new_setpoint;
+}
+
+/* FAN LOOPS */
+static void pm121_create_sys_fans(int loop_id)
+{
+	struct pm121_sys_param *param = NULL;
+	struct wf_pid_param pid_param;
+	struct wf_control *control = NULL;
+	int i;
+
+	/* First, locate the params for this model */
+	for (i = 0; i < PM121_NUM_CONFIGS; i++) {
+		if (pm121_sys_all_params[loop_id][i].model_id == pm121_mach_model) {
+			param = &(pm121_sys_all_params[loop_id][i]);
+			break;
+		}
+	}
+
+	/* No params found, put fans to max */
+	if (param == NULL) {
+		printk(KERN_WARNING "pm121: %s fan config not found "
+		       " for this machine model\n",
+		       loop_names[loop_id]);
+		goto fail;
+	}
+
+	control = controls[param->control_id];
+
+	/* Alloc & initialize state */
+	pm121_sys_state[loop_id] = kmalloc(sizeof(struct pm121_sys_state),
+					   GFP_KERNEL);
+	if (pm121_sys_state[loop_id] == NULL) {
+		printk(KERN_WARNING "pm121: Memory allocation error\n");
+		goto fail;
+	}
+	pm121_sys_state[loop_id]->ticks = 1;
+
+	/* Fill PID params */
+	pid_param.gd		= PM121_SYS_GD;
+	pid_param.gp		= param->gp;
+	pid_param.gr		= PM121_SYS_GR;
+	pid_param.interval	= PM121_SYS_INTERVAL;
+	pid_param.history_len	= PM121_SYS_HISTORY_SIZE;
+	pid_param.itarget	= param->itarget;
+	if(control)
+	{
+		pid_param.min		= control->ops->get_min(control);
+		pid_param.max		= control->ops->get_max(control);
+	} else {
+		/*
+		 * This is probably not the right!?
+		 * Perhaps goto fail  if control == NULL  above?
+		 */
+		pid_param.min		= 0;
+		pid_param.max		= 0;
+	}
+
+	wf_pid_init(&pm121_sys_state[loop_id]->pid, &pid_param);
+
+	pr_debug("pm121: %s Fan control loop initialized.\n"
+		 "       itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+		 loop_names[loop_id], FIX32TOPRINT(pid_param.itarget),
+		 pid_param.min, pid_param.max);
+	return;
+
+ fail:
+	/* note that this is not optimal since another loop may still
+	   control the same control */
+	printk(KERN_WARNING "pm121: failed to set up %s loop "
+	       "setting \"%s\" to max speed.\n",
+	       loop_names[loop_id], control ? control->name : "uninitialized value");
+
+	if (control)
+		wf_control_set_max(control);
+}
+
+static void pm121_sys_fans_tick(int loop_id)
+{
+	struct pm121_sys_param *param;
+	struct pm121_sys_state *st;
+	struct wf_sensor *sensor;
+	struct wf_control *control;
+	s32 temp, new_setpoint;
+	int rc;
+
+	param = &(pm121_sys_all_params[loop_id][pm121_mach_model-2]);
+	st = pm121_sys_state[loop_id];
+	sensor = *(param->sensor);
+	control = controls[param->control_id];
+
+	if (--st->ticks != 0) {
+		if (pm121_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = PM121_SYS_INTERVAL;
+
+	rc = sensor->ops->get_value(sensor, &temp);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: %s sensor error %d\n",
+		       sensor->name, rc);
+		pm121_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	pr_debug("pm121: %s Fan tick ! %s: %d.%03d\n",
+		 loop_names[loop_id], sensor->name,
+		 FIX32TOPRINT(temp));
+
+	new_setpoint = wf_pid_run(&st->pid, temp);
+
+	/* correction */
+	new_setpoint = pm121_correct(new_setpoint,
+				     param->control_id,
+				     st->pid.param.min);
+	/* linked corretion */
+	new_setpoint = pm121_connect(param->control_id, new_setpoint);
+
+	if (new_setpoint == st->setpoint)
+		return;
+	st->setpoint = new_setpoint;
+	pr_debug("pm121: %s corrected setpoint: %d RPM\n",
+		 control->name, (int)new_setpoint);
+ readjust:
+	if (control && pm121_failure_state == 0) {
+		rc = control->ops->set_value(control, st->setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: %s fan error %d\n",
+			       control->name, rc);
+			pm121_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+
+/* CPU LOOP */
+static void pm121_create_cpu_fans(void)
+{
+	struct wf_cpu_pid_param pid_param;
+	const struct smu_sdbp_header *hdr;
+	struct smu_sdbp_cpupiddata *piddata;
+	struct smu_sdbp_fvt *fvt;
+	struct wf_control *fan_cpu;
+	s32 tmax, tdelta, maxpow, powadj;
+
+	fan_cpu = controls[FAN_CPU];
+
+	/* First, locate the PID params in SMU SBD */
+	hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
+	if (hdr == 0) {
+		printk(KERN_WARNING "pm121: CPU PID fan config not found.\n");
+		goto fail;
+	}
+	piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+	/* Get the FVT params for operating point 0 (the only supported one
+	 * for now) in order to get tmax
+	 */
+	hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
+	if (hdr) {
+		fvt = (struct smu_sdbp_fvt *)&hdr[1];
+		tmax = ((s32)fvt->maxtemp) << 16;
+	} else
+		tmax = 0x5e0000; /* 94 degree default */
+
+	/* Alloc & initialize state */
+	pm121_cpu_state = kmalloc(sizeof(struct pm121_cpu_state),
+				  GFP_KERNEL);
+	if (pm121_cpu_state == NULL)
+		goto fail;
+	pm121_cpu_state->ticks = 1;
+
+	/* Fill PID params */
+	pid_param.interval = PM121_CPU_INTERVAL;
+	pid_param.history_len = piddata->history_len;
+	if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
+		printk(KERN_WARNING "pm121: History size overflow on "
+		       "CPU control loop (%d)\n", piddata->history_len);
+		pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
+	}
+	pid_param.gd = piddata->gd;
+	pid_param.gp = piddata->gp;
+	pid_param.gr = piddata->gr / pid_param.history_len;
+
+	tdelta = ((s32)piddata->target_temp_delta) << 16;
+	maxpow = ((s32)piddata->max_power) << 16;
+	powadj = ((s32)piddata->power_adj) << 16;
+
+	pid_param.tmax = tmax;
+	pid_param.ttarget = tmax - tdelta;
+	pid_param.pmaxadj = maxpow - powadj;
+
+	pid_param.min = fan_cpu->ops->get_min(fan_cpu);
+	pid_param.max = fan_cpu->ops->get_max(fan_cpu);
+
+	wf_cpu_pid_init(&pm121_cpu_state->pid, &pid_param);
+
+	pr_debug("pm121: CPU Fan control initialized.\n");
+	pr_debug("       ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM,\n",
+		 FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
+		 pid_param.min, pid_param.max);
+
+	return;
+
+ fail:
+	printk(KERN_WARNING "pm121: CPU fan config not found, max fan speed\n");
+
+	if (controls[CPUFREQ])
+		wf_control_set_max(controls[CPUFREQ]);
+	if (fan_cpu)
+		wf_control_set_max(fan_cpu);
+}
+
+
+static void pm121_cpu_fans_tick(struct pm121_cpu_state *st)
+{
+	s32 new_setpoint, temp, power;
+	struct wf_control *fan_cpu = NULL;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (pm121_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = PM121_CPU_INTERVAL;
+
+	fan_cpu = controls[FAN_CPU];
+
+	rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
+	if (rc) {
+		printk(KERN_WARNING "pm121: CPU temp sensor error %d\n",
+		       rc);
+		pm121_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
+	if (rc) {
+		printk(KERN_WARNING "pm121: CPU power sensor error %d\n",
+		       rc);
+		pm121_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	pr_debug("pm121: CPU Fans tick ! CPU temp: %d.%03d°C, power: %d.%03d\n",
+		 FIX32TOPRINT(temp), FIX32TOPRINT(power));
+
+	if (temp > st->pid.param.tmax)
+		pm121_failure_state |= FAILURE_OVERTEMP;
+
+	new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
+
+	/* correction */
+	new_setpoint = pm121_correct(new_setpoint,
+				     FAN_CPU,
+				     st->pid.param.min);
+
+	/* connected correction */
+	new_setpoint = pm121_connect(FAN_CPU, new_setpoint);
+
+	if (st->setpoint == new_setpoint)
+		return;
+	st->setpoint = new_setpoint;
+	pr_debug("pm121: CPU corrected setpoint: %d RPM\n", (int)new_setpoint);
+
+ readjust:
+	if (fan_cpu && pm121_failure_state == 0) {
+		rc = fan_cpu->ops->set_value(fan_cpu, st->setpoint);
+		if (rc) {
+			printk(KERN_WARNING "pm121: %s fan error %d\n",
+			       fan_cpu->name, rc);
+			pm121_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+/*
+ * ****** Common ******
+ *
+ */
+
+static void pm121_tick(void)
+{
+	unsigned int last_failure = pm121_failure_state;
+	unsigned int new_failure;
+	s32 total_power;
+	int i;
+
+	if (!pm121_started) {
+		pr_debug("pm121: creating control loops !\n");
+		for (i = 0; i < N_LOOPS; i++)
+			pm121_create_sys_fans(i);
+
+		pm121_create_cpu_fans();
+		pm121_started = 1;
+	}
+
+	/* skipping ticks */
+	if (pm121_skipping && --pm121_skipping)
+		return;
+
+	/* compute average power */
+	total_power = 0;
+	for (i = 0; i < pm121_cpu_state->pid.param.history_len; i++)
+		total_power += pm121_cpu_state->pid.powers[i];
+
+	average_power = total_power / pm121_cpu_state->pid.param.history_len;
+
+
+	pm121_failure_state = 0;
+	for (i = 0 ; i < N_LOOPS; i++) {
+		if (pm121_sys_state[i])
+			pm121_sys_fans_tick(i);
+	}
+
+	if (pm121_cpu_state)
+		pm121_cpu_fans_tick(pm121_cpu_state);
+
+	pm121_readjust = 0;
+	new_failure = pm121_failure_state & ~last_failure;
+
+	/* If entering failure mode, clamp cpufreq and ramp all
+	 * fans to full speed.
+	 */
+	if (pm121_failure_state && !last_failure) {
+		for (i = 0; i < N_CONTROLS; i++) {
+			if (controls[i])
+				wf_control_set_max(controls[i]);
+		}
+	}
+
+	/* If leaving failure mode, unclamp cpufreq and readjust
+	 * all fans on next iteration
+	 */
+	if (!pm121_failure_state && last_failure) {
+		if (controls[CPUFREQ])
+			wf_control_set_min(controls[CPUFREQ]);
+		pm121_readjust = 1;
+	}
+
+	/* Overtemp condition detected, notify and start skipping a couple
+	 * ticks to let the temperature go down
+	 */
+	if (new_failure & FAILURE_OVERTEMP) {
+		wf_set_overtemp();
+		pm121_skipping = 2;
+		pm121_overtemp = true;
+	}
+
+	/* We only clear the overtemp condition if overtemp is cleared
+	 * _and_ no other failure is present. Since a sensor error will
+	 * clear the overtemp condition (can't measure temperature) at
+	 * the control loop levels, but we don't want to keep it clear
+	 * here in this case
+	 */
+	if (!pm121_failure_state && pm121_overtemp) {
+		wf_clear_overtemp();
+		pm121_overtemp = false;
+	}
+}
+
+
+static struct wf_control* pm121_register_control(struct wf_control *ct,
+						 const char *match,
+						 unsigned int id)
+{
+	if (controls[id] == NULL && !strcmp(ct->name, match)) {
+		if (wf_get_control(ct) == 0)
+			controls[id] = ct;
+	}
+	return controls[id];
+}
+
+static void pm121_new_control(struct wf_control *ct)
+{
+	int all = 1;
+
+	if (pm121_all_controls_ok)
+		return;
+
+	all = pm121_register_control(ct, "optical-drive-fan", FAN_OD) && all;
+	all = pm121_register_control(ct, "hard-drive-fan", FAN_HD) && all;
+	all = pm121_register_control(ct, "cpu-fan", FAN_CPU) && all;
+	all = pm121_register_control(ct, "cpufreq-clamp", CPUFREQ) && all;
+
+	if (all)
+		pm121_all_controls_ok = 1;
+}
+
+
+
+
+static struct wf_sensor* pm121_register_sensor(struct wf_sensor *sensor,
+					       const char *match,
+					       struct wf_sensor **var)
+{
+	if (*var == NULL && !strcmp(sensor->name, match)) {
+		if (wf_get_sensor(sensor) == 0)
+			*var = sensor;
+	}
+	return *var;
+}
+
+static void pm121_new_sensor(struct wf_sensor *sr)
+{
+	int all = 1;
+
+	if (pm121_all_sensors_ok)
+		return;
+
+	all = pm121_register_sensor(sr, "cpu-temp",
+				    &sensor_cpu_temp) && all;
+	all = pm121_register_sensor(sr, "cpu-current",
+				    &sensor_cpu_current) && all;
+	all = pm121_register_sensor(sr, "cpu-voltage",
+				    &sensor_cpu_voltage) && all;
+	all = pm121_register_sensor(sr, "cpu-power",
+				    &sensor_cpu_power) && all;
+	all = pm121_register_sensor(sr, "hard-drive-temp",
+				    &sensor_hard_drive_temp) && all;
+	all = pm121_register_sensor(sr, "optical-drive-temp",
+				    &sensor_optical_drive_temp) && all;
+	all = pm121_register_sensor(sr, "incoming-air-temp",
+				    &sensor_incoming_air_temp) && all;
+	all = pm121_register_sensor(sr, "north-bridge-temp",
+				    &sensor_north_bridge_temp) && all;
+	all = pm121_register_sensor(sr, "gpu-temp",
+				    &sensor_gpu_temp) && all;
+
+	if (all)
+		pm121_all_sensors_ok = 1;
+}
+
+
+
+static int pm121_notify(struct notifier_block *self,
+			unsigned long event, void *data)
+{
+	switch (event) {
+	case WF_EVENT_NEW_CONTROL:
+		pr_debug("pm121: new control %s detected\n",
+			 ((struct wf_control *)data)->name);
+		pm121_new_control(data);
+		break;
+	case WF_EVENT_NEW_SENSOR:
+		pr_debug("pm121: new sensor %s detected\n",
+			 ((struct wf_sensor *)data)->name);
+		pm121_new_sensor(data);
+		break;
+	case WF_EVENT_TICK:
+		if (pm121_all_controls_ok && pm121_all_sensors_ok)
+			pm121_tick();
+		break;
+	}
+
+	return 0;
+}
+
+static struct notifier_block pm121_events = {
+	.notifier_call	= pm121_notify,
+};
+
+static int pm121_init_pm(void)
+{
+	const struct smu_sdbp_header *hdr;
+
+	hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
+	if (hdr != 0) {
+		struct smu_sdbp_sensortree *st =
+			(struct smu_sdbp_sensortree *)&hdr[1];
+		pm121_mach_model = st->model_id;
+	}
+
+	pm121_connection = &pm121_connections[pm121_mach_model - 2];
+
+	printk(KERN_INFO "pm121: Initializing for iMac G5 iSight model ID %d\n",
+	       pm121_mach_model);
+
+	return 0;
+}
+
+
+static int pm121_probe(struct platform_device *ddev)
+{
+	wf_register_client(&pm121_events);
+
+	return 0;
+}
+
+static int pm121_remove(struct platform_device *ddev)
+{
+	wf_unregister_client(&pm121_events);
+	return 0;
+}
+
+static struct platform_driver pm121_driver = {
+	.probe = pm121_probe,
+	.remove = pm121_remove,
+	.driver = {
+		.name = "windfarm",
+		.bus = &platform_bus_type,
+	},
+};
+
+
+static int __init pm121_init(void)
+{
+	int rc = -ENODEV;
+
+	if (of_machine_is_compatible("PowerMac12,1"))
+		rc = pm121_init_pm();
+
+	if (rc == 0) {
+		request_module("windfarm_smu_controls");
+		request_module("windfarm_smu_sensors");
+		request_module("windfarm_smu_sat");
+		request_module("windfarm_lm75_sensor");
+		request_module("windfarm_max6690_sensor");
+		request_module("windfarm_cpufreq_clamp");
+		platform_driver_register(&pm121_driver);
+	}
+
+	return rc;
+}
+
+static void __exit pm121_exit(void)
+{
+
+	platform_driver_unregister(&pm121_driver);
+}
+
+
+module_init(pm121_init);
+module_exit(pm121_exit);
+
+MODULE_AUTHOR("Étienne Bersac <bersace@gmail.com>");
+MODULE_DESCRIPTION("Thermal control logic for iMac G5 (iSight)");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm72.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm72.c
new file mode 100644
index 0000000..e88cfb3
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm72.c
@@ -0,0 +1,846 @@
+/*
+ * Windfarm PowerMac thermal control.
+ * Control loops for PowerMac7,2 and 7,3
+ *
+ * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Use and redistribute under the terms of the GNU GPL v2.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <asm/prom.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+#include "windfarm_mpu.h"
+
+#define VERSION "1.0"
+
+#undef DEBUG
+#undef LOTSA_DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+#ifdef LOTSA_DEBUG
+#define DBG_LOTS(args...)	printk(args)
+#else
+#define DBG_LOTS(args...)	do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 60 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+/* We currently only handle 2 chips */
+#define NR_CHIPS	2
+#define NR_CPU_FANS	3 * NR_CHIPS
+
+/* Controls and sensors */
+static struct wf_sensor *sens_cpu_temp[NR_CHIPS];
+static struct wf_sensor *sens_cpu_volts[NR_CHIPS];
+static struct wf_sensor *sens_cpu_amps[NR_CHIPS];
+static struct wf_sensor *backside_temp;
+static struct wf_sensor *drives_temp;
+
+static struct wf_control *cpu_front_fans[NR_CHIPS];
+static struct wf_control *cpu_rear_fans[NR_CHIPS];
+static struct wf_control *cpu_pumps[NR_CHIPS];
+static struct wf_control *backside_fan;
+static struct wf_control *drives_fan;
+static struct wf_control *slots_fan;
+static struct wf_control *cpufreq_clamp;
+
+/* We keep a temperature history for average calculation of 180s */
+#define CPU_TEMP_HIST_SIZE	180
+
+/* Fixed speed for slot fan */
+#define	SLOTS_FAN_DEFAULT_PWM	40
+
+/* Scale value for CPU intake fans */
+#define CPU_INTAKE_SCALE	0x0000f852
+
+/* PID loop state */
+static const struct mpu_data *cpu_mpu_data[NR_CHIPS];
+static struct wf_cpu_pid_state cpu_pid[NR_CHIPS];
+static bool cpu_pid_combined;
+static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
+static int cpu_thist_pt;
+static s64 cpu_thist_total;
+static s32 cpu_all_tmax = 100 << 16;
+static struct wf_pid_state backside_pid;
+static int backside_tick;
+static struct wf_pid_state drives_pid;
+static int drives_tick;
+
+static int nr_chips;
+static bool have_all_controls;
+static bool have_all_sensors;
+static bool started;
+
+static int failure_state;
+#define FAILURE_SENSOR		1
+#define FAILURE_FAN		2
+#define FAILURE_PERM		4
+#define FAILURE_LOW_OVERTEMP	8
+#define FAILURE_HIGH_OVERTEMP	16
+
+/* Overtemp values */
+#define LOW_OVER_AVERAGE	0
+#define LOW_OVER_IMMEDIATE	(10 << 16)
+#define LOW_OVER_CLEAR		((-10) << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+#define HIGH_OVER_AVERAGE	(10 << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+
+
+static void cpu_max_all_fans(void)
+{
+	int i;
+
+	/* We max all CPU fans in case of a sensor error. We also do the
+	 * cpufreq clamping now, even if it's supposedly done later by the
+	 * generic code anyway, we do it earlier here to react faster
+	 */
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	for (i = 0; i < nr_chips; i++) {
+		if (cpu_front_fans[i])
+			wf_control_set_max(cpu_front_fans[i]);
+		if (cpu_rear_fans[i])
+			wf_control_set_max(cpu_rear_fans[i]);
+		if (cpu_pumps[i])
+			wf_control_set_max(cpu_pumps[i]);
+	}
+}
+
+static int cpu_check_overtemp(s32 temp)
+{
+	int new_state = 0;
+	s32 t_avg, t_old;
+	static bool first = true;
+
+	/* First check for immediate overtemps */
+	if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
+			       " temperature !\n");
+	}
+	if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " immediate CPU temperature !\n");
+	}
+
+	/*
+	 * The first time around, initialize the array with the first
+	 * temperature reading
+	 */
+	if (first) {
+		int i;
+
+		cpu_thist_total = 0;
+		for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) {
+			cpu_thist[i] = temp;
+			cpu_thist_total += temp;
+		}
+		first = false;
+	}
+
+	/*
+	 * We calculate a history of max temperatures and use that for the
+	 * overtemp management
+	 */
+	t_old = cpu_thist[cpu_thist_pt];
+	cpu_thist[cpu_thist_pt] = temp;
+	cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
+	cpu_thist_total -= t_old;
+	cpu_thist_total += temp;
+	t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;
+
+	DBG_LOTS("  t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
+		 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));
+
+	/* Now check for average overtemps */
+	if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to average CPU"
+			       " temperature !\n");
+	}
+	if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " average CPU temperature !\n");
+	}
+
+	/* Now handle overtemp conditions. We don't currently use the windfarm
+	 * overtemp handling core as it's not fully suited to the needs of those
+	 * new machine. This will be fixed later.
+	 */
+	if (new_state) {
+		/* High overtemp -> immediate shutdown */
+		if (new_state & FAILURE_HIGH_OVERTEMP)
+			machine_power_off();
+		if ((failure_state & new_state) != new_state)
+			cpu_max_all_fans();
+		failure_state |= new_state;
+	} else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
+		   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
+		printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
+		failure_state &= ~FAILURE_LOW_OVERTEMP;
+	}
+
+	return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
+}
+
+static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power)
+{
+	s32 dtemp, volts, amps;
+	int rc;
+
+	/* Get diode temperature */
+	rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp);
+	if (rc) {
+		DBG("  CPU%d: temp reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: temp   = %d.%03d\n", cpu, FIX32TOPRINT((dtemp)));
+	*temp = dtemp;
+
+	/* Get voltage */
+	rc = wf_sensor_get(sens_cpu_volts[cpu], &volts);
+	if (rc) {
+		DBG("  CPU%d, volts reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: volts  = %d.%03d\n", cpu, FIX32TOPRINT((volts)));
+
+	/* Get current */
+	rc = wf_sensor_get(sens_cpu_amps[cpu], &amps);
+	if (rc) {
+		DBG("  CPU%d, current reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: amps   = %d.%03d\n", cpu, FIX32TOPRINT((amps)));
+
+	/* Calculate power */
+
+	/* Scale voltage and current raw sensor values according to fixed scales
+	 * obtained in Darwin and calculate power from I and V
+	 */
+	*power = (((u64)volts) * ((u64)amps)) >> 16;
+
+	DBG_LOTS("  CPU%d: power  = %d.%03d\n", cpu, FIX32TOPRINT((*power)));
+
+	return 0;
+
+}
+
+static void cpu_fans_tick_split(void)
+{
+	int err, cpu;
+	s32 intake, temp, power, t_max = 0;
+
+	DBG_LOTS("* cpu fans_tick_split()\n");
+
+	for (cpu = 0; cpu < nr_chips; ++cpu) {
+		struct wf_cpu_pid_state *sp = &cpu_pid[cpu];
+
+		/* Read current speed */
+		wf_control_get(cpu_rear_fans[cpu], &sp->target);
+
+		DBG_LOTS("  CPU%d: cur_target = %d RPM\n", cpu, sp->target);
+
+		err = read_one_cpu_vals(cpu, &temp, &power);
+		if (err) {
+			failure_state |= FAILURE_SENSOR;
+			cpu_max_all_fans();
+			return;
+		}
+
+		/* Keep track of highest temp */
+		t_max = max(t_max, temp);
+
+		/* Handle possible overtemps */
+		if (cpu_check_overtemp(t_max))
+			return;
+
+		/* Run PID */
+		wf_cpu_pid_run(sp, power, temp);
+
+		DBG_LOTS("  CPU%d: target = %d RPM\n", cpu, sp->target);
+
+		/* Apply result directly to exhaust fan */
+		err = wf_control_set(cpu_rear_fans[cpu], sp->target);
+		if (err) {
+			pr_warning("wf_pm72: Fan %s reports error %d\n",
+			       cpu_rear_fans[cpu]->name, err);
+			failure_state |= FAILURE_FAN;
+			break;
+		}
+
+		/* Scale result for intake fan */
+		intake = (sp->target * CPU_INTAKE_SCALE) >> 16;
+		DBG_LOTS("  CPU%d: intake = %d RPM\n", cpu, intake);
+		err = wf_control_set(cpu_front_fans[cpu], intake);
+		if (err) {
+			pr_warning("wf_pm72: Fan %s reports error %d\n",
+			       cpu_front_fans[cpu]->name, err);
+			failure_state |= FAILURE_FAN;
+			break;
+		}
+	}
+}
+
+static void cpu_fans_tick_combined(void)
+{
+	s32 temp0, power0, temp1, power1, t_max = 0;
+	s32 temp, power, intake, pump;
+	struct wf_control *pump0, *pump1;
+	struct wf_cpu_pid_state *sp = &cpu_pid[0];
+	int err, cpu;
+
+	DBG_LOTS("* cpu fans_tick_combined()\n");
+
+	/* Read current speed from cpu 0 */
+	wf_control_get(cpu_rear_fans[0], &sp->target);
+
+	DBG_LOTS("  CPUs: cur_target = %d RPM\n", sp->target);
+
+	/* Read values for both CPUs */
+	err = read_one_cpu_vals(0, &temp0, &power0);
+	if (err) {
+		failure_state |= FAILURE_SENSOR;
+		cpu_max_all_fans();
+		return;
+	}
+	err = read_one_cpu_vals(1, &temp1, &power1);
+	if (err) {
+		failure_state |= FAILURE_SENSOR;
+		cpu_max_all_fans();
+		return;
+	}
+
+	/* Keep track of highest temp */
+	t_max = max(t_max, max(temp0, temp1));
+
+	/* Handle possible overtemps */
+	if (cpu_check_overtemp(t_max))
+		return;
+
+	/* Use the max temp & power of both */
+	temp = max(temp0, temp1);
+	power = max(power0, power1);
+
+	/* Run PID */
+	wf_cpu_pid_run(sp, power, temp);
+
+	/* Scale result for intake fan */
+	intake = (sp->target * CPU_INTAKE_SCALE) >> 16;
+
+	/* Same deal with pump speed */
+	pump0 = cpu_pumps[0];
+	pump1 = cpu_pumps[1];
+	if (!pump0) {
+		pump0 = pump1;
+		pump1 = NULL;
+	}
+	pump = (sp->target * wf_control_get_max(pump0)) /
+		cpu_mpu_data[0]->rmaxn_exhaust_fan;
+
+	DBG_LOTS("  CPUs: target = %d RPM\n", sp->target);
+	DBG_LOTS("  CPUs: intake = %d RPM\n", intake);
+	DBG_LOTS("  CPUs: pump   = %d RPM\n", pump);
+
+	for (cpu = 0; cpu < nr_chips; cpu++) {
+		err = wf_control_set(cpu_rear_fans[cpu], sp->target);
+		if (err) {
+			pr_warning("wf_pm72: Fan %s reports error %d\n",
+				   cpu_rear_fans[cpu]->name, err);
+			failure_state |= FAILURE_FAN;
+		}
+		err = wf_control_set(cpu_front_fans[cpu], intake);
+		if (err) {
+			pr_warning("wf_pm72: Fan %s reports error %d\n",
+				   cpu_front_fans[cpu]->name, err);
+			failure_state |= FAILURE_FAN;
+		}
+		err = 0;
+		if (cpu_pumps[cpu])
+			err = wf_control_set(cpu_pumps[cpu], pump);
+		if (err) {
+			pr_warning("wf_pm72: Pump %s reports error %d\n",
+				   cpu_pumps[cpu]->name, err);
+			failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+/* Implementation... */
+static int cpu_setup_pid(int cpu)
+{
+	struct wf_cpu_pid_param pid;
+	const struct mpu_data *mpu = cpu_mpu_data[cpu];
+	s32 tmax, ttarget, ptarget;
+	int fmin, fmax, hsize;
+
+	/* Get PID params from the appropriate MPU EEPROM */
+	tmax = mpu->tmax << 16;
+	ttarget = mpu->ttarget << 16;
+	ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16;
+
+	DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n",
+	    cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax));
+
+	/* We keep a global tmax for overtemp calculations */
+	if (tmax < cpu_all_tmax)
+		cpu_all_tmax = tmax;
+
+	/* Set PID min/max by using the rear fan min/max */
+	fmin = wf_control_get_min(cpu_rear_fans[cpu]);
+	fmax = wf_control_get_max(cpu_rear_fans[cpu]);
+	DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax);
+
+	/* History size */
+	hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY);
+	DBG("wf_72: CPU%d history size = %d\n", cpu, hsize);
+
+	/* Initialize PID loop */
+	pid.interval	= 1;	/* seconds */
+	pid.history_len = hsize;
+	pid.gd		= mpu->pid_gd;
+	pid.gp		= mpu->pid_gp;
+	pid.gr		= mpu->pid_gr;
+	pid.tmax	= tmax;
+	pid.ttarget	= ttarget;
+	pid.pmaxadj	= ptarget;
+	pid.min		= fmin;
+	pid.max		= fmax;
+
+	wf_cpu_pid_init(&cpu_pid[cpu], &pid);
+	cpu_pid[cpu].target = 1000;
+
+	return 0;
+}
+
+/* Backside/U3 fan */
+static struct wf_pid_param backside_u3_param = {
+	.interval	= 5,
+	.history_len	= 2,
+	.gd		= 40 << 20,
+	.gp		= 5 << 20,
+	.gr		= 0,
+	.itarget	= 65 << 16,
+	.additive	= 1,
+	.min		= 20,
+	.max		= 100,
+};
+
+static struct wf_pid_param backside_u3h_param = {
+	.interval	= 5,
+	.history_len	= 2,
+	.gd		= 20 << 20,
+	.gp		= 5 << 20,
+	.gr		= 0,
+	.itarget	= 75 << 16,
+	.additive	= 1,
+	.min		= 20,
+	.max		= 100,
+};
+
+static void backside_fan_tick(void)
+{
+	s32 temp;
+	int speed;
+	int err;
+
+	if (!backside_fan || !backside_temp || !backside_tick)
+		return;
+	if (--backside_tick > 0)
+		return;
+	backside_tick = backside_pid.param.interval;
+
+	DBG_LOTS("* backside fans tick\n");
+
+	/* Update fan speed from actual fans */
+	err = wf_control_get(backside_fan, &speed);
+	if (!err)
+		backside_pid.target = speed;
+
+	err = wf_sensor_get(backside_temp, &temp);
+	if (err) {
+		printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n",
+		       err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(backside_fan);
+		return;
+	}
+	speed = wf_pid_run(&backside_pid, temp);
+
+	DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	err = wf_control_set(backside_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+static void backside_setup_pid(void)
+{
+	/* first time initialize things */
+	s32 fmin = wf_control_get_min(backside_fan);
+	s32 fmax = wf_control_get_max(backside_fan);
+	struct wf_pid_param param;
+	struct device_node *u3;
+	int u3h = 1; /* conservative by default */
+
+	u3 = of_find_node_by_path("/u3@0,f8000000");
+	if (u3 != NULL) {
+		const u32 *vers = of_get_property(u3, "device-rev", NULL);
+		if (vers)
+			if (((*vers) & 0x3f) < 0x34)
+				u3h = 0;
+		of_node_put(u3);
+	}
+
+	param = u3h ? backside_u3h_param : backside_u3_param;
+
+	param.min = max(param.min, fmin);
+	param.max = min(param.max, fmax);
+	wf_pid_init(&backside_pid, &param);
+	backside_tick = 1;
+
+	pr_info("wf_pm72: Backside control loop started.\n");
+}
+
+/* Drive bay fan */
+static const struct wf_pid_param drives_param = {
+	.interval	= 5,
+	.history_len	= 2,
+	.gd		= 30 << 20,
+	.gp		= 5 << 20,
+	.gr		= 0,
+	.itarget	= 40 << 16,
+	.additive	= 1,
+	.min		= 300,
+	.max		= 4000,
+};
+
+static void drives_fan_tick(void)
+{
+	s32 temp;
+	int speed;
+	int err;
+
+	if (!drives_fan || !drives_temp || !drives_tick)
+		return;
+	if (--drives_tick > 0)
+		return;
+	drives_tick = drives_pid.param.interval;
+
+	DBG_LOTS("* drives fans tick\n");
+
+	/* Update fan speed from actual fans */
+	err = wf_control_get(drives_fan, &speed);
+	if (!err)
+		drives_pid.target = speed;
+
+	err = wf_sensor_get(drives_temp, &temp);
+	if (err) {
+		pr_warning("wf_pm72: drive bay temp sensor error %d\n", err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(drives_fan);
+		return;
+	}
+	speed = wf_pid_run(&drives_pid, temp);
+
+	DBG_LOTS("drives PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	err = wf_control_set(drives_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+static void drives_setup_pid(void)
+{
+	/* first time initialize things */
+	s32 fmin = wf_control_get_min(drives_fan);
+	s32 fmax = wf_control_get_max(drives_fan);
+	struct wf_pid_param param = drives_param;
+
+	param.min = max(param.min, fmin);
+	param.max = min(param.max, fmax);
+	wf_pid_init(&drives_pid, &param);
+	drives_tick = 1;
+
+	pr_info("wf_pm72: Drive bay control loop started.\n");
+}
+
+static void set_fail_state(void)
+{
+	cpu_max_all_fans();
+
+	if (backside_fan)
+		wf_control_set_max(backside_fan);
+	if (slots_fan)
+		wf_control_set_max(slots_fan);
+	if (drives_fan)
+		wf_control_set_max(drives_fan);
+}
+
+static void pm72_tick(void)
+{
+	int i, last_failure;
+
+	if (!started) {
+		started = 1;
+		printk(KERN_INFO "windfarm: CPUs control loops started.\n");
+		for (i = 0; i < nr_chips; ++i) {
+			if (cpu_setup_pid(i) < 0) {
+				failure_state = FAILURE_PERM;
+				set_fail_state();
+				break;
+			}
+		}
+		DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));
+
+		backside_setup_pid();
+		drives_setup_pid();
+
+		/*
+		 * We don't have the right stuff to drive the PCI fan
+		 * so we fix it to a default value
+		 */
+		wf_control_set(slots_fan, SLOTS_FAN_DEFAULT_PWM);
+
+#ifdef HACKED_OVERTEMP
+		cpu_all_tmax = 60 << 16;
+#endif
+	}
+
+	/* Permanent failure, bail out */
+	if (failure_state & FAILURE_PERM)
+		return;
+
+	/*
+	 * Clear all failure bits except low overtemp which will be eventually
+	 * cleared by the control loop itself
+	 */
+	last_failure = failure_state;
+	failure_state &= FAILURE_LOW_OVERTEMP;
+	if (cpu_pid_combined)
+		cpu_fans_tick_combined();
+	else
+		cpu_fans_tick_split();
+	backside_fan_tick();
+	drives_fan_tick();
+
+	DBG_LOTS("  last_failure: 0x%x, failure_state: %x\n",
+		 last_failure, failure_state);
+
+	/* Check for failures. Any failure causes cpufreq clamping */
+	if (failure_state && last_failure == 0 && cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	if (failure_state == 0 && last_failure && cpufreq_clamp)
+		wf_control_set_min(cpufreq_clamp);
+
+	/* That's it for now, we might want to deal with other failures
+	 * differently in the future though
+	 */
+}
+
+static void pm72_new_control(struct wf_control *ct)
+{
+	bool all_controls;
+	bool had_pump = cpu_pumps[0] || cpu_pumps[1];
+
+	if (!strcmp(ct->name, "cpu-front-fan-0"))
+		cpu_front_fans[0] = ct;
+	else if (!strcmp(ct->name, "cpu-front-fan-1"))
+		cpu_front_fans[1] = ct;
+	else if (!strcmp(ct->name, "cpu-rear-fan-0"))
+		cpu_rear_fans[0] = ct;
+	else if (!strcmp(ct->name, "cpu-rear-fan-1"))
+		cpu_rear_fans[1] = ct;
+	else if (!strcmp(ct->name, "cpu-pump-0"))
+		cpu_pumps[0] = ct;
+	else if (!strcmp(ct->name, "cpu-pump-1"))
+		cpu_pumps[1] = ct;
+	else if (!strcmp(ct->name, "backside-fan"))
+		backside_fan = ct;
+	else if (!strcmp(ct->name, "slots-fan"))
+		slots_fan = ct;
+	else if (!strcmp(ct->name, "drive-bay-fan"))
+		drives_fan = ct;
+	else if (!strcmp(ct->name, "cpufreq-clamp"))
+		cpufreq_clamp = ct;
+
+	all_controls =
+		cpu_front_fans[0] &&
+		cpu_rear_fans[0] &&
+		backside_fan &&
+		slots_fan &&
+		drives_fan;
+	if (nr_chips > 1)
+		all_controls &=
+			cpu_front_fans[1] &&
+			cpu_rear_fans[1];
+	have_all_controls = all_controls;
+
+	if ((cpu_pumps[0] || cpu_pumps[1]) && !had_pump) {
+		pr_info("wf_pm72: Liquid cooling pump(s) detected,"
+			" using new algorithm !\n");
+		cpu_pid_combined = true;
+	}
+}
+
+
+static void pm72_new_sensor(struct wf_sensor *sr)
+{
+	bool all_sensors;
+
+	if (!strcmp(sr->name, "cpu-diode-temp-0"))
+		sens_cpu_temp[0] = sr;
+	else if (!strcmp(sr->name, "cpu-diode-temp-1"))
+		sens_cpu_temp[1] = sr;
+	else if (!strcmp(sr->name, "cpu-voltage-0"))
+		sens_cpu_volts[0] = sr;
+	else if (!strcmp(sr->name, "cpu-voltage-1"))
+		sens_cpu_volts[1] = sr;
+	else if (!strcmp(sr->name, "cpu-current-0"))
+		sens_cpu_amps[0] = sr;
+	else if (!strcmp(sr->name, "cpu-current-1"))
+		sens_cpu_amps[1] = sr;
+	else if (!strcmp(sr->name, "backside-temp"))
+		backside_temp = sr;
+	else if (!strcmp(sr->name, "hd-temp"))
+		drives_temp = sr;
+
+	all_sensors =
+		sens_cpu_temp[0] &&
+		sens_cpu_volts[0] &&
+		sens_cpu_amps[0] &&
+		backside_temp &&
+		drives_temp;
+	if (nr_chips > 1)
+		all_sensors &=
+			sens_cpu_temp[1] &&
+			sens_cpu_volts[1] &&
+			sens_cpu_amps[1];
+
+	have_all_sensors = all_sensors;
+}
+
+static int pm72_wf_notify(struct notifier_block *self,
+			  unsigned long event, void *data)
+{
+	switch (event) {
+	case WF_EVENT_NEW_SENSOR:
+		pm72_new_sensor(data);
+		break;
+	case WF_EVENT_NEW_CONTROL:
+		pm72_new_control(data);
+		break;
+	case WF_EVENT_TICK:
+		if (have_all_controls && have_all_sensors)
+			pm72_tick();
+	}
+	return 0;
+}
+
+static struct notifier_block pm72_events = {
+	.notifier_call = pm72_wf_notify,
+};
+
+static int wf_pm72_probe(struct platform_device *dev)
+{
+	wf_register_client(&pm72_events);
+	return 0;
+}
+
+static int wf_pm72_remove(struct platform_device *dev)
+{
+	wf_unregister_client(&pm72_events);
+
+	/* should release all sensors and controls */
+	return 0;
+}
+
+static struct platform_driver wf_pm72_driver = {
+	.probe	= wf_pm72_probe,
+	.remove	= wf_pm72_remove,
+	.driver	= {
+		.name = "windfarm",
+	},
+};
+
+static int __init wf_pm72_init(void)
+{
+	struct device_node *cpu;
+	int i;
+
+	if (!of_machine_is_compatible("PowerMac7,2") &&
+	    !of_machine_is_compatible("PowerMac7,3"))
+		return -ENODEV;
+
+	/* Count the number of CPU cores */
+	nr_chips = 0;
+	for_each_node_by_type(cpu, "cpu")
+		++nr_chips;
+	if (nr_chips > NR_CHIPS)
+		nr_chips = NR_CHIPS;
+
+	pr_info("windfarm: Initializing for desktop G5 with %d chips\n",
+		nr_chips);
+
+	/* Get MPU data for each CPU */
+	for (i = 0; i < nr_chips; i++) {
+		cpu_mpu_data[i] = wf_get_mpu(i);
+		if (!cpu_mpu_data[i]) {
+			pr_err("wf_pm72: Failed to find MPU data for CPU %d\n", i);
+			return -ENXIO;
+		}
+	}
+
+#ifdef MODULE
+	request_module("windfarm_fcu_controls");
+	request_module("windfarm_lm75_sensor");
+	request_module("windfarm_ad7417_sensor");
+	request_module("windfarm_max6690_sensor");
+	request_module("windfarm_cpufreq_clamp");
+#endif /* MODULE */
+
+	platform_driver_register(&wf_pm72_driver);
+	return 0;
+}
+
+static void __exit wf_pm72_exit(void)
+{
+	platform_driver_unregister(&wf_pm72_driver);
+}
+
+module_init(wf_pm72_init);
+module_exit(wf_pm72_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control for AGP PowerMac G5s");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:windfarm");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm81.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm81.c
new file mode 100644
index 0000000..93faf29
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm81.c
@@ -0,0 +1,812 @@
+/*
+ * Windfarm PowerMac thermal control. iMac G5
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * The algorithm used is the PID control algorithm, used the same
+ * way the published Darwin code does, using the same values that
+ * are present in the Darwin 8.2 snapshot property lists (note however
+ * that none of the code has been re-used, it's a complete re-implementation
+ *
+ * The various control loops found in Darwin config file are:
+ *
+ * PowerMac8,1 and PowerMac8,2
+ * ===========================
+ *
+ * System Fans control loop. Different based on models. In addition to the
+ * usual PID algorithm, the control loop gets 2 additional pairs of linear
+ * scaling factors (scale/offsets) expressed as 4.12 fixed point values
+ * signed offset, unsigned scale)
+ *
+ * The targets are modified such as:
+ *  - the linked control (second control) gets the target value as-is
+ *    (typically the drive fan)
+ *  - the main control (first control) gets the target value scaled with
+ *    the first pair of factors, and is then modified as below
+ *  - the value of the target of the CPU Fan control loop is retrieved,
+ *    scaled with the second pair of factors, and the max of that and
+ *    the scaled target is applied to the main control.
+ *
+ * # model_id: 2
+ *   controls       : system-fan, drive-bay-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x15400000
+ *                    G_p = 0x00200000
+ *                    G_r = 0x000002fd
+ *                    History = 2 entries
+ *                    Input target = 0x3a0000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0xff38 scale  = 0x0ccd
+ *                    offset = 0x0208 scale  = 0x07ae
+ *
+ * # model_id: 3
+ *   controls       : system-fan, drive-bay-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x08e00000
+ *                    G_p = 0x00566666
+ *                    G_r = 0x0000072b
+ *                    History = 2 entries
+ *                    Input target = 0x350000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0xff38 scale  = 0x0ccd
+ *                    offset = 0x0000 scale  = 0x0000
+ *
+ * # model_id: 5
+ *   controls       : system-fan
+ *   sensors        : hd-temp
+ *   PID params     : G_d = 0x15400000
+ *                    G_p = 0x00233333
+ *                    G_r = 0x000002fd
+ *                    History = 2 entries
+ *                    Input target = 0x3a0000
+ *                    Interval = 5s
+ *   linear-factors : offset = 0x0000 scale  = 0x1000
+ *                    offset = 0x0091 scale  = 0x0bae
+ *
+ * CPU Fan control loop. The loop is identical for all models. it
+ * has an additional pair of scaling factor. This is used to scale the
+ * systems fan control loop target result (the one before it gets scaled
+ * by the System Fans control loop itself). Then, the max value of the
+ * calculated target value and system fan value is sent to the fans
+ *
+ *   controls       : cpu-fan
+ *   sensors        : cpu-temp cpu-power
+ *   PID params     : From SMU sdb partition
+ *   linear-factors : offset = 0xfb50 scale  = 0x1000
+ *
+ * CPU Slew control loop. Not implemented. The cpufreq driver in linux is
+ * completely separate for now, though we could find a way to link it, either
+ * as a client reacting to overtemp notifications, or directling monitoring
+ * the CPU temperature
+ *
+ * WARNING ! The CPU control loop requires the CPU tmax for the current
+ * operating point. However, we currently are completely separated from
+ * the cpufreq driver and thus do not know what the current operating
+ * point is. Fortunately, we also do not have any hardware supporting anything
+ * but operating point 0 at the moment, thus we just peek that value directly
+ * from the SDB partition. If we ever end up with actually slewing the system
+ * clock and thus changing operating points, we'll have to find a way to
+ * communicate with the CPU freq driver;
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kmod.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.4"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 74 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+static int wf_smu_mach_model;	/* machine model id */
+
+/* Controls & sensors */
+static struct wf_sensor	*sensor_cpu_power;
+static struct wf_sensor	*sensor_cpu_temp;
+static struct wf_sensor	*sensor_hd_temp;
+static struct wf_control *fan_cpu_main;
+static struct wf_control *fan_hd;
+static struct wf_control *fan_system;
+static struct wf_control *cpufreq_clamp;
+
+/* Set to kick the control loop into life */
+static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
+
+/* Failure handling.. could be nicer */
+#define FAILURE_FAN		0x01
+#define FAILURE_SENSOR		0x02
+#define FAILURE_OVERTEMP	0x04
+
+static unsigned int wf_smu_failure_state;
+static int wf_smu_readjust, wf_smu_skipping;
+static bool wf_smu_overtemp;
+
+/*
+ * ****** System Fans Control Loop ******
+ *
+ */
+
+/* Parameters for the System Fans control loop. Parameters
+ * not in this table such as interval, history size, ...
+ * are common to all versions and thus hard coded for now.
+ */
+struct wf_smu_sys_fans_param {
+	int	model_id;
+	s32	itarget;
+	s32	gd, gp, gr;
+
+	s16	offset0;
+	u16	scale0;
+	s16	offset1;
+	u16	scale1;
+};
+
+#define WF_SMU_SYS_FANS_INTERVAL	5
+#define WF_SMU_SYS_FANS_HISTORY_SIZE	2
+
+/* State data used by the system fans control loop
+ */
+struct wf_smu_sys_fans_state {
+	int			ticks;
+	s32			sys_setpoint;
+	s32			hd_setpoint;
+	s16			offset0;
+	u16			scale0;
+	s16			offset1;
+	u16			scale1;
+	struct wf_pid_state	pid;
+};
+
+/*
+ * Configs for SMU System Fan control loop
+ */
+static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {
+	/* Model ID 2 */
+	{
+		.model_id	= 2,
+		.itarget	= 0x3a0000,
+		.gd		= 0x15400000,
+		.gp		= 0x00200000,
+		.gr		= 0x000002fd,
+		.offset0	= 0xff38,
+		.scale0		= 0x0ccd,
+		.offset1	= 0x0208,
+		.scale1		= 0x07ae,
+	},
+	/* Model ID 3 */
+	{
+		.model_id	= 3,
+		.itarget	= 0x350000,
+		.gd		= 0x08e00000,
+		.gp		= 0x00566666,
+		.gr		= 0x0000072b,
+		.offset0	= 0xff38,
+		.scale0		= 0x0ccd,
+		.offset1	= 0x0000,
+		.scale1		= 0x0000,
+	},
+	/* Model ID 5 */
+	{
+		.model_id	= 5,
+		.itarget	= 0x3a0000,
+		.gd		= 0x15400000,
+		.gp		= 0x00233333,
+		.gr		= 0x000002fd,
+		.offset0	= 0x0000,
+		.scale0		= 0x1000,
+		.offset1	= 0x0091,
+		.scale1		= 0x0bae,
+	},
+};
+#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)
+
+static struct wf_smu_sys_fans_state *wf_smu_sys_fans;
+
+/*
+ * ****** CPU Fans Control Loop ******
+ *
+ */
+
+
+#define WF_SMU_CPU_FANS_INTERVAL	1
+#define WF_SMU_CPU_FANS_MAX_HISTORY	16
+#define WF_SMU_CPU_FANS_SIBLING_SCALE	0x00001000
+#define WF_SMU_CPU_FANS_SIBLING_OFFSET	0xfffffb50
+
+/* State data used by the cpu fans control loop
+ */
+struct wf_smu_cpu_fans_state {
+	int			ticks;
+	s32			cpu_setpoint;
+	s32			scale;
+	s32			offset;
+	struct wf_cpu_pid_state	pid;
+};
+
+static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
+
+
+
+/*
+ * ***** Implementation *****
+ *
+ */
+
+static void wf_smu_create_sys_fans(void)
+{
+	struct wf_smu_sys_fans_param *param = NULL;
+	struct wf_pid_param pid_param;
+	int i;
+
+	/* First, locate the params for this model */
+	for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)
+		if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {
+			param = &wf_smu_sys_all_params[i];
+			break;
+		}
+
+	/* No params found, put fans to max */
+	if (param == NULL) {
+		printk(KERN_WARNING "windfarm: System fan config not found "
+		       "for this machine model, max fan speed\n");
+		goto fail;
+	}
+
+	/* Alloc & initialize state */
+	wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),
+				  GFP_KERNEL);
+	if (wf_smu_sys_fans == NULL) {
+		printk(KERN_WARNING "windfarm: Memory allocation error"
+		       " max fan speed\n");
+		goto fail;
+	}
+	wf_smu_sys_fans->ticks = 1;
+	wf_smu_sys_fans->scale0 = param->scale0;
+	wf_smu_sys_fans->offset0 = param->offset0;
+	wf_smu_sys_fans->scale1 = param->scale1;
+	wf_smu_sys_fans->offset1 = param->offset1;
+
+	/* Fill PID params */
+	pid_param.gd = param->gd;
+	pid_param.gp = param->gp;
+	pid_param.gr = param->gr;
+	pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;
+	pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;
+	pid_param.itarget = param->itarget;
+	pid_param.min = wf_control_get_min(fan_system);
+	pid_param.max = wf_control_get_max(fan_system);
+	if (fan_hd) {
+		pid_param.min =
+			max(pid_param.min, wf_control_get_min(fan_hd));
+		pid_param.max =
+			min(pid_param.max, wf_control_get_max(fan_hd));
+	}
+	wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);
+
+	DBG("wf: System Fan control initialized.\n");
+	DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+	    FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);
+	return;
+
+ fail:
+
+	if (fan_system)
+		wf_control_set_max(fan_system);
+	if (fan_hd)
+		wf_control_set_max(fan_hd);
+}
+
+static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)
+{
+	s32 new_setpoint, temp, scaled, cputarget;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (wf_smu_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = WF_SMU_SYS_FANS_INTERVAL;
+
+	rc = wf_sensor_get(sensor_hd_temp, &temp);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",
+	    FIX32TOPRINT(temp));
+
+	if (temp > (st->pid.param.itarget + 0x50000))
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+
+	new_setpoint = wf_pid_run(&st->pid, temp);
+
+	DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+	scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;
+
+	DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);
+
+	cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;
+	cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;
+	scaled = max(scaled, cputarget);
+	scaled = max(scaled, st->pid.param.min);
+	scaled = min(scaled, st->pid.param.max);
+
+	DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);
+
+	if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)
+		return;
+	st->sys_setpoint = scaled;
+	st->hd_setpoint = new_setpoint;
+ readjust:
+	if (fan_system && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_system, st->sys_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: Sys fan error %d\n",
+			       rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+	if (fan_hd && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_hd, st->hd_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: HD fan error %d\n",
+			       rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+static void wf_smu_create_cpu_fans(void)
+{
+	struct wf_cpu_pid_param pid_param;
+	const struct smu_sdbp_header *hdr;
+	struct smu_sdbp_cpupiddata *piddata;
+	struct smu_sdbp_fvt *fvt;
+	s32 tmax, tdelta, maxpow, powadj;
+
+	/* First, locate the PID params in SMU SBD */
+	hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
+	if (hdr == 0) {
+		printk(KERN_WARNING "windfarm: CPU PID fan config not found "
+		       "max fan speed\n");
+		goto fail;
+	}
+	piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+	/* Get the FVT params for operating point 0 (the only supported one
+	 * for now) in order to get tmax
+	 */
+	hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
+	if (hdr) {
+		fvt = (struct smu_sdbp_fvt *)&hdr[1];
+		tmax = ((s32)fvt->maxtemp) << 16;
+	} else
+		tmax = 0x5e0000; /* 94 degree default */
+
+	/* Alloc & initialize state */
+	wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
+				  GFP_KERNEL);
+	if (wf_smu_cpu_fans == NULL)
+		goto fail;
+       	wf_smu_cpu_fans->ticks = 1;
+
+	wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;
+	wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;
+
+	/* Fill PID params */
+	pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
+	pid_param.history_len = piddata->history_len;
+	if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
+		printk(KERN_WARNING "windfarm: History size overflow on "
+		       "CPU control loop (%d)\n", piddata->history_len);
+		pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
+	}
+	pid_param.gd = piddata->gd;
+	pid_param.gp = piddata->gp;
+	pid_param.gr = piddata->gr / pid_param.history_len;
+
+	tdelta = ((s32)piddata->target_temp_delta) << 16;
+	maxpow = ((s32)piddata->max_power) << 16;
+	powadj = ((s32)piddata->power_adj) << 16;
+
+	pid_param.tmax = tmax;
+	pid_param.ttarget = tmax - tdelta;
+	pid_param.pmaxadj = maxpow - powadj;
+
+	pid_param.min = wf_control_get_min(fan_cpu_main);
+	pid_param.max = wf_control_get_max(fan_cpu_main);
+
+	wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
+
+	DBG("wf: CPU Fan control initialized.\n");
+	DBG("    ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
+	    FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
+	    pid_param.min, pid_param.max);
+
+	return;
+
+ fail:
+	printk(KERN_WARNING "windfarm: CPU fan config not found\n"
+	       "for this machine model, max fan speed\n");
+
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	if (fan_cpu_main)
+		wf_control_set_max(fan_cpu_main);
+}
+
+static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
+{
+	s32 new_setpoint, temp, power, systarget;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (wf_smu_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = WF_SMU_CPU_FANS_INTERVAL;
+
+	rc = wf_sensor_get(sensor_cpu_temp, &temp);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	rc = wf_sensor_get(sensor_cpu_power, &power);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
+	    FIX32TOPRINT(temp), FIX32TOPRINT(power));
+
+#ifdef HACKED_OVERTEMP
+	if (temp > 0x4a0000)
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+#else
+	if (temp > st->pid.param.tmax)
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+	new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
+
+	DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+	systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;
+	systarget = ((((s64)systarget) * (s64)st->scale) >> 12)
+		+ st->offset;
+	new_setpoint = max(new_setpoint, systarget);
+	new_setpoint = max(new_setpoint, st->pid.param.min);
+	new_setpoint = min(new_setpoint, st->pid.param.max);
+
+	DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);
+
+	if (st->cpu_setpoint == new_setpoint)
+		return;
+	st->cpu_setpoint = new_setpoint;
+ readjust:
+	if (fan_cpu_main && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_cpu_main, st->cpu_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: CPU main fan"
+			       " error %d\n", rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+/*
+ * ****** Setup / Init / Misc ... ******
+ *
+ */
+
+static void wf_smu_tick(void)
+{
+	unsigned int last_failure = wf_smu_failure_state;
+	unsigned int new_failure;
+
+	if (!wf_smu_started) {
+		DBG("wf: creating control loops !\n");
+		wf_smu_create_sys_fans();
+		wf_smu_create_cpu_fans();
+		wf_smu_started = 1;
+	}
+
+	/* Skipping ticks */
+	if (wf_smu_skipping && --wf_smu_skipping)
+		return;
+
+	wf_smu_failure_state = 0;
+	if (wf_smu_sys_fans)
+		wf_smu_sys_fans_tick(wf_smu_sys_fans);
+	if (wf_smu_cpu_fans)
+		wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
+
+	wf_smu_readjust = 0;
+	new_failure = wf_smu_failure_state & ~last_failure;
+
+	/* If entering failure mode, clamp cpufreq and ramp all
+	 * fans to full speed.
+	 */
+	if (wf_smu_failure_state && !last_failure) {
+		if (cpufreq_clamp)
+			wf_control_set_max(cpufreq_clamp);
+		if (fan_system)
+			wf_control_set_max(fan_system);
+		if (fan_cpu_main)
+			wf_control_set_max(fan_cpu_main);
+		if (fan_hd)
+			wf_control_set_max(fan_hd);
+	}
+
+	/* If leaving failure mode, unclamp cpufreq and readjust
+	 * all fans on next iteration
+	 */
+	if (!wf_smu_failure_state && last_failure) {
+		if (cpufreq_clamp)
+			wf_control_set_min(cpufreq_clamp);
+		wf_smu_readjust = 1;
+	}
+
+	/* Overtemp condition detected, notify and start skipping a couple
+	 * ticks to let the temperature go down
+	 */
+	if (new_failure & FAILURE_OVERTEMP) {
+		wf_set_overtemp();
+		wf_smu_skipping = 2;
+		wf_smu_overtemp = true;
+	}
+
+	/* We only clear the overtemp condition if overtemp is cleared
+	 * _and_ no other failure is present. Since a sensor error will
+	 * clear the overtemp condition (can't measure temperature) at
+	 * the control loop levels, but we don't want to keep it clear
+	 * here in this case
+	 */
+	if (!wf_smu_failure_state && wf_smu_overtemp) {
+		wf_clear_overtemp();
+		wf_smu_overtemp = false;
+	}
+}
+
+static void wf_smu_new_control(struct wf_control *ct)
+{
+	if (wf_smu_all_controls_ok)
+		return;
+
+	if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {
+		if (wf_get_control(ct) == 0)
+			fan_cpu_main = ct;
+	}
+
+	if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {
+		if (wf_get_control(ct) == 0)
+			fan_system = ct;
+	}
+
+	if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
+		if (wf_get_control(ct) == 0)
+			cpufreq_clamp = ct;
+	}
+
+	/* Darwin property list says the HD fan is only for model ID
+	 * 0, 1, 2 and 3
+	 */
+
+	if (wf_smu_mach_model > 3) {
+		if (fan_system && fan_cpu_main && cpufreq_clamp)
+			wf_smu_all_controls_ok = 1;
+		return;
+	}
+
+	if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
+		if (wf_get_control(ct) == 0)
+			fan_hd = ct;
+	}
+
+	if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)
+		wf_smu_all_controls_ok = 1;
+}
+
+static void wf_smu_new_sensor(struct wf_sensor *sr)
+{
+	if (wf_smu_all_sensors_ok)
+		return;
+
+	if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_cpu_power = sr;
+	}
+
+	if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_cpu_temp = sr;
+	}
+
+	if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_hd_temp = sr;
+	}
+
+	if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)
+		wf_smu_all_sensors_ok = 1;
+}
+
+
+static int wf_smu_notify(struct notifier_block *self,
+			       unsigned long event, void *data)
+{
+	switch(event) {
+	case WF_EVENT_NEW_CONTROL:
+		DBG("wf: new control %s detected\n",
+		    ((struct wf_control *)data)->name);
+		wf_smu_new_control(data);
+		wf_smu_readjust = 1;
+		break;
+	case WF_EVENT_NEW_SENSOR:
+		DBG("wf: new sensor %s detected\n",
+		    ((struct wf_sensor *)data)->name);
+		wf_smu_new_sensor(data);
+		break;
+	case WF_EVENT_TICK:
+		if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
+			wf_smu_tick();
+	}
+
+	return 0;
+}
+
+static struct notifier_block wf_smu_events = {
+	.notifier_call	= wf_smu_notify,
+};
+
+static int wf_init_pm(void)
+{
+	const struct smu_sdbp_header *hdr;
+
+	hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
+	if (hdr != 0) {
+		struct smu_sdbp_sensortree *st =
+			(struct smu_sdbp_sensortree *)&hdr[1];
+		wf_smu_mach_model = st->model_id;
+	}
+
+	printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",
+	       wf_smu_mach_model);
+
+	return 0;
+}
+
+static int wf_smu_probe(struct platform_device *ddev)
+{
+	wf_register_client(&wf_smu_events);
+
+	return 0;
+}
+
+static int wf_smu_remove(struct platform_device *ddev)
+{
+	wf_unregister_client(&wf_smu_events);
+
+	/* XXX We don't have yet a guarantee that our callback isn't
+	 * in progress when returning from wf_unregister_client, so
+	 * we add an arbitrary delay. I'll have to fix that in the core
+	 */
+	msleep(1000);
+
+	/* Release all sensors */
+	/* One more crappy race: I don't think we have any guarantee here
+	 * that the attribute callback won't race with the sensor beeing
+	 * disposed of, and I'm not 100% certain what best way to deal
+	 * with that except by adding locks all over... I'll do that
+	 * eventually but heh, who ever rmmod this module anyway ?
+	 */
+	if (sensor_cpu_power)
+		wf_put_sensor(sensor_cpu_power);
+	if (sensor_cpu_temp)
+		wf_put_sensor(sensor_cpu_temp);
+	if (sensor_hd_temp)
+		wf_put_sensor(sensor_hd_temp);
+
+	/* Release all controls */
+	if (fan_cpu_main)
+		wf_put_control(fan_cpu_main);
+	if (fan_hd)
+		wf_put_control(fan_hd);
+	if (fan_system)
+		wf_put_control(fan_system);
+	if (cpufreq_clamp)
+		wf_put_control(cpufreq_clamp);
+
+	/* Destroy control loops state structures */
+	kfree(wf_smu_sys_fans);
+	kfree(wf_smu_cpu_fans);
+
+	return 0;
+}
+
+static struct platform_driver wf_smu_driver = {
+        .probe = wf_smu_probe,
+        .remove = wf_smu_remove,
+	.driver = {
+		.name = "windfarm",
+	},
+};
+
+
+static int __init wf_smu_init(void)
+{
+	int rc = -ENODEV;
+
+	if (of_machine_is_compatible("PowerMac8,1") ||
+	    of_machine_is_compatible("PowerMac8,2"))
+		rc = wf_init_pm();
+
+	if (rc == 0) {
+#ifdef MODULE
+		request_module("windfarm_smu_controls");
+		request_module("windfarm_smu_sensors");
+		request_module("windfarm_lm75_sensor");
+		request_module("windfarm_cpufreq_clamp");
+
+#endif /* MODULE */
+		platform_driver_register(&wf_smu_driver);
+	}
+
+	return rc;
+}
+
+static void __exit wf_smu_exit(void)
+{
+
+	platform_driver_unregister(&wf_smu_driver);
+}
+
+
+module_init(wf_smu_init);
+module_exit(wf_smu_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control logic for iMac G5");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:windfarm");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm91.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm91.c
new file mode 100644
index 0000000..81fdf40
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_pm91.c
@@ -0,0 +1,741 @@
+/*
+ * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ *
+ * The algorithm used is the PID control algorithm, used the same
+ * way the published Darwin code does, using the same values that
+ * are present in the Darwin 8.2 snapshot property lists (note however
+ * that none of the code has been re-used, it's a complete re-implementation
+ *
+ * The various control loops found in Darwin config file are:
+ *
+ * PowerMac9,1
+ * ===========
+ *
+ * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't
+ * try to play with other control loops fans). Drive bay is rather basic PID
+ * with one sensor and one fan. Slots area is a bit different as the Darwin
+ * driver is supposed to be capable of working in a special "AGP" mode which
+ * involves the presence of an AGP sensor and an AGP fan (possibly on the
+ * AGP card itself). I can't deal with that special mode as I don't have
+ * access to those additional sensor/fans for now (though ultimately, it would
+ * be possible to add sensor objects for them) so I'm only implementing the
+ * basic PCI slot control loop
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kmod.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+
+#define VERSION "0.4"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 74 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+/* Controls & sensors */
+static struct wf_sensor	*sensor_cpu_power;
+static struct wf_sensor	*sensor_cpu_temp;
+static struct wf_sensor	*sensor_hd_temp;
+static struct wf_sensor	*sensor_slots_power;
+static struct wf_control *fan_cpu_main;
+static struct wf_control *fan_cpu_second;
+static struct wf_control *fan_cpu_third;
+static struct wf_control *fan_hd;
+static struct wf_control *fan_slots;
+static struct wf_control *cpufreq_clamp;
+
+/* Set to kick the control loop into life */
+static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
+static bool wf_smu_overtemp;
+
+/* Failure handling.. could be nicer */
+#define FAILURE_FAN		0x01
+#define FAILURE_SENSOR		0x02
+#define FAILURE_OVERTEMP	0x04
+
+static unsigned int wf_smu_failure_state;
+static int wf_smu_readjust, wf_smu_skipping;
+
+/*
+ * ****** CPU Fans Control Loop ******
+ *
+ */
+
+
+#define WF_SMU_CPU_FANS_INTERVAL	1
+#define WF_SMU_CPU_FANS_MAX_HISTORY	16
+
+/* State data used by the cpu fans control loop
+ */
+struct wf_smu_cpu_fans_state {
+	int			ticks;
+	s32			cpu_setpoint;
+	struct wf_cpu_pid_state	pid;
+};
+
+static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
+
+
+
+/*
+ * ****** Drive Fan Control Loop ******
+ *
+ */
+
+struct wf_smu_drive_fans_state {
+	int			ticks;
+	s32			setpoint;
+	struct wf_pid_state	pid;
+};
+
+static struct wf_smu_drive_fans_state *wf_smu_drive_fans;
+
+/*
+ * ****** Slots Fan Control Loop ******
+ *
+ */
+
+struct wf_smu_slots_fans_state {
+	int			ticks;
+	s32			setpoint;
+	struct wf_pid_state	pid;
+};
+
+static struct wf_smu_slots_fans_state *wf_smu_slots_fans;
+
+/*
+ * ***** Implementation *****
+ *
+ */
+
+
+static void wf_smu_create_cpu_fans(void)
+{
+	struct wf_cpu_pid_param pid_param;
+	const struct smu_sdbp_header *hdr;
+	struct smu_sdbp_cpupiddata *piddata;
+	struct smu_sdbp_fvt *fvt;
+	s32 tmax, tdelta, maxpow, powadj;
+
+	/* First, locate the PID params in SMU SBD */
+	hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
+	if (hdr == 0) {
+		printk(KERN_WARNING "windfarm: CPU PID fan config not found "
+		       "max fan speed\n");
+		goto fail;
+	}
+	piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
+
+	/* Get the FVT params for operating point 0 (the only supported one
+	 * for now) in order to get tmax
+	 */
+	hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
+	if (hdr) {
+		fvt = (struct smu_sdbp_fvt *)&hdr[1];
+		tmax = ((s32)fvt->maxtemp) << 16;
+	} else
+		tmax = 0x5e0000; /* 94 degree default */
+
+	/* Alloc & initialize state */
+	wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
+				  GFP_KERNEL);
+	if (wf_smu_cpu_fans == NULL)
+		goto fail;
+       	wf_smu_cpu_fans->ticks = 1;
+
+	/* Fill PID params */
+	pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
+	pid_param.history_len = piddata->history_len;
+	if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
+		printk(KERN_WARNING "windfarm: History size overflow on "
+		       "CPU control loop (%d)\n", piddata->history_len);
+		pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
+	}
+	pid_param.gd = piddata->gd;
+	pid_param.gp = piddata->gp;
+	pid_param.gr = piddata->gr / pid_param.history_len;
+
+	tdelta = ((s32)piddata->target_temp_delta) << 16;
+	maxpow = ((s32)piddata->max_power) << 16;
+	powadj = ((s32)piddata->power_adj) << 16;
+
+	pid_param.tmax = tmax;
+	pid_param.ttarget = tmax - tdelta;
+	pid_param.pmaxadj = maxpow - powadj;
+
+	pid_param.min = wf_control_get_min(fan_cpu_main);
+	pid_param.max = wf_control_get_max(fan_cpu_main);
+
+	wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
+
+	DBG("wf: CPU Fan control initialized.\n");
+	DBG("    ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
+	    FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
+	    pid_param.min, pid_param.max);
+
+	return;
+
+ fail:
+	printk(KERN_WARNING "windfarm: CPU fan config not found\n"
+	       "for this machine model, max fan speed\n");
+
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	if (fan_cpu_main)
+		wf_control_set_max(fan_cpu_main);
+}
+
+static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
+{
+	s32 new_setpoint, temp, power;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (wf_smu_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = WF_SMU_CPU_FANS_INTERVAL;
+
+	rc = wf_sensor_get(sensor_cpu_temp, &temp);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	rc = wf_sensor_get(sensor_cpu_power, &power);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
+	    FIX32TOPRINT(temp), FIX32TOPRINT(power));
+
+#ifdef HACKED_OVERTEMP
+	if (temp > 0x4a0000)
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+#else
+	if (temp > st->pid.param.tmax)
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+	new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
+
+	DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
+
+	if (st->cpu_setpoint == new_setpoint)
+		return;
+	st->cpu_setpoint = new_setpoint;
+ readjust:
+	if (fan_cpu_main && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_cpu_main, st->cpu_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: CPU main fan"
+			       " error %d\n", rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+	if (fan_cpu_second && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_cpu_second, st->cpu_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: CPU second fan"
+			       " error %d\n", rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+	if (fan_cpu_third && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_cpu_third, st->cpu_setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: CPU third fan"
+			       " error %d\n", rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+static void wf_smu_create_drive_fans(void)
+{
+	struct wf_pid_param param = {
+		.interval	= 5,
+		.history_len	= 2,
+		.gd		= 0x01e00000,
+		.gp		= 0x00500000,
+		.gr		= 0x00000000,
+		.itarget	= 0x00200000,
+	};
+
+	/* Alloc & initialize state */
+	wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),
+					GFP_KERNEL);
+	if (wf_smu_drive_fans == NULL) {
+		printk(KERN_WARNING "windfarm: Memory allocation error"
+		       " max fan speed\n");
+		goto fail;
+	}
+       	wf_smu_drive_fans->ticks = 1;
+
+	/* Fill PID params */
+	param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);
+	param.min = wf_control_get_min(fan_hd);
+	param.max = wf_control_get_max(fan_hd);
+	wf_pid_init(&wf_smu_drive_fans->pid, &param);
+
+	DBG("wf: Drive Fan control initialized.\n");
+	DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+	    FIX32TOPRINT(param.itarget), param.min, param.max);
+	return;
+
+ fail:
+	if (fan_hd)
+		wf_control_set_max(fan_hd);
+}
+
+static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)
+{
+	s32 new_setpoint, temp;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (wf_smu_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = st->pid.param.interval;
+
+	rc = wf_sensor_get(sensor_hd_temp, &temp);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",
+	    FIX32TOPRINT(temp));
+
+	if (temp > (st->pid.param.itarget + 0x50000))
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+
+	new_setpoint = wf_pid_run(&st->pid, temp);
+
+	DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
+
+	if (st->setpoint == new_setpoint)
+		return;
+	st->setpoint = new_setpoint;
+ readjust:
+	if (fan_hd && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_hd, st->setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: HD fan error %d\n",
+			       rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+static void wf_smu_create_slots_fans(void)
+{
+	struct wf_pid_param param = {
+		.interval	= 1,
+		.history_len	= 8,
+		.gd		= 0x00000000,
+		.gp		= 0x00000000,
+		.gr		= 0x00020000,
+		.itarget	= 0x00000000
+	};
+
+	/* Alloc & initialize state */
+	wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),
+					GFP_KERNEL);
+	if (wf_smu_slots_fans == NULL) {
+		printk(KERN_WARNING "windfarm: Memory allocation error"
+		       " max fan speed\n");
+		goto fail;
+	}
+       	wf_smu_slots_fans->ticks = 1;
+
+	/* Fill PID params */
+	param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);
+	param.min = wf_control_get_min(fan_slots);
+	param.max = wf_control_get_max(fan_slots);
+	wf_pid_init(&wf_smu_slots_fans->pid, &param);
+
+	DBG("wf: Slots Fan control initialized.\n");
+	DBG("    itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
+	    FIX32TOPRINT(param.itarget), param.min, param.max);
+	return;
+
+ fail:
+	if (fan_slots)
+		wf_control_set_max(fan_slots);
+}
+
+static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)
+{
+	s32 new_setpoint, power;
+	int rc;
+
+	if (--st->ticks != 0) {
+		if (wf_smu_readjust)
+			goto readjust;
+		return;
+	}
+	st->ticks = st->pid.param.interval;
+
+	rc = wf_sensor_get(sensor_slots_power, &power);
+	if (rc) {
+		printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",
+		       rc);
+		wf_smu_failure_state |= FAILURE_SENSOR;
+		return;
+	}
+
+	DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",
+	    FIX32TOPRINT(power));
+
+#if 0 /* Check what makes a good overtemp condition */
+	if (power > (st->pid.param.itarget + 0x50000))
+		wf_smu_failure_state |= FAILURE_OVERTEMP;
+#endif
+
+	new_setpoint = wf_pid_run(&st->pid, power);
+
+	DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
+
+	if (st->setpoint == new_setpoint)
+		return;
+	st->setpoint = new_setpoint;
+ readjust:
+	if (fan_slots && wf_smu_failure_state == 0) {
+		rc = wf_control_set(fan_slots, st->setpoint);
+		if (rc) {
+			printk(KERN_WARNING "windfarm: Slots fan error %d\n",
+			       rc);
+			wf_smu_failure_state |= FAILURE_FAN;
+		}
+	}
+}
+
+
+/*
+ * ****** Setup / Init / Misc ... ******
+ *
+ */
+
+static void wf_smu_tick(void)
+{
+	unsigned int last_failure = wf_smu_failure_state;
+	unsigned int new_failure;
+
+	if (!wf_smu_started) {
+		DBG("wf: creating control loops !\n");
+		wf_smu_create_drive_fans();
+		wf_smu_create_slots_fans();
+		wf_smu_create_cpu_fans();
+		wf_smu_started = 1;
+	}
+
+	/* Skipping ticks */
+	if (wf_smu_skipping && --wf_smu_skipping)
+		return;
+
+	wf_smu_failure_state = 0;
+	if (wf_smu_drive_fans)
+		wf_smu_drive_fans_tick(wf_smu_drive_fans);
+	if (wf_smu_slots_fans)
+		wf_smu_slots_fans_tick(wf_smu_slots_fans);
+	if (wf_smu_cpu_fans)
+		wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
+
+	wf_smu_readjust = 0;
+	new_failure = wf_smu_failure_state & ~last_failure;
+
+	/* If entering failure mode, clamp cpufreq and ramp all
+	 * fans to full speed.
+	 */
+	if (wf_smu_failure_state && !last_failure) {
+		if (cpufreq_clamp)
+			wf_control_set_max(cpufreq_clamp);
+		if (fan_cpu_main)
+			wf_control_set_max(fan_cpu_main);
+		if (fan_cpu_second)
+			wf_control_set_max(fan_cpu_second);
+		if (fan_cpu_third)
+			wf_control_set_max(fan_cpu_third);
+		if (fan_hd)
+			wf_control_set_max(fan_hd);
+		if (fan_slots)
+			wf_control_set_max(fan_slots);
+	}
+
+	/* If leaving failure mode, unclamp cpufreq and readjust
+	 * all fans on next iteration
+	 */
+	if (!wf_smu_failure_state && last_failure) {
+		if (cpufreq_clamp)
+			wf_control_set_min(cpufreq_clamp);
+		wf_smu_readjust = 1;
+	}
+
+	/* Overtemp condition detected, notify and start skipping a couple
+	 * ticks to let the temperature go down
+	 */
+	if (new_failure & FAILURE_OVERTEMP) {
+		wf_set_overtemp();
+		wf_smu_skipping = 2;
+		wf_smu_overtemp = true;
+	}
+
+	/* We only clear the overtemp condition if overtemp is cleared
+	 * _and_ no other failure is present. Since a sensor error will
+	 * clear the overtemp condition (can't measure temperature) at
+	 * the control loop levels, but we don't want to keep it clear
+	 * here in this case
+	 */
+	if (!wf_smu_failure_state && wf_smu_overtemp) {
+		wf_clear_overtemp();
+		wf_smu_overtemp = false;
+	}
+}
+
+
+static void wf_smu_new_control(struct wf_control *ct)
+{
+	if (wf_smu_all_controls_ok)
+		return;
+
+	if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {
+		if (wf_get_control(ct) == 0)
+			fan_cpu_main = ct;
+	}
+
+	if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {
+		if (wf_get_control(ct) == 0)
+			fan_cpu_second = ct;
+	}
+
+	if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {
+		if (wf_get_control(ct) == 0)
+			fan_cpu_third = ct;
+	}
+
+	if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
+		if (wf_get_control(ct) == 0)
+			cpufreq_clamp = ct;
+	}
+
+	if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
+		if (wf_get_control(ct) == 0)
+			fan_hd = ct;
+	}
+
+	if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {
+		if (wf_get_control(ct) == 0)
+			fan_slots = ct;
+	}
+
+	if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&
+	    fan_slots && cpufreq_clamp)
+		wf_smu_all_controls_ok = 1;
+}
+
+static void wf_smu_new_sensor(struct wf_sensor *sr)
+{
+	if (wf_smu_all_sensors_ok)
+		return;
+
+	if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_cpu_power = sr;
+	}
+
+	if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_cpu_temp = sr;
+	}
+
+	if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_hd_temp = sr;
+	}
+
+	if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {
+		if (wf_get_sensor(sr) == 0)
+			sensor_slots_power = sr;
+	}
+
+	if (sensor_cpu_power && sensor_cpu_temp &&
+	    sensor_hd_temp && sensor_slots_power)
+		wf_smu_all_sensors_ok = 1;
+}
+
+
+static int wf_smu_notify(struct notifier_block *self,
+			       unsigned long event, void *data)
+{
+	switch(event) {
+	case WF_EVENT_NEW_CONTROL:
+		DBG("wf: new control %s detected\n",
+		    ((struct wf_control *)data)->name);
+		wf_smu_new_control(data);
+		wf_smu_readjust = 1;
+		break;
+	case WF_EVENT_NEW_SENSOR:
+		DBG("wf: new sensor %s detected\n",
+		    ((struct wf_sensor *)data)->name);
+		wf_smu_new_sensor(data);
+		break;
+	case WF_EVENT_TICK:
+		if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
+			wf_smu_tick();
+	}
+
+	return 0;
+}
+
+static struct notifier_block wf_smu_events = {
+	.notifier_call	= wf_smu_notify,
+};
+
+static int wf_init_pm(void)
+{
+	printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");
+
+	return 0;
+}
+
+static int wf_smu_probe(struct platform_device *ddev)
+{
+	wf_register_client(&wf_smu_events);
+
+	return 0;
+}
+
+static int wf_smu_remove(struct platform_device *ddev)
+{
+	wf_unregister_client(&wf_smu_events);
+
+	/* XXX We don't have yet a guarantee that our callback isn't
+	 * in progress when returning from wf_unregister_client, so
+	 * we add an arbitrary delay. I'll have to fix that in the core
+	 */
+	msleep(1000);
+
+	/* Release all sensors */
+	/* One more crappy race: I don't think we have any guarantee here
+	 * that the attribute callback won't race with the sensor beeing
+	 * disposed of, and I'm not 100% certain what best way to deal
+	 * with that except by adding locks all over... I'll do that
+	 * eventually but heh, who ever rmmod this module anyway ?
+	 */
+	if (sensor_cpu_power)
+		wf_put_sensor(sensor_cpu_power);
+	if (sensor_cpu_temp)
+		wf_put_sensor(sensor_cpu_temp);
+	if (sensor_hd_temp)
+		wf_put_sensor(sensor_hd_temp);
+	if (sensor_slots_power)
+		wf_put_sensor(sensor_slots_power);
+
+	/* Release all controls */
+	if (fan_cpu_main)
+		wf_put_control(fan_cpu_main);
+	if (fan_cpu_second)
+		wf_put_control(fan_cpu_second);
+	if (fan_cpu_third)
+		wf_put_control(fan_cpu_third);
+	if (fan_hd)
+		wf_put_control(fan_hd);
+	if (fan_slots)
+		wf_put_control(fan_slots);
+	if (cpufreq_clamp)
+		wf_put_control(cpufreq_clamp);
+
+	/* Destroy control loops state structures */
+	kfree(wf_smu_slots_fans);
+	kfree(wf_smu_drive_fans);
+	kfree(wf_smu_cpu_fans);
+
+	return 0;
+}
+
+static struct platform_driver wf_smu_driver = {
+        .probe = wf_smu_probe,
+        .remove = wf_smu_remove,
+	.driver = {
+		.name = "windfarm",
+	},
+};
+
+
+static int __init wf_smu_init(void)
+{
+	int rc = -ENODEV;
+
+	if (of_machine_is_compatible("PowerMac9,1"))
+		rc = wf_init_pm();
+
+	if (rc == 0) {
+#ifdef MODULE
+		request_module("windfarm_smu_controls");
+		request_module("windfarm_smu_sensors");
+		request_module("windfarm_lm75_sensor");
+		request_module("windfarm_cpufreq_clamp");
+
+#endif /* MODULE */
+		platform_driver_register(&wf_smu_driver);
+	}
+
+	return rc;
+}
+
+static void __exit wf_smu_exit(void)
+{
+
+	platform_driver_unregister(&wf_smu_driver);
+}
+
+
+module_init(wf_smu_init);
+module_exit(wf_smu_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("platform:windfarm");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_rm31.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_rm31.c
new file mode 100644
index 0000000..a0cd9c7
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_rm31.c
@@ -0,0 +1,739 @@
+/*
+ * Windfarm PowerMac thermal control.
+ * Control loops for RackMack3,1 (Xserve G5)
+ *
+ * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Use and redistribute under the terms of the GNU GPL v2.
+ */
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <asm/prom.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+#include "windfarm_pid.h"
+#include "windfarm_mpu.h"
+
+#define VERSION "1.0"
+
+#undef DEBUG
+#undef LOTSA_DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+#ifdef LOTSA_DEBUG
+#define DBG_LOTS(args...)	printk(args)
+#else
+#define DBG_LOTS(args...)	do { } while(0)
+#endif
+
+/* define this to force CPU overtemp to 60 degree, useful for testing
+ * the overtemp code
+ */
+#undef HACKED_OVERTEMP
+
+/* We currently only handle 2 chips */
+#define NR_CHIPS	2
+#define NR_CPU_FANS	3 * NR_CHIPS
+
+/* Controls and sensors */
+static struct wf_sensor *sens_cpu_temp[NR_CHIPS];
+static struct wf_sensor *sens_cpu_volts[NR_CHIPS];
+static struct wf_sensor *sens_cpu_amps[NR_CHIPS];
+static struct wf_sensor *backside_temp;
+static struct wf_sensor *slots_temp;
+static struct wf_sensor *dimms_temp;
+
+static struct wf_control *cpu_fans[NR_CHIPS][3];
+static struct wf_control *backside_fan;
+static struct wf_control *slots_fan;
+static struct wf_control *cpufreq_clamp;
+
+/* We keep a temperature history for average calculation of 180s */
+#define CPU_TEMP_HIST_SIZE	180
+
+/* PID loop state */
+static const struct mpu_data *cpu_mpu_data[NR_CHIPS];
+static struct wf_cpu_pid_state cpu_pid[NR_CHIPS];
+static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
+static int cpu_thist_pt;
+static s64 cpu_thist_total;
+static s32 cpu_all_tmax = 100 << 16;
+static struct wf_pid_state backside_pid;
+static int backside_tick;
+static struct wf_pid_state slots_pid;
+static int slots_tick;
+static int slots_speed;
+static struct wf_pid_state dimms_pid;
+static int dimms_output_clamp;
+
+static int nr_chips;
+static bool have_all_controls;
+static bool have_all_sensors;
+static bool started;
+
+static int failure_state;
+#define FAILURE_SENSOR		1
+#define FAILURE_FAN		2
+#define FAILURE_PERM		4
+#define FAILURE_LOW_OVERTEMP	8
+#define FAILURE_HIGH_OVERTEMP	16
+
+/* Overtemp values */
+#define LOW_OVER_AVERAGE	0
+#define LOW_OVER_IMMEDIATE	(10 << 16)
+#define LOW_OVER_CLEAR		((-10) << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+#define HIGH_OVER_AVERAGE	(10 << 16)
+#define HIGH_OVER_IMMEDIATE	(14 << 16)
+
+
+static void cpu_max_all_fans(void)
+{
+	int i;
+
+	/* We max all CPU fans in case of a sensor error. We also do the
+	 * cpufreq clamping now, even if it's supposedly done later by the
+	 * generic code anyway, we do it earlier here to react faster
+	 */
+	if (cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	for (i = 0; i < nr_chips; i++) {
+		if (cpu_fans[i][0])
+			wf_control_set_max(cpu_fans[i][0]);
+		if (cpu_fans[i][1])
+			wf_control_set_max(cpu_fans[i][1]);
+		if (cpu_fans[i][2])
+			wf_control_set_max(cpu_fans[i][2]);
+	}
+}
+
+static int cpu_check_overtemp(s32 temp)
+{
+	int new_state = 0;
+	s32 t_avg, t_old;
+	static bool first = true;
+
+	/* First check for immediate overtemps */
+	if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
+			       " temperature !\n");
+	}
+	if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " immediate CPU temperature !\n");
+	}
+
+	/*
+	 * The first time around, initialize the array with the first
+	 * temperature reading
+	 */
+	if (first) {
+		int i;
+
+		cpu_thist_total = 0;
+		for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) {
+			cpu_thist[i] = temp;
+			cpu_thist_total += temp;
+		}
+		first = false;
+	}
+
+	/*
+	 * We calculate a history of max temperatures and use that for the
+	 * overtemp management
+	 */
+	t_old = cpu_thist[cpu_thist_pt];
+	cpu_thist[cpu_thist_pt] = temp;
+	cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
+	cpu_thist_total -= t_old;
+	cpu_thist_total += temp;
+	t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;
+
+	DBG_LOTS("  t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
+		 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));
+
+	/* Now check for average overtemps */
+	if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
+		new_state |= FAILURE_LOW_OVERTEMP;
+		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Overtemp due to average CPU"
+			       " temperature !\n");
+	}
+	if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
+		new_state |= FAILURE_HIGH_OVERTEMP;
+		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
+			printk(KERN_ERR "windfarm: Critical overtemp due to"
+			       " average CPU temperature !\n");
+	}
+
+	/* Now handle overtemp conditions. We don't currently use the windfarm
+	 * overtemp handling core as it's not fully suited to the needs of those
+	 * new machine. This will be fixed later.
+	 */
+	if (new_state) {
+		/* High overtemp -> immediate shutdown */
+		if (new_state & FAILURE_HIGH_OVERTEMP)
+			machine_power_off();
+		if ((failure_state & new_state) != new_state)
+			cpu_max_all_fans();
+		failure_state |= new_state;
+	} else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
+		   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
+		printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
+		failure_state &= ~FAILURE_LOW_OVERTEMP;
+	}
+
+	return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
+}
+
+static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power)
+{
+	s32 dtemp, volts, amps;
+	int rc;
+
+	/* Get diode temperature */
+	rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp);
+	if (rc) {
+		DBG("  CPU%d: temp reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: temp   = %d.%03d\n", cpu, FIX32TOPRINT((dtemp)));
+	*temp = dtemp;
+
+	/* Get voltage */
+	rc = wf_sensor_get(sens_cpu_volts[cpu], &volts);
+	if (rc) {
+		DBG("  CPU%d, volts reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: volts  = %d.%03d\n", cpu, FIX32TOPRINT((volts)));
+
+	/* Get current */
+	rc = wf_sensor_get(sens_cpu_amps[cpu], &amps);
+	if (rc) {
+		DBG("  CPU%d, current reading error !\n", cpu);
+		return -EIO;
+	}
+	DBG_LOTS("  CPU%d: amps   = %d.%03d\n", cpu, FIX32TOPRINT((amps)));
+
+	/* Calculate power */
+
+	/* Scale voltage and current raw sensor values according to fixed scales
+	 * obtained in Darwin and calculate power from I and V
+	 */
+	*power = (((u64)volts) * ((u64)amps)) >> 16;
+
+	DBG_LOTS("  CPU%d: power  = %d.%03d\n", cpu, FIX32TOPRINT((*power)));
+
+	return 0;
+
+}
+
+static void cpu_fans_tick(void)
+{
+	int err, cpu, i;
+	s32 speed, temp, power, t_max = 0;
+
+	DBG_LOTS("* cpu fans_tick_split()\n");
+
+	for (cpu = 0; cpu < nr_chips; ++cpu) {
+		struct wf_cpu_pid_state *sp = &cpu_pid[cpu];
+
+		/* Read current speed */
+		wf_control_get(cpu_fans[cpu][0], &sp->target);
+
+		err = read_one_cpu_vals(cpu, &temp, &power);
+		if (err) {
+			failure_state |= FAILURE_SENSOR;
+			cpu_max_all_fans();
+			return;
+		}
+
+		/* Keep track of highest temp */
+		t_max = max(t_max, temp);
+
+		/* Handle possible overtemps */
+		if (cpu_check_overtemp(t_max))
+			return;
+
+		/* Run PID */
+		wf_cpu_pid_run(sp, power, temp);
+
+		DBG_LOTS("  CPU%d: target = %d RPM\n", cpu, sp->target);
+
+		/* Apply DIMMs clamp */
+		speed = max(sp->target, dimms_output_clamp);
+
+		/* Apply result to all cpu fans */
+		for (i = 0; i < 3; i++) {
+			err = wf_control_set(cpu_fans[cpu][i], speed);
+			if (err) {
+				pr_warning("wf_rm31: Fan %s reports error %d\n",
+					   cpu_fans[cpu][i]->name, err);
+				failure_state |= FAILURE_FAN;
+			}
+		}
+	}
+}
+
+/* Implementation... */
+static int cpu_setup_pid(int cpu)
+{
+	struct wf_cpu_pid_param pid;
+	const struct mpu_data *mpu = cpu_mpu_data[cpu];
+	s32 tmax, ttarget, ptarget;
+	int fmin, fmax, hsize;
+
+	/* Get PID params from the appropriate MPU EEPROM */
+	tmax = mpu->tmax << 16;
+	ttarget = mpu->ttarget << 16;
+	ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16;
+
+	DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n",
+	    cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax));
+
+	/* We keep a global tmax for overtemp calculations */
+	if (tmax < cpu_all_tmax)
+		cpu_all_tmax = tmax;
+
+	/* Set PID min/max by using the rear fan min/max */
+	fmin = wf_control_get_min(cpu_fans[cpu][0]);
+	fmax = wf_control_get_max(cpu_fans[cpu][0]);
+	DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax);
+
+	/* History size */
+	hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY);
+	DBG("wf_72: CPU%d history size = %d\n", cpu, hsize);
+
+	/* Initialize PID loop */
+	pid.interval	= 1;	/* seconds */
+	pid.history_len = hsize;
+	pid.gd		= mpu->pid_gd;
+	pid.gp		= mpu->pid_gp;
+	pid.gr		= mpu->pid_gr;
+	pid.tmax	= tmax;
+	pid.ttarget	= ttarget;
+	pid.pmaxadj	= ptarget;
+	pid.min		= fmin;
+	pid.max		= fmax;
+
+	wf_cpu_pid_init(&cpu_pid[cpu], &pid);
+	cpu_pid[cpu].target = 4000;
+	
+	return 0;
+}
+
+/* Backside/U3 fan */
+static const struct wf_pid_param backside_param = {
+	.interval	= 1,
+	.history_len	= 2,
+	.gd		= 0x00500000,
+	.gp		= 0x0004cccc,
+	.gr		= 0,
+	.itarget	= 70 << 16,
+	.additive	= 0,
+	.min		= 20,
+	.max		= 100,
+};
+
+/* DIMMs temperature (clamp the backside fan) */
+static const struct wf_pid_param dimms_param = {
+	.interval	= 1,
+	.history_len	= 20,
+	.gd		= 0,
+	.gp		= 0,
+	.gr		= 0x06553600,
+	.itarget	= 50 << 16,
+	.additive	= 0,
+	.min		= 4000,
+	.max		= 14000,
+};
+
+static void backside_fan_tick(void)
+{
+	s32 temp, dtemp;
+	int speed, dspeed, fan_min;
+	int err;
+
+	if (!backside_fan || !backside_temp || !dimms_temp || !backside_tick)
+		return;
+	if (--backside_tick > 0)
+		return;
+	backside_tick = backside_pid.param.interval;
+
+	DBG_LOTS("* backside fans tick\n");
+
+	/* Update fan speed from actual fans */
+	err = wf_control_get(backside_fan, &speed);
+	if (!err)
+		backside_pid.target = speed;
+
+	err = wf_sensor_get(backside_temp, &temp);
+	if (err) {
+		printk(KERN_WARNING "windfarm: U3 temp sensor error %d\n",
+		       err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(backside_fan);
+		return;
+	}
+	speed = wf_pid_run(&backside_pid, temp);
+
+	DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	err = wf_sensor_get(dimms_temp, &dtemp);
+	if (err) {
+		printk(KERN_WARNING "windfarm: DIMMs temp sensor error %d\n",
+		       err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(backside_fan);
+		return;
+	}
+	dspeed = wf_pid_run(&dimms_pid, dtemp);
+	dimms_output_clamp = dspeed;
+
+	fan_min = (dspeed * 100) / 14000;
+	fan_min = max(fan_min, backside_param.min);
+	speed = max(speed, fan_min);
+
+	err = wf_control_set(backside_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+static void backside_setup_pid(void)
+{
+	/* first time initialize things */
+	s32 fmin = wf_control_get_min(backside_fan);
+	s32 fmax = wf_control_get_max(backside_fan);
+	struct wf_pid_param param;
+
+	param = backside_param;
+	param.min = max(param.min, fmin);
+	param.max = min(param.max, fmax);
+	wf_pid_init(&backside_pid, &param);
+
+	param = dimms_param;
+	wf_pid_init(&dimms_pid, &param);
+
+	backside_tick = 1;
+
+	pr_info("wf_rm31: Backside control loop started.\n");
+}
+
+/* Slots fan */
+static const struct wf_pid_param slots_param = {
+	.interval	= 1,
+	.history_len	= 20,
+	.gd		= 0,
+	.gp		= 0,
+	.gr		= 0x00100000,
+	.itarget	= 3200000,
+	.additive	= 0,
+	.min		= 20,
+	.max		= 100,
+};
+
+static void slots_fan_tick(void)
+{
+	s32 temp;
+	int speed;
+	int err;
+
+	if (!slots_fan || !slots_temp || !slots_tick)
+		return;
+	if (--slots_tick > 0)
+		return;
+	slots_tick = slots_pid.param.interval;
+
+	DBG_LOTS("* slots fans tick\n");
+
+	err = wf_sensor_get(slots_temp, &temp);
+	if (err) {
+		pr_warning("wf_rm31: slots temp sensor error %d\n", err);
+		failure_state |= FAILURE_SENSOR;
+		wf_control_set_max(slots_fan);
+		return;
+	}
+	speed = wf_pid_run(&slots_pid, temp);
+
+	DBG_LOTS("slots PID temp=%d.%.3d speed=%d\n",
+		 FIX32TOPRINT(temp), speed);
+
+	slots_speed = speed;
+	err = wf_control_set(slots_fan, speed);
+	if (err) {
+		printk(KERN_WARNING "windfarm: slots bay fan error %d\n", err);
+		failure_state |= FAILURE_FAN;
+	}
+}
+
+static void slots_setup_pid(void)
+{
+	/* first time initialize things */
+	s32 fmin = wf_control_get_min(slots_fan);
+	s32 fmax = wf_control_get_max(slots_fan);
+	struct wf_pid_param param = slots_param;
+
+	param.min = max(param.min, fmin);
+	param.max = min(param.max, fmax);
+	wf_pid_init(&slots_pid, &param);
+	slots_tick = 1;
+
+	pr_info("wf_rm31: Slots control loop started.\n");
+}
+
+static void set_fail_state(void)
+{
+	cpu_max_all_fans();
+
+	if (backside_fan)
+		wf_control_set_max(backside_fan);
+	if (slots_fan)
+		wf_control_set_max(slots_fan);
+}
+
+static void rm31_tick(void)
+{
+	int i, last_failure;
+
+	if (!started) {
+		started = 1;
+		printk(KERN_INFO "windfarm: CPUs control loops started.\n");
+		for (i = 0; i < nr_chips; ++i) {
+			if (cpu_setup_pid(i) < 0) {
+				failure_state = FAILURE_PERM;
+				set_fail_state();
+				break;
+			}
+		}
+		DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));
+
+		backside_setup_pid();
+		slots_setup_pid();
+
+#ifdef HACKED_OVERTEMP
+		cpu_all_tmax = 60 << 16;
+#endif
+	}
+
+	/* Permanent failure, bail out */
+	if (failure_state & FAILURE_PERM)
+		return;
+
+	/*
+	 * Clear all failure bits except low overtemp which will be eventually
+	 * cleared by the control loop itself
+	 */
+	last_failure = failure_state;
+	failure_state &= FAILURE_LOW_OVERTEMP;
+	backside_fan_tick();
+	slots_fan_tick();
+
+	/* We do CPUs last because they can be clamped high by
+	 * DIMM temperature
+	 */
+	cpu_fans_tick();
+
+	DBG_LOTS("  last_failure: 0x%x, failure_state: %x\n",
+		 last_failure, failure_state);
+
+	/* Check for failures. Any failure causes cpufreq clamping */
+	if (failure_state && last_failure == 0 && cpufreq_clamp)
+		wf_control_set_max(cpufreq_clamp);
+	if (failure_state == 0 && last_failure && cpufreq_clamp)
+		wf_control_set_min(cpufreq_clamp);
+
+	/* That's it for now, we might want to deal with other failures
+	 * differently in the future though
+	 */
+}
+
+static void rm31_new_control(struct wf_control *ct)
+{
+	bool all_controls;
+
+	if (!strcmp(ct->name, "cpu-fan-a-0"))
+		cpu_fans[0][0] = ct;
+	else if (!strcmp(ct->name, "cpu-fan-b-0"))
+		cpu_fans[0][1] = ct;
+	else if (!strcmp(ct->name, "cpu-fan-c-0"))
+		cpu_fans[0][2] = ct;
+	else if (!strcmp(ct->name, "cpu-fan-a-1"))
+		cpu_fans[1][0] = ct;
+	else if (!strcmp(ct->name, "cpu-fan-b-1"))
+		cpu_fans[1][1] = ct;
+	else if (!strcmp(ct->name, "cpu-fan-c-1"))
+		cpu_fans[1][2] = ct;
+	else if (!strcmp(ct->name, "backside-fan"))
+		backside_fan = ct;
+	else if (!strcmp(ct->name, "slots-fan"))
+		slots_fan = ct;
+	else if (!strcmp(ct->name, "cpufreq-clamp"))
+		cpufreq_clamp = ct;
+
+	all_controls =
+		cpu_fans[0][0] &&
+		cpu_fans[0][1] &&
+		cpu_fans[0][2] &&
+		backside_fan &&
+		slots_fan;
+	if (nr_chips > 1)
+		all_controls &=
+			cpu_fans[1][0] &&
+			cpu_fans[1][1] &&
+			cpu_fans[1][2];
+	have_all_controls = all_controls;
+}
+
+
+static void rm31_new_sensor(struct wf_sensor *sr)
+{
+	bool all_sensors;
+
+	if (!strcmp(sr->name, "cpu-diode-temp-0"))
+		sens_cpu_temp[0] = sr;
+	else if (!strcmp(sr->name, "cpu-diode-temp-1"))
+		sens_cpu_temp[1] = sr;
+	else if (!strcmp(sr->name, "cpu-voltage-0"))
+		sens_cpu_volts[0] = sr;
+	else if (!strcmp(sr->name, "cpu-voltage-1"))
+		sens_cpu_volts[1] = sr;
+	else if (!strcmp(sr->name, "cpu-current-0"))
+		sens_cpu_amps[0] = sr;
+	else if (!strcmp(sr->name, "cpu-current-1"))
+		sens_cpu_amps[1] = sr;
+	else if (!strcmp(sr->name, "backside-temp"))
+		backside_temp = sr;
+	else if (!strcmp(sr->name, "slots-temp"))
+		slots_temp = sr;
+	else if (!strcmp(sr->name, "dimms-temp"))
+		dimms_temp = sr;
+
+	all_sensors =
+		sens_cpu_temp[0] &&
+		sens_cpu_volts[0] &&
+		sens_cpu_amps[0] &&
+		backside_temp &&
+		slots_temp &&
+		dimms_temp;
+	if (nr_chips > 1)
+		all_sensors &=
+			sens_cpu_temp[1] &&
+			sens_cpu_volts[1] &&
+			sens_cpu_amps[1];
+
+	have_all_sensors = all_sensors;
+}
+
+static int rm31_wf_notify(struct notifier_block *self,
+			  unsigned long event, void *data)
+{
+	switch (event) {
+	case WF_EVENT_NEW_SENSOR:
+		rm31_new_sensor(data);
+		break;
+	case WF_EVENT_NEW_CONTROL:
+		rm31_new_control(data);
+		break;
+	case WF_EVENT_TICK:
+		if (have_all_controls && have_all_sensors)
+			rm31_tick();
+	}
+	return 0;
+}
+
+static struct notifier_block rm31_events = {
+	.notifier_call = rm31_wf_notify,
+};
+
+static int wf_rm31_probe(struct platform_device *dev)
+{
+	wf_register_client(&rm31_events);
+	return 0;
+}
+
+static int wf_rm31_remove(struct platform_device *dev)
+{
+	wf_unregister_client(&rm31_events);
+
+	/* should release all sensors and controls */
+	return 0;
+}
+
+static struct platform_driver wf_rm31_driver = {
+	.probe	= wf_rm31_probe,
+	.remove	= wf_rm31_remove,
+	.driver	= {
+		.name = "windfarm",
+	},
+};
+
+static int __init wf_rm31_init(void)
+{
+	struct device_node *cpu;
+	int i;
+
+	if (!of_machine_is_compatible("RackMac3,1"))
+		return -ENODEV;
+
+	/* Count the number of CPU cores */
+	nr_chips = 0;
+	for_each_node_by_type(cpu, "cpu")
+		++nr_chips;
+	if (nr_chips > NR_CHIPS)
+		nr_chips = NR_CHIPS;
+
+	pr_info("windfarm: Initializing for desktop G5 with %d chips\n",
+		nr_chips);
+
+	/* Get MPU data for each CPU */
+	for (i = 0; i < nr_chips; i++) {
+		cpu_mpu_data[i] = wf_get_mpu(i);
+		if (!cpu_mpu_data[i]) {
+			pr_err("wf_rm31: Failed to find MPU data for CPU %d\n", i);
+			return -ENXIO;
+		}
+	}
+
+#ifdef MODULE
+	request_module("windfarm_fcu_controls");
+	request_module("windfarm_lm75_sensor");
+	request_module("windfarm_lm87_sensor");
+	request_module("windfarm_ad7417_sensor");
+	request_module("windfarm_max6690_sensor");
+	request_module("windfarm_cpufreq_clamp");
+#endif /* MODULE */
+
+	platform_driver_register(&wf_rm31_driver);
+	return 0;
+}
+
+static void __exit wf_rm31_exit(void)
+{
+	platform_driver_unregister(&wf_rm31_driver);
+}
+
+module_init(wf_rm31_init);
+module_exit(wf_rm31_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Thermal control for Xserve G5");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:windfarm");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_controls.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_controls.c
new file mode 100644
index 0000000..d174c74
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_controls.c
@@ -0,0 +1,328 @@
+/*
+ * Windfarm PowerMac thermal control. SMU based controls
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/completion.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+
+#define VERSION "0.4"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+static int smu_supports_new_fans_ops = 1;
+
+/*
+ * SMU fans control object
+ */
+
+static LIST_HEAD(smu_fans);
+
+struct smu_fan_control {
+	struct list_head	link;
+	int    			fan_type;	/* 0 = rpm, 1 = pwm */
+	u32			reg;		/* index in SMU */
+	s32			value;		/* current value */
+	s32			min, max;	/* min/max values */
+	struct wf_control	ctrl;
+};
+#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
+
+static int smu_set_fan(int pwm, u8 id, u16 value)
+{
+	struct smu_cmd cmd;
+	u8 buffer[16];
+	DECLARE_COMPLETION_ONSTACK(comp);
+	int rc;
+
+	/* Fill SMU command structure */
+	cmd.cmd = SMU_CMD_FAN_COMMAND;
+
+	/* The SMU has an "old" and a "new" way of setting the fan speed
+	 * Unfortunately, I found no reliable way to know which one works
+	 * on a given machine model. After some investigations it appears
+	 * that MacOS X just tries the new one, and if it fails fallbacks
+	 * to the old ones ... Ugh.
+	 */
+ retry:
+	if (smu_supports_new_fans_ops) {
+		buffer[0] = 0x30;
+		buffer[1] = id;
+		*((u16 *)(&buffer[2])) = value;
+		cmd.data_len = 4;
+	} else {
+		if (id > 7)
+			return -EINVAL;
+		/* Fill argument buffer */
+		memset(buffer, 0, 16);
+		buffer[0] = pwm ? 0x10 : 0x00;
+		buffer[1] = 0x01 << id;
+		*((u16 *)&buffer[2 + id * 2]) = value;
+		cmd.data_len = 14;
+	}
+
+	cmd.reply_len = 16;
+	cmd.data_buf = cmd.reply_buf = buffer;
+	cmd.status = 0;
+	cmd.done = smu_done_complete;
+	cmd.misc = &comp;
+
+	rc = smu_queue_cmd(&cmd);
+	if (rc)
+		return rc;
+	wait_for_completion(&comp);
+
+	/* Handle fallback (see coment above) */
+	if (cmd.status != 0 && smu_supports_new_fans_ops) {
+		printk(KERN_WARNING "windfarm: SMU failed new fan command "
+		       "falling back to old method\n");
+		smu_supports_new_fans_ops = 0;
+		goto retry;
+	}
+
+	return cmd.status;
+}
+
+static void smu_fan_release(struct wf_control *ct)
+{
+	struct smu_fan_control *fct = to_smu_fan(ct);
+
+	kfree(fct);
+}
+
+static int smu_fan_set(struct wf_control *ct, s32 value)
+{
+	struct smu_fan_control *fct = to_smu_fan(ct);
+
+	if (value < fct->min)
+		value = fct->min;
+	if (value > fct->max)
+		value = fct->max;
+	fct->value = value;
+
+	return smu_set_fan(fct->fan_type, fct->reg, value);
+}
+
+static int smu_fan_get(struct wf_control *ct, s32 *value)
+{
+	struct smu_fan_control *fct = to_smu_fan(ct);
+	*value = fct->value; /* todo: read from SMU */
+	return 0;
+}
+
+static s32 smu_fan_min(struct wf_control *ct)
+{
+	struct smu_fan_control *fct = to_smu_fan(ct);
+	return fct->min;
+}
+
+static s32 smu_fan_max(struct wf_control *ct)
+{
+	struct smu_fan_control *fct = to_smu_fan(ct);
+	return fct->max;
+}
+
+static const struct wf_control_ops smu_fan_ops = {
+	.set_value	= smu_fan_set,
+	.get_value	= smu_fan_get,
+	.get_min	= smu_fan_min,
+	.get_max	= smu_fan_max,
+	.release	= smu_fan_release,
+	.owner		= THIS_MODULE,
+};
+
+static struct smu_fan_control *smu_fan_create(struct device_node *node,
+					      int pwm_fan)
+{
+	struct smu_fan_control *fct;
+	const s32 *v;
+	const u32 *reg;
+	const char *l;
+
+	fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
+	if (fct == NULL)
+		return NULL;
+	fct->ctrl.ops = &smu_fan_ops;
+	l = of_get_property(node, "location", NULL);
+	if (l == NULL)
+		goto fail;
+
+	fct->fan_type = pwm_fan;
+	fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
+
+	/* We use the name & location here the same way we do for SMU sensors,
+	 * see the comment in windfarm_smu_sensors.c. The locations are a bit
+	 * less consistent here between the iMac and the desktop models, but
+	 * that is good enough for our needs for now at least.
+	 *
+	 * One problem though is that Apple seem to be inconsistent with case
+	 * and the kernel doesn't have strcasecmp =P
+	 */
+
+	fct->ctrl.name = NULL;
+
+	/* Names used on desktop models */
+	if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
+	    !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") ||
+	    !strcmp(l, "CPU A EXHAUST"))
+		fct->ctrl.name = "cpu-rear-fan-0";
+	else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") ||
+		 !strcmp(l, "CPU B EXHAUST"))
+		fct->ctrl.name = "cpu-rear-fan-1";
+	else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
+		 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") ||
+		 !strcmp(l, "CPU A INTAKE"))
+		fct->ctrl.name = "cpu-front-fan-0";
+	else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") ||
+		 !strcmp(l, "CPU B INTAKE"))
+		fct->ctrl.name = "cpu-front-fan-1";
+	else if (!strcmp(l, "CPU A PUMP"))
+		fct->ctrl.name = "cpu-pump-0";
+	else if (!strcmp(l, "CPU B PUMP"))
+		fct->ctrl.name = "cpu-pump-1";
+	else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") ||
+		 !strcmp(l, "EXPANSION SLOTS INTAKE"))
+		fct->ctrl.name = "slots-fan";
+	else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") ||
+		 !strcmp(l, "DRIVE BAY A INTAKE"))
+		fct->ctrl.name = "drive-bay-fan";
+	else if (!strcmp(l, "BACKSIDE"))
+		fct->ctrl.name = "backside-fan";
+
+	/* Names used on iMac models */
+	if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
+		fct->ctrl.name = "system-fan";
+	else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
+		fct->ctrl.name = "cpu-fan";
+	else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
+		fct->ctrl.name = "drive-bay-fan";
+	else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */
+		fct->ctrl.name = "hard-drive-fan";
+	else if (!strcmp(l, "ODD Fan")) /* same */
+		fct->ctrl.name = "optical-drive-fan";
+
+	/* Unrecognized fan, bail out */
+	if (fct->ctrl.name == NULL)
+		goto fail;
+
+	/* Get min & max values*/
+	v = of_get_property(node, "min-value", NULL);
+	if (v == NULL)
+		goto fail;
+	fct->min = *v;
+	v = of_get_property(node, "max-value", NULL);
+	if (v == NULL)
+		goto fail;
+	fct->max = *v;
+
+	/* Get "reg" value */
+	reg = of_get_property(node, "reg", NULL);
+	if (reg == NULL)
+		goto fail;
+	fct->reg = *reg;
+
+	if (wf_register_control(&fct->ctrl))
+		goto fail;
+
+	return fct;
+ fail:
+	kfree(fct);
+	return NULL;
+}
+
+
+static int __init smu_controls_init(void)
+{
+	struct device_node *smu, *fans, *fan;
+
+	if (!smu_present())
+		return -ENODEV;
+
+	smu = of_find_node_by_type(NULL, "smu");
+	if (smu == NULL)
+		return -ENODEV;
+
+	/* Look for RPM fans */
+	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
+		if (!strcmp(fans->name, "rpm-fans") ||
+		    of_device_is_compatible(fans, "smu-rpm-fans"))
+			break;
+	for (fan = NULL;
+	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
+		struct smu_fan_control *fct;
+
+		fct = smu_fan_create(fan, 0);
+		if (fct == NULL) {
+			printk(KERN_WARNING "windfarm: Failed to create SMU "
+			       "RPM fan %s\n", fan->name);
+			continue;
+		}
+		list_add(&fct->link, &smu_fans);
+	}
+	of_node_put(fans);
+
+
+	/* Look for PWM fans */
+	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
+		if (!strcmp(fans->name, "pwm-fans"))
+			break;
+	for (fan = NULL;
+	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
+		struct smu_fan_control *fct;
+
+		fct = smu_fan_create(fan, 1);
+		if (fct == NULL) {
+			printk(KERN_WARNING "windfarm: Failed to create SMU "
+			       "PWM fan %s\n", fan->name);
+			continue;
+		}
+		list_add(&fct->link, &smu_fans);
+	}
+	of_node_put(fans);
+	of_node_put(smu);
+
+	return 0;
+}
+
+static void __exit smu_controls_exit(void)
+{
+	struct smu_fan_control *fct;
+
+	while (!list_empty(&smu_fans)) {
+		fct = list_entry(smu_fans.next, struct smu_fan_control, link);
+		list_del(&fct->link);
+		wf_unregister_control(&fct->ctrl);
+	}
+}
+
+
+module_init(smu_controls_init);
+module_exit(smu_controls_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sat.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sat.c
new file mode 100644
index 0000000..a0f61eb
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sat.c
@@ -0,0 +1,359 @@
+/*
+ * Windfarm PowerMac thermal control.  SMU "satellite" controller sensors.
+ *
+ * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
+ *
+ * Released under the terms of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <asm/prom.h>
+#include <asm/smu.h>
+#include <asm/pmac_low_i2c.h>
+
+#include "windfarm.h"
+
+#define VERSION "1.0"
+
+/* If the cache is older than 800ms we'll refetch it */
+#define MAX_AGE		msecs_to_jiffies(800)
+
+struct wf_sat {
+	struct kref		ref;
+	int			nr;
+	struct mutex		mutex;
+	unsigned long		last_read; /* jiffies when cache last updated */
+	u8			cache[16];
+	struct list_head	sensors;
+	struct i2c_client	*i2c;
+	struct device_node	*node;
+};
+
+static struct wf_sat *sats[2];
+
+struct wf_sat_sensor {
+	struct list_head	link;
+	int			index;
+	int			index2;		/* used for power sensors */
+	int			shift;
+	struct wf_sat		*sat;
+	struct wf_sensor 	sens;
+};
+
+#define wf_to_sat(c)	container_of(c, struct wf_sat_sensor, sens)
+
+struct smu_sdbp_header *smu_sat_get_sdb_partition(unsigned int sat_id, int id,
+						  unsigned int *size)
+{
+	struct wf_sat *sat;
+	int err;
+	unsigned int i, len;
+	u8 *buf;
+	u8 data[4];
+
+	/* TODO: Add the resulting partition to the device-tree */
+
+	if (sat_id > 1 || (sat = sats[sat_id]) == NULL)
+		return NULL;
+
+	err = i2c_smbus_write_word_data(sat->i2c, 8, id << 8);
+	if (err) {
+		printk(KERN_ERR "smu_sat_get_sdb_part wr error %d\n", err);
+		return NULL;
+	}
+
+	err = i2c_smbus_read_word_data(sat->i2c, 9);
+	if (err < 0) {
+		printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");
+		return NULL;
+	}
+	len = err;
+	if (len == 0) {
+		printk(KERN_ERR "smu_sat_get_sdb_part no partition %x\n", id);
+		return NULL;
+	}
+
+	len = le16_to_cpu(len);
+	len = (len + 3) & ~3;
+	buf = kmalloc(len, GFP_KERNEL);
+	if (buf == NULL)
+		return NULL;
+
+	for (i = 0; i < len; i += 4) {
+		err = i2c_smbus_read_i2c_block_data(sat->i2c, 0xa, 4, data);
+		if (err < 0) {
+			printk(KERN_ERR "smu_sat_get_sdb_part rd err %d\n",
+			       err);
+			goto fail;
+		}
+		buf[i] = data[1];
+		buf[i+1] = data[0];
+		buf[i+2] = data[3];
+		buf[i+3] = data[2];
+	}
+
+	printk(KERN_DEBUG "sat %d partition %x:", sat_id, id);
+	print_hex_dump(KERN_DEBUG, "  ", DUMP_PREFIX_OFFSET,
+		       16, 1, buf, len, false);
+	if (size)
+		*size = len;
+	return (struct smu_sdbp_header *) buf;
+
+ fail:
+	kfree(buf);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(smu_sat_get_sdb_partition);
+
+/* refresh the cache */
+static int wf_sat_read_cache(struct wf_sat *sat)
+{
+	int err;
+
+	err = i2c_smbus_read_i2c_block_data(sat->i2c, 0x3f, 16, sat->cache);
+	if (err < 0)
+		return err;
+	sat->last_read = jiffies;
+
+#ifdef LOTSA_DEBUG
+	{
+		int i;
+		printk(KERN_DEBUG "wf_sat_get: data is");
+		print_hex_dump(KERN_DEBUG, "  ", DUMP_PREFIX_OFFSET,
+			       16, 1, sat->cache, 16, false);
+	}
+#endif
+	return 0;
+}
+
+static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value)
+{
+	struct wf_sat_sensor *sens = wf_to_sat(sr);
+	struct wf_sat *sat = sens->sat;
+	int i, err;
+	s32 val;
+
+	if (sat->i2c == NULL)
+		return -ENODEV;
+
+	mutex_lock(&sat->mutex);
+	if (time_after(jiffies, (sat->last_read + MAX_AGE))) {
+		err = wf_sat_read_cache(sat);
+		if (err)
+			goto fail;
+	}
+
+	i = sens->index * 2;
+	val = ((sat->cache[i] << 8) + sat->cache[i+1]) << sens->shift;
+	if (sens->index2 >= 0) {
+		i = sens->index2 * 2;
+		/* 4.12 * 8.8 -> 12.20; shift right 4 to get 16.16 */
+		val = (val * ((sat->cache[i] << 8) + sat->cache[i+1])) >> 4;
+	}
+
+	*value = val;
+	err = 0;
+
+ fail:
+	mutex_unlock(&sat->mutex);
+	return err;
+}
+
+static void wf_sat_release(struct kref *ref)
+{
+	struct wf_sat *sat = container_of(ref, struct wf_sat, ref);
+
+	if (sat->nr >= 0)
+		sats[sat->nr] = NULL;
+	kfree(sat);
+}
+
+static void wf_sat_sensor_release(struct wf_sensor *sr)
+{
+	struct wf_sat_sensor *sens = wf_to_sat(sr);
+	struct wf_sat *sat = sens->sat;
+
+	kfree(sens);
+	kref_put(&sat->ref, wf_sat_release);
+}
+
+static const struct wf_sensor_ops wf_sat_ops = {
+	.get_value	= wf_sat_sensor_get,
+	.release	= wf_sat_sensor_release,
+	.owner		= THIS_MODULE,
+};
+
+static int wf_sat_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct device_node *dev = client->dev.of_node;
+	struct wf_sat *sat;
+	struct wf_sat_sensor *sens;
+	const u32 *reg;
+	const char *loc, *type;
+	u8 chip, core;
+	struct device_node *child;
+	int shift, cpu, index;
+	char *name;
+	int vsens[2], isens[2];
+
+	sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);
+	if (sat == NULL)
+		return -ENOMEM;
+	sat->nr = -1;
+	sat->node = of_node_get(dev);
+	kref_init(&sat->ref);
+	mutex_init(&sat->mutex);
+	sat->i2c = client;
+	INIT_LIST_HEAD(&sat->sensors);
+	i2c_set_clientdata(client, sat);
+
+	vsens[0] = vsens[1] = -1;
+	isens[0] = isens[1] = -1;
+	child = NULL;
+	while ((child = of_get_next_child(dev, child)) != NULL) {
+		reg = of_get_property(child, "reg", NULL);
+		type = of_get_property(child, "device_type", NULL);
+		loc = of_get_property(child, "location", NULL);
+		if (reg == NULL || loc == NULL)
+			continue;
+
+		/* the cooked sensors are between 0x30 and 0x37 */
+		if (*reg < 0x30 || *reg > 0x37)
+			continue;
+		index = *reg - 0x30;
+
+		/* expect location to be CPU [AB][01] ... */
+		if (strncmp(loc, "CPU ", 4) != 0)
+			continue;
+		chip = loc[4] - 'A';
+		core = loc[5] - '0';
+		if (chip > 1 || core > 1) {
+			printk(KERN_ERR "wf_sat_create: don't understand "
+			       "location %s for %pOF\n", loc, child);
+			continue;
+		}
+		cpu = 2 * chip + core;
+		if (sat->nr < 0)
+			sat->nr = chip;
+		else if (sat->nr != chip) {
+			printk(KERN_ERR "wf_sat_create: can't cope with "
+			       "multiple CPU chips on one SAT (%s)\n", loc);
+			continue;
+		}
+
+		if (strcmp(type, "voltage-sensor") == 0) {
+			name = "cpu-voltage";
+			shift = 4;
+			vsens[core] = index;
+		} else if (strcmp(type, "current-sensor") == 0) {
+			name = "cpu-current";
+			shift = 8;
+			isens[core] = index;
+		} else if (strcmp(type, "temp-sensor") == 0) {
+			name = "cpu-temp";
+			shift = 10;
+		} else
+			continue;	/* hmmm shouldn't happen */
+
+		/* the +16 is enough for "cpu-voltage-n" */
+		sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
+		if (sens == NULL) {
+			printk(KERN_ERR "wf_sat_create: couldn't create "
+			       "%s sensor %d (no memory)\n", name, cpu);
+			continue;
+		}
+		sens->index = index;
+		sens->index2 = -1;
+		sens->shift = shift;
+		sens->sat = sat;
+		sens->sens.ops = &wf_sat_ops;
+		sens->sens.name = (char *) (sens + 1);
+		snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu);
+
+		if (wf_register_sensor(&sens->sens))
+			kfree(sens);
+		else {
+			list_add(&sens->link, &sat->sensors);
+			kref_get(&sat->ref);
+		}
+	}
+
+	/* make the power sensors */
+	for (core = 0; core < 2; ++core) {
+		if (vsens[core] < 0 || isens[core] < 0)
+			continue;
+		cpu = 2 * sat->nr + core;
+		sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
+		if (sens == NULL) {
+			printk(KERN_ERR "wf_sat_create: couldn't create power "
+			       "sensor %d (no memory)\n", cpu);
+			continue;
+		}
+		sens->index = vsens[core];
+		sens->index2 = isens[core];
+		sens->shift = 0;
+		sens->sat = sat;
+		sens->sens.ops = &wf_sat_ops;
+		sens->sens.name = (char *) (sens + 1);
+		snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu);
+
+		if (wf_register_sensor(&sens->sens))
+			kfree(sens);
+		else {
+			list_add(&sens->link, &sat->sensors);
+			kref_get(&sat->ref);
+		}
+	}
+
+	if (sat->nr >= 0)
+		sats[sat->nr] = sat;
+
+	return 0;
+}
+
+static int wf_sat_remove(struct i2c_client *client)
+{
+	struct wf_sat *sat = i2c_get_clientdata(client);
+	struct wf_sat_sensor *sens;
+
+	/* release sensors */
+	while(!list_empty(&sat->sensors)) {
+		sens = list_first_entry(&sat->sensors,
+					struct wf_sat_sensor, link);
+		list_del(&sens->link);
+		wf_unregister_sensor(&sens->sens);
+	}
+	sat->i2c = NULL;
+	kref_put(&sat->ref, wf_sat_release);
+
+	return 0;
+}
+
+static const struct i2c_device_id wf_sat_id[] = {
+	{ "MAC,smu-sat", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wf_sat_id);
+
+static struct i2c_driver wf_sat_driver = {
+	.driver = {
+		.name		= "wf_smu_sat",
+	},
+	.probe		= wf_sat_probe,
+	.remove		= wf_sat_remove,
+	.id_table	= wf_sat_id,
+};
+
+module_i2c_driver(wf_sat_driver);
+
+MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
+MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sensors.c b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sensors.c
new file mode 100644
index 0000000..172fd26
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/macintosh/windfarm_smu_sensors.c
@@ -0,0 +1,482 @@
+/*
+ * Windfarm PowerMac thermal control. SMU based sensors
+ *
+ * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
+ *                    <benh@kernel.crashing.org>
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/completion.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/smu.h>
+
+#include "windfarm.h"
+
+#define VERSION "0.2"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...)	printk(args)
+#else
+#define DBG(args...)	do { } while(0)
+#endif
+
+/*
+ * Various SMU "partitions" calibration objects for which we
+ * keep pointers here for use by bits & pieces of the driver
+ */
+static struct smu_sdbp_cpuvcp *cpuvcp;
+static int  cpuvcp_version;
+static struct smu_sdbp_cpudiode *cpudiode;
+static struct smu_sdbp_slotspow *slotspow;
+static u8 *debugswitches;
+
+/*
+ * SMU basic sensors objects
+ */
+
+static LIST_HEAD(smu_ads);
+
+struct smu_ad_sensor {
+	struct list_head	link;
+	u32			reg;		/* index in SMU */
+	struct wf_sensor	sens;
+};
+#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
+
+static void smu_ads_release(struct wf_sensor *sr)
+{
+	struct smu_ad_sensor *ads = to_smu_ads(sr);
+
+	kfree(ads);
+}
+
+static int smu_read_adc(u8 id, s32 *value)
+{
+	struct smu_simple_cmd	cmd;
+	DECLARE_COMPLETION_ONSTACK(comp);
+	int rc;
+
+	rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
+			      smu_done_complete, &comp, id);
+	if (rc)
+		return rc;
+	wait_for_completion(&comp);
+	if (cmd.cmd.status != 0)
+		return cmd.cmd.status;
+	if (cmd.cmd.reply_len != 2) {
+		printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
+		       id, cmd.cmd.reply_len);
+		return -EIO;
+	}
+	*value = *((u16 *)cmd.buffer);
+	return 0;
+}
+
+static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
+{
+	struct smu_ad_sensor *ads = to_smu_ads(sr);
+	int rc;
+	s32 val;
+	s64 scaled;
+
+	rc = smu_read_adc(ads->reg, &val);
+	if (rc) {
+		printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
+		       rc);
+		return rc;
+	}
+
+	/* Ok, we have to scale & adjust, taking units into account */
+	scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
+	scaled >>= 3;
+	scaled += ((s64)cpudiode->b_value) << 9;
+	*value = (s32)(scaled << 1);
+
+	return 0;
+}
+
+static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
+{
+	struct smu_ad_sensor *ads = to_smu_ads(sr);
+	s32 val, scaled;
+	int rc;
+
+	rc = smu_read_adc(ads->reg, &val);
+	if (rc) {
+		printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
+		       rc);
+		return rc;
+	}
+
+	/* Ok, we have to scale & adjust, taking units into account */
+	scaled = (s32)(val * (u32)cpuvcp->curr_scale);
+	scaled += (s32)cpuvcp->curr_offset;
+	*value = scaled << 4;
+
+	return 0;
+}
+
+static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
+{
+	struct smu_ad_sensor *ads = to_smu_ads(sr);
+	s32 val, scaled;
+	int rc;
+
+	rc = smu_read_adc(ads->reg, &val);
+	if (rc) {
+		printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
+		       rc);
+		return rc;
+	}
+
+	/* Ok, we have to scale & adjust, taking units into account */
+	scaled = (s32)(val * (u32)cpuvcp->volt_scale);
+	scaled += (s32)cpuvcp->volt_offset;
+	*value = scaled << 4;
+
+	return 0;
+}
+
+static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
+{
+	struct smu_ad_sensor *ads = to_smu_ads(sr);
+	s32 val, scaled;
+	int rc;
+
+	rc = smu_read_adc(ads->reg, &val);
+	if (rc) {
+		printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
+		       rc);
+		return rc;
+	}
+
+	/* Ok, we have to scale & adjust, taking units into account */
+	scaled = (s32)(val * (u32)slotspow->pow_scale);
+	scaled += (s32)slotspow->pow_offset;
+	*value = scaled << 4;
+
+	return 0;
+}
+
+
+static const struct wf_sensor_ops smu_cputemp_ops = {
+	.get_value	= smu_cputemp_get,
+	.release	= smu_ads_release,
+	.owner		= THIS_MODULE,
+};
+static const struct wf_sensor_ops smu_cpuamp_ops = {
+	.get_value	= smu_cpuamp_get,
+	.release	= smu_ads_release,
+	.owner		= THIS_MODULE,
+};
+static const struct wf_sensor_ops smu_cpuvolt_ops = {
+	.get_value	= smu_cpuvolt_get,
+	.release	= smu_ads_release,
+	.owner		= THIS_MODULE,
+};
+static const struct wf_sensor_ops smu_slotspow_ops = {
+	.get_value	= smu_slotspow_get,
+	.release	= smu_ads_release,
+	.owner		= THIS_MODULE,
+};
+
+
+static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
+{
+	struct smu_ad_sensor *ads;
+	const char *c, *l;
+	const u32 *v;
+
+	ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
+	if (ads == NULL)
+		return NULL;
+	c = of_get_property(node, "device_type", NULL);
+	l = of_get_property(node, "location", NULL);
+	if (c == NULL || l == NULL)
+		goto fail;
+
+	/* We currently pick the sensors based on the OF name and location
+	 * properties, while Darwin uses the sensor-id's.
+	 * The problem with the IDs is that they are model specific while it
+	 * looks like apple has been doing a reasonably good job at keeping
+	 * the names and locations consistents so I'll stick with the names
+	 * and locations for now.
+	 */
+	if (!strcmp(c, "temp-sensor") &&
+	    !strcmp(l, "CPU T-Diode")) {
+		ads->sens.ops = &smu_cputemp_ops;
+		ads->sens.name = "cpu-temp";
+		if (cpudiode == NULL) {
+			DBG("wf: cpudiode partition (%02x) not found\n",
+			    SMU_SDB_CPUDIODE_ID);
+			goto fail;
+		}
+	} else if (!strcmp(c, "current-sensor") &&
+		   !strcmp(l, "CPU Current")) {
+		ads->sens.ops = &smu_cpuamp_ops;
+		ads->sens.name = "cpu-current";
+		if (cpuvcp == NULL) {
+			DBG("wf: cpuvcp partition (%02x) not found\n",
+			    SMU_SDB_CPUVCP_ID);
+			goto fail;
+		}
+	} else if (!strcmp(c, "voltage-sensor") &&
+		   !strcmp(l, "CPU Voltage")) {
+		ads->sens.ops = &smu_cpuvolt_ops;
+		ads->sens.name = "cpu-voltage";
+		if (cpuvcp == NULL) {
+			DBG("wf: cpuvcp partition (%02x) not found\n",
+			    SMU_SDB_CPUVCP_ID);
+			goto fail;
+		}
+	} else if (!strcmp(c, "power-sensor") &&
+		   !strcmp(l, "Slots Power")) {
+		ads->sens.ops = &smu_slotspow_ops;
+		ads->sens.name = "slots-power";
+		if (slotspow == NULL) {
+			DBG("wf: slotspow partition (%02x) not found\n",
+			    SMU_SDB_SLOTSPOW_ID);
+			goto fail;
+		}
+	} else
+		goto fail;
+
+	v = of_get_property(node, "reg", NULL);
+	if (v == NULL)
+		goto fail;
+	ads->reg = *v;
+
+	if (wf_register_sensor(&ads->sens))
+		goto fail;
+	return ads;
+ fail:
+	kfree(ads);
+	return NULL;
+}
+
+/*
+ * SMU Power combo sensor object
+ */
+
+struct smu_cpu_power_sensor {
+	struct list_head	link;
+	struct wf_sensor	*volts;
+	struct wf_sensor	*amps;
+	int			fake_volts : 1;
+	int			quadratic : 1;
+	struct wf_sensor	sens;
+};
+#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
+
+static struct smu_cpu_power_sensor *smu_cpu_power;
+
+static void smu_cpu_power_release(struct wf_sensor *sr)
+{
+	struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
+
+	if (pow->volts)
+		wf_put_sensor(pow->volts);
+	if (pow->amps)
+		wf_put_sensor(pow->amps);
+	kfree(pow);
+}
+
+static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
+{
+	struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
+	s32 volts, amps, power;
+	u64 tmps, tmpa, tmpb;
+	int rc;
+
+	rc = pow->amps->ops->get_value(pow->amps, &amps);
+	if (rc)
+		return rc;
+
+	if (pow->fake_volts) {
+		*value = amps * 12 - 0x30000;
+		return 0;
+	}
+
+	rc = pow->volts->ops->get_value(pow->volts, &volts);
+	if (rc)
+		return rc;
+
+	power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
+	if (!pow->quadratic) {
+		*value = power;
+		return 0;
+	}
+	tmps = (((u64)power) * ((u64)power)) >> 16;
+	tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
+	tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
+	*value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
+
+	return 0;
+}
+
+static const struct wf_sensor_ops smu_cpu_power_ops = {
+	.get_value	= smu_cpu_power_get,
+	.release	= smu_cpu_power_release,
+	.owner		= THIS_MODULE,
+};
+
+
+static struct smu_cpu_power_sensor *
+smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
+{
+	struct smu_cpu_power_sensor *pow;
+
+	pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
+	if (pow == NULL)
+		return NULL;
+	pow->sens.ops = &smu_cpu_power_ops;
+	pow->sens.name = "cpu-power";
+
+	wf_get_sensor(volts);
+	pow->volts = volts;
+	wf_get_sensor(amps);
+	pow->amps = amps;
+
+	/* Some early machines need a faked voltage */
+	if (debugswitches && ((*debugswitches) & 0x80)) {
+		printk(KERN_INFO "windfarm: CPU Power sensor using faked"
+		       " voltage !\n");
+		pow->fake_volts = 1;
+	} else
+		pow->fake_volts = 0;
+
+	/* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
+	 * I yet have to figure out what's up with 8,2 and will have to
+	 * adjust for later, unless we can 100% trust the SDB partition...
+	 */
+	if ((of_machine_is_compatible("PowerMac8,1") ||
+	     of_machine_is_compatible("PowerMac8,2") ||
+	     of_machine_is_compatible("PowerMac9,1")) &&
+	    cpuvcp_version >= 2) {
+		pow->quadratic = 1;
+		DBG("windfarm: CPU Power using quadratic transform\n");
+	} else
+		pow->quadratic = 0;
+
+	if (wf_register_sensor(&pow->sens))
+		goto fail;
+	return pow;
+ fail:
+	kfree(pow);
+	return NULL;
+}
+
+static void smu_fetch_param_partitions(void)
+{
+	const struct smu_sdbp_header *hdr;
+
+	/* Get CPU voltage/current/power calibration data */
+	hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
+	if (hdr != NULL) {
+		cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
+		/* Keep version around */
+		cpuvcp_version = hdr->version;
+	}
+
+	/* Get CPU diode calibration data */
+	hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
+	if (hdr != NULL)
+		cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
+
+	/* Get slots power calibration data if any */
+	hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
+	if (hdr != NULL)
+		slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
+
+	/* Get debug switches if any */
+	hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
+	if (hdr != NULL)
+		debugswitches = (u8 *)&hdr[1];
+}
+
+static int __init smu_sensors_init(void)
+{
+	struct device_node *smu, *sensors, *s;
+	struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
+
+	if (!smu_present())
+		return -ENODEV;
+
+	/* Get parameters partitions */
+	smu_fetch_param_partitions();
+
+	smu = of_find_node_by_type(NULL, "smu");
+	if (smu == NULL)
+		return -ENODEV;
+
+	/* Look for sensors subdir */
+	for (sensors = NULL;
+	     (sensors = of_get_next_child(smu, sensors)) != NULL;)
+		if (!strcmp(sensors->name, "sensors"))
+			break;
+
+	of_node_put(smu);
+
+	/* Create basic sensors */
+	for (s = NULL;
+	     sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
+		struct smu_ad_sensor *ads;
+
+		ads = smu_ads_create(s);
+		if (ads == NULL)
+			continue;
+		list_add(&ads->link, &smu_ads);
+		/* keep track of cpu voltage & current */
+		if (!strcmp(ads->sens.name, "cpu-voltage"))
+			volt_sensor = ads;
+		else if (!strcmp(ads->sens.name, "cpu-current"))
+			curr_sensor = ads;
+	}
+
+	of_node_put(sensors);
+
+	/* Create CPU power sensor if possible */
+	if (volt_sensor && curr_sensor)
+		smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
+						     &curr_sensor->sens);
+
+	return 0;
+}
+
+static void __exit smu_sensors_exit(void)
+{
+	struct smu_ad_sensor *ads;
+
+	/* dispose of power sensor */
+	if (smu_cpu_power)
+		wf_unregister_sensor(&smu_cpu_power->sens);
+
+	/* dispose of basic sensors */
+	while (!list_empty(&smu_ads)) {
+		ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
+		list_del(&ads->link);
+		wf_unregister_sensor(&ads->sens);
+	}
+}
+
+
+module_init(smu_sensors_init);
+module_exit(smu_sensors_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+