summaryrefslogtreecommitdiffstats
path: root/drivers/macintosh
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/macintosh')
-rw-r--r--drivers/macintosh/Kconfig256
-rw-r--r--drivers/macintosh/Makefile50
-rw-r--r--drivers/macintosh/adb-iop.c286
-rw-r--r--drivers/macintosh/adb.c869
-rw-r--r--drivers/macintosh/adbhid.c1287
-rw-r--r--drivers/macintosh/ans-lcd.c190
-rw-r--r--drivers/macintosh/ans-lcd.h11
-rw-r--r--drivers/macintosh/apm_emu.c119
-rw-r--r--drivers/macintosh/mac_hid.c153
-rw-r--r--drivers/macintosh/macio-adb.c284
-rw-r--r--drivers/macintosh/macio_asic.c733
-rw-r--r--drivers/macintosh/macio_sysfs.c74
-rw-r--r--drivers/macintosh/mediabay.c837
-rw-r--r--drivers/macintosh/nvram.c131
-rw-r--r--drivers/macintosh/rack-meter.c615
-rw-r--r--drivers/macintosh/smu.c1333
-rw-r--r--drivers/macintosh/therm_adt746x.c669
-rw-r--r--drivers/macintosh/therm_pm72.c2279
-rw-r--r--drivers/macintosh/therm_pm72.h326
-rw-r--r--drivers/macintosh/therm_windtunnel.c534
-rw-r--r--drivers/macintosh/via-cuda.c633
-rw-r--r--drivers/macintosh/via-macii.c551
-rw-r--r--drivers/macintosh/via-maciisi.c678
-rw-r--r--drivers/macintosh/via-pmu-backlight.c191
-rw-r--r--drivers/macintosh/via-pmu-event.c80
-rw-r--r--drivers/macintosh/via-pmu-event.h8
-rw-r--r--drivers/macintosh/via-pmu-led.c115
-rw-r--r--drivers/macintosh/via-pmu.c2540
-rw-r--r--drivers/macintosh/via-pmu68k.c819
-rw-r--r--drivers/macintosh/windfarm.h134
-rw-r--r--drivers/macintosh/windfarm_core.c495
-rw-r--r--drivers/macintosh/windfarm_cpufreq_clamp.c112
-rw-r--r--drivers/macintosh/windfarm_lm75_sensor.c242
-rw-r--r--drivers/macintosh/windfarm_max6690_sensor.c187
-rw-r--r--drivers/macintosh/windfarm_pid.c149
-rw-r--r--drivers/macintosh/windfarm_pid.h85
-rw-r--r--drivers/macintosh/windfarm_pm112.c714
-rw-r--r--drivers/macintosh/windfarm_pm121.c1040
-rw-r--r--drivers/macintosh/windfarm_pm81.c813
-rw-r--r--drivers/macintosh/windfarm_pm91.c745
-rw-r--r--drivers/macintosh/windfarm_smu_controls.c327
-rw-r--r--drivers/macintosh/windfarm_smu_sat.c395
-rw-r--r--drivers/macintosh/windfarm_smu_sensors.c483
43 files changed, 22572 insertions, 0 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig
new file mode 100644
index 0000000..b526596
--- /dev/null
+++ b/drivers/macintosh/Kconfig
@@ -0,0 +1,256 @@
+
+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_MACIISI
+ bool "Include Mac IIsi ADB driver"
+ depends on ADB && MAC
+ help
+ Say Y here if want your kernel to support Macintosh systems that use
+ the Mac IIsi style ADB. This includes the IIsi, IIvi, IIvx, Classic
+ II, LC, LC II, LC III, Performa 460, and the Performa 600.
+
+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 based Macs and PowerMacs"
+ depends on (ADB || PPC_PMAC) && !PPC_PMAC64
+ help
+ This provides support for CUDA based Macintosh and Power Macintosh
+ systems. This includes many m68k based Macs (Color Classic, Mac TV,
+ Performa 475, Performa 520, Performa 550, Performa 575,
+ Performa 588, Quadra 605, Quadra 630, Quadra/Centris 660AV, and
+ Quadra 840AV), most OldWorld 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.
+
+ 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 ide-disk LED trigger and configure appropriately through
+ sysfs.
+
+config ADB_PMU_LED_IDE
+ bool "Use front LED as IDE LED by default"
+ depends on ADB_PMU_LED
+ select LEDS_TRIGGERS
+ select LEDS_TRIGGER_IDE_DISK
+ help
+ This option makes the front LED default to the IDE trigger
+ so that it blinks on IDE 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
+ 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 an 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 "Keyboard support" (CONFIG_INPUT_KEYBDEV),
+ "Mouse Support" (CONFIG_INPUT_MOUSEDEV) and "Event interface
+ support" (CONFIG_INPUT_EVDEV) as well.
+
+ If unsure, say Y.
+
+config MAC_EMUMOUSEBTN
+ bool "Support for mouse button 2+3 emulation"
+ select 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.
+
+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 THERM_PM72
+ tristate "Support for thermal management on PowerMac G5"
+ depends on I2C && I2C_POWERMAC && PPC_PMAC64
+ help
+ This driver provides thermostat and fan control for the desktop
+ G5 machines.
+
+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_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.
+
+endif # MACINTOSH_DRIVERS
diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile
new file mode 100644
index 0000000..e3132ef
--- /dev/null
+++ b/drivers/macintosh/Makefile
@@ -0,0 +1,50 @@
+#
+# 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_MACIISI) += via-maciisi.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_PM72) += therm_pm72.o
+obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
+obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o
+obj-$(CONFIG_WINDFARM) += windfarm_core.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
diff --git a/drivers/macintosh/adb-iop.c b/drivers/macintosh/adb-iop.c
new file mode 100644
index 0000000..4446966
--- /dev/null
+++ b/drivers/macintosh/adb-iop.c
@@ -0,0 +1,286 @@
+/*
+ * 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;
+ uint 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;
+ uint 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/drivers/macintosh/adb.c b/drivers/macintosh/adb.c
new file mode 100644
index 0000000..23741ce
--- /dev/null
+++ b/drivers/macintosh/adb.c
@@ -0,0 +1,869 @@
+/*
+ * 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.h>
+#include <linux/smp_lock.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 <asm/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_maciisi_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 struct adb_driver *adb_driver_list[] = {
+#ifdef CONFIG_ADB_MACII
+ &via_macii_driver,
+#endif
+#ifdef CONFIG_ADB_MACIISI
+ &via_maciisi_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 DECLARE_MUTEX(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;
+}
+
+/*
+ * 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;
+}
+#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()) {
+ printk(KERN_WARNING "Warning: no ADB interface detected\n");
+ adb_controller = NULL;
+ } else {
+#ifdef CONFIG_PPC
+ if (machine_is_compatible("AAPL,PowerBook1998") ||
+ 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();
+}
+
+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;
+}
+
+ /* 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;
+}
+
+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;
+}
+
+void
+adb_input(unsigned char *buf, int nb, int autopoll)
+{
+ int i, id;
+ static int dump_adb_input = 0;
+ 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;
+}
+
+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;
+
+ lock_kernel();
+ 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:
+ unlock_kernel();
+ return ret;
+}
+
+static int adb_release(struct inode *inode, struct file *file)
+{
+ struct adbdev_state *state = file->private_data;
+ unsigned long flags;
+
+ lock_kernel();
+ 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);
+ }
+ }
+ unlock_kernel();
+ 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;
+ wait_queue_t wait = __WAITQUEUE_INITIALIZER(wait,current);
+ unsigned long flags;
+
+ if (count < 2)
+ return -EINVAL;
+ if (count > sizeof(req->reply))
+ count = sizeof(req->reply);
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+
+ req = NULL;
+ spin_lock_irqsave(&state->lock, flags);
+ add_wait_queue(&state->wait_queue, &wait);
+ 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);
+ }
+
+ 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;
+ if (!access_ok(VERIFY_READ, buf, count))
+ return -EFAULT;
+
+ 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,
+};
+
+static struct platform_driver adb_pfdrv = {
+ .driver = {
+ .name = "adb",
+ },
+#ifdef CONFIG_PM
+ .suspend = adb_suspend,
+ .resume = adb_resume,
+#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/drivers/macintosh/adbhid.c b/drivers/macintosh/adbhid.c
new file mode 100644
index 0000000..5396c67
--- /dev/null
+++ b/drivers/macintosh/adbhid.c
@@ -0,0 +1,1287 @@
+/*
+ * 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 everytime. */
+ 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/drivers/macintosh/ans-lcd.c b/drivers/macintosh/ans-lcd.c
new file mode 100644
index 0000000..6a82218
--- /dev/null
+++ b/drivers/macintosh/ans-lcd.c
@@ -0,0 +1,190 @@
+/*
+ * /dev/lcd driver for Apple Network Servers.
+ */
+
+#include <linux/types.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+
+#include <asm/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;
+
+#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;
+ for ( i = *ppos; count > 0; ++i, ++p, --count )
+ {
+ char c;
+ __get_user(c, p);
+ anslcd_write_byte_data( c );
+ }
+ *ppos = i;
+ return p - buf;
+}
+
+static int
+anslcd_ioctl( struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg )
+{
+ char ch, __user *temp;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg);
+#endif
+
+ 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 );
+ return 0;
+ 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);
+ }
+ return 0;
+ case ANSLCD_SETSHORTDELAY:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+ anslcd_short_delay=arg;
+ return 0;
+ case ANSLCD_SETLONGDELAY:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+ anslcd_long_delay=arg;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int
+anslcd_open( struct inode * inode, struct file * file )
+{
+ cycle_kernel_lock();
+ return 0;
+}
+
+const struct file_operations anslcd_fops = {
+ .write = anslcd_write,
+ .ioctl = anslcd_ioctl,
+ .open = anslcd_open,
+};
+
+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
+
+ 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]);
+ }
+ 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/drivers/macintosh/ans-lcd.h b/drivers/macintosh/ans-lcd.h
new file mode 100644
index 0000000..d795b9f
--- /dev/null
+++ b/drivers/macintosh/ans-lcd.h
@@ -0,0 +1,11 @@
+#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/drivers/macintosh/apm_emu.c b/drivers/macintosh/apm_emu.c
new file mode 100644
index 0000000..9821e63
--- /dev/null
+++ b/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/drivers/macintosh/mac_hid.c b/drivers/macintosh/mac_hid.c
new file mode 100644
index 0000000..cc9f275
--- /dev/null
+++ b/drivers/macintosh/mac_hid.c
@@ -0,0 +1,153 @@
+/*
+ * 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/kbd_kern.h>
+
+
+static struct input_dev *emumousebtn;
+static int emumousebtn_input_register(void);
+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 int mouse_last_keycode;
+
+#if defined(CONFIG_SYSCTL)
+/* file(s) in /proc/sys/dev/mac_hid */
+static ctl_table mac_hid_files[] = {
+ {
+ .ctl_name = DEV_MAC_HID_MOUSE_BUTTON_EMULATION,
+ .procname = "mouse_button_emulation",
+ .data = &mouse_emulate_buttons,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = DEV_MAC_HID_MOUSE_BUTTON2_KEYCODE,
+ .procname = "mouse_button2_keycode",
+ .data = &mouse_button2_keycode,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = DEV_MAC_HID_MOUSE_BUTTON3_KEYCODE,
+ .procname = "mouse_button3_keycode",
+ .data = &mouse_button3_keycode,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ { .ctl_name = 0 }
+};
+
+/* dir in /proc/sys/dev */
+static ctl_table mac_hid_dir[] = {
+ {
+ .ctl_name = DEV_MAC_HID,
+ .procname = "mac_hid",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = mac_hid_files,
+ },
+ { .ctl_name = 0 }
+};
+
+/* /proc/sys/dev itself, in case that is not there yet */
+static ctl_table mac_hid_root_dir[] = {
+ {
+ .ctl_name = CTL_DEV,
+ .procname = "dev",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = mac_hid_dir,
+ },
+ { .ctl_name = 0 }
+};
+
+static struct ctl_table_header *mac_hid_sysctl_header;
+
+#endif /* endif CONFIG_SYSCTL */
+
+int mac_hid_mouse_emulate_buttons(int caller, unsigned int keycode, int down)
+{
+ switch (caller) {
+ case 1:
+ /* Called from keyboard.c */
+ if (mouse_emulate_buttons
+ && (keycode == mouse_button2_keycode
+ || keycode == mouse_button3_keycode)) {
+ if (mouse_emulate_buttons == 1) {
+ input_report_key(emumousebtn,
+ keycode == mouse_button2_keycode ? BTN_MIDDLE : BTN_RIGHT,
+ down);
+ input_sync(emumousebtn);
+ return 1;
+ }
+ mouse_last_keycode = down ? keycode : 0;
+ }
+ break;
+ }
+ return 0;
+}
+
+static struct lock_class_key emumousebtn_event_class;
+static struct lock_class_key emumousebtn_mutex_class;
+
+static int emumousebtn_input_register(void)
+{
+ int ret;
+
+ emumousebtn = input_allocate_device();
+ if (!emumousebtn)
+ return -ENOMEM;
+
+ lockdep_set_class(&emumousebtn->event_lock, &emumousebtn_event_class);
+ lockdep_set_class(&emumousebtn->mutex, &emumousebtn_mutex_class);
+
+ emumousebtn->name = "Macintosh mouse button emulation";
+ emumousebtn->id.bustype = BUS_ADB;
+ emumousebtn->id.vendor = 0x0001;
+ emumousebtn->id.product = 0x0001;
+ emumousebtn->id.version = 0x0100;
+
+ emumousebtn->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+ emumousebtn->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
+ BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT);
+ emumousebtn->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+
+ ret = input_register_device(emumousebtn);
+ if (ret)
+ input_free_device(emumousebtn);
+
+ return ret;
+}
+
+static int __init mac_hid_init(void)
+{
+ int err;
+
+ err = emumousebtn_input_register();
+ if (err)
+ return err;
+
+#if defined(CONFIG_SYSCTL)
+ mac_hid_sysctl_header = register_sysctl_table(mac_hid_root_dir);
+#endif /* CONFIG_SYSCTL */
+
+ return 0;
+}
+
+device_initcall(mac_hid_init);
diff --git a/drivers/macintosh/macio-adb.c b/drivers/macintosh/macio-adb.c
new file mode 100644
index 0000000..79119f5
--- /dev/null
+++ b/drivers/macintosh/macio-adb.c
@@ -0,0 +1,284 @@
+/*
+ * 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 <asm/system.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 seldomly 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);
+ 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/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c
new file mode 100644
index 0000000..ec9e5f3
--- /dev/null
+++ b/drivers/macintosh/macio_asic.c
@@ -0,0 +1,733 @@
+/*
+ * 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 <asm/machdep.h>
+#include <asm/macio.h>
+#include <asm/pmac_feature.h>
+#include <asm/prom.h>
+#include <asm/pci-bridge.h>
+
+#undef DEBUG
+
+#define MAX_NODE_NAME_SIZE (BUS_ID_SIZE - 12)
+
+static struct macio_chip *macio_on_hold;
+
+static int macio_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct macio_dev * macio_dev = to_macio_device(dev);
+ struct macio_driver * macio_drv = to_macio_driver(drv);
+ const struct of_device_id * matches = macio_drv->match_table;
+
+ if (!matches)
+ return 0;
+
+ return of_match_device(matches, &macio_dev->ofdev) != 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->match_table, &macio_dev->ofdev);
+ 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 struct device_attribute macio_dev_attrs[];
+
+struct bus_type macio_bus_type = {
+ .name = "macio",
+ .match = macio_bus_match,
+ .uevent = of_device_uevent,
+ .probe = macio_device_probe,
+ .remove = macio_device_remove,
+ .shutdown = macio_device_shutdown,
+ .suspend = macio_device_suspend,
+ .resume = macio_device_resume,
+ .dev_attrs = macio_dev_attrs,
+};
+
+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 != NO_IRQ) {
+ dev->interrupt[index].start = irq;
+ dev->interrupt[index].flags = IORESOURCE_IRQ;
+ dev->interrupt[index].name = dev->ofdev.dev.bus_id;
+ }
+ 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.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.node;
+ unsigned int irq;
+ int i = 0, j = 0;
+
+ for (;;) {
+ struct resource *res = &dev->interrupt[j];
+
+ if (j >= MACIO_DEV_COUNT_IRQS)
+ break;
+ irq = irq_of_parse_and_map(np, i++);
+ if (irq == NO_IRQ)
+ break;
+ res->start = irq;
+ res->flags = IORESOURCE_IRQ;
+ res->name = dev->ofdev.dev.bus_id;
+ 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.node;
+ struct resource r;
+ int index;
+
+ for (index = 0; of_address_to_resource(np, index, &r) == 0; index++) {
+ struct resource *res = &dev->resource[index];
+ if (index >= MACIO_DEV_COUNT_RESOURCES)
+ break;
+ *res = r;
+ res->name = dev->ofdev.dev.bus_id;
+
+ 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->ofdev.dev.bus_id);
+ }
+ }
+ 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.node = np;
+ dev->ofdev.dma_mask = 0xffffffffUL;
+ dev->ofdev.dev.dma_mask = &dev->ofdev.dma_mask;
+ dev->ofdev.dev.parent = parent;
+ dev->ofdev.dev.bus = &macio_bus_type;
+ dev->ofdev.dev.release = macio_release_dev;
+
+#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) {
+ sprintf(dev->ofdev.dev.bus_id, "%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);
+ sprintf(dev->ofdev.dev.bus_id, "%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->ofdev.dev.bus_id);
+ 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)
+ for (np = NULL; (np = of_get_next_child(mbdev->ofdev.node, 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) {
+ for (np = NULL; (np = of_get_next_child(sdev->ofdev.node, 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.name = drv->name;
+ 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);
+}
+
+/**
+ * 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)
+{
+ 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;
+
+ 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->ofdev.dev.bus_id);
+ 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)
+{
+ if (macio_resource_len(dev, resource_no) == 0)
+ return;
+ release_mem_region(macio_resource_start(dev, resource_no),
+ macio_resource_len(dev, 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 __devinit 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 __devexit 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 __devinitdata 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);
diff --git a/drivers/macintosh/macio_sysfs.c b/drivers/macintosh/macio_sysfs.c
new file mode 100644
index 0000000..9e9453b
--- /dev/null
+++ b/drivers/macintosh/macio_sysfs.c
@@ -0,0 +1,74 @@
+#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.node->field); \
+}
+
+static ssize_t
+compatible_show (struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct of_device *of;
+ const char *compat;
+ int cplen;
+ int length = 0;
+
+ of = &to_macio_device (dev)->ofdev;
+ compat = of_get_property(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 ssize_t modalias_show (struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct of_device *ofdev = to_of_device(dev);
+ int len;
+
+ len = of_device_get_modalias(ofdev, buf, PAGE_SIZE - 2);
+
+ buf[len] = '\n';
+ buf[len+1] = 0;
+
+ return len+1;
+}
+
+static ssize_t devspec_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct of_device *ofdev;
+
+ ofdev = to_of_device(dev);
+ return sprintf(buf, "%s\n", ofdev->node->full_name);
+}
+
+macio_config_of_attr (name, "%s\n");
+macio_config_of_attr (type, "%s\n");
+
+struct device_attribute macio_dev_attrs[] = {
+ __ATTR_RO(name),
+ __ATTR_RO(type),
+ __ATTR_RO(compatible),
+ __ATTR_RO(modalias),
+ __ATTR_RO(devspec),
+ __ATTR_NULL
+};
diff --git a/drivers/macintosh/mediabay.c b/drivers/macintosh/mediabay.c
new file mode 100644
index 0000000..d7e46d3
--- /dev/null
+++ b/drivers/macintosh/mediabay.c
@@ -0,0 +1,837 @@
+/*
+ * 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/ide.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_DEBUG
+
+#ifdef MB_DEBUG
+#define MBDBG(fmt, arg...) printk(KERN_INFO fmt , ## arg)
+#else
+#define MBDBG(fmt, arg...) do { } while (0)
+#endif
+
+#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;
+ struct mb_ops* ops;
+ int index;
+ int cached_gpio;
+ int sleeping;
+ struct mutex lock;
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ ide_hwif_t *cd_port;
+ void __iomem *cd_base;
+ int cd_irq;
+ int cd_retry;
+#endif
+#if defined(CONFIG_BLK_DEV_IDE_PMAC)
+ int cd_index;
+#endif
+};
+
+#define MAX_BAYS 2
+
+static struct media_bay_info media_bays[MAX_BAYS];
+int media_bay_count = 0;
+
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+/* check the busy bit in the media-bay ide interface
+ (assumes the media-bay contains an ide device) */
+#define MB_IDE_READY(i) ((readb(media_bays[i].cd_base + 0x70) & 0x80) == 0)
+#endif
+
+/*
+ * 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 waiting for busy bit to disappear
+ */
+#define MB_IDE_WAIT 1000
+
+/*
+ * Timeout waiting for busy bit of an IDE device to go down
+ */
+#define MB_IDE_TIMEOUT 5000
+
+/*
+ * Max retries of the full power up/down sequence for an IDE device
+ */
+#define MAX_CD_RETRIES 3
+
+/*
+ * 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_ide_waiting, /* Waiting for BUSY bit to go away until MB_IDE_TIMEOUT */
+ 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;
+ MBDBG("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;
+ MBDBG("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);
+
+ if (id == bay->last_value) {
+ if (id != bay->content_id) {
+ 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;
+ MBDBG("mediabay%d: forcing MB_NO\n", bay->index);
+ }
+ MBDBG("mediabay%d: switching to %d\n", bay->index, id);
+ set_mb_power(bay, id != MB_NO);
+ bay->content_id = id;
+ if (id == MB_NO) {
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ bay->cd_retry = 0;
+#endif
+ printk(KERN_INFO "media bay %d is empty\n", bay->index);
+ }
+ }
+ }
+ } else {
+ bay->last_value = id;
+ bay->value_count = 0;
+ }
+}
+
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+int check_media_bay(struct device_node *which_bay, int what)
+{
+ int i;
+
+ for (i=0; i<media_bay_count; i++)
+ if (media_bays[i].mdev && which_bay == media_bays[i].mdev->ofdev.node) {
+ if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
+ return 0;
+ media_bays[i].cd_index = -1;
+ return -EINVAL;
+ }
+ return -ENODEV;
+}
+EXPORT_SYMBOL(check_media_bay);
+
+int check_media_bay_by_base(unsigned long base, int what)
+{
+ int i;
+
+ for (i=0; i<media_bay_count; i++)
+ if (media_bays[i].mdev && base == (unsigned long) media_bays[i].cd_base) {
+ if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
+ return 0;
+ media_bays[i].cd_index = -1;
+ return -EINVAL;
+ }
+
+ return -ENODEV;
+}
+
+int media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base,
+ int irq, ide_hwif_t *hwif)
+{
+ int i;
+
+ for (i=0; i<media_bay_count; i++) {
+ struct media_bay_info* bay = &media_bays[i];
+
+ if (bay->mdev && which_bay == bay->mdev->ofdev.node) {
+ int timeout = 5000, index = hwif->index;
+
+ mutex_lock(&bay->lock);
+
+ bay->cd_port = hwif;
+ bay->cd_base = (void __iomem *) base;
+ bay->cd_irq = irq;
+
+ if ((MB_CD != bay->content_id) || bay->state != mb_up) {
+ mutex_unlock(&bay->lock);
+ return 0;
+ }
+ printk(KERN_DEBUG "Registered ide%d for media bay %d\n", index, i);
+ do {
+ if (MB_IDE_READY(i)) {
+ bay->cd_index = index;
+ mutex_unlock(&bay->lock);
+ return 0;
+ }
+ mdelay(1);
+ } while(--timeout);
+ printk(KERN_DEBUG "Timeount waiting IDE in bay %d\n", i);
+ mutex_unlock(&bay->lock);
+ return -ENODEV;
+ }
+ }
+
+ return -ENODEV;
+}
+#endif /* CONFIG_BLK_DEV_IDE_PMAC */
+
+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 or polling IDE busy, run state machine */
+ if ((bay->state != mb_ide_waiting) && (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) {
+ MBDBG("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;
+ MBDBG("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;
+ MBDBG("mediabay%d: waiting reset (kind:%d)\n", i, bay->content_id);
+ break;
+ case mb_resetting:
+ if (bay->content_id != MB_CD) {
+ MBDBG("mediabay%d: bay is up (kind:%d)\n", i, bay->content_id);
+ bay->state = mb_up;
+ break;
+ }
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ MBDBG("mediabay%d: waiting IDE 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;
+#else
+ printk(KERN_DEBUG "media-bay %d is ide (not compiled in kernel)\n", i);
+ set_mb_power(bay, 0);
+#endif /* CONFIG_BLK_DEV_IDE_PMAC */
+ break;
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ case mb_ide_resetting:
+ bay->timer = msecs_to_jiffies(MB_IDE_TIMEOUT);
+ bay->state = mb_ide_waiting;
+ MBDBG("mediabay%d: waiting IDE ready (kind:%d)\n", i, bay->content_id);
+ break;
+ case mb_ide_waiting:
+ if (bay->cd_base == NULL) {
+ bay->timer = 0;
+ bay->state = mb_up;
+ MBDBG("mediabay%d: up before IDE init\n", i);
+ break;
+ } else if (MB_IDE_READY(i)) {
+ bay->timer = 0;
+ bay->state = mb_up;
+ if (bay->cd_index < 0) {
+ printk("mediabay %d, registering IDE...\n", i);
+ pmu_suspend();
+ ide_port_scan(bay->cd_port);
+ if (bay->cd_port->present)
+ bay->cd_index = bay->cd_port->index;
+ pmu_resume();
+ }
+ if (bay->cd_index == -1) {
+ /* We eventually do a retry */
+ bay->cd_retry++;
+ printk("IDE register error\n");
+ set_mb_power(bay, 0);
+ } else {
+ printk(KERN_DEBUG "media-bay %d is ide%d\n", i, bay->cd_index);
+ MBDBG("mediabay %d IDE ready\n", i);
+ }
+ break;
+ } else if (bay->timer > 0)
+ bay->timer -= msecs_to_jiffies(MB_POLL_DELAY);
+ if (bay->timer <= 0) {
+ printk("\nIDE Timeout in bay %d !, IDE state is: 0x%02x\n",
+ i, readb(bay->cd_base + 0x70));
+ MBDBG("mediabay%d: nIDE Timeout !\n", i);
+ set_mb_power(bay, 0);
+ bay->timer = 0;
+ }
+ break;
+#endif /* CONFIG_BLK_DEV_IDE_PMAC */
+ case mb_powering_down:
+ bay->state = mb_empty;
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ if (bay->cd_index >= 0) {
+ printk(KERN_DEBUG "Unregistering mb %d ide, index:%d\n", i,
+ bay->cd_index);
+ ide_port_unregister_devices(bay->cd_port);
+ bay->cd_index = -1;
+ }
+ if (bay->cd_retry) {
+ if (bay->cd_retry > MAX_CD_RETRIES) {
+ /* Should add an error sound (sort of beep in dmasound) */
+ printk("\nmedia-bay %d, IDE device badly inserted or unrecognised\n", i);
+ } else {
+ /* Force a new power down/up sequence */
+ bay->content_id = MB_NO;
+ }
+ }
+#endif /* CONFIG_BLK_DEV_IDE_PMAC */
+ MBDBG("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 __devinit 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.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;
+ do {
+ msleep(MB_POLL_DELAY);
+ media_bay_step(i);
+ } while((bay->state != mb_empty) &&
+ (bay->state != mb_up));
+
+ /* 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);
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ bay->cd_retry = 0;
+#endif
+ 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 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 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 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 =
+{
+ .name = "media-bay",
+ .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;
+#ifdef CONFIG_BLK_DEV_IDE_PMAC
+ media_bays[i].cd_index = -1;
+#endif
+ }
+ if (!machine_is(powermac))
+ return 0;
+
+ macio_register_driver(&media_bay_driver);
+
+ return 0;
+}
+
+device_initcall(media_bay_init);
diff --git a/drivers/macintosh/nvram.c b/drivers/macintosh/nvram.c
new file mode 100644
index 0000000..b195d75
--- /dev/null
+++ b/drivers/macintosh/nvram.c
@@ -0,0 +1,131 @@
+/*
+ * /dev/nvram driver for Power Macintosh.
+ */
+
+#define NVRAM_VERSION "1.0"
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/nvram.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include <asm/nvram.h>
+
+#define NVRAM_SIZE 8192
+
+static loff_t nvram_llseek(struct file *file, loff_t offset, int origin)
+{
+ lock_kernel();
+ switch (origin) {
+ case 1:
+ offset += file->f_pos;
+ break;
+ case 2:
+ offset += NVRAM_SIZE;
+ break;
+ }
+ if (offset < 0) {
+ unlock_kernel();
+ return -EINVAL;
+ }
+ file->f_pos = offset;
+ unlock_kernel();
+ return file->f_pos;
+}
+
+static ssize_t read_nvram(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int i;
+ char __user *p = buf;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+ if (*ppos >= NVRAM_SIZE)
+ return 0;
+ for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count)
+ if (__put_user(nvram_read_byte(i), p))
+ return -EFAULT;
+ *ppos = i;
+ return p - buf;
+}
+
+static ssize_t write_nvram(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int i;
+ const char __user *p = buf;
+ char c;
+
+ if (!access_ok(VERIFY_READ, buf, count))
+ return -EFAULT;
+ if (*ppos >= NVRAM_SIZE)
+ return 0;
+ for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) {
+ if (__get_user(c, p))
+ return -EFAULT;
+ nvram_write_byte(c, i);
+ }
+ *ppos = i;
+ return p - buf;
+}
+
+static int nvram_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch(cmd) {
+ case PMAC_NVRAM_GET_OFFSET:
+ {
+ int part, offset;
+ if (copy_from_user(&part, (void __user*)arg, sizeof(part)) != 0)
+ return -EFAULT;
+ if (part < pmac_nvram_OF || part > pmac_nvram_NR)
+ return -EINVAL;
+ offset = pmac_get_partition(part);
+ if (copy_to_user((void __user*)arg, &offset, sizeof(offset)) != 0)
+ return -EFAULT;
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+const struct file_operations nvram_fops = {
+ .owner = THIS_MODULE,
+ .llseek = nvram_llseek,
+ .read = read_nvram,
+ .write = write_nvram,
+ .ioctl = nvram_ioctl,
+};
+
+static struct miscdevice nvram_dev = {
+ NVRAM_MINOR,
+ "nvram",
+ &nvram_fops
+};
+
+int __init nvram_init(void)
+{
+ printk(KERN_INFO "Macintosh non-volatile memory driver v%s\n",
+ NVRAM_VERSION);
+ return misc_register(&nvram_dev);
+}
+
+void __exit nvram_cleanup(void)
+{
+ misc_deregister( &nvram_dev );
+}
+
+module_init(nvram_init);
+module_exit(nvram_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/macintosh/rack-meter.c b/drivers/macintosh/rack-meter.c
new file mode 100644
index 0000000..a98ab72
--- /dev/null
+++ b/drivers/macintosh/rack-meter.c
@@ -0,0 +1,615 @@
+/*
+ * 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/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 <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;
+ cputime64_t prev_wall;
+ cputime64_t 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 cputime64_t get_cpu_idle_time(unsigned int cpu)
+{
+ cputime64_t retval;
+
+ retval = cputime64_add(kstat_cpu(cpu).cpustat.idle,
+ kstat_cpu(cpu).cpustat.iowait);
+
+ if (rackmeter_ignore_nice)
+ retval = cputime64_add(retval, kstat_cpu(cpu).cpustat.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, SAMPLE_COUNT & sizeof(u32));
+ memset(rdma->buf2, 0, SAMPLE_COUNT & sizeof(u32));
+
+ 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));
+ st_le16(&cmd->req_count, 4);
+ st_le16(&cmd->command, STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
+ st_le32(&cmd->phy_addr, rm->dma_buf_p +
+ offsetof(struct rackmeter_dma, mark));
+ st_le32(&cmd->cmd_dep, 0x02000000);
+ cmd++;
+
+ st_le16(&cmd->req_count, SAMPLE_COUNT * 4);
+ st_le16(&cmd->command, OUTPUT_MORE);
+ st_le32(&cmd->phy_addr, rm->dma_buf_p +
+ offsetof(struct rackmeter_dma, buf1));
+ cmd++;
+
+ st_le16(&cmd->req_count, 4);
+ st_le16(&cmd->command, STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
+ st_le32(&cmd->phy_addr, rm->dma_buf_p +
+ offsetof(struct rackmeter_dma, mark));
+ st_le32(&cmd->cmd_dep, 0x01000000);
+ cmd++;
+
+ st_le16(&cmd->req_count, SAMPLE_COUNT * 4);
+ st_le16(&cmd->command, OUTPUT_MORE | BR_ALWAYS);
+ st_le32(&cmd->phy_addr, rm->dma_buf_p +
+ offsetof(struct rackmeter_dma, buf2));
+ st_le32(&cmd->cmd_dep, 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();
+ cputime64_t cur_jiffies, total_idle_ticks;
+ unsigned int total_ticks, idle_ticks;
+ int i, offset, load, cumm, pause;
+
+ cur_jiffies = jiffies64_to_cputime64(get_jiffies_64());
+ total_ticks = (unsigned int)cputime64_sub(cur_jiffies,
+ rcpu->prev_wall);
+ rcpu->prev_wall = cur_jiffies;
+
+ total_idle_ticks = get_cpu_idle_time(cpu);
+ idle_ticks = (unsigned int) cputime64_sub(total_idle_ticks,
+ rcpu->prev_idle);
+ rcpu->prev_idle = total_idle_ticks;
+
+ /* We do a very dumb calculation to update the LEDs for now,
+ * we'll do better once we have actual PWM implemented
+ */
+ load = (9 * (total_ticks - idle_ticks)) / total_ticks;
+
+ 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 __devinit 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_cputime64(get_jiffies_64());
+ schedule_delayed_work_on(cpu, &rm->cpu[cpu].sniffer,
+ msecs_to_jiffies(CPU_SAMPLING_RATE));
+ }
+}
+
+static void __devexit rackmeter_stop_cpu_sniffer(struct rackmeter *rm)
+{
+ cancel_rearming_delayed_work(&rm->cpu[0].sniffer);
+ cancel_rearming_delayed_work(&rm->cpu[1].sniffer);
+}
+
+static int __devinit 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 __devinit 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.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: %s"
+ " (%d resources, %d interrupts)\n",
+ mdev->ofdev.node->full_name);
+ rc = -ENXIO;
+ goto bail_free;
+ }
+ if (macio_request_resources(mdev, "rackmeter")) {
+ printk(KERN_ERR
+ "rackmeter: failed to request resources: %s\n",
+ mdev->ofdev.node->full_name);
+ rc = -EBUSY;
+ goto bail_free;
+ }
+ rm->irq = macio_irq(mdev, 1);
+#else
+ rm->irq = irq_of_parse_and_map(i2s, 1);
+ if (rm->irq == NO_IRQ ||
+ of_address_to_resource(i2s, 0, &ri2s) ||
+ of_address_to_resource(i2s, 1, &rdma)) {
+ printk(KERN_ERR
+ "rackmeter: found match but lacks resources: %s",
+ mdev->ofdev.node->full_name);
+ 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 __devexit 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 struct of_device_id rackmeter_match[] = {
+ { .name = "i2s" },
+ { }
+};
+
+static struct macio_driver rackmeter_driver = {
+ .name = "rackmeter",
+ .owner = THIS_MODULE,
+ .match_table = rackmeter_match,
+ .probe = rackmeter_probe,
+ .remove = __devexit_p(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/drivers/macintosh/smu.c b/drivers/macintosh/smu.c
new file mode 100644
index 0000000..96faa79
--- /dev/null
+++ b/drivers/macintosh/smu.c
@@ -0,0 +1,1333 @@
+/*
+ * 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/smp_lock.h>
+#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/sysdev.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.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 <asm/abs_addr.h>
+#include <asm/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 of_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 struct smu_device *smu;
+static DEFINE_MUTEX(smu_part_access);
+static int smu_irq_inited;
+
+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: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ ((u8 *)cmd->data_buf)[0], ((u8 *)cmd->data_buf)[1],
+ ((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3],
+ ((u8 *)cmd->data_buf)[4], ((u8 *)cmd->data_buf)[5],
+ ((u8 *)cmd->data_buf)[6], ((u8 *)cmd->data_buf)[7]);
+
+ /* 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 == NO_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);
+
+ if (smu_cmdbuf_abs == 0) {
+ printk(KERN_ERR "SMU: Command buffer not allocated !\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 = NO_IRQ;
+ smu->msg_irq = NO_IRQ;
+
+ /* 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 = (struct smu_cmd_buf *)abs_to_virt(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:
+ if (smu->msg_node)
+ of_node_put(smu->msg_node);
+fail_db_node:
+ of_node_put(smu->db_node);
+fail_bootmem:
+ free_bootmem((unsigned long)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 == NO_IRQ)
+ printk(KERN_ERR "smu: failed to map irq for node %s\n",
+ smu->db_node->full_name);
+ }
+ if (smu->msg_node) {
+ smu->msg_irq = irq_of_parse_and_map(smu->msg_node, 0);
+ if (smu->msg_irq == NO_IRQ)
+ printk(KERN_ERR "smu: failed to map irq for node %s\n",
+ smu->msg_node->full_name);
+ }
+
+ /*
+ * Try to request the interrupts
+ */
+
+ if (smu->db_irq != NO_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 = NO_IRQ;
+ }
+ }
+
+ if (smu->msg_irq != NO_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 = NO_IRQ;
+ }
+ }
+
+ 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 of_device* dev,
+ const struct of_device_id *match)
+{
+ 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 struct of_device_id smu_platform_match[] =
+{
+ {
+ .type = "smu",
+ },
+ {},
+};
+
+static struct of_platform_driver smu_of_platform_driver =
+{
+ .name = "smu",
+ .match_table = smu_platform_match,
+ .probe = smu_platform_probe,
+};
+
+static int __init smu_init_sysfs(void)
+{
+ /*
+ * Due to sysfs bogosity, a sysdev is not a real device, so
+ * we should in fact create both if we want sysdev semantics
+ * for power management.
+ * 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
+ */
+ of_register_platform_driver(&smu_of_platform_driver);
+ return 0;
+}
+
+device_initcall(smu_init_sysfs);
+
+struct of_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 (prom_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);
+
+ lock_kernel();
+ spin_lock_irqsave(&smu_clist_lock, flags);
+ list_add(&pp->list, &smu_clist);
+ spin_unlock_irqrestore(&smu_clist_lock, flags);
+ file->private_data = pp;
+ unlock_kernel();
+
+ 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)
+ 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/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c
new file mode 100644
index 0000000..22bf981
--- /dev/null
+++ b/drivers/macintosh/therm_adt746x.c
@@ -0,0 +1,669 @@
+/*
+ * 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
+ * http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.pdf
+ * http://www.analog.com/UploadedFiles/Data_Sheets/3686221171167ADT7460_b.pdf
+ *
+ */
+
+#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/system.h>
+#include <asm/sections.h>
+
+#undef DEBUG
+
+#define CONFIG_REG 0x40
+#define MANUAL_MASK 0xe0
+#define AUTO_MASK 0x20
+
+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 int 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];
+};
+
+static enum {ADT7460, ADT7467} therm_type;
+static int therm_bus, therm_address;
+static struct of_device * of_dev;
+static struct thermostat* thermostat;
+static struct task_struct *thread_therm = NULL;
+
+static int attach_one_thermostat(struct i2c_adapter *adapter, int addr,
+ int busno);
+
+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
+attach_thermostat(struct i2c_adapter *adapter)
+{
+ unsigned long bus_no;
+
+ if (strncmp(adapter->name, "uni-n", 5))
+ return -ENODEV;
+ bus_no = simple_strtoul(adapter->name + 6, NULL, 10);
+ if (bus_no != therm_bus)
+ return -ENODEV;
+ return attach_one_thermostat(adapter, therm_address, bus_no);
+}
+
+static int
+detach_thermostat(struct i2c_adapter *adapter)
+{
+ struct thermostat* th;
+ int i;
+
+ if (thermostat == NULL)
+ return 0;
+
+ th = thermostat;
+
+ if (thread_therm != NULL) {
+ kthread_stop(thread_therm);
+ }
+
+ 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);
+
+ i2c_detach_client(&th->clt);
+
+ thermostat = NULL;
+
+ kfree(th);
+
+ return 0;
+}
+
+static struct i2c_driver thermostat_driver = {
+ .driver = {
+ .name = "therm_adt746x",
+ },
+ .attach_adapter = attach_thermostat,
+ .detach_adapter = detach_thermostat,
+};
+
+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 (therm_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 (therm_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]);
+ write_reg(th, MANUAL_MODE[fan], manual|MANUAL_MASK);
+ write_reg(th, FAN_SPD_SET[fan], speed);
+ } else {
+ /* back to automatic */
+ if(therm_type == ADT7460) {
+ manual = read_reg(th,
+ MANUAL_MODE[fan]) & (~MANUAL_MASK);
+
+ write_reg(th,
+ MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
+ } else {
+ manual = read_reg(th, MANUAL_MODE[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 = (therm_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;
+}
+
+static int attach_one_thermostat(struct i2c_adapter *adapter, int addr,
+ int busno)
+{
+ struct thermostat* th;
+ int rc;
+ int i;
+
+ if (thermostat)
+ return 0;
+
+ th = kzalloc(sizeof(struct thermostat), GFP_KERNEL);
+ if (!th)
+ return -ENOMEM;
+
+ th->clt.addr = addr;
+ th->clt.adapter = adapter;
+ th->clt.driver = &thermostat_driver;
+ strcpy(th->clt.name, "thermostat");
+
+ rc = read_reg(th, 0);
+ if (rc < 0) {
+ printk(KERN_ERR "adt746x: Thermostat failed to read config "
+ "from bus %d !\n",
+ busno);
+ kfree(th);
+ return -ENODEV;
+ }
+
+ /* force manual control to start the fan quieter */
+ if (fan_speed == -1)
+ fan_speed = 64;
+
+ if(therm_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]);
+
+ thermostat = th;
+
+ if (i2c_attach_client(&th->clt)) {
+ printk(KERN_INFO "adt746x: Thermostat failed to attach "
+ "client !\n");
+ thermostat = NULL;
+ kfree(th);
+ return -ENODEV;
+ }
+
+ /* 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);
+ }
+
+ thread_therm = kthread_run(monitor_task, th, "kfand");
+
+ if (thread_therm == ERR_PTR(-ENOMEM)) {
+ printk(KERN_INFO "adt746x: Kthread creation failed\n");
+ thread_therm = NULL;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Now, unfortunately, sysfs doesn't give us a nice void * we could
+ * pass around to the attribute functions, so we don't really have
+ * choice but implement a bunch of them...
+ *
+ * FIXME, it does now...
+ */
+#define BUILD_SHOW_FUNC_INT(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) \
+{ \
+ return sprintf(buf, "%d (%d rpm)\n", \
+ thermostat->last_speed[data], \
+ read_fan_speed(thermostat, 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) \
+{ \
+ 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(thermostat, 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) \
+{ \
+ u32 val; \
+ val = simple_strtoul(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(thermostat, TEMP_REG[1])))
+BUILD_SHOW_FUNC_INT(sensor2_temperature, (read_reg(thermostat, TEMP_REG[2])))
+BUILD_SHOW_FUNC_INT(sensor1_limit, thermostat->limits[1])
+BUILD_SHOW_FUNC_INT(sensor2_limit, thermostat->limits[2])
+BUILD_SHOW_FUNC_STR(sensor1_location, sensor_location[1])
+BUILD_SHOW_FUNC_STR(sensor2_location, sensor_location[2])
+
+BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed)
+BUILD_SHOW_FUNC_FAN(sensor1_fan_speed, 0)
+BUILD_SHOW_FUNC_FAN(sensor2_fan_speed, 1)
+
+BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed)
+BUILD_SHOW_FUNC_INT(limit_adjust, limit_adjust)
+BUILD_STORE_FUNC_DEG(limit_adjust, thermostat)
+
+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 int __init
+thermostat_init(void)
+{
+ struct device_node* np;
+ const u32 *prop;
+ int i = 0, offset = 0;
+ int err;
+
+ np = of_find_node_by_name(NULL, "fan");
+ if (!np)
+ return -ENODEV;
+ if (of_device_is_compatible(np, "adt7460"))
+ therm_type = ADT7460;
+ else if (of_device_is_compatible(np, "adt7467"))
+ therm_type = ADT7467;
+ else {
+ of_node_put(np);
+ return -ENODEV;
+ }
+
+ prop = of_get_property(np, "hwsensor-params-version", NULL);
+ printk(KERN_INFO "adt746x: version %d (%ssupported)\n", *prop,
+ (*prop == 1)?"":"un");
+ if (*prop != 1) {
+ of_node_put(np);
+ return -ENODEV;
+ }
+
+ prop = of_get_property(np, "reg", NULL);
+ if (!prop) {
+ of_node_put(np);
+ return -ENODEV;
+ }
+
+ /* look for bus either by path or using "reg" */
+ if (strstr(np->full_name, "/i2c-bus@") != NULL) {
+ const char *tmp_bus = (strstr(np->full_name, "/i2c-bus@") + 9);
+ therm_bus = tmp_bus[0]-'0';
+ } else {
+ therm_bus = ((*prop) >> 8) & 0x0f;
+ }
+
+ therm_address = ((*prop) & 0xff) >> 1;
+
+ printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, "
+ "limit_adjust: %d, fan_speed: %d\n",
+ therm_bus, therm_address, limit_adjust, fan_speed);
+
+ 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;
+ }
+ } else {
+ sensor_location[0] = "?";
+ sensor_location[1] = "?";
+ sensor_location[2] = "?";
+ }
+
+ of_dev = of_platform_device_create(np, "temperatures", NULL);
+
+ if (of_dev == NULL) {
+ printk(KERN_ERR "Can't register temperatures device !\n");
+ of_node_put(np);
+ return -ENODEV;
+ }
+
+ err = device_create_file(&of_dev->dev, &dev_attr_sensor1_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_limit);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_limit);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_location);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_location);
+ err |= device_create_file(&of_dev->dev, &dev_attr_limit_adjust);
+ err |= device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed);
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_fan_speed);
+ if(therm_type == ADT7460)
+ err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_fan_speed);
+ if (err)
+ printk(KERN_WARNING
+ "Failed to create tempertaure attribute file(s).\n");
+
+#ifndef CONFIG_I2C_POWERMAC
+ request_module("i2c-powermac");
+#endif
+
+ return i2c_add_driver(&thermostat_driver);
+}
+
+static void __exit
+thermostat_exit(void)
+{
+ if (of_dev) {
+ device_remove_file(&of_dev->dev, &dev_attr_sensor1_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor2_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor1_limit);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor2_limit);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor1_location);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor2_location);
+ device_remove_file(&of_dev->dev, &dev_attr_limit_adjust);
+ device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed);
+ device_remove_file(&of_dev->dev, &dev_attr_sensor1_fan_speed);
+
+ if(therm_type == ADT7460)
+ device_remove_file(&of_dev->dev,
+ &dev_attr_sensor2_fan_speed);
+
+ of_device_unregister(of_dev);
+ }
+ i2c_del_driver(&thermostat_driver);
+}
+
+module_init(thermostat_init);
+module_exit(thermostat_exit);
diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c
new file mode 100644
index 0000000..817607e
--- /dev/null
+++ b/drivers/macintosh/therm_pm72.c
@@ -0,0 +1,2279 @@
+/*
+ * Device driver for the thermostats & fan controller of the
+ * Apple G5 "PowerMac7,2" desktop machines.
+ *
+ * (c) Copyright IBM Corp. 2003-2004
+ *
+ * Maintained by: Benjamin Herrenschmidt
+ * <benh@kernel.crashing.org>
+ *
+ *
+ * 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 7.0 snapshot property lists.
+ *
+ * As far as the CPUs control loops are concerned, I use the
+ * calibration & PID constants provided by the EEPROM,
+ * I do _not_ embed any value from the property lists, as the ones
+ * provided by Darwin 7.0 seem to always have an older version that
+ * what I've seen on the actual computers.
+ * It would be interesting to verify that though. Darwin has a
+ * version code of 1.0.0d11 for all control loops it seems, while
+ * so far, the machines EEPROMs contain a dataset versioned 1.0.0f
+ *
+ * Darwin doesn't provide source to all parts, some missing
+ * bits like the AppleFCU driver or the actual scale of some
+ * of the values returned by sensors had to be "guessed" some
+ * way... or based on what Open Firmware does.
+ *
+ * I didn't yet figure out how to get the slots power consumption
+ * out of the FCU, so that part has not been implemented yet and
+ * the slots fan is set to a fixed 50% PWM, hoping this value is
+ * safe enough ...
+ *
+ * Note: I have observed strange oscillations of the CPU control
+ * loop on a dual G5 here. When idle, the CPU exhaust fan tend to
+ * oscillates slowly (over several minutes) between the minimum
+ * of 300RPMs and approx. 1000 RPMs. I don't know what is causing
+ * this, it could be some incorrect constant or an error in the
+ * way I ported the algorithm, or it could be just normal. I
+ * don't have full understanding on the way Apple tweaked the PID
+ * algorithm for the CPU control, it is definitely not a standard
+ * implementation...
+ *
+ * TODO: - Check MPU structure version/signature
+ * - Add things like /sbin/overtemp for non-critical
+ * overtemp conditions so userland can take some policy
+ * decisions, like slewing down CPUs
+ * - Deal with fan and i2c failures in a better way
+ * - Maybe do a generic PID based on params used for
+ * U3 and Drives ? Definitely need to factor code a bit
+ * bettter... also make sensor detection more robust using
+ * the device-tree to probe for them
+ * - Figure out how to get the slots consumption and set the
+ * slots fan accordingly
+ *
+ * History:
+ *
+ * Nov. 13, 2003 : 0.5
+ * - First release
+ *
+ * Nov. 14, 2003 : 0.6
+ * - Read fan speed from FCU, low level fan routines now deal
+ * with errors & check fan status, though higher level don't
+ * do much.
+ * - Move a bunch of definitions to .h file
+ *
+ * Nov. 18, 2003 : 0.7
+ * - Fix build on ppc64 kernel
+ * - Move back statics definitions to .c file
+ * - Avoid calling schedule_timeout with a negative number
+ *
+ * Dec. 18, 2003 : 0.8
+ * - Fix typo when reading back fan speed on 2 CPU machines
+ *
+ * Mar. 11, 2004 : 0.9
+ * - Rework code accessing the ADC chips, make it more robust and
+ * closer to the chip spec. Also make sure it is configured properly,
+ * I've seen yet unexplained cases where on startup, I would have stale
+ * values in the configuration register
+ * - Switch back to use of target fan speed for PID, thus lowering
+ * pressure on i2c
+ *
+ * Oct. 20, 2004 : 1.1
+ * - Add device-tree lookup for fan IDs, should detect liquid cooling
+ * pumps when present
+ * - Enable driver for PowerMac7,3 machines
+ * - Split the U3/Backside cooling on U3 & U3H versions as Darwin does
+ * - Add new CPU cooling algorithm for machines with liquid cooling
+ * - Workaround for some PowerMac7,3 with empty "fan" node in the devtree
+ * - Fix a signed/unsigned compare issue in some PID loops
+ *
+ * Mar. 10, 2005 : 1.2
+ * - Add basic support for Xserve G5
+ * - Retreive pumps min/max from EEPROM image in device-tree (broken)
+ * - Use min/max macros here or there
+ * - Latest darwin updated U3H min fan speed to 20% PWM
+ *
+ * July. 06, 2006 : 1.3
+ * - Fix setting of RPM fans on Xserve G5 (they were going too fast)
+ * - Add missing slots fan control loop for Xserve G5
+ * - Lower fixed slots fan speed from 50% to 40% on desktop G5s. We
+ * still can't properly implement the control loop for these, so let's
+ * reduce the noise a little bit, it appears that 40% still gives us
+ * a pretty good air flow
+ * - Add code to "tickle" the FCU regulary so it doesn't think that
+ * we are gone while in fact, the machine just didn't need any fan
+ * speed change lately
+ *
+ */
+
+#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/slab.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/reboot.h>
+#include <linux/kmod.h>
+#include <linux/i2c.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sections.h>
+#include <asm/macio.h>
+
+#include "therm_pm72.h"
+
+#define VERSION "1.3"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(args...) printk(args)
+#else
+#define DBG(args...) do { } while(0)
+#endif
+
+
+/*
+ * Driver statics
+ */
+
+static struct of_device * of_dev;
+static struct i2c_adapter * u3_0;
+static struct i2c_adapter * u3_1;
+static struct i2c_adapter * k2;
+static struct i2c_client * fcu;
+static struct cpu_pid_state cpu_state[2];
+static struct basckside_pid_params backside_params;
+static struct backside_pid_state backside_state;
+static struct drives_pid_state drives_state;
+static struct dimm_pid_state dimms_state;
+static struct slots_pid_state slots_state;
+static int state;
+static int cpu_count;
+static int cpu_pid_type;
+static struct task_struct *ctrl_task;
+static struct completion ctrl_complete;
+static int critical_state;
+static int rackmac;
+static s32 dimm_output_clamp;
+static int fcu_rpm_shift;
+static int fcu_tickle_ticks;
+static DEFINE_MUTEX(driver_lock);
+
+/*
+ * We have 3 types of CPU PID control. One is "split" old style control
+ * for intake & exhaust fans, the other is "combined" control for both
+ * CPUs that also deals with the pumps when present. To be "compatible"
+ * with OS X at this point, we only use "COMBINED" on the machines that
+ * are identified as having the pumps (though that identification is at
+ * least dodgy). Ultimately, we could probably switch completely to this
+ * algorithm provided we hack it to deal with the UP case
+ */
+#define CPU_PID_TYPE_SPLIT 0
+#define CPU_PID_TYPE_COMBINED 1
+#define CPU_PID_TYPE_RACKMAC 2
+
+/*
+ * This table describes all fans in the FCU. The "id" and "type" values
+ * are defaults valid for all earlier machines. Newer machines will
+ * eventually override the table content based on the device-tree
+ */
+struct fcu_fan_table
+{
+ char* loc; /* location code */
+ int type; /* 0 = rpm, 1 = pwm, 2 = pump */
+ int id; /* id or -1 */
+};
+
+#define FCU_FAN_RPM 0
+#define FCU_FAN_PWM 1
+
+#define FCU_FAN_ABSENT_ID -1
+
+#define FCU_FAN_COUNT ARRAY_SIZE(fcu_fans)
+
+struct fcu_fan_table fcu_fans[] = {
+ [BACKSIDE_FAN_PWM_INDEX] = {
+ .loc = "BACKSIDE,SYS CTRLR FAN",
+ .type = FCU_FAN_PWM,
+ .id = BACKSIDE_FAN_PWM_DEFAULT_ID,
+ },
+ [DRIVES_FAN_RPM_INDEX] = {
+ .loc = "DRIVE BAY",
+ .type = FCU_FAN_RPM,
+ .id = DRIVES_FAN_RPM_DEFAULT_ID,
+ },
+ [SLOTS_FAN_PWM_INDEX] = {
+ .loc = "SLOT,PCI FAN",
+ .type = FCU_FAN_PWM,
+ .id = SLOTS_FAN_PWM_DEFAULT_ID,
+ },
+ [CPUA_INTAKE_FAN_RPM_INDEX] = {
+ .loc = "CPU A INTAKE",
+ .type = FCU_FAN_RPM,
+ .id = CPUA_INTAKE_FAN_RPM_DEFAULT_ID,
+ },
+ [CPUA_EXHAUST_FAN_RPM_INDEX] = {
+ .loc = "CPU A EXHAUST",
+ .type = FCU_FAN_RPM,
+ .id = CPUA_EXHAUST_FAN_RPM_DEFAULT_ID,
+ },
+ [CPUB_INTAKE_FAN_RPM_INDEX] = {
+ .loc = "CPU B INTAKE",
+ .type = FCU_FAN_RPM,
+ .id = CPUB_INTAKE_FAN_RPM_DEFAULT_ID,
+ },
+ [CPUB_EXHAUST_FAN_RPM_INDEX] = {
+ .loc = "CPU B EXHAUST",
+ .type = FCU_FAN_RPM,
+ .id = CPUB_EXHAUST_FAN_RPM_DEFAULT_ID,
+ },
+ /* pumps aren't present by default, have to be looked up in the
+ * device-tree
+ */
+ [CPUA_PUMP_RPM_INDEX] = {
+ .loc = "CPU A PUMP",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPUB_PUMP_RPM_INDEX] = {
+ .loc = "CPU B PUMP",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ /* Xserve fans */
+ [CPU_A1_FAN_RPM_INDEX] = {
+ .loc = "CPU A 1",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPU_A2_FAN_RPM_INDEX] = {
+ .loc = "CPU A 2",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPU_A3_FAN_RPM_INDEX] = {
+ .loc = "CPU A 3",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPU_B1_FAN_RPM_INDEX] = {
+ .loc = "CPU B 1",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPU_B2_FAN_RPM_INDEX] = {
+ .loc = "CPU B 2",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+ [CPU_B3_FAN_RPM_INDEX] = {
+ .loc = "CPU B 3",
+ .type = FCU_FAN_RPM,
+ .id = FCU_FAN_ABSENT_ID,
+ },
+};
+
+/*
+ * i2c_driver structure to attach to the host i2c controller
+ */
+
+static int therm_pm72_attach(struct i2c_adapter *adapter);
+static int therm_pm72_detach(struct i2c_adapter *adapter);
+
+static struct i2c_driver therm_pm72_driver =
+{
+ .driver = {
+ .name = "therm_pm72",
+ },
+ .attach_adapter = therm_pm72_attach,
+ .detach_adapter = therm_pm72_detach,
+};
+
+/*
+ * Utility function to create an i2c_client structure and
+ * attach it to one of u3 adapters
+ */
+static struct i2c_client *attach_i2c_chip(int id, const char *name)
+{
+ struct i2c_client *clt;
+ struct i2c_adapter *adap;
+
+ if (id & 0x200)
+ adap = k2;
+ else if (id & 0x100)
+ adap = u3_1;
+ else
+ adap = u3_0;
+ if (adap == NULL)
+ return NULL;
+
+ clt = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (clt == NULL)
+ return NULL;
+
+ clt->addr = (id >> 1) & 0x7f;
+ clt->adapter = adap;
+ clt->driver = &therm_pm72_driver;
+ strncpy(clt->name, name, I2C_NAME_SIZE-1);
+
+ if (i2c_attach_client(clt)) {
+ printk(KERN_ERR "therm_pm72: Failed to attach to i2c ID 0x%x\n", id);
+ kfree(clt);
+ return NULL;
+ }
+ return clt;
+}
+
+/*
+ * Utility function to get rid of the i2c_client structure
+ * (will also detach from the adapter hopepfully)
+ */
+static void detach_i2c_chip(struct i2c_client *clt)
+{
+ i2c_detach_client(clt);
+ kfree(clt);
+}
+
+/*
+ * Here are the i2c chip access wrappers
+ */
+
+static void initialize_adc(struct cpu_pid_state *state)
+{
+ int rc;
+ u8 buf[2];
+
+ /* 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(state->monitor, buf, 2);
+
+ /* Read & cache Config1 */
+ buf[0] = 1;
+ rc = i2c_master_send(state->monitor, buf, 1);
+ if (rc > 0) {
+ rc = i2c_master_recv(state->monitor, buf, 1);
+ if (rc > 0) {
+ state->adc_config = buf[0];
+ DBG("ADC config reg: %02x\n", state->adc_config);
+ /* Disable shutdown mode */
+ state->adc_config &= 0xfe;
+ buf[0] = 1;
+ buf[1] = state->adc_config;
+ rc = i2c_master_send(state->monitor, buf, 2);
+ }
+ }
+ if (rc <= 0)
+ printk(KERN_ERR "therm_pm72: Error reading ADC config"
+ " register !\n");
+}
+
+static int read_smon_adc(struct cpu_pid_state *state, int chan)
+{
+ int rc, data, tries = 0;
+ u8 buf[2];
+
+ for (;;) {
+ /* Set channel */
+ buf[0] = 1;
+ buf[1] = (state->adc_config & 0x1f) | (chan << 5);
+ rc = i2c_master_send(state->monitor, buf, 2);
+ if (rc <= 0)
+ goto error;
+ /* Wait for convertion */
+ msleep(1);
+ /* Switch to data register */
+ buf[0] = 4;
+ rc = i2c_master_send(state->monitor, buf, 1);
+ if (rc <= 0)
+ goto error;
+ /* Read result */
+ rc = i2c_master_recv(state->monitor, buf, 2);
+ if (rc < 0)
+ goto error;
+ data = ((u16)buf[0]) << 8 | (u16)buf[1];
+ return data >> 6;
+ error:
+ DBG("Error reading ADC, retrying...\n");
+ if (++tries > 10) {
+ printk(KERN_ERR "therm_pm72: Error reading ADC !\n");
+ return -1;
+ }
+ msleep(10);
+ }
+}
+
+static int read_lm87_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("Error reading LM87, retrying...\n");
+ if (++tries > 10) {
+ printk(KERN_ERR "therm_pm72: Error reading LM87 !\n");
+ return -1;
+ }
+ msleep(10);
+ }
+}
+
+static int fan_read_reg(int reg, unsigned char *buf, int nb)
+{
+ int tries, nr, nw;
+
+ buf[0] = reg;
+ tries = 0;
+ for (;;) {
+ nw = i2c_master_send(fcu, buf, 1);
+ if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
+ break;
+ msleep(10);
+ ++tries;
+ }
+ if (nw <= 0) {
+ printk(KERN_ERR "Failure writing address to FCU: %d", nw);
+ return -EIO;
+ }
+ tries = 0;
+ for (;;) {
+ nr = i2c_master_recv(fcu, buf, nb);
+ if (nr > 0 || (nr < 0 && nr != ENODEV) || tries >= 100)
+ break;
+ msleep(10);
+ ++tries;
+ }
+ if (nr <= 0)
+ printk(KERN_ERR "Failure reading data from FCU: %d", nw);
+ return nr;
+}
+
+static int fan_write_reg(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(fcu, buf, nb);
+ if (nw > 0 || (nw < 0 && nw != EIO) || tries >= 100)
+ break;
+ msleep(10);
+ ++tries;
+ }
+ if (nw < 0)
+ printk(KERN_ERR "Failure writing to FCU: %d", nw);
+ return nw;
+}
+
+static int start_fcu(void)
+{
+ unsigned char buf = 0xff;
+ int rc;
+
+ rc = fan_write_reg(0xe, &buf, 1);
+ if (rc < 0)
+ return -EIO;
+ rc = fan_write_reg(0x2e, &buf, 1);
+ if (rc < 0)
+ return -EIO;
+ rc = fan_read_reg(0, &buf, 1);
+ if (rc < 0)
+ return -EIO;
+ fcu_rpm_shift = (buf == 1) ? 2 : 3;
+ printk(KERN_DEBUG "FCU Initialized, RPM fan shift is %d\n",
+ fcu_rpm_shift);
+
+ return 0;
+}
+
+static int set_rpm_fan(int fan_index, int rpm)
+{
+ unsigned char buf[2];
+ int rc, id, min, max;
+
+ if (fcu_fans[fan_index].type != FCU_FAN_RPM)
+ return -EINVAL;
+ id = fcu_fans[fan_index].id;
+ if (id == FCU_FAN_ABSENT_ID)
+ return -EINVAL;
+
+ min = 2400 >> fcu_rpm_shift;
+ max = 56000 >> fcu_rpm_shift;
+
+ if (rpm < min)
+ rpm = min;
+ else if (rpm > max)
+ rpm = max;
+ buf[0] = rpm >> (8 - fcu_rpm_shift);
+ buf[1] = rpm << fcu_rpm_shift;
+ rc = fan_write_reg(0x10 + (id * 2), buf, 2);
+ if (rc < 0)
+ return -EIO;
+ return 0;
+}
+
+static int get_rpm_fan(int fan_index, int programmed)
+{
+ unsigned char failure;
+ unsigned char active;
+ unsigned char buf[2];
+ int rc, id, reg_base;
+
+ if (fcu_fans[fan_index].type != FCU_FAN_RPM)
+ return -EINVAL;
+ id = fcu_fans[fan_index].id;
+ if (id == FCU_FAN_ABSENT_ID)
+ return -EINVAL;
+
+ rc = fan_read_reg(0xb, &failure, 1);
+ if (rc != 1)
+ return -EIO;
+ if ((failure & (1 << id)) != 0)
+ return -EFAULT;
+ rc = fan_read_reg(0xd, &active, 1);
+ if (rc != 1)
+ return -EIO;
+ if ((active & (1 << id)) == 0)
+ return -ENXIO;
+
+ /* Programmed value or real current speed */
+ reg_base = programmed ? 0x10 : 0x11;
+ rc = fan_read_reg(reg_base + (id * 2), buf, 2);
+ if (rc != 2)
+ return -EIO;
+
+ return (buf[0] << (8 - fcu_rpm_shift)) | buf[1] >> fcu_rpm_shift;
+}
+
+static int set_pwm_fan(int fan_index, int pwm)
+{
+ unsigned char buf[2];
+ int rc, id;
+
+ if (fcu_fans[fan_index].type != FCU_FAN_PWM)
+ return -EINVAL;
+ id = fcu_fans[fan_index].id;
+ if (id == FCU_FAN_ABSENT_ID)
+ return -EINVAL;
+
+ if (pwm < 10)
+ pwm = 10;
+ else if (pwm > 100)
+ pwm = 100;
+ pwm = (pwm * 2559) / 1000;
+ buf[0] = pwm;
+ rc = fan_write_reg(0x30 + (id * 2), buf, 1);
+ if (rc < 0)
+ return rc;
+ return 0;
+}
+
+static int get_pwm_fan(int fan_index)
+{
+ unsigned char failure;
+ unsigned char active;
+ unsigned char buf[2];
+ int rc, id;
+
+ if (fcu_fans[fan_index].type != FCU_FAN_PWM)
+ return -EINVAL;
+ id = fcu_fans[fan_index].id;
+ if (id == FCU_FAN_ABSENT_ID)
+ return -EINVAL;
+
+ rc = fan_read_reg(0x2b, &failure, 1);
+ if (rc != 1)
+ return -EIO;
+ if ((failure & (1 << id)) != 0)
+ return -EFAULT;
+ rc = fan_read_reg(0x2d, &active, 1);
+ if (rc != 1)
+ return -EIO;
+ if ((active & (1 << id)) == 0)
+ return -ENXIO;
+
+ /* Programmed value or real current speed */
+ rc = fan_read_reg(0x30 + (id * 2), buf, 1);
+ if (rc != 1)
+ return -EIO;
+
+ return (buf[0] * 1000) / 2559;
+}
+
+static void tickle_fcu(void)
+{
+ int pwm;
+
+ pwm = get_pwm_fan(SLOTS_FAN_PWM_INDEX);
+
+ DBG("FCU Tickle, slots fan is: %d\n", pwm);
+ if (pwm < 0)
+ pwm = 100;
+
+ if (!rackmac) {
+ pwm = SLOTS_FAN_DEFAULT_PWM;
+ } else if (pwm < SLOTS_PID_OUTPUT_MIN)
+ pwm = SLOTS_PID_OUTPUT_MIN;
+
+ /* That is hopefully enough to make the FCU happy */
+ set_pwm_fan(SLOTS_FAN_PWM_INDEX, pwm);
+}
+
+
+/*
+ * Utility routine to read the CPU calibration EEPROM data
+ * from the device-tree
+ */
+static int read_eeprom(int cpu, struct mpu_data *out)
+{
+ struct device_node *np;
+ char nodename[64];
+ const u8 *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 == NULL) {
+ printk(KERN_ERR "therm_pm72: Failed to retrieve cpuid node from device-tree\n");
+ return -ENODEV;
+ }
+ data = of_get_property(np, "cpuid", &len);
+ if (data == NULL) {
+ printk(KERN_ERR "therm_pm72: Failed to retrieve cpuid property from device-tree\n");
+ of_node_put(np);
+ return -ENODEV;
+ }
+ memcpy(out, data, sizeof(struct mpu_data));
+ of_node_put(np);
+
+ return 0;
+}
+
+static void fetch_cpu_pumps_minmax(void)
+{
+ struct cpu_pid_state *state0 = &cpu_state[0];
+ struct cpu_pid_state *state1 = &cpu_state[1];
+ u16 pump_min = 0, pump_max = 0xffff;
+ u16 tmp[4];
+
+ /* Try to fetch pumps min/max infos from eeprom */
+
+ memcpy(&tmp, &state0->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;
+ }
+
+ state0->pump_min = state1->pump_min = pump_min;
+ state0->pump_max = state1->pump_max = pump_max;
+}
+
+/*
+ * Now, unfortunately, sysfs doesn't give us a nice void * we could
+ * pass around to the attribute functions, so we don't really have
+ * choice but implement a bunch of them...
+ *
+ * That sucks a bit, we take the lock because FIX32TOPRINT evaluates
+ * the input twice... I accept patches :)
+ */
+#define BUILD_SHOW_FUNC_FIX(name, data) \
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ ssize_t r; \
+ mutex_lock(&driver_lock); \
+ r = sprintf(buf, "%d.%03d", FIX32TOPRINT(data)); \
+ mutex_unlock(&driver_lock); \
+ return r; \
+}
+#define BUILD_SHOW_FUNC_INT(name, data) \
+static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ return sprintf(buf, "%d", data); \
+}
+
+BUILD_SHOW_FUNC_FIX(cpu0_temperature, cpu_state[0].last_temp)
+BUILD_SHOW_FUNC_FIX(cpu0_voltage, cpu_state[0].voltage)
+BUILD_SHOW_FUNC_FIX(cpu0_current, cpu_state[0].current_a)
+BUILD_SHOW_FUNC_INT(cpu0_exhaust_fan_rpm, cpu_state[0].rpm)
+BUILD_SHOW_FUNC_INT(cpu0_intake_fan_rpm, cpu_state[0].intake_rpm)
+
+BUILD_SHOW_FUNC_FIX(cpu1_temperature, cpu_state[1].last_temp)
+BUILD_SHOW_FUNC_FIX(cpu1_voltage, cpu_state[1].voltage)
+BUILD_SHOW_FUNC_FIX(cpu1_current, cpu_state[1].current_a)
+BUILD_SHOW_FUNC_INT(cpu1_exhaust_fan_rpm, cpu_state[1].rpm)
+BUILD_SHOW_FUNC_INT(cpu1_intake_fan_rpm, cpu_state[1].intake_rpm)
+
+BUILD_SHOW_FUNC_FIX(backside_temperature, backside_state.last_temp)
+BUILD_SHOW_FUNC_INT(backside_fan_pwm, backside_state.pwm)
+
+BUILD_SHOW_FUNC_FIX(drives_temperature, drives_state.last_temp)
+BUILD_SHOW_FUNC_INT(drives_fan_rpm, drives_state.rpm)
+
+BUILD_SHOW_FUNC_FIX(slots_temperature, slots_state.last_temp)
+BUILD_SHOW_FUNC_INT(slots_fan_pwm, slots_state.pwm)
+
+BUILD_SHOW_FUNC_FIX(dimms_temperature, dimms_state.last_temp)
+
+static DEVICE_ATTR(cpu0_temperature,S_IRUGO,show_cpu0_temperature,NULL);
+static DEVICE_ATTR(cpu0_voltage,S_IRUGO,show_cpu0_voltage,NULL);
+static DEVICE_ATTR(cpu0_current,S_IRUGO,show_cpu0_current,NULL);
+static DEVICE_ATTR(cpu0_exhaust_fan_rpm,S_IRUGO,show_cpu0_exhaust_fan_rpm,NULL);
+static DEVICE_ATTR(cpu0_intake_fan_rpm,S_IRUGO,show_cpu0_intake_fan_rpm,NULL);
+
+static DEVICE_ATTR(cpu1_temperature,S_IRUGO,show_cpu1_temperature,NULL);
+static DEVICE_ATTR(cpu1_voltage,S_IRUGO,show_cpu1_voltage,NULL);
+static DEVICE_ATTR(cpu1_current,S_IRUGO,show_cpu1_current,NULL);
+static DEVICE_ATTR(cpu1_exhaust_fan_rpm,S_IRUGO,show_cpu1_exhaust_fan_rpm,NULL);
+static DEVICE_ATTR(cpu1_intake_fan_rpm,S_IRUGO,show_cpu1_intake_fan_rpm,NULL);
+
+static DEVICE_ATTR(backside_temperature,S_IRUGO,show_backside_temperature,NULL);
+static DEVICE_ATTR(backside_fan_pwm,S_IRUGO,show_backside_fan_pwm,NULL);
+
+static DEVICE_ATTR(drives_temperature,S_IRUGO,show_drives_temperature,NULL);
+static DEVICE_ATTR(drives_fan_rpm,S_IRUGO,show_drives_fan_rpm,NULL);
+
+static DEVICE_ATTR(slots_temperature,S_IRUGO,show_slots_temperature,NULL);
+static DEVICE_ATTR(slots_fan_pwm,S_IRUGO,show_slots_fan_pwm,NULL);
+
+static DEVICE_ATTR(dimms_temperature,S_IRUGO,show_dimms_temperature,NULL);
+
+/*
+ * CPUs fans control loop
+ */
+
+static int do_read_one_cpu_values(struct cpu_pid_state *state, s32 *temp, s32 *power)
+{
+ s32 ltemp, volts, amps;
+ int index, rc = 0;
+
+ /* Default (in case of error) */
+ *temp = state->cur_temp;
+ *power = state->cur_power;
+
+ if (cpu_pid_type == CPU_PID_TYPE_RACKMAC)
+ index = (state->index == 0) ?
+ CPU_A1_FAN_RPM_INDEX : CPU_B1_FAN_RPM_INDEX;
+ else
+ index = (state->index == 0) ?
+ CPUA_EXHAUST_FAN_RPM_INDEX : CPUB_EXHAUST_FAN_RPM_INDEX;
+
+ /* Read current fan status */
+ rc = get_rpm_fan(index, !RPM_PID_USE_ACTUAL_SPEED);
+ if (rc < 0) {
+ /* XXX What do we do now ? Nothing for now, keep old value, but
+ * return error upstream
+ */
+ DBG(" cpu %d, fan reading error !\n", state->index);
+ } else {
+ state->rpm = rc;
+ DBG(" cpu %d, exhaust RPM: %d\n", state->index, state->rpm);
+ }
+
+ /* Get some sensor readings and scale it */
+ ltemp = read_smon_adc(state, 1);
+ if (ltemp == -1) {
+ /* XXX What do we do now ? */
+ state->overtemp++;
+ if (rc == 0)
+ rc = -EIO;
+ DBG(" cpu %d, temp reading error !\n", state->index);
+ } else {
+ /* Fixup temperature according to diode calibration
+ */
+ DBG(" cpu %d, temp raw: %04x, m_diode: %04x, b_diode: %04x\n",
+ state->index,
+ ltemp, state->mpu.mdiode, state->mpu.bdiode);
+ *temp = ((s32)ltemp * (s32)state->mpu.mdiode + ((s32)state->mpu.bdiode << 12)) >> 2;
+ state->last_temp = *temp;
+ DBG(" temp: %d.%03d\n", FIX32TOPRINT((*temp)));
+ }
+
+ /*
+ * Read voltage & current and calculate power
+ */
+ volts = read_smon_adc(state, 3);
+ amps = read_smon_adc(state, 4);
+
+ /* Scale voltage and current raw sensor values according to fixed scales
+ * obtained in Darwin and calculate power from I and V
+ */
+ volts *= ADC_CPU_VOLTAGE_SCALE;
+ amps *= ADC_CPU_CURRENT_SCALE;
+ *power = (((u64)volts) * ((u64)amps)) >> 16;
+ state->voltage = volts;
+ state->current_a = amps;
+ state->last_power = *power;
+
+ DBG(" cpu %d, current: %d.%03d, voltage: %d.%03d, power: %d.%03d W\n",
+ state->index, FIX32TOPRINT(state->current_a),
+ FIX32TOPRINT(state->voltage), FIX32TOPRINT(*power));
+
+ return 0;
+}
+
+static void do_cpu_pid(struct cpu_pid_state *state, s32 temp, s32 power)
+{
+ s32 power_target, integral, derivative, proportional, adj_in_target, sval;
+ s64 integ_p, deriv_p, prop_p, sum;
+ int i;
+
+ /* Calculate power target value (could be done once for all)
+ * and convert to a 16.16 fp number
+ */
+ power_target = ((u32)(state->mpu.pmaxh - state->mpu.padjmax)) << 16;
+ DBG(" power target: %d.%03d, error: %d.%03d\n",
+ FIX32TOPRINT(power_target), FIX32TOPRINT(power_target - power));
+
+ /* Store temperature and power in history array */
+ state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE;
+ state->temp_history[state->cur_temp] = temp;
+ state->cur_power = (state->cur_power + 1) % state->count_power;
+ state->power_history[state->cur_power] = power;
+ state->error_history[state->cur_power] = power_target - power;
+
+ /* If first loop, fill the history table */
+ if (state->first) {
+ for (i = 0; i < (state->count_power - 1); i++) {
+ state->cur_power = (state->cur_power + 1) % state->count_power;
+ state->power_history[state->cur_power] = power;
+ state->error_history[state->cur_power] = power_target - power;
+ }
+ for (i = 0; i < (CPU_TEMP_HISTORY_SIZE - 1); i++) {
+ state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE;
+ state->temp_history[state->cur_temp] = temp;
+ }
+ state->first = 0;
+ }
+
+ /* Calculate the integral term normally based on the "power" values */
+ sum = 0;
+ integral = 0;
+ for (i = 0; i < state->count_power; i++)
+ integral += state->error_history[i];
+ integral *= CPU_PID_INTERVAL;
+ DBG(" integral: %08x\n", integral);
+
+ /* Calculate the adjusted input (sense value).
+ * G_r is 12.20
+ * integ is 16.16
+ * so the result is 28.36
+ *
+ * input target is mpu.ttarget, input max is mpu.tmax
+ */
+ integ_p = ((s64)state->mpu.pid_gr) * (s64)integral;
+ DBG(" integ_p: %d\n", (int)(integ_p >> 36));
+ sval = (state->mpu.tmax << 16) - ((integ_p >> 20) & 0xffffffff);
+ adj_in_target = (state->mpu.ttarget << 16);
+ if (adj_in_target > sval)
+ adj_in_target = sval;
+ DBG(" adj_in_target: %d.%03d, ttarget: %d\n", FIX32TOPRINT(adj_in_target),
+ state->mpu.ttarget);
+
+ /* Calculate the derivative term */
+ derivative = state->temp_history[state->cur_temp] -
+ state->temp_history[(state->cur_temp + CPU_TEMP_HISTORY_SIZE - 1)
+ % CPU_TEMP_HISTORY_SIZE];
+ derivative /= CPU_PID_INTERVAL;
+ deriv_p = ((s64)state->mpu.pid_gd) * (s64)derivative;
+ DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
+ sum += deriv_p;
+
+ /* Calculate the proportional term */
+ proportional = temp - adj_in_target;
+ prop_p = ((s64)state->mpu.pid_gp) * (s64)proportional;
+ DBG(" prop_p: %d\n", (int)(prop_p >> 36));
+ sum += prop_p;
+
+ /* Scale sum */
+ sum >>= 36;
+
+ DBG(" sum: %d\n", (int)sum);
+ state->rpm += (s32)sum;
+}
+
+static void do_monitor_cpu_combined(void)
+{
+ struct cpu_pid_state *state0 = &cpu_state[0];
+ struct cpu_pid_state *state1 = &cpu_state[1];
+ s32 temp0, power0, temp1, power1;
+ s32 temp_combi, power_combi;
+ int rc, intake, pump;
+
+ rc = do_read_one_cpu_values(state0, &temp0, &power0);
+ if (rc < 0) {
+ /* XXX What do we do now ? */
+ }
+ state1->overtemp = 0;
+ rc = do_read_one_cpu_values(state1, &temp1, &power1);
+ if (rc < 0) {
+ /* XXX What do we do now ? */
+ }
+ if (state1->overtemp)
+ state0->overtemp++;
+
+ temp_combi = max(temp0, temp1);
+ power_combi = max(power0, power1);
+
+ /* Check tmax, increment overtemp if we are there. At tmax+8, we go
+ * full blown immediately and try to trigger a shutdown
+ */
+ if (temp_combi >= ((state0->mpu.tmax + 8) << 16)) {
+ printk(KERN_WARNING "Warning ! Temperature way above maximum (%d) !\n",
+ temp_combi >> 16);
+ state0->overtemp += CPU_MAX_OVERTEMP / 4;
+ } else if (temp_combi > (state0->mpu.tmax << 16))
+ state0->overtemp++;
+ else
+ state0->overtemp = 0;
+ if (state0->overtemp >= CPU_MAX_OVERTEMP)
+ critical_state = 1;
+ if (state0->overtemp > 0) {
+ state0->rpm = state0->mpu.rmaxn_exhaust_fan;
+ state0->intake_rpm = intake = state0->mpu.rmaxn_intake_fan;
+ pump = state0->pump_max;
+ goto do_set_fans;
+ }
+
+ /* Do the PID */
+ do_cpu_pid(state0, temp_combi, power_combi);
+
+ /* Range check */
+ state0->rpm = max(state0->rpm, (int)state0->mpu.rminn_exhaust_fan);
+ state0->rpm = min(state0->rpm, (int)state0->mpu.rmaxn_exhaust_fan);
+
+ /* Calculate intake fan speed */
+ intake = (state0->rpm * CPU_INTAKE_SCALE) >> 16;
+ intake = max(intake, (int)state0->mpu.rminn_intake_fan);
+ intake = min(intake, (int)state0->mpu.rmaxn_intake_fan);
+ state0->intake_rpm = intake;
+
+ /* Calculate pump speed */
+ pump = (state0->rpm * state0->pump_max) /
+ state0->mpu.rmaxn_exhaust_fan;
+ pump = min(pump, state0->pump_max);
+ pump = max(pump, state0->pump_min);
+
+ do_set_fans:
+ /* We copy values from state 0 to state 1 for /sysfs */
+ state1->rpm = state0->rpm;
+ state1->intake_rpm = state0->intake_rpm;
+
+ DBG("** CPU %d RPM: %d Ex, %d, Pump: %d, In, overtemp: %d\n",
+ state1->index, (int)state1->rpm, intake, pump, state1->overtemp);
+
+ /* We should check for errors, shouldn't we ? But then, what
+ * do we do once the error occurs ? For FCU notified fan
+ * failures (-EFAULT) we probably want to notify userland
+ * some way...
+ */
+ set_rpm_fan(CPUA_INTAKE_FAN_RPM_INDEX, intake);
+ set_rpm_fan(CPUA_EXHAUST_FAN_RPM_INDEX, state0->rpm);
+ set_rpm_fan(CPUB_INTAKE_FAN_RPM_INDEX, intake);
+ set_rpm_fan(CPUB_EXHAUST_FAN_RPM_INDEX, state0->rpm);
+
+ if (fcu_fans[CPUA_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID)
+ set_rpm_fan(CPUA_PUMP_RPM_INDEX, pump);
+ if (fcu_fans[CPUB_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID)
+ set_rpm_fan(CPUB_PUMP_RPM_INDEX, pump);
+}
+
+static void do_monitor_cpu_split(struct cpu_pid_state *state)
+{
+ s32 temp, power;
+ int rc, intake;
+
+ /* Read current fan status */
+ rc = do_read_one_cpu_values(state, &temp, &power);
+ if (rc < 0) {
+ /* XXX What do we do now ? */
+ }
+
+ /* Check tmax, increment overtemp if we are there. At tmax+8, we go
+ * full blown immediately and try to trigger a shutdown
+ */
+ if (temp >= ((state->mpu.tmax + 8) << 16)) {
+ printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum"
+ " (%d) !\n",
+ state->index, temp >> 16);
+ state->overtemp += CPU_MAX_OVERTEMP / 4;
+ } else if (temp > (state->mpu.tmax << 16))
+ state->overtemp++;
+ else
+ state->overtemp = 0;
+ if (state->overtemp >= CPU_MAX_OVERTEMP)
+ critical_state = 1;
+ if (state->overtemp > 0) {
+ state->rpm = state->mpu.rmaxn_exhaust_fan;
+ state->intake_rpm = intake = state->mpu.rmaxn_intake_fan;
+ goto do_set_fans;
+ }
+
+ /* Do the PID */
+ do_cpu_pid(state, temp, power);
+
+ /* Range check */
+ state->rpm = max(state->rpm, (int)state->mpu.rminn_exhaust_fan);
+ state->rpm = min(state->rpm, (int)state->mpu.rmaxn_exhaust_fan);
+
+ /* Calculate intake fan */
+ intake = (state->rpm * CPU_INTAKE_SCALE) >> 16;
+ intake = max(intake, (int)state->mpu.rminn_intake_fan);
+ intake = min(intake, (int)state->mpu.rmaxn_intake_fan);
+ state->intake_rpm = intake;
+
+ do_set_fans:
+ DBG("** CPU %d RPM: %d Ex, %d In, overtemp: %d\n",
+ state->index, (int)state->rpm, intake, state->overtemp);
+
+ /* We should check for errors, shouldn't we ? But then, what
+ * do we do once the error occurs ? For FCU notified fan
+ * failures (-EFAULT) we probably want to notify userland
+ * some way...
+ */
+ if (state->index == 0) {
+ set_rpm_fan(CPUA_INTAKE_FAN_RPM_INDEX, intake);
+ set_rpm_fan(CPUA_EXHAUST_FAN_RPM_INDEX, state->rpm);
+ } else {
+ set_rpm_fan(CPUB_INTAKE_FAN_RPM_INDEX, intake);
+ set_rpm_fan(CPUB_EXHAUST_FAN_RPM_INDEX, state->rpm);
+ }
+}
+
+static void do_monitor_cpu_rack(struct cpu_pid_state *state)
+{
+ s32 temp, power, fan_min;
+ int rc;
+
+ /* Read current fan status */
+ rc = do_read_one_cpu_values(state, &temp, &power);
+ if (rc < 0) {
+ /* XXX What do we do now ? */
+ }
+
+ /* Check tmax, increment overtemp if we are there. At tmax+8, we go
+ * full blown immediately and try to trigger a shutdown
+ */
+ if (temp >= ((state->mpu.tmax + 8) << 16)) {
+ printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum"
+ " (%d) !\n",
+ state->index, temp >> 16);
+ state->overtemp = CPU_MAX_OVERTEMP / 4;
+ } else if (temp > (state->mpu.tmax << 16))
+ state->overtemp++;
+ else
+ state->overtemp = 0;
+ if (state->overtemp >= CPU_MAX_OVERTEMP)
+ critical_state = 1;
+ if (state->overtemp > 0) {
+ state->rpm = state->intake_rpm = state->mpu.rmaxn_intake_fan;
+ goto do_set_fans;
+ }
+
+ /* Do the PID */
+ do_cpu_pid(state, temp, power);
+
+ /* Check clamp from dimms */
+ fan_min = dimm_output_clamp;
+ fan_min = max(fan_min, (int)state->mpu.rminn_intake_fan);
+
+ DBG(" CPU min mpu = %d, min dimm = %d\n",
+ state->mpu.rminn_intake_fan, dimm_output_clamp);
+
+ state->rpm = max(state->rpm, (int)fan_min);
+ state->rpm = min(state->rpm, (int)state->mpu.rmaxn_intake_fan);
+ state->intake_rpm = state->rpm;
+
+ do_set_fans:
+ DBG("** CPU %d RPM: %d overtemp: %d\n",
+ state->index, (int)state->rpm, state->overtemp);
+
+ /* We should check for errors, shouldn't we ? But then, what
+ * do we do once the error occurs ? For FCU notified fan
+ * failures (-EFAULT) we probably want to notify userland
+ * some way...
+ */
+ if (state->index == 0) {
+ set_rpm_fan(CPU_A1_FAN_RPM_INDEX, state->rpm);
+ set_rpm_fan(CPU_A2_FAN_RPM_INDEX, state->rpm);
+ set_rpm_fan(CPU_A3_FAN_RPM_INDEX, state->rpm);
+ } else {
+ set_rpm_fan(CPU_B1_FAN_RPM_INDEX, state->rpm);
+ set_rpm_fan(CPU_B2_FAN_RPM_INDEX, state->rpm);
+ set_rpm_fan(CPU_B3_FAN_RPM_INDEX, state->rpm);
+ }
+}
+
+/*
+ * Initialize the state structure for one CPU control loop
+ */
+static int init_cpu_state(struct cpu_pid_state *state, int index)
+{
+ int err;
+
+ state->index = index;
+ state->first = 1;
+ state->rpm = (cpu_pid_type == CPU_PID_TYPE_RACKMAC) ? 4000 : 1000;
+ state->overtemp = 0;
+ state->adc_config = 0x00;
+
+
+ if (index == 0)
+ state->monitor = attach_i2c_chip(SUPPLY_MONITOR_ID, "CPU0_monitor");
+ else if (index == 1)
+ state->monitor = attach_i2c_chip(SUPPLY_MONITORB_ID, "CPU1_monitor");
+ if (state->monitor == NULL)
+ goto fail;
+
+ if (read_eeprom(index, &state->mpu))
+ goto fail;
+
+ state->count_power = state->mpu.tguardband;
+ if (state->count_power > CPU_POWER_HISTORY_SIZE) {
+ printk(KERN_WARNING "Warning ! too many power history slots\n");
+ state->count_power = CPU_POWER_HISTORY_SIZE;
+ }
+ DBG("CPU %d Using %d power history entries\n", index, state->count_power);
+
+ if (index == 0) {
+ err = device_create_file(&of_dev->dev, &dev_attr_cpu0_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu0_voltage);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu0_current);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm);
+ } else {
+ err = device_create_file(&of_dev->dev, &dev_attr_cpu1_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu1_voltage);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu1_current);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm);
+ err |= device_create_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm);
+ }
+ if (err)
+ printk(KERN_WARNING "Failed to create some of the atribute"
+ "files for CPU %d\n", index);
+
+ return 0;
+ fail:
+ if (state->monitor)
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+
+ return -ENODEV;
+}
+
+/*
+ * Dispose of the state data for one CPU control loop
+ */
+static void dispose_cpu_state(struct cpu_pid_state *state)
+{
+ if (state->monitor == NULL)
+ return;
+
+ if (state->index == 0) {
+ device_remove_file(&of_dev->dev, &dev_attr_cpu0_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu0_voltage);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu0_current);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm);
+ } else {
+ device_remove_file(&of_dev->dev, &dev_attr_cpu1_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu1_voltage);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu1_current);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm);
+ device_remove_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm);
+ }
+
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+}
+
+/*
+ * Motherboard backside & U3 heatsink fan control loop
+ */
+static void do_monitor_backside(struct backside_pid_state *state)
+{
+ s32 temp, integral, derivative, fan_min;
+ s64 integ_p, deriv_p, prop_p, sum;
+ int i, rc;
+
+ if (--state->ticks != 0)
+ return;
+ state->ticks = backside_params.interval;
+
+ DBG("backside:\n");
+
+ /* Check fan status */
+ rc = get_pwm_fan(BACKSIDE_FAN_PWM_INDEX);
+ if (rc < 0) {
+ printk(KERN_WARNING "Error %d reading backside fan !\n", rc);
+ /* XXX What do we do now ? */
+ } else
+ state->pwm = rc;
+ DBG(" current pwm: %d\n", state->pwm);
+
+ /* Get some sensor readings */
+ temp = i2c_smbus_read_byte_data(state->monitor, MAX6690_EXT_TEMP) << 16;
+ state->last_temp = temp;
+ DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
+ FIX32TOPRINT(backside_params.input_target));
+
+ /* Store temperature and error in history array */
+ state->cur_sample = (state->cur_sample + 1) % BACKSIDE_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] = temp - backside_params.input_target;
+
+ /* If first loop, fill the history table */
+ if (state->first) {
+ for (i = 0; i < (BACKSIDE_PID_HISTORY_SIZE - 1); i++) {
+ state->cur_sample = (state->cur_sample + 1) %
+ BACKSIDE_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] =
+ temp - backside_params.input_target;
+ }
+ state->first = 0;
+ }
+
+ /* Calculate the integral term */
+ sum = 0;
+ integral = 0;
+ for (i = 0; i < BACKSIDE_PID_HISTORY_SIZE; i++)
+ integral += state->error_history[i];
+ integral *= backside_params.interval;
+ DBG(" integral: %08x\n", integral);
+ integ_p = ((s64)backside_params.G_r) * (s64)integral;
+ DBG(" integ_p: %d\n", (int)(integ_p >> 36));
+ sum += integ_p;
+
+ /* Calculate the derivative term */
+ derivative = state->error_history[state->cur_sample] -
+ state->error_history[(state->cur_sample + BACKSIDE_PID_HISTORY_SIZE - 1)
+ % BACKSIDE_PID_HISTORY_SIZE];
+ derivative /= backside_params.interval;
+ deriv_p = ((s64)backside_params.G_d) * (s64)derivative;
+ DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
+ sum += deriv_p;
+
+ /* Calculate the proportional term */
+ prop_p = ((s64)backside_params.G_p) * (s64)(state->error_history[state->cur_sample]);
+ DBG(" prop_p: %d\n", (int)(prop_p >> 36));
+ sum += prop_p;
+
+ /* Scale sum */
+ sum >>= 36;
+
+ DBG(" sum: %d\n", (int)sum);
+ if (backside_params.additive)
+ state->pwm += (s32)sum;
+ else
+ state->pwm = sum;
+
+ /* Check for clamp */
+ fan_min = (dimm_output_clamp * 100) / 14000;
+ fan_min = max(fan_min, backside_params.output_min);
+
+ state->pwm = max(state->pwm, fan_min);
+ state->pwm = min(state->pwm, backside_params.output_max);
+
+ DBG("** BACKSIDE PWM: %d\n", (int)state->pwm);
+ set_pwm_fan(BACKSIDE_FAN_PWM_INDEX, state->pwm);
+}
+
+/*
+ * Initialize the state structure for the backside fan control loop
+ */
+static int init_backside_state(struct backside_pid_state *state)
+{
+ struct device_node *u3;
+ int u3h = 1; /* conservative by default */
+ int err;
+
+ /*
+ * There are different PID params for machines with U3 and machines
+ * with U3H, pick the right ones now
+ */
+ 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);
+ }
+
+ if (rackmac) {
+ backside_params.G_d = BACKSIDE_PID_RACK_G_d;
+ backside_params.input_target = BACKSIDE_PID_RACK_INPUT_TARGET;
+ backside_params.output_min = BACKSIDE_PID_U3H_OUTPUT_MIN;
+ backside_params.interval = BACKSIDE_PID_RACK_INTERVAL;
+ backside_params.G_p = BACKSIDE_PID_RACK_G_p;
+ backside_params.G_r = BACKSIDE_PID_G_r;
+ backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX;
+ backside_params.additive = 0;
+ } else if (u3h) {
+ backside_params.G_d = BACKSIDE_PID_U3H_G_d;
+ backside_params.input_target = BACKSIDE_PID_U3H_INPUT_TARGET;
+ backside_params.output_min = BACKSIDE_PID_U3H_OUTPUT_MIN;
+ backside_params.interval = BACKSIDE_PID_INTERVAL;
+ backside_params.G_p = BACKSIDE_PID_G_p;
+ backside_params.G_r = BACKSIDE_PID_G_r;
+ backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX;
+ backside_params.additive = 1;
+ } else {
+ backside_params.G_d = BACKSIDE_PID_U3_G_d;
+ backside_params.input_target = BACKSIDE_PID_U3_INPUT_TARGET;
+ backside_params.output_min = BACKSIDE_PID_U3_OUTPUT_MIN;
+ backside_params.interval = BACKSIDE_PID_INTERVAL;
+ backside_params.G_p = BACKSIDE_PID_G_p;
+ backside_params.G_r = BACKSIDE_PID_G_r;
+ backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX;
+ backside_params.additive = 1;
+ }
+
+ state->ticks = 1;
+ state->first = 1;
+ state->pwm = 50;
+
+ state->monitor = attach_i2c_chip(BACKSIDE_MAX_ID, "backside_temp");
+ if (state->monitor == NULL)
+ return -ENODEV;
+
+ err = device_create_file(&of_dev->dev, &dev_attr_backside_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_backside_fan_pwm);
+ if (err)
+ printk(KERN_WARNING "Failed to create attribute file(s)"
+ " for backside fan\n");
+
+ return 0;
+}
+
+/*
+ * Dispose of the state data for the backside control loop
+ */
+static void dispose_backside_state(struct backside_pid_state *state)
+{
+ if (state->monitor == NULL)
+ return;
+
+ device_remove_file(&of_dev->dev, &dev_attr_backside_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_backside_fan_pwm);
+
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+}
+
+/*
+ * Drives bay fan control loop
+ */
+static void do_monitor_drives(struct drives_pid_state *state)
+{
+ s32 temp, integral, derivative;
+ s64 integ_p, deriv_p, prop_p, sum;
+ int i, rc;
+
+ if (--state->ticks != 0)
+ return;
+ state->ticks = DRIVES_PID_INTERVAL;
+
+ DBG("drives:\n");
+
+ /* Check fan status */
+ rc = get_rpm_fan(DRIVES_FAN_RPM_INDEX, !RPM_PID_USE_ACTUAL_SPEED);
+ if (rc < 0) {
+ printk(KERN_WARNING "Error %d reading drives fan !\n", rc);
+ /* XXX What do we do now ? */
+ } else
+ state->rpm = rc;
+ DBG(" current rpm: %d\n", state->rpm);
+
+ /* Get some sensor readings */
+ temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor,
+ DS1775_TEMP)) << 8;
+ state->last_temp = temp;
+ DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
+ FIX32TOPRINT(DRIVES_PID_INPUT_TARGET));
+
+ /* Store temperature and error in history array */
+ state->cur_sample = (state->cur_sample + 1) % DRIVES_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] = temp - DRIVES_PID_INPUT_TARGET;
+
+ /* If first loop, fill the history table */
+ if (state->first) {
+ for (i = 0; i < (DRIVES_PID_HISTORY_SIZE - 1); i++) {
+ state->cur_sample = (state->cur_sample + 1) %
+ DRIVES_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] =
+ temp - DRIVES_PID_INPUT_TARGET;
+ }
+ state->first = 0;
+ }
+
+ /* Calculate the integral term */
+ sum = 0;
+ integral = 0;
+ for (i = 0; i < DRIVES_PID_HISTORY_SIZE; i++)
+ integral += state->error_history[i];
+ integral *= DRIVES_PID_INTERVAL;
+ DBG(" integral: %08x\n", integral);
+ integ_p = ((s64)DRIVES_PID_G_r) * (s64)integral;
+ DBG(" integ_p: %d\n", (int)(integ_p >> 36));
+ sum += integ_p;
+
+ /* Calculate the derivative term */
+ derivative = state->error_history[state->cur_sample] -
+ state->error_history[(state->cur_sample + DRIVES_PID_HISTORY_SIZE - 1)
+ % DRIVES_PID_HISTORY_SIZE];
+ derivative /= DRIVES_PID_INTERVAL;
+ deriv_p = ((s64)DRIVES_PID_G_d) * (s64)derivative;
+ DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
+ sum += deriv_p;
+
+ /* Calculate the proportional term */
+ prop_p = ((s64)DRIVES_PID_G_p) * (s64)(state->error_history[state->cur_sample]);
+ DBG(" prop_p: %d\n", (int)(prop_p >> 36));
+ sum += prop_p;
+
+ /* Scale sum */
+ sum >>= 36;
+
+ DBG(" sum: %d\n", (int)sum);
+ state->rpm += (s32)sum;
+
+ state->rpm = max(state->rpm, DRIVES_PID_OUTPUT_MIN);
+ state->rpm = min(state->rpm, DRIVES_PID_OUTPUT_MAX);
+
+ DBG("** DRIVES RPM: %d\n", (int)state->rpm);
+ set_rpm_fan(DRIVES_FAN_RPM_INDEX, state->rpm);
+}
+
+/*
+ * Initialize the state structure for the drives bay fan control loop
+ */
+static int init_drives_state(struct drives_pid_state *state)
+{
+ int err;
+
+ state->ticks = 1;
+ state->first = 1;
+ state->rpm = 1000;
+
+ state->monitor = attach_i2c_chip(DRIVES_DALLAS_ID, "drives_temp");
+ if (state->monitor == NULL)
+ return -ENODEV;
+
+ err = device_create_file(&of_dev->dev, &dev_attr_drives_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_drives_fan_rpm);
+ if (err)
+ printk(KERN_WARNING "Failed to create attribute file(s)"
+ " for drives bay fan\n");
+
+ return 0;
+}
+
+/*
+ * Dispose of the state data for the drives control loop
+ */
+static void dispose_drives_state(struct drives_pid_state *state)
+{
+ if (state->monitor == NULL)
+ return;
+
+ device_remove_file(&of_dev->dev, &dev_attr_drives_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_drives_fan_rpm);
+
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+}
+
+/*
+ * DIMMs temp control loop
+ */
+static void do_monitor_dimms(struct dimm_pid_state *state)
+{
+ s32 temp, integral, derivative, fan_min;
+ s64 integ_p, deriv_p, prop_p, sum;
+ int i;
+
+ if (--state->ticks != 0)
+ return;
+ state->ticks = DIMM_PID_INTERVAL;
+
+ DBG("DIMM:\n");
+
+ DBG(" current value: %d\n", state->output);
+
+ temp = read_lm87_reg(state->monitor, LM87_INT_TEMP);
+ if (temp < 0)
+ return;
+ temp <<= 16;
+ state->last_temp = temp;
+ DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
+ FIX32TOPRINT(DIMM_PID_INPUT_TARGET));
+
+ /* Store temperature and error in history array */
+ state->cur_sample = (state->cur_sample + 1) % DIMM_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] = temp - DIMM_PID_INPUT_TARGET;
+
+ /* If first loop, fill the history table */
+ if (state->first) {
+ for (i = 0; i < (DIMM_PID_HISTORY_SIZE - 1); i++) {
+ state->cur_sample = (state->cur_sample + 1) %
+ DIMM_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] =
+ temp - DIMM_PID_INPUT_TARGET;
+ }
+ state->first = 0;
+ }
+
+ /* Calculate the integral term */
+ sum = 0;
+ integral = 0;
+ for (i = 0; i < DIMM_PID_HISTORY_SIZE; i++)
+ integral += state->error_history[i];
+ integral *= DIMM_PID_INTERVAL;
+ DBG(" integral: %08x\n", integral);
+ integ_p = ((s64)DIMM_PID_G_r) * (s64)integral;
+ DBG(" integ_p: %d\n", (int)(integ_p >> 36));
+ sum += integ_p;
+
+ /* Calculate the derivative term */
+ derivative = state->error_history[state->cur_sample] -
+ state->error_history[(state->cur_sample + DIMM_PID_HISTORY_SIZE - 1)
+ % DIMM_PID_HISTORY_SIZE];
+ derivative /= DIMM_PID_INTERVAL;
+ deriv_p = ((s64)DIMM_PID_G_d) * (s64)derivative;
+ DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
+ sum += deriv_p;
+
+ /* Calculate the proportional term */
+ prop_p = ((s64)DIMM_PID_G_p) * (s64)(state->error_history[state->cur_sample]);
+ DBG(" prop_p: %d\n", (int)(prop_p >> 36));
+ sum += prop_p;
+
+ /* Scale sum */
+ sum >>= 36;
+
+ DBG(" sum: %d\n", (int)sum);
+ state->output = (s32)sum;
+ state->output = max(state->output, DIMM_PID_OUTPUT_MIN);
+ state->output = min(state->output, DIMM_PID_OUTPUT_MAX);
+ dimm_output_clamp = state->output;
+
+ DBG("** DIMM clamp value: %d\n", (int)state->output);
+
+ /* Backside PID is only every 5 seconds, force backside fan clamping now */
+ fan_min = (dimm_output_clamp * 100) / 14000;
+ fan_min = max(fan_min, backside_params.output_min);
+ if (backside_state.pwm < fan_min) {
+ backside_state.pwm = fan_min;
+ DBG(" -> applying clamp to backside fan now: %d !\n", fan_min);
+ set_pwm_fan(BACKSIDE_FAN_PWM_INDEX, fan_min);
+ }
+}
+
+/*
+ * Initialize the state structure for the DIMM temp control loop
+ */
+static int init_dimms_state(struct dimm_pid_state *state)
+{
+ state->ticks = 1;
+ state->first = 1;
+ state->output = 4000;
+
+ state->monitor = attach_i2c_chip(XSERVE_DIMMS_LM87, "dimms_temp");
+ if (state->monitor == NULL)
+ return -ENODEV;
+
+ if (device_create_file(&of_dev->dev, &dev_attr_dimms_temperature))
+ printk(KERN_WARNING "Failed to create attribute file"
+ " for DIMM temperature\n");
+
+ return 0;
+}
+
+/*
+ * Dispose of the state data for the DIMM control loop
+ */
+static void dispose_dimms_state(struct dimm_pid_state *state)
+{
+ if (state->monitor == NULL)
+ return;
+
+ device_remove_file(&of_dev->dev, &dev_attr_dimms_temperature);
+
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+}
+
+/*
+ * Slots fan control loop
+ */
+static void do_monitor_slots(struct slots_pid_state *state)
+{
+ s32 temp, integral, derivative;
+ s64 integ_p, deriv_p, prop_p, sum;
+ int i, rc;
+
+ if (--state->ticks != 0)
+ return;
+ state->ticks = SLOTS_PID_INTERVAL;
+
+ DBG("slots:\n");
+
+ /* Check fan status */
+ rc = get_pwm_fan(SLOTS_FAN_PWM_INDEX);
+ if (rc < 0) {
+ printk(KERN_WARNING "Error %d reading slots fan !\n", rc);
+ /* XXX What do we do now ? */
+ } else
+ state->pwm = rc;
+ DBG(" current pwm: %d\n", state->pwm);
+
+ /* Get some sensor readings */
+ temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor,
+ DS1775_TEMP)) << 8;
+ state->last_temp = temp;
+ DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp),
+ FIX32TOPRINT(SLOTS_PID_INPUT_TARGET));
+
+ /* Store temperature and error in history array */
+ state->cur_sample = (state->cur_sample + 1) % SLOTS_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] = temp - SLOTS_PID_INPUT_TARGET;
+
+ /* If first loop, fill the history table */
+ if (state->first) {
+ for (i = 0; i < (SLOTS_PID_HISTORY_SIZE - 1); i++) {
+ state->cur_sample = (state->cur_sample + 1) %
+ SLOTS_PID_HISTORY_SIZE;
+ state->sample_history[state->cur_sample] = temp;
+ state->error_history[state->cur_sample] =
+ temp - SLOTS_PID_INPUT_TARGET;
+ }
+ state->first = 0;
+ }
+
+ /* Calculate the integral term */
+ sum = 0;
+ integral = 0;
+ for (i = 0; i < SLOTS_PID_HISTORY_SIZE; i++)
+ integral += state->error_history[i];
+ integral *= SLOTS_PID_INTERVAL;
+ DBG(" integral: %08x\n", integral);
+ integ_p = ((s64)SLOTS_PID_G_r) * (s64)integral;
+ DBG(" integ_p: %d\n", (int)(integ_p >> 36));
+ sum += integ_p;
+
+ /* Calculate the derivative term */
+ derivative = state->error_history[state->cur_sample] -
+ state->error_history[(state->cur_sample + SLOTS_PID_HISTORY_SIZE - 1)
+ % SLOTS_PID_HISTORY_SIZE];
+ derivative /= SLOTS_PID_INTERVAL;
+ deriv_p = ((s64)SLOTS_PID_G_d) * (s64)derivative;
+ DBG(" deriv_p: %d\n", (int)(deriv_p >> 36));
+ sum += deriv_p;
+
+ /* Calculate the proportional term */
+ prop_p = ((s64)SLOTS_PID_G_p) * (s64)(state->error_history[state->cur_sample]);
+ DBG(" prop_p: %d\n", (int)(prop_p >> 36));
+ sum += prop_p;
+
+ /* Scale sum */
+ sum >>= 36;
+
+ DBG(" sum: %d\n", (int)sum);
+ state->pwm = (s32)sum;
+
+ state->pwm = max(state->pwm, SLOTS_PID_OUTPUT_MIN);
+ state->pwm = min(state->pwm, SLOTS_PID_OUTPUT_MAX);
+
+ DBG("** DRIVES PWM: %d\n", (int)state->pwm);
+ set_pwm_fan(SLOTS_FAN_PWM_INDEX, state->pwm);
+}
+
+/*
+ * Initialize the state structure for the slots bay fan control loop
+ */
+static int init_slots_state(struct slots_pid_state *state)
+{
+ int err;
+
+ state->ticks = 1;
+ state->first = 1;
+ state->pwm = 50;
+
+ state->monitor = attach_i2c_chip(XSERVE_SLOTS_LM75, "slots_temp");
+ if (state->monitor == NULL)
+ return -ENODEV;
+
+ err = device_create_file(&of_dev->dev, &dev_attr_slots_temperature);
+ err |= device_create_file(&of_dev->dev, &dev_attr_slots_fan_pwm);
+ if (err)
+ printk(KERN_WARNING "Failed to create attribute file(s)"
+ " for slots bay fan\n");
+
+ return 0;
+}
+
+/*
+ * Dispose of the state data for the slots control loop
+ */
+static void dispose_slots_state(struct slots_pid_state *state)
+{
+ if (state->monitor == NULL)
+ return;
+
+ device_remove_file(&of_dev->dev, &dev_attr_slots_temperature);
+ device_remove_file(&of_dev->dev, &dev_attr_slots_fan_pwm);
+
+ detach_i2c_chip(state->monitor);
+ state->monitor = NULL;
+}
+
+
+static int call_critical_overtemp(void)
+{
+ char *argv[] = { 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);
+}
+
+
+/*
+ * Here's the kernel thread that calls the various control loops
+ */
+static int main_control_loop(void *x)
+{
+ DBG("main_control_loop started\n");
+
+ mutex_lock(&driver_lock);
+
+ if (start_fcu() < 0) {
+ printk(KERN_ERR "kfand: failed to start FCU\n");
+ mutex_unlock(&driver_lock);
+ goto out;
+ }
+
+ /* Set the PCI fan once for now on non-RackMac */
+ if (!rackmac)
+ set_pwm_fan(SLOTS_FAN_PWM_INDEX, SLOTS_FAN_DEFAULT_PWM);
+
+ /* Initialize ADCs */
+ initialize_adc(&cpu_state[0]);
+ if (cpu_state[1].monitor != NULL)
+ initialize_adc(&cpu_state[1]);
+
+ fcu_tickle_ticks = FCU_TICKLE_TICKS;
+
+ mutex_unlock(&driver_lock);
+
+ while (state == state_attached) {
+ unsigned long elapsed, start;
+
+ start = jiffies;
+
+ mutex_lock(&driver_lock);
+
+ /* Tickle the FCU just in case */
+ if (--fcu_tickle_ticks < 0) {
+ fcu_tickle_ticks = FCU_TICKLE_TICKS;
+ tickle_fcu();
+ }
+
+ /* First, we always calculate the new DIMMs state on an Xserve */
+ if (rackmac)
+ do_monitor_dimms(&dimms_state);
+
+ /* Then, the CPUs */
+ if (cpu_pid_type == CPU_PID_TYPE_COMBINED)
+ do_monitor_cpu_combined();
+ else if (cpu_pid_type == CPU_PID_TYPE_RACKMAC) {
+ do_monitor_cpu_rack(&cpu_state[0]);
+ if (cpu_state[1].monitor != NULL)
+ do_monitor_cpu_rack(&cpu_state[1]);
+ // better deal with UP
+ } else {
+ do_monitor_cpu_split(&cpu_state[0]);
+ if (cpu_state[1].monitor != NULL)
+ do_monitor_cpu_split(&cpu_state[1]);
+ // better deal with UP
+ }
+ /* Then, the rest */
+ do_monitor_backside(&backside_state);
+ if (rackmac)
+ do_monitor_slots(&slots_state);
+ else
+ do_monitor_drives(&drives_state);
+ mutex_unlock(&driver_lock);
+
+ if (critical_state == 1) {
+ printk(KERN_WARNING "Temperature control detected a critical condition\n");
+ printk(KERN_WARNING "Attempting to shut down...\n");
+ if (call_critical_overtemp()) {
+ printk(KERN_WARNING "Can't call %s, power off now!\n",
+ critical_overtemp_path);
+ machine_power_off();
+ }
+ }
+ if (critical_state > 0)
+ critical_state++;
+ if (critical_state > MAX_CRITICAL_STATE) {
+ printk(KERN_WARNING "Shutdown timed out, power off now !\n");
+ machine_power_off();
+ }
+
+ // FIXME: Deal with signals
+ elapsed = jiffies - start;
+ if (elapsed < HZ)
+ schedule_timeout_interruptible(HZ - elapsed);
+ }
+
+ out:
+ DBG("main_control_loop ended\n");
+
+ ctrl_task = 0;
+ complete_and_exit(&ctrl_complete, 0);
+}
+
+/*
+ * Dispose the control loops when tearing down
+ */
+static void dispose_control_loops(void)
+{
+ dispose_cpu_state(&cpu_state[0]);
+ dispose_cpu_state(&cpu_state[1]);
+ dispose_backside_state(&backside_state);
+ dispose_drives_state(&drives_state);
+ dispose_slots_state(&slots_state);
+ dispose_dimms_state(&dimms_state);
+}
+
+/*
+ * Create the control loops. U3-0 i2c bus is up, so we can now
+ * get to the various sensors
+ */
+static int create_control_loops(void)
+{
+ struct device_node *np;
+
+ /* Count CPUs from the device-tree, we don't care how many are
+ * actually used by Linux
+ */
+ cpu_count = 0;
+ for (np = NULL; NULL != (np = of_find_node_by_type(np, "cpu"));)
+ cpu_count++;
+
+ DBG("counted %d CPUs in the device-tree\n", cpu_count);
+
+ /* Decide the type of PID algorithm to use based on the presence of
+ * the pumps, though that may not be the best way, that is good enough
+ * for now
+ */
+ if (rackmac)
+ cpu_pid_type = CPU_PID_TYPE_RACKMAC;
+ else if (machine_is_compatible("PowerMac7,3")
+ && (cpu_count > 1)
+ && fcu_fans[CPUA_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID
+ && fcu_fans[CPUB_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID) {
+ printk(KERN_INFO "Liquid cooling pumps detected, using new algorithm !\n");
+ cpu_pid_type = CPU_PID_TYPE_COMBINED;
+ } else
+ cpu_pid_type = CPU_PID_TYPE_SPLIT;
+
+ /* Create control loops for everything. If any fail, everything
+ * fails
+ */
+ if (init_cpu_state(&cpu_state[0], 0))
+ goto fail;
+ if (cpu_pid_type == CPU_PID_TYPE_COMBINED)
+ fetch_cpu_pumps_minmax();
+
+ if (cpu_count > 1 && init_cpu_state(&cpu_state[1], 1))
+ goto fail;
+ if (init_backside_state(&backside_state))
+ goto fail;
+ if (rackmac && init_dimms_state(&dimms_state))
+ goto fail;
+ if (rackmac && init_slots_state(&slots_state))
+ goto fail;
+ if (!rackmac && init_drives_state(&drives_state))
+ goto fail;
+
+ DBG("all control loops up !\n");
+
+ return 0;
+
+ fail:
+ DBG("failure creating control loops, disposing\n");
+
+ dispose_control_loops();
+
+ return -ENODEV;
+}
+
+/*
+ * Start the control loops after everything is up, that is create
+ * the thread that will make them run
+ */
+static void start_control_loops(void)
+{
+ init_completion(&ctrl_complete);
+
+ ctrl_task = kthread_run(main_control_loop, NULL, "kfand");
+}
+
+/*
+ * Stop the control loops when tearing down
+ */
+static void stop_control_loops(void)
+{
+ if (ctrl_task)
+ wait_for_completion(&ctrl_complete);
+}
+
+/*
+ * Attach to the i2c FCU after detecting U3-1 bus
+ */
+static int attach_fcu(void)
+{
+ fcu = attach_i2c_chip(FAN_CTRLER_ID, "fcu");
+ if (fcu == NULL)
+ return -ENODEV;
+
+ DBG("FCU attached\n");
+
+ return 0;
+}
+
+/*
+ * Detach from the i2c FCU when tearing down
+ */
+static void detach_fcu(void)
+{
+ if (fcu)
+ detach_i2c_chip(fcu);
+ fcu = NULL;
+}
+
+/*
+ * Attach to the i2c controller. We probe the various chips based
+ * on the device-tree nodes and build everything for the driver to
+ * run, we then kick the driver monitoring thread
+ */
+static int therm_pm72_attach(struct i2c_adapter *adapter)
+{
+ mutex_lock(&driver_lock);
+
+ /* Check state */
+ if (state == state_detached)
+ state = state_attaching;
+ if (state != state_attaching) {
+ mutex_unlock(&driver_lock);
+ return 0;
+ }
+
+ /* Check if we are looking for one of these */
+ if (u3_0 == NULL && !strcmp(adapter->name, "u3 0")) {
+ u3_0 = adapter;
+ DBG("found U3-0\n");
+ if (k2 || !rackmac)
+ if (create_control_loops())
+ u3_0 = NULL;
+ } else if (u3_1 == NULL && !strcmp(adapter->name, "u3 1")) {
+ u3_1 = adapter;
+ DBG("found U3-1, attaching FCU\n");
+ if (attach_fcu())
+ u3_1 = NULL;
+ } else if (k2 == NULL && !strcmp(adapter->name, "mac-io 0")) {
+ k2 = adapter;
+ DBG("Found K2\n");
+ if (u3_0 && rackmac)
+ if (create_control_loops())
+ k2 = NULL;
+ }
+ /* We got all we need, start control loops */
+ if (u3_0 != NULL && u3_1 != NULL && (k2 || !rackmac)) {
+ DBG("everything up, starting control loops\n");
+ state = state_attached;
+ start_control_loops();
+ }
+ mutex_unlock(&driver_lock);
+
+ return 0;
+}
+
+/*
+ * Called on every adapter when the driver or the i2c controller
+ * is going away.
+ */
+static int therm_pm72_detach(struct i2c_adapter *adapter)
+{
+ mutex_lock(&driver_lock);
+
+ if (state != state_detached)
+ state = state_detaching;
+
+ /* Stop control loops if any */
+ DBG("stopping control loops\n");
+ mutex_unlock(&driver_lock);
+ stop_control_loops();
+ mutex_lock(&driver_lock);
+
+ if (u3_0 != NULL && !strcmp(adapter->name, "u3 0")) {
+ DBG("lost U3-0, disposing control loops\n");
+ dispose_control_loops();
+ u3_0 = NULL;
+ }
+
+ if (u3_1 != NULL && !strcmp(adapter->name, "u3 1")) {
+ DBG("lost U3-1, detaching FCU\n");
+ detach_fcu();
+ u3_1 = NULL;
+ }
+ if (u3_0 == NULL && u3_1 == NULL)
+ state = state_detached;
+
+ mutex_unlock(&driver_lock);
+
+ return 0;
+}
+
+static int fan_check_loc_match(const char *loc, int fan)
+{
+ char tmp[64];
+ char *c, *e;
+
+ strlcpy(tmp, fcu_fans[fan].loc, 64);
+
+ c = tmp;
+ for (;;) {
+ e = strchr(c, ',');
+ if (e)
+ *e = 0;
+ if (strcmp(loc, c) == 0)
+ return 1;
+ if (e == NULL)
+ break;
+ c = e + 1;
+ }
+ return 0;
+}
+
+static void fcu_lookup_fans(struct device_node *fcu_node)
+{
+ struct device_node *np = NULL;
+ int i;
+
+ /* The table is filled by default with values that are suitable
+ * for the old machines without device-tree informations. We scan
+ * the device-tree and override those values with whatever is
+ * there
+ */
+
+ DBG("Looking up FCU controls in device-tree...\n");
+
+ while ((np = of_get_next_child(fcu_node, np)) != NULL) {
+ int type = -1;
+ const char *loc;
+ 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 < FCU_FAN_COUNT; i++) {
+ int fan_id;
+
+ if (!fan_check_loc_match(loc, i))
+ continue;
+ DBG(" location match, index: %d\n", i);
+ fcu_fans[i].id = FCU_FAN_ABSENT_ID;
+ if (type != fcu_fans[i].type) {
+ printk(KERN_WARNING "therm_pm72: Fan type mismatch "
+ "in device-tree for %s\n", np->full_name);
+ break;
+ }
+ if (type == FCU_FAN_RPM)
+ fan_id = ((*reg) - 0x10) / 2;
+ else
+ fan_id = ((*reg) - 0x30) / 2;
+ if (fan_id > 7) {
+ printk(KERN_WARNING "therm_pm72: Can't parse "
+ "fan ID in device-tree for %s\n", np->full_name);
+ break;
+ }
+ DBG(" fan id -> %d, type -> %d\n", fan_id, type);
+ fcu_fans[i].id = fan_id;
+ }
+ }
+
+ /* Now dump the array */
+ printk(KERN_INFO "Detected fan controls:\n");
+ for (i = 0; i < FCU_FAN_COUNT; i++) {
+ if (fcu_fans[i].id == FCU_FAN_ABSENT_ID)
+ continue;
+ printk(KERN_INFO " %d: %s fan, id %d, location: %s\n", i,
+ fcu_fans[i].type == FCU_FAN_RPM ? "RPM" : "PWM",
+ fcu_fans[i].id, fcu_fans[i].loc);
+ }
+}
+
+static int fcu_of_probe(struct of_device* dev, const struct of_device_id *match)
+{
+ state = state_detached;
+
+ /* Lookup the fans in the device tree */
+ fcu_lookup_fans(dev->node);
+
+ /* Add the driver */
+ return i2c_add_driver(&therm_pm72_driver);
+}
+
+static int fcu_of_remove(struct of_device* dev)
+{
+ i2c_del_driver(&therm_pm72_driver);
+
+ return 0;
+}
+
+static struct of_device_id fcu_match[] =
+{
+ {
+ .type = "fcu",
+ },
+ {},
+};
+
+static struct of_platform_driver fcu_of_platform_driver =
+{
+ .name = "temperature",
+ .match_table = fcu_match,
+ .probe = fcu_of_probe,
+ .remove = fcu_of_remove
+};
+
+/*
+ * Check machine type, attach to i2c controller
+ */
+static int __init therm_pm72_init(void)
+{
+ struct device_node *np;
+
+ rackmac = machine_is_compatible("RackMac3,1");
+
+ if (!machine_is_compatible("PowerMac7,2") &&
+ !machine_is_compatible("PowerMac7,3") &&
+ !rackmac)
+ return -ENODEV;
+
+ printk(KERN_INFO "PowerMac G5 Thermal control driver %s\n", VERSION);
+
+ np = of_find_node_by_type(NULL, "fcu");
+ if (np == NULL) {
+ /* Some machines have strangely broken device-tree */
+ np = of_find_node_by_path("/u3@0,f8000000/i2c@f8001000/fan@15e");
+ if (np == NULL) {
+ printk(KERN_ERR "Can't find FCU in device-tree !\n");
+ return -ENODEV;
+ }
+ }
+ of_dev = of_platform_device_create(np, "temperature", NULL);
+ if (of_dev == NULL) {
+ printk(KERN_ERR "Can't register FCU platform device !\n");
+ return -ENODEV;
+ }
+
+ of_register_platform_driver(&fcu_of_platform_driver);
+
+ return 0;
+}
+
+static void __exit therm_pm72_exit(void)
+{
+ of_unregister_platform_driver(&fcu_of_platform_driver);
+
+ if (of_dev)
+ of_device_unregister(of_dev);
+}
+
+module_init(therm_pm72_init);
+module_exit(therm_pm72_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("Driver for Apple's PowerMac G5 thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/macintosh/therm_pm72.h b/drivers/macintosh/therm_pm72.h
new file mode 100644
index 0000000..393cc9d
--- /dev/null
+++ b/drivers/macintosh/therm_pm72.h
@@ -0,0 +1,326 @@
+#ifndef __THERM_PMAC_7_2_H__
+#define __THERM_PMAC_7_2_H__
+
+typedef unsigned short fu16;
+typedef int fs32;
+typedef short fs16;
+
+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 */
+
+/* Display a 16.16 fixed point value */
+#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
+
+/*
+ * Maximum number of seconds to be in critical state (after a
+ * normal shutdown attempt). If the machine isn't down after
+ * this counter elapses, we force an immediate machine power
+ * off.
+ */
+#define MAX_CRITICAL_STATE 30
+static char * critical_overtemp_path = "/sbin/critical_overtemp";
+
+/*
+ * 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 not sure which of these Apple's algorithm is supposed
+ * to use
+ */
+#define RPM_PID_USE_ACTUAL_SPEED 0
+
+/*
+ * i2c IDs. Currently, we hard code those and assume that
+ * the FCU is on U3 bus 1 while all sensors are on U3 bus
+ * 0. This appear to be safe enough for this first version
+ * of the driver, though I would accept any clean patch
+ * doing a better use of the device-tree without turning the
+ * while i2c registration mechanism into a racy mess
+ *
+ * Note: Xserve changed this. We have some bits on the K2 bus,
+ * which I arbitrarily set to 0x200. Ultimately, we really want
+ * too lookup these in the device-tree though
+ */
+#define FAN_CTRLER_ID 0x15e
+#define SUPPLY_MONITOR_ID 0x58
+#define SUPPLY_MONITORB_ID 0x5a
+#define DRIVES_DALLAS_ID 0x94
+#define BACKSIDE_MAX_ID 0x98
+#define XSERVE_DIMMS_LM87 0x25a
+#define XSERVE_SLOTS_LM75 0x290
+
+/*
+ * Some MAX6690, DS1775, LM87 register definitions
+ */
+#define MAX6690_INT_TEMP 0
+#define MAX6690_EXT_TEMP 1
+#define DS1775_TEMP 0
+#define LM87_INT_TEMP 0x27
+
+/*
+ * 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 */
+
+/*
+ * PID factors for the U3/Backside fan control loop. We have 2 sets
+ * of values here, one set for U3 and one set for U3H
+ */
+#define BACKSIDE_FAN_PWM_DEFAULT_ID 1
+#define BACKSIDE_FAN_PWM_INDEX 0
+#define BACKSIDE_PID_U3_G_d 0x02800000
+#define BACKSIDE_PID_U3H_G_d 0x01400000
+#define BACKSIDE_PID_RACK_G_d 0x00500000
+#define BACKSIDE_PID_G_p 0x00500000
+#define BACKSIDE_PID_RACK_G_p 0x0004cccc
+#define BACKSIDE_PID_G_r 0x00000000
+#define BACKSIDE_PID_U3_INPUT_TARGET 0x00410000
+#define BACKSIDE_PID_U3H_INPUT_TARGET 0x004b0000
+#define BACKSIDE_PID_RACK_INPUT_TARGET 0x00460000
+#define BACKSIDE_PID_INTERVAL 5
+#define BACKSIDE_PID_RACK_INTERVAL 1
+#define BACKSIDE_PID_OUTPUT_MAX 100
+#define BACKSIDE_PID_U3_OUTPUT_MIN 20
+#define BACKSIDE_PID_U3H_OUTPUT_MIN 20
+#define BACKSIDE_PID_HISTORY_SIZE 2
+
+struct basckside_pid_params
+{
+ s32 G_d;
+ s32 G_p;
+ s32 G_r;
+ s32 input_target;
+ s32 output_min;
+ s32 output_max;
+ s32 interval;
+ int additive;
+};
+
+struct backside_pid_state
+{
+ int ticks;
+ struct i2c_client * monitor;
+ s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
+ s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
+ int cur_sample;
+ s32 last_temp;
+ int pwm;
+ int first;
+};
+
+/*
+ * PID factors for the Drive Bay fan control loop
+ */
+#define DRIVES_FAN_RPM_DEFAULT_ID 2
+#define DRIVES_FAN_RPM_INDEX 1
+#define DRIVES_PID_G_d 0x01e00000
+#define DRIVES_PID_G_p 0x00500000
+#define DRIVES_PID_G_r 0x00000000
+#define DRIVES_PID_INPUT_TARGET 0x00280000
+#define DRIVES_PID_INTERVAL 5
+#define DRIVES_PID_OUTPUT_MAX 4000
+#define DRIVES_PID_OUTPUT_MIN 300
+#define DRIVES_PID_HISTORY_SIZE 2
+
+struct drives_pid_state
+{
+ int ticks;
+ struct i2c_client * monitor;
+ s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
+ s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
+ int cur_sample;
+ s32 last_temp;
+ int rpm;
+ int first;
+};
+
+#define SLOTS_FAN_PWM_DEFAULT_ID 2
+#define SLOTS_FAN_PWM_INDEX 2
+#define SLOTS_FAN_DEFAULT_PWM 40 /* Do better here ! */
+
+
+/*
+ * PID factors for the Xserve DIMM control loop
+ */
+#define DIMM_PID_G_d 0
+#define DIMM_PID_G_p 0
+#define DIMM_PID_G_r 0x06553600
+#define DIMM_PID_INPUT_TARGET 3276800
+#define DIMM_PID_INTERVAL 1
+#define DIMM_PID_OUTPUT_MAX 14000
+#define DIMM_PID_OUTPUT_MIN 4000
+#define DIMM_PID_HISTORY_SIZE 20
+
+struct dimm_pid_state
+{
+ int ticks;
+ struct i2c_client * monitor;
+ s32 sample_history[DIMM_PID_HISTORY_SIZE];
+ s32 error_history[DIMM_PID_HISTORY_SIZE];
+ int cur_sample;
+ s32 last_temp;
+ int first;
+ int output;
+};
+
+
+/*
+ * PID factors for the Xserve Slots control loop
+ */
+#define SLOTS_PID_G_d 0
+#define SLOTS_PID_G_p 0
+#define SLOTS_PID_G_r 0x00100000
+#define SLOTS_PID_INPUT_TARGET 3200000
+#define SLOTS_PID_INTERVAL 1
+#define SLOTS_PID_OUTPUT_MAX 100
+#define SLOTS_PID_OUTPUT_MIN 20
+#define SLOTS_PID_HISTORY_SIZE 20
+
+struct slots_pid_state
+{
+ int ticks;
+ struct i2c_client * monitor;
+ s32 sample_history[SLOTS_PID_HISTORY_SIZE];
+ s32 error_history[SLOTS_PID_HISTORY_SIZE];
+ int cur_sample;
+ s32 last_temp;
+ int first;
+ int pwm;
+};
+
+
+
+/* Desktops */
+
+#define CPUA_INTAKE_FAN_RPM_DEFAULT_ID 3
+#define CPUA_EXHAUST_FAN_RPM_DEFAULT_ID 4
+#define CPUB_INTAKE_FAN_RPM_DEFAULT_ID 5
+#define CPUB_EXHAUST_FAN_RPM_DEFAULT_ID 6
+
+#define CPUA_INTAKE_FAN_RPM_INDEX 3
+#define CPUA_EXHAUST_FAN_RPM_INDEX 4
+#define CPUB_INTAKE_FAN_RPM_INDEX 5
+#define CPUB_EXHAUST_FAN_RPM_INDEX 6
+
+#define CPU_INTAKE_SCALE 0x0000f852
+#define CPU_TEMP_HISTORY_SIZE 2
+#define CPU_POWER_HISTORY_SIZE 10
+#define CPU_PID_INTERVAL 1
+#define CPU_MAX_OVERTEMP 30
+
+#define CPUA_PUMP_RPM_INDEX 7
+#define CPUB_PUMP_RPM_INDEX 8
+#define CPU_PUMP_OUTPUT_MAX 3200
+#define CPU_PUMP_OUTPUT_MIN 1250
+
+/* Xserve */
+#define CPU_A1_FAN_RPM_INDEX 9
+#define CPU_A2_FAN_RPM_INDEX 10
+#define CPU_A3_FAN_RPM_INDEX 11
+#define CPU_B1_FAN_RPM_INDEX 12
+#define CPU_B2_FAN_RPM_INDEX 13
+#define CPU_B3_FAN_RPM_INDEX 14
+
+
+struct cpu_pid_state
+{
+ int index;
+ struct i2c_client * monitor;
+ struct mpu_data mpu;
+ int overtemp;
+ s32 temp_history[CPU_TEMP_HISTORY_SIZE];
+ int cur_temp;
+ s32 power_history[CPU_POWER_HISTORY_SIZE];
+ s32 error_history[CPU_POWER_HISTORY_SIZE];
+ int cur_power;
+ int count_power;
+ int rpm;
+ int intake_rpm;
+ s32 voltage;
+ s32 current_a;
+ s32 last_temp;
+ s32 last_power;
+ int first;
+ u8 adc_config;
+ s32 pump_min;
+ s32 pump_max;
+};
+
+/* Tickle FCU every 10 seconds */
+#define FCU_TICKLE_TICKS 10
+
+/*
+ * Driver state
+ */
+enum {
+ state_detached,
+ state_attaching,
+ state_attached,
+ state_detaching,
+};
+
+
+#endif /* __THERM_PMAC_7_2_H__ */
diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c
new file mode 100644
index 0000000..3da0a02
--- /dev/null
+++ b/drivers/macintosh/therm_windtunnel.c
@@ -0,0 +1,534 @@
+/*
+ * 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
+ * temperatur 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/slab.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/system.h>
+#include <asm/sections.h>
+#include <asm/macio.h>
+
+#define LOG_TEMP 0 /* continously log temperature */
+
+static int do_probe( struct i2c_adapter *adapter, int addr, int kind);
+
+/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */
+static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b,
+ 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x2c, 0x2d, 0x2e, 0x2f,
+ I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD;
+
+static struct {
+ volatile int running;
+ struct task_struct *poll_task;
+
+ struct mutex lock;
+ struct of_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 = 65 << 8;
+ x.overheat_hyst = 60 << 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 )
+{
+ int ret = 0;
+
+ if( strncmp(adapter->name, "uni-n", 5) )
+ return 0;
+
+ if( !x.running ) {
+ ret = i2c_probe( adapter, &addr_data, &do_probe );
+ if( x.thermostat && x.fan ) {
+ x.running = 1;
+ x.poll_task = kthread_run(control_loop, NULL, "g4fand");
+ }
+ }
+ return ret;
+}
+
+static int
+do_detach( struct i2c_client *client )
+{
+ int err;
+
+ if( (err=i2c_detach_client(client)) )
+ printk(KERN_ERR "failed to detach thermostat client\n");
+ else {
+ 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");
+ }
+ kfree( client );
+ }
+ return err;
+}
+
+static struct i2c_driver g4fan_driver = {
+ .driver = {
+ .name = "therm_windtunnel",
+ },
+ .attach_adapter = do_attach,
+ .detach_client = do_detach,
+};
+
+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 );
+
+ strlcpy( cl->name, "ADM1030 fan controller", sizeof(cl->name) );
+
+ if( !i2c_attach_client(cl) )
+ x.fan = cl;
+ out:
+ if( cl != x.fan )
+ kfree( cl );
+ 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;
+
+ strlcpy( cl->name, "DS1775 thermostat", sizeof(cl->name) );
+
+ if( !i2c_attach_client(cl) )
+ x.thermostat = cl;
+out:
+ if( cl != x.thermostat )
+ kfree( cl );
+ return 0;
+}
+
+static int
+do_probe( struct i2c_adapter *adapter, int addr, int kind )
+{
+ struct i2c_client *cl;
+
+ if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA
+ | I2C_FUNC_SMBUS_WRITE_BYTE) )
+ return 0;
+
+ if( !(cl=kzalloc(sizeof(*cl), GFP_KERNEL)) )
+ return -ENOMEM;
+
+ cl->addr = addr;
+ cl->adapter = adapter;
+ cl->driver = &g4fan_driver;
+ cl->flags = 0;
+
+ if( addr < 0x48 )
+ return attach_fan( cl );
+ return attach_thermostat( cl );
+}
+
+
+/************************************************************************/
+/* initialization / cleanup */
+/************************************************************************/
+
+static int
+therm_of_probe( struct of_device *dev, const struct of_device_id *match )
+{
+ return i2c_add_driver( &g4fan_driver );
+}
+
+static int
+therm_of_remove( struct of_device *dev )
+{
+ i2c_del_driver( &g4fan_driver );
+ return 0;
+}
+
+static struct of_device_id therm_of_match[] = {{
+ .name = "fan",
+ .compatible = "adm1030"
+ }, {}
+};
+
+static struct of_platform_driver therm_of_driver = {
+ .name = "temperature",
+ .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 || !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;
+ }
+
+ of_register_platform_driver( &therm_of_driver );
+ return 0;
+}
+
+static void __exit
+g4fan_exit( void )
+{
+ of_unregister_platform_driver( &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/drivers/macintosh/via-cuda.c b/drivers/macintosh/via-cuda.c
new file mode 100644
index 0000000..741a93a
--- /dev/null
+++ b/drivers/macintosh/via-cuda.c
@@ -0,0 +1,633 @@
+/*
+ * Device driver for the via-cuda on Apple Powermacs.
+ *
+ * The VIA (versatile interface adapter) interfaces to the CUDA,
+ * a 6805 microprocessor core which controls the ADB (Apple Desktop
+ * Bus) which connects to the keyboard and mouse. The CUDA also
+ * controls system power and the RTC (real time clock) chip.
+ *
+ * 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/machw.h>
+#include <asm/mac_via.h>
+#endif
+#include <asm/io.h>
+#include <asm/system.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 */
+
+/* Bits in B data register: all active low */
+#define TREQ 0x08 /* Transfer request (input) */
+#define TACK 0x10 /* Transfer acknowledge (output) */
+#define TIP 0x20 /* Transfer in progress (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 */
+
+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_init(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 = {
+ "CUDA",
+ cuda_probe,
+ cuda_init,
+ cuda_send_request,
+ cuda_adb_autopoll,
+ cuda_poll,
+ cuda_reset_adb_bus
+};
+#endif /* CONFIG_ADB */
+
+#ifdef CONFIG_PPC
+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 /* CONFIG_PPC */
+
+static int __init via_cuda_start(void)
+{
+ if (via == NULL)
+ return -ENODEV;
+
+#ifdef CONFIG_MAC
+ cuda_irq = IRQ_MAC_ADB;
+#else /* CONFIG_MAC */
+ cuda_irq = irq_of_parse_and_map(vias, 0);
+ if (cuda_irq == NO_IRQ) {
+ printk(KERN_ERR "via-cuda: can't map interrupts for %s\n",
+ vias->full_name);
+ return -ENODEV;
+ }
+#endif /* CONFIG_MAC */
+
+ 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;
+ }
+
+ printk("Macintosh CUDA driver v0.5 for Unified ADB.\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)
+ return -ENODEV;
+ via = via1;
+#endif
+ return 0;
+}
+
+static int __init
+cuda_init(void)
+{
+#ifdef CONFIG_PPC
+ if (via == NULL)
+ return -ENODEV;
+ return 0;
+#else
+ int err = cuda_init_via();
+ if (err) {
+ printk(KERN_ERR "cuda_init_via() failed\n");
+ return -ENODEV;
+ }
+ out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */
+
+ return via_cuda_start();
+#endif
+}
+#endif /* CONFIG_ADB */
+
+#define WAIT_FOR(cond, what) \
+ do { \
+ int x; \
+ for (x = 1000; !(cond); --x) { \
+ if (x == 0) { \
+ printk("Timeout waiting for " what "\n"); \
+ return -ENXIO; \
+ } \
+ udelay(100); \
+ } \
+ } while (0)
+
+static int
+cuda_init_via(void)
+{
+ out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ); /* TACK & TIP out */
+ out_8(&via[B], in_8(&via[B]) | TACK | TIP); /* negate them */
+ 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 */
+#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
+
+ /* 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 */
+ out_8(&via[B], in_8(&via[B]) & ~TACK);
+
+ /* wait for the CUDA to assert TREQ in response */
+ WAIT_FOR((in_8(&via[B]) & TREQ) == 0, "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 */
+ out_8(&via[B], in_8(&via[B]) | TACK);
+
+ /* wait for the CUDA to negate TREQ and the corresponding interrupt */
+ WAIT_FOR(in_8(&via[B]) & TREQ, "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);
+ out_8(&via[B], in_8(&via[B]) | TIP); /* should be unnecessary */
+
+ 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);
+}
+
+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)
+{
+ struct adb_request *req;
+
+ /* assert cuda_state == idle */
+ /* get the packet to send */
+ req = current_req;
+ if (req == 0)
+ return;
+ if ((in_8(&via[B]) & TREQ) == 0)
+ 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], req->data[0]);
+ out_8(&via[B], in_8(&via[B]) & ~TIP);
+ cuda_state = sent_first_byte;
+}
+
+void
+cuda_poll(void)
+{
+ /* cuda_interrupt only takes a normal lock, we disable
+ * interrupts here to avoid re-entering and thus deadlocking.
+ */
+ disable_irq(cuda_irq);
+ cuda_interrupt(0, NULL);
+ enable_irq(cuda_irq);
+}
+
+static irqreturn_t
+cuda_interrupt(int irq, void *arg)
+{
+ int status;
+ struct adb_request *req = NULL;
+ unsigned char ibuf[16];
+ int ibuf_len = 0;
+ int complete = 0;
+
+ spin_lock(&cuda_lock);
+
+ /* On powermacs, this handler is registered for the VIA IRQ. But it uses
+ * 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(&cuda_lock);
+ return IRQ_NONE;
+ } else {
+ out_8(&via[IFR], SR_INT);
+ }
+ }
+
+ status = (~in_8(&via[B]) & (TIP|TREQ)) | (in_8(&via[ACR]) & SR_OUT);
+ /* printk("cuda_interrupt: state=%d status=%x\n", cuda_state, status); */
+ switch (cuda_state) {
+ case idle:
+ /* CUDA has sent us the first byte of data - unsolicited */
+ if (status != TREQ)
+ printk("cuda: state=idle, status=%x\n", status);
+ (void)in_8(&via[SR]);
+ out_8(&via[B], in_8(&via[B]) & ~TIP);
+ cuda_state = reading;
+ reply_ptr = cuda_rbuf;
+ reading_reply = 0;
+ break;
+
+ case awaiting_reply:
+ /* CUDA has sent us the first byte of data of a reply */
+ if (status != TREQ)
+ printk("cuda: state=awaiting_reply, status=%x\n", status);
+ (void)in_8(&via[SR]);
+ out_8(&via[B], in_8(&via[B]) & ~TIP);
+ cuda_state = reading;
+ reply_ptr = current_req->reply;
+ reading_reply = 1;
+ break;
+
+ case sent_first_byte:
+ if (status == TREQ + TIP + SR_OUT) {
+ /* collision */
+ out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT);
+ (void)in_8(&via[SR]);
+ out_8(&via[B], in_8(&via[B]) | TIP | TACK);
+ cuda_state = idle;
+ } else {
+ /* assert status == TIP + SR_OUT */
+ if (status != TIP + SR_OUT)
+ printk("cuda: state=sent_first_byte status=%x\n", status);
+ out_8(&via[SR], current_req->data[1]);
+ out_8(&via[B], in_8(&via[B]) ^ TACK);
+ data_index = 2;
+ 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]);
+ out_8(&via[B], in_8(&via[B]) | TACK | TIP);
+ 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++]);
+ out_8(&via[B], in_8(&via[B]) ^ TACK);
+ }
+ break;
+
+ case reading:
+ *reply_ptr++ = in_8(&via[SR]);
+ if (status == TIP) {
+ /* that's all folks */
+ out_8(&via[B], in_8(&via[B]) | TACK | TIP);
+ cuda_state = read_done;
+ } else {
+ /* assert status == TIP | TREQ */
+ if (status != TIP + TREQ)
+ printk("cuda: state=reading status=%x\n", status);
+ out_8(&via[B], in_8(&via[B]) ^ TACK);
+ }
+ break;
+
+ case read_done:
+ (void)in_8(&via[SR]);
+ 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;
+ } 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);
+ }
+ if (status == TREQ) {
+ out_8(&via[B], in_8(&via[B]) & ~TIP);
+ cuda_state = reading;
+ reply_ptr = cuda_rbuf;
+ reading_reply = 0;
+ } else {
+ cuda_state = idle;
+ cuda_start();
+ }
+ break;
+
+ default:
+ printk("cuda_interrupt: unknown cuda_state %d?\n", cuda_state);
+ }
+ spin_unlock(&cuda_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)
+ cuda_input(ibuf, ibuf_len);
+ return IRQ_HANDLED;
+}
+
+static void
+cuda_input(unsigned char *buf, int nb)
+{
+ int i;
+
+ 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;
+
+ default:
+ printk("data from cuda (%d bytes):", nb);
+ for (i = 0; i < nb; ++i)
+ printk(" %.2x", buf[i]);
+ printk("\n");
+ }
+}
diff --git a/drivers/macintosh/via-macii.c b/drivers/macintosh/via-macii.c
new file mode 100644
index 0000000..6e6dd17
--- /dev/null
+++ b/drivers/macintosh/via-macii.c
@@ -0,0 +1,551 @@
+/*
+ * 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 <fthain@telegraphics.com.au> - 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/machw.h>
+#include <asm/mac_via.h>
+#include <asm/system.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, IRQ_FLG_LOCK, "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/drivers/macintosh/via-maciisi.c b/drivers/macintosh/via-maciisi.c
new file mode 100644
index 0000000..2dc7880
--- /dev/null
+++ b/drivers/macintosh/via-maciisi.c
@@ -0,0 +1,678 @@
+/*
+ * Device driver for the IIsi-style ADB on some Mac LC and II-class machines
+ *
+ * Based on via-cuda.c and via-macii.c, as well as the original
+ * adb-bus.c, which in turn is somewhat influenced by (but uses no
+ * code from) the NetBSD HWDIRECT ADB code. Original IIsi driver work
+ * was done by Robert Thompson and integrated into the old style
+ * driver by Michael Schmitz.
+ *
+ * Original sources (c) Alan Cox, Paul Mackerras, and others.
+ *
+ * Rewritten for Unified ADB by David Huggins-Daines <dhd@debian.org>
+ *
+ * 7/13/2000- extensive changes by Andrew McPherson <andrew@macduff.dhs.org>
+ * Works about 30% of the time now.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/adb.h>
+#include <linux/cuda.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <asm/macintosh.h>
+#include <asm/macints.h>
+#include <asm/machw.h>
+#include <asm/mac_via.h>
+
+static volatile unsigned char *via;
+
+/* VIA registers - spaced 0x200 bytes apart - only the ones we actually use */
+#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 SR (10*RS) /* Shift register */
+#define ACR (11*RS) /* Auxiliary control register */
+#define IFR (13*RS) /* Interrupt flag register */
+#define IER (14*RS) /* Interrupt enable register */
+
+/* Bits in B data register: all active low */
+#define TREQ 0x08 /* Transfer request (input) */
+#define TACK 0x10 /* Transfer acknowledge (output) */
+#define TIP 0x20 /* Transfer in progress (output) */
+#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 */
+#define SR_DATA 0x08 /* Shift register data */
+#define SR_CLOCK 0x10 /* Shift register clock */
+
+#define ADB_DELAY 150
+
+#undef DEBUG_MACIISI_ADB
+
+static struct adb_request* current_req;
+static struct adb_request* last_req;
+static unsigned char maciisi_rbuf[16];
+static unsigned char *reply_ptr;
+static int data_index;
+static int reading_reply;
+static int reply_len;
+static int tmp;
+static int need_sync;
+
+static enum maciisi_state {
+ idle,
+ sending,
+ reading,
+} maciisi_state;
+
+static int maciisi_probe(void);
+static int maciisi_init(void);
+static int maciisi_send_request(struct adb_request* req, int sync);
+static void maciisi_sync(struct adb_request *req);
+static int maciisi_write(struct adb_request* req);
+static irqreturn_t maciisi_interrupt(int irq, void* arg);
+static void maciisi_input(unsigned char *buf, int nb);
+static int maciisi_init_via(void);
+static void maciisi_poll(void);
+static int maciisi_start(void);
+
+struct adb_driver via_maciisi_driver = {
+ "Mac IIsi",
+ maciisi_probe,
+ maciisi_init,
+ maciisi_send_request,
+ NULL, /* maciisi_adb_autopoll, */
+ maciisi_poll,
+ NULL /* maciisi_reset_adb_bus */
+};
+
+static int
+maciisi_probe(void)
+{
+ if (macintosh_config->adb_type != MAC_ADB_IISI)
+ return -ENODEV;
+
+ via = via1;
+ return 0;
+}
+
+static int
+maciisi_init(void)
+{
+ int err;
+
+ if (via == NULL)
+ return -ENODEV;
+
+ if ((err = maciisi_init_via())) {
+ printk(KERN_ERR "maciisi_init: maciisi_init_via() failed, code %d\n", err);
+ via = NULL;
+ return err;
+ }
+
+ if (request_irq(IRQ_MAC_ADB, maciisi_interrupt, IRQ_FLG_LOCK | IRQ_FLG_FAST,
+ "ADB", maciisi_interrupt)) {
+ printk(KERN_ERR "maciisi_init: can't get irq %d\n", IRQ_MAC_ADB);
+ return -EAGAIN;
+ }
+
+ printk("adb: Mac IIsi driver v0.2 for Unified ADB.\n");
+ return 0;
+}
+
+/* Flush data from the ADB controller */
+static void
+maciisi_stfu(void)
+{
+ int status = via[B] & (TIP|TREQ);
+
+ if (status & TREQ) {
+#ifdef DEBUG_MACIISI_ADB
+ printk (KERN_DEBUG "maciisi_stfu called with TREQ high!\n");
+#endif
+ return;
+ }
+
+ udelay(ADB_DELAY);
+ via[ACR] &= ~SR_OUT;
+ via[IER] = IER_CLR | SR_INT;
+
+ udelay(ADB_DELAY);
+
+ status = via[B] & (TIP|TREQ);
+
+ if (!(status & TREQ))
+ {
+ via[B] |= TIP;
+
+ while(1)
+ {
+ int poll_timeout = ADB_DELAY * 5;
+ /* Poll for SR interrupt */
+ while (!(via[IFR] & SR_INT) && poll_timeout-- > 0)
+ status = via[B] & (TIP|TREQ);
+
+ tmp = via[SR]; /* Clear shift register */
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_stfu: status %x timeout %d data %x\n",
+ status, poll_timeout, tmp);
+#endif
+ if(via[B] & TREQ)
+ break;
+
+ /* ACK on-off */
+ via[B] |= TACK;
+ udelay(ADB_DELAY);
+ via[B] &= ~TACK;
+ }
+
+ /* end frame */
+ via[B] &= ~TIP;
+ udelay(ADB_DELAY);
+ }
+
+ via[IER] = IER_SET | SR_INT;
+}
+
+/* All specifically VIA-related initialization goes here */
+static int
+maciisi_init_via(void)
+{
+ int i;
+
+ /* Set the lines up. We want TREQ as input TACK|TIP as output */
+ via[DIRB] = (via[DIRB] | TACK | TIP) & ~TREQ;
+ /* Shift register on input */
+ via[ACR] = (via[ACR] & ~SR_CTRL) | SR_EXT;
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_init_via: initial status %x\n", via[B] & (TIP|TREQ));
+#endif
+ /* Wipe any pending data and int */
+ tmp = via[SR];
+ /* Enable keyboard interrupts */
+ via[IER] = IER_SET | SR_INT;
+ /* Set initial state: idle */
+ via[B] &= ~(TACK|TIP);
+ /* Clear interrupt bit */
+ via[IFR] = SR_INT;
+
+ for(i = 0; i < 60; i++) {
+ udelay(ADB_DELAY);
+ maciisi_stfu();
+ udelay(ADB_DELAY);
+ if(via[B] & TREQ)
+ break;
+ }
+ if (i == 60)
+ printk(KERN_ERR "maciisi_init_via: bus jam?\n");
+
+ maciisi_state = idle;
+ need_sync = 0;
+
+ return 0;
+}
+
+/* Send a request, possibly waiting for a reply */
+static int
+maciisi_send_request(struct adb_request* req, int sync)
+{
+ int i;
+
+#ifdef DEBUG_MACIISI_ADB
+ static int dump_packet = 0;
+#endif
+
+ if (via == NULL) {
+ req->complete = 1;
+ return -ENXIO;
+ }
+
+#ifdef DEBUG_MACIISI_ADB
+ if (dump_packet) {
+ printk(KERN_DEBUG "maciisi_send_request:");
+ for (i = 0; i < req->nbytes; i++) {
+ printk(" %.2x", req->data[i]);
+ }
+ printk(" sync %d\n", sync);
+ }
+#endif
+
+ req->reply_expected = 1;
+
+ i = maciisi_write(req);
+ if (i)
+ {
+ /* Normally, if a packet requires syncing, that happens at the end of
+ * maciisi_send_request. But if the transfer fails, it will be restarted
+ * by maciisi_interrupt(). We use need_sync to tell maciisi_interrupt
+ * when to sync a packet that it sends out.
+ *
+ * Suggestions on a better way to do this are welcome.
+ */
+ if(i == -EBUSY && sync)
+ need_sync = 1;
+ else
+ need_sync = 0;
+ return i;
+ }
+ if(sync)
+ maciisi_sync(req);
+
+ return 0;
+}
+
+/* Poll the ADB chip until the request completes */
+static void maciisi_sync(struct adb_request *req)
+{
+ int count = 0;
+
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_sync called\n");
+#endif
+
+ /* If for some reason the ADB chip shuts up on us, we want to avoid an endless loop. */
+ while (!req->complete && count++ < 50) {
+ maciisi_poll();
+ }
+ /* This could be BAD... when the ADB controller doesn't respond
+ * for this long, it's probably not coming back :-( */
+ if(count >= 50) /* Hopefully shouldn't happen */
+ printk(KERN_ERR "maciisi_send_request: poll timed out!\n");
+}
+
+int
+maciisi_request(struct adb_request *req, void (*done)(struct adb_request *),
+ int nbytes, ...)
+{
+ va_list list;
+ int i;
+
+ req->nbytes = nbytes;
+ req->done = done;
+ req->reply_expected = 0;
+ va_start(list, nbytes);
+ for (i = 0; i < nbytes; i++)
+ req->data[i++] = va_arg(list, int);
+ va_end(list);
+
+ return maciisi_send_request(req, 1);
+}
+
+/* Enqueue a request, and run the queue if possible */
+static int
+maciisi_write(struct adb_request* req)
+{
+ unsigned long flags;
+ int i;
+
+ /* We will accept CUDA packets - the VIA sends them to us, so
+ it figures that we should be able to send them to it */
+ if (req->nbytes < 2 || req->data[0] > CUDA_PACKET) {
+ printk(KERN_ERR "maciisi_write: packet too small or not an ADB or CUDA packet\n");
+ req->complete = 1;
+ return -EINVAL;
+ }
+ req->next = NULL;
+ req->sent = 0;
+ req->complete = 0;
+ req->reply_len = 0;
+
+ local_irq_save(flags);
+
+ if (current_req) {
+ last_req->next = req;
+ last_req = req;
+ } else {
+ current_req = req;
+ last_req = req;
+ }
+ if (maciisi_state == idle)
+ {
+ i = maciisi_start();
+ if(i != 0)
+ {
+ local_irq_restore(flags);
+ return i;
+ }
+ }
+ else
+ {
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_write: would start, but state is %d\n", maciisi_state);
+#endif
+ local_irq_restore(flags);
+ return -EBUSY;
+ }
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static int
+maciisi_start(void)
+{
+ struct adb_request* req;
+ int status;
+
+#ifdef DEBUG_MACIISI_ADB
+ status = via[B] & (TIP | TREQ);
+
+ printk(KERN_DEBUG "maciisi_start called, state=%d, status=%x, ifr=%x\n", maciisi_state, status, via[IFR]);
+#endif
+
+ if (maciisi_state != idle) {
+ /* shouldn't happen */
+ printk(KERN_ERR "maciisi_start: maciisi_start called when driver busy!\n");
+ return -EBUSY;
+ }
+
+ req = current_req;
+ if (req == NULL)
+ return -EINVAL;
+
+ status = via[B] & (TIP|TREQ);
+ if (!(status & TREQ)) {
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_start: bus busy - aborting\n");
+#endif
+ return -EBUSY;
+ }
+
+ /* Okay, send */
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "maciisi_start: sending\n");
+#endif
+ /* Set state to active */
+ via[B] |= TIP;
+ /* ACK off */
+ via[B] &= ~TACK;
+ /* Delay */
+ udelay(ADB_DELAY);
+ /* Shift out and send */
+ via[ACR] |= SR_OUT;
+ via[SR] = req->data[0];
+ data_index = 1;
+ /* ACK on */
+ via[B] |= TACK;
+ maciisi_state = sending;
+
+ return 0;
+}
+
+void
+maciisi_poll(void)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ if (via[IFR] & SR_INT) {
+ maciisi_interrupt(0, NULL);
+ }
+ else /* avoid calling this function too quickly in a loop */
+ udelay(ADB_DELAY);
+
+ local_irq_restore(flags);
+}
+
+/* Shift register interrupt - this is *supposed* to mean that the
+ register is either full or empty. In practice, I have no idea what
+ it means :( */
+static irqreturn_t
+maciisi_interrupt(int irq, void* arg)
+{
+ int status;
+ struct adb_request *req;
+#ifdef DEBUG_MACIISI_ADB
+ static int dump_reply = 0;
+#endif
+ int i;
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ status = via[B] & (TIP|TREQ);
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "state %d status %x ifr %x\n", maciisi_state, status, via[IFR]);
+#endif
+
+ if (!(via[IFR] & SR_INT)) {
+ /* Shouldn't happen, we hope */
+ printk(KERN_ERR "maciisi_interrupt: called without interrupt flag set\n");
+ local_irq_restore(flags);
+ return IRQ_NONE;
+ }
+
+ /* Clear the interrupt */
+ /* via[IFR] = SR_INT; */
+
+ switch_start:
+ switch (maciisi_state) {
+ case idle:
+ if (status & TIP)
+ printk(KERN_ERR "maciisi_interrupt: state is idle but TIP asserted!\n");
+
+ if(!reading_reply)
+ udelay(ADB_DELAY);
+ /* Shift in */
+ via[ACR] &= ~SR_OUT;
+ /* Signal start of frame */
+ via[B] |= TIP;
+ /* Clear the interrupt (throw this value on the floor, it's useless) */
+ tmp = via[SR];
+ /* ACK adb chip, high-low */
+ via[B] |= TACK;
+ udelay(ADB_DELAY);
+ via[B] &= ~TACK;
+ reply_len = 0;
+ maciisi_state = reading;
+ if (reading_reply) {
+ reply_ptr = current_req->reply;
+ } else {
+ reply_ptr = maciisi_rbuf;
+ }
+ break;
+
+ case sending:
+ /* via[SR]; */
+ /* Set ACK off */
+ via[B] &= ~TACK;
+ req = current_req;
+
+ if (!(status & TREQ)) {
+ /* collision */
+ printk(KERN_ERR "maciisi_interrupt: send collision\n");
+ /* Set idle and input */
+ via[ACR] &= ~SR_OUT;
+ tmp = via[SR];
+ via[B] &= ~TIP;
+ /* Must re-send */
+ reading_reply = 0;
+ reply_len = 0;
+ maciisi_state = idle;
+ udelay(ADB_DELAY);
+ /* process this now, because the IFR has been cleared */
+ goto switch_start;
+ }
+
+ udelay(ADB_DELAY);
+
+ if (data_index >= req->nbytes) {
+ /* Sent the whole packet, put the bus back in idle state */
+ /* Shift in, we are about to read a reply (hopefully) */
+ via[ACR] &= ~SR_OUT;
+ tmp = via[SR];
+ /* End of frame */
+ via[B] &= ~TIP;
+ req->sent = 1;
+ maciisi_state = idle;
+ if (req->reply_expected) {
+ /* Note: only set this once we've
+ successfully sent the packet */
+ reading_reply = 1;
+ } else {
+ current_req = req->next;
+ if (req->done)
+ (*req->done)(req);
+ /* Do any queued requests now */
+ i = maciisi_start();
+ if(i == 0 && need_sync) {
+ /* Packet needs to be synced */
+ maciisi_sync(current_req);
+ }
+ if(i != -EBUSY)
+ need_sync = 0;
+ }
+ } else {
+ /* Sending more stuff */
+ /* Shift out */
+ via[ACR] |= SR_OUT;
+ /* Write */
+ via[SR] = req->data[data_index++];
+ /* Signal 'byte ready' */
+ via[B] |= TACK;
+ }
+ break;
+
+ case reading:
+ /* Shift in */
+ /* via[ACR] &= ~SR_OUT; */ /* Not in 2.2 */
+ if (reply_len++ > 16) {
+ printk(KERN_ERR "maciisi_interrupt: reply too long, aborting read\n");
+ via[B] |= TACK;
+ udelay(ADB_DELAY);
+ via[B] &= ~(TACK|TIP);
+ maciisi_state = idle;
+ i = maciisi_start();
+ if(i == 0 && need_sync) {
+ /* Packet needs to be synced */
+ maciisi_sync(current_req);
+ }
+ if(i != -EBUSY)
+ need_sync = 0;
+ break;
+ }
+ /* Read data */
+ *reply_ptr++ = via[SR];
+ status = via[B] & (TIP|TREQ);
+ /* ACK on/off */
+ via[B] |= TACK;
+ udelay(ADB_DELAY);
+ via[B] &= ~TACK;
+ if (!(status & TREQ))
+ break; /* more stuff to deal with */
+
+ /* end of frame */
+ via[B] &= ~TIP;
+ tmp = via[SR]; /* That's what happens in 2.2 */
+ udelay(ADB_DELAY); /* Give controller time to recover */
+
+ /* end of packet, deal with it */
+ 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);
+ }
+ }
+#ifdef DEBUG_MACIISI_ADB
+ if (dump_reply) {
+ int i;
+ printk(KERN_DEBUG "maciisi_interrupt: reply is ");
+ for (i = 0; i < req->reply_len; ++i)
+ printk(" %.2x", req->reply[i]);
+ printk("\n");
+ }
+#endif
+ req->complete = 1;
+ current_req = req->next;
+ if (req->done)
+ (*req->done)(req);
+ /* Obviously, we got it */
+ reading_reply = 0;
+ } else {
+ maciisi_input(maciisi_rbuf, reply_ptr - maciisi_rbuf);
+ }
+ maciisi_state = idle;
+ status = via[B] & (TIP|TREQ);
+ if (!(status & TREQ)) {
+ /* Timeout?! More likely, another packet coming in already */
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "extra data after packet: status %x ifr %x\n",
+ status, via[IFR]);
+#endif
+#if 0
+ udelay(ADB_DELAY);
+ via[B] |= TIP;
+
+ maciisi_state = reading;
+ reading_reply = 0;
+ reply_ptr = maciisi_rbuf;
+#else
+ /* Process the packet now */
+ reading_reply = 0;
+ goto switch_start;
+#endif
+ /* We used to do this... but the controller might actually have data for us */
+ /* maciisi_stfu(); */
+ }
+ else {
+ /* Do any queued requests now if possible */
+ i = maciisi_start();
+ if(i == 0 && need_sync) {
+ /* Packet needs to be synced */
+ maciisi_sync(current_req);
+ }
+ if(i != -EBUSY)
+ need_sync = 0;
+ }
+ break;
+
+ default:
+ printk("maciisi_interrupt: unknown maciisi_state %d?\n", maciisi_state);
+ }
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+}
+
+static void
+maciisi_input(unsigned char *buf, int nb)
+{
+#ifdef DEBUG_MACIISI_ADB
+ int i;
+#endif
+
+ switch (buf[0]) {
+ case ADB_PACKET:
+ adb_input(buf+2, nb-2, buf[1] & 0x40);
+ break;
+ default:
+#ifdef DEBUG_MACIISI_ADB
+ printk(KERN_DEBUG "data from IIsi ADB (%d bytes):", nb);
+ for (i = 0; i < nb; ++i)
+ printk(" %.2x", buf[i]);
+ printk("\n");
+#endif
+ break;
+ }
+}
diff --git a/drivers/macintosh/via-pmu-backlight.c b/drivers/macintosh/via-pmu-backlight.c
new file mode 100644
index 0000000..a348bb0
--- /dev/null
+++ b/drivers/macintosh/via-pmu-backlight.c
@@ -0,0 +1,191 @@
+/*
+ * 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 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 int pmu_backlight_get_brightness(struct backlight_device *bd)
+{
+ return bd->props.brightness;
+}
+
+static struct backlight_ops pmu_backlight_data = {
+ .get_brightness = pmu_backlight_get_brightness,
+ .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_device *bd;
+ char name[10];
+ int level, autosave;
+
+ /* Special case for the old PowerBook since I can't test on it */
+ autosave =
+ machine_is_compatible("AAPL,3400/2400") ||
+ machine_is_compatible("AAPL,3500");
+
+ if (!autosave &&
+ !pmac_has_backlight_type("pmu") &&
+ !machine_is_compatible("AAPL,PowerBook1998") &&
+ !machine_is_compatible("PowerBook1,1"))
+ return;
+
+ snprintf(name, sizeof(name), "pmubl");
+
+ bd = backlight_device_register(name, NULL, NULL, &pmu_backlight_data);
+ if (IS_ERR(bd)) {
+ printk(KERN_ERR "PMU Backlight registration failed\n");
+ return;
+ }
+ uses_pmu_bl = 1;
+ bd->props.max_brightness = FB_BACKLIGHT_LEVELS - 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/drivers/macintosh/via-pmu-event.c b/drivers/macintosh/via-pmu-event.c
new file mode 100644
index 0000000..25cd565
--- /dev/null
+++ b/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/drivers/macintosh/via-pmu-event.h b/drivers/macintosh/via-pmu-event.h
new file mode 100644
index 0000000..72c54de
--- /dev/null
+++ b/drivers/macintosh/via-pmu-event.h
@@ -0,0 +1,8 @@
+#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/drivers/macintosh/via-pmu-led.c b/drivers/macintosh/via-pmu-led.c
new file mode 100644
index 0000000..55ad956
--- /dev/null
+++ b/drivers/macintosh/via-pmu-led.c
@@ -0,0 +1,115 @@
+/*
+ * 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-front-led",
+#ifdef CONFIG_ADB_PMU_LED_IDE
+ .default_trigger = "ide-disk",
+#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)
+ 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/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c
new file mode 100644
index 0000000..d524dc2
--- /dev/null
+++ b/drivers/macintosh/via-pmu.c
@@ -0,0 +1,2540 @@
+/*
+ * 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/smp_lock.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.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/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/sysdev.h>
+#include <linux/freezer.h>
+#include <linux/syscalls.h>
+#include <linux/suspend.h>
+#include <linux/cpu.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/system.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 <asm/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 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 = NO_IRQ;
+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 int proc_get_info(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+static int proc_get_irqstats(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+static void pmu_pass_intr(unsigned char *data, int len);
+static int proc_get_batt(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+static int proc_read_options(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+static int proc_write_options(struct file *file, const char __user *buffer,
+ unsigned long count, void *data);
+
+#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);
+ }
+ if (gpio_reg == NULL) {
+ printk(KERN_ERR "via-pmu: Can't find GPIO reg !\n");
+ goto fail_gpio;
+ }
+ } else
+ pmu_kind = PMU_UNKNOWN;
+
+ via = ioremap(taddr, 0x2000);
+ if (via == NULL) {
+ printk(KERN_ERR "via-pmu: Can't map address !\n");
+ goto fail;
+ }
+
+ out_8(&via[IER], IER_CLR | 0x7f); /* disable all intrs */
+ out_8(&via[IFR], 0x7f); /* clear IFR */
+
+ pmu_state = idle;
+
+ if (!init_pmu()) {
+ via = NULL;
+ return 0;
+ }
+
+ 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:
+ of_node_put(vias);
+ iounmap(gpio_reg);
+ gpio_reg = NULL;
+ fail_gpio:
+ 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 == NO_IRQ) {
+ printk(KERN_ERR "via-pmu: can't map interrupt\n");
+ return -ENODEV;
+ }
+ if (request_irq(irq, via_pmu_interrupt, 0, "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 != NO_IRQ) {
+ if (request_irq(gpio_irq, gpio1_interrupt, 0,
+ "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 (machine_is_compatible("AAPL,3400/2400") ||
+ 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 (machine_is_compatible("AAPL,PowerBook1998") ||
+ 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] = create_proc_read_entry(title, 0, proc_pmu_root,
+ proc_get_batt, (void *)i);
+ }
+
+ proc_pmu_info = create_proc_read_entry("info", 0, proc_pmu_root,
+ proc_get_info, NULL);
+ proc_pmu_irqstats = create_proc_read_entry("interrupts", 0, proc_pmu_root,
+ proc_get_irqstats, NULL);
+ proc_pmu_options = create_proc_entry("options", 0600, proc_pmu_root);
+ if (proc_pmu_options) {
+ proc_pmu_options->read_proc = proc_read_options;
+ proc_pmu_options->write_proc = proc_write_options;
+ }
+ }
+ return 0;
+}
+
+device_initcall(via_pmu_dev_init);
+
+static int
+init_pmu(void)
+{
+ int timeout;
+ struct adb_request req;
+
+ out_8(&via[B], via[B] | TREQ); /* negate TREQ */
+ out_8(&via[DIRB], (via[DIRB] | TREQ) & ~TACK); /* TACK in, TREQ out */
+
+ 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:
+ printk(KERN_WARNING "pmu.c : unrecognized battery info, len: %d, %02x %02x %02x %02x\n",
+ req->reply_len, req->reply[0], req->reply[1], req->reply[2], req->reply[3]);
+ 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
+proc_get_info(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char* p = page;
+
+ p += sprintf(p, "PMU driver version : %d\n", PMU_DRIVER_VERSION);
+ p += sprintf(p, "PMU firmware version : %02x\n", pmu_version);
+ p += sprintf(p, "AC Power : %d\n",
+ ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0) || pmu_battery_count == 0);
+ p += sprintf(p, "Battery count : %d\n", pmu_battery_count);
+
+ return p - page;
+}
+
+static int
+proc_get_irqstats(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int i;
+ char* p = page;
+ 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++) {
+ p += sprintf(p, " %2u: %10u (%s)\n",
+ i, pmu_irq_stats[i], irq_names[i]);
+ }
+ return p - page;
+}
+
+static int
+proc_get_batt(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ long batnum = (long)data;
+ char *p = page;
+
+ p += sprintf(p, "\n");
+ p += sprintf(p, "flags : %08x\n",
+ pmu_batteries[batnum].flags);
+ p += sprintf(p, "charge : %d\n",
+ pmu_batteries[batnum].charge);
+ p += sprintf(p, "max_charge : %d\n",
+ pmu_batteries[batnum].max_charge);
+ p += sprintf(p, "current : %d\n",
+ pmu_batteries[batnum].amperage);
+ p += sprintf(p, "voltage : %d\n",
+ pmu_batteries[batnum].voltage);
+ p += sprintf(p, "time rem. : %d\n",
+ pmu_batteries[batnum].time_remaining);
+
+ return p - page;
+}
+
+static int
+proc_read_options(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *p = page;
+
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+ if (pmu_kind == PMU_KEYLARGO_BASED &&
+ pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0)
+ p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup);
+#endif
+ if (pmu_kind == PMU_KEYLARGO_BASED)
+ p += sprintf(p, "server_mode=%d\n", option_server_mode);
+
+ return p - page;
+}
+
+static int
+proc_write_options(struct file *file, const char __user *buffer,
+ unsigned long count, void *data)
+{
+ char tmp[33];
+ char *label, *val;
+ unsigned long 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;
+}
+
+#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 ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb)
+ 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_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 = 5;
+ req.done = NULL;
+ req.data[0] = PMU_ADB_CMD;
+ req.data[1] = 0;
+ req.data[2] = 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;
+ }
+ 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 (via[B] & TREQ) {
+ printk(KERN_ERR "PMU: spurious SR intr (%x)\n", 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 */
+ set_context(current->active_mm->context.id, current->active_mm->pgd);
+
+ /* 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 */
+ set_context(current->active_mm->context.id, current->active_mm->pgd);
+
+ /* 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);
+ lock_kernel();
+ 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;
+ unlock_kernel();
+ 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);
+ 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);
+ }
+ 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 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 inode * inode, 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 const struct file_operations pmu_device_fops = {
+ .read = pmu_read,
+ .write = pmu_write,
+ .poll = pmu_fpoll,
+ .ioctl = pmu_ioctl,
+ .open = pmu_open,
+ .release = pmu_release,
+};
+
+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_sys_suspend(struct sys_device *sysdev, pm_message_t state)
+{
+ if (state.event != PM_EVENT_SUSPEND || pmu_sys_suspended)
+ return 0;
+
+ /* 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 int pmu_sys_resume(struct sys_device *sysdev)
+{
+ struct adb_request req;
+
+ if (!pmu_sys_suspended)
+ return 0;
+
+ /* 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;
+
+ return 0;
+}
+
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+
+static struct sysdev_class pmu_sysclass = {
+ .name = "pmu",
+};
+
+static struct sys_device device_pmu = {
+ .cls = &pmu_sysclass,
+};
+
+static struct sysdev_driver driver_pmu = {
+#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32)
+ .suspend = &pmu_sys_suspend,
+ .resume = &pmu_sys_resume,
+#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */
+};
+
+static int __init init_pmu_sysfs(void)
+{
+ int rc;
+
+ rc = sysdev_class_register(&pmu_sysclass);
+ if (rc) {
+ printk(KERN_ERR "Failed registering PMU sys class\n");
+ return -ENODEV;
+ }
+ rc = sysdev_register(&device_pmu);
+ if (rc) {
+ printk(KERN_ERR "Failed registering PMU sys device\n");
+ return -ENODEV;
+ }
+ rc = sysdev_driver_register(&pmu_sysclass, &driver_pmu);
+ if (rc) {
+ printk(KERN_ERR "Failed registering PMU sys driver\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+subsys_initcall(init_pmu_sysfs);
+
+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/drivers/macintosh/via-pmu68k.c b/drivers/macintosh/via-pmu68k.c
new file mode 100644
index 0000000..b64741c
--- /dev/null
+++ b/drivers/macintosh/via-pmu68k.c
@@ -0,0 +1,819 @@
+/*
+ * 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/slab.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/machw.h>
+#include <asm/mac_via.h>
+
+#include <asm/pgtable.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/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/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h
new file mode 100644
index 0000000..7a2482c
--- /dev/null
+++ b/drivers/macintosh/windfarm.h
@@ -0,0 +1,134 @@
+/*
+ * 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) ((f) >> 16),((((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;
+ struct wf_control_ops *ops;
+ char *name;
+ int type;
+ struct kref ref;
+ struct device_attribute attr;
+};
+
+#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.
+ * wf_find_control will inc the refcount for you
+ */
+extern int wf_register_control(struct wf_control *ct);
+extern void wf_unregister_control(struct wf_control *ct);
+extern struct wf_control * wf_find_control(const char *name);
+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);
+}
+
+/*
+ * 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;
+ struct wf_sensor_ops *ops;
+ char *name;
+ struct kref ref;
+ struct device_attribute attr;
+};
+
+/* Same lifetime rules as controls */
+extern int wf_register_sensor(struct wf_sensor *sr);
+extern void wf_unregister_sensor(struct wf_sensor *sr);
+extern struct wf_sensor * wf_find_sensor(const char *name);
+extern int wf_get_sensor(struct wf_sensor *sr);
+extern void wf_put_sensor(struct wf_sensor *sr);
+
+/* 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);
+extern int wf_is_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/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c
new file mode 100644
index 0000000..075b4d9
--- /dev/null
+++ b/drivers/macintosh/windfarm_core.c
@@ -0,0 +1,495 @@
+/*
+ * 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/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);
+}
+
+int wf_critical_overtemp(void)
+{
+ static char * critical_overtemp_path = "/sbin/critical_overtemp";
+ char *argv[] = { 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);
+}
+EXPORT_SYMBOL_GPL(wf_critical_overtemp);
+
+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);
+ s32 val = 0;
+ int err;
+
+ err = ctrl->ops->get_value(ctrl, &val);
+ if (err < 0)
+ return err;
+ return sprintf(buf, "%d\n", val);
+}
+
+/* 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);
+
+ 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);
+
+struct wf_control * wf_find_control(const char *name)
+{
+ struct wf_control *ct;
+
+ mutex_lock(&wf_lock);
+ list_for_each_entry(ct, &wf_controls, link) {
+ if (!strcmp(ct->name, name)) {
+ if (wf_get_control(ct))
+ ct = NULL;
+ mutex_unlock(&wf_lock);
+ return ct;
+ }
+ }
+ mutex_unlock(&wf_lock);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(wf_find_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);
+
+ 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);
+
+struct wf_sensor * wf_find_sensor(const char *name)
+{
+ struct wf_sensor *sr;
+
+ mutex_lock(&wf_lock);
+ list_for_each_entry(sr, &wf_sensors, link) {
+ if (!strcmp(sr->name, name)) {
+ if (wf_get_sensor(sr))
+ sr = NULL;
+ mutex_unlock(&wf_lock);
+ return sr;
+ }
+ }
+ mutex_unlock(&wf_lock);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(wf_find_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);
+
+int wf_is_overtemp(void)
+{
+ return (wf_overtemp != 0);
+}
+EXPORT_SYMBOL_GPL(wf_is_overtemp);
+
+static int __init windfarm_core_init(void)
+{
+ DBG("wf: core loaded\n");
+
+ /* Don't register on old machines that use therm_pm72 for now */
+ if (machine_is_compatible("PowerMac7,2") ||
+ machine_is_compatible("PowerMac7,3") ||
+ machine_is_compatible("RackMac3,1"))
+ return -ENODEV;
+ 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/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c
new file mode 100644
index 0000000..900aade
--- /dev/null
+++ b/drivers/macintosh/windfarm_cpufreq_clamp.c
@@ -0,0 +1,112 @@
+#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 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;
+
+ /* Don't register on old machines that use therm_pm72 for now */
+ if (machine_is_compatible("PowerMac7,2") ||
+ machine_is_compatible("PowerMac7,3") ||
+ machine_is_compatible("RackMac3,1"))
+ return -ENODEV;
+
+ 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/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c
new file mode 100644
index 0000000..b92b959
--- /dev/null
+++ b/drivers/macintosh/windfarm_lm75_sensor.c
@@ -0,0 +1,242 @@
+/*
+ * 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/system.h>
+#include <asm/sections.h>
+#include <asm/pmac_low_i2c.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
+
+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)
+#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c)
+
+static int wf_lm75_attach(struct i2c_adapter *adapter);
+static int wf_lm75_detach(struct i2c_client *client);
+
+static struct i2c_driver wf_lm75_driver = {
+ .driver = {
+ .name = "wf_lm75",
+ },
+ .attach_adapter = wf_lm75_attach,
+ .detach_client = wf_lm75_detach,
+};
+
+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.adapter == 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);
+
+ /* check if client is registered and detach from i2c */
+ if (lm->i2c.adapter) {
+ i2c_detach_client(&lm->i2c);
+ lm->i2c.adapter = NULL;
+ }
+
+ kfree(lm);
+}
+
+static struct wf_sensor_ops wf_lm75_ops = {
+ .get_value = wf_lm75_get,
+ .release = wf_lm75_release,
+ .owner = THIS_MODULE,
+};
+
+static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter,
+ u8 addr, int ds1775,
+ const char *loc)
+{
+ struct wf_lm75_sensor *lm;
+ int rc;
+
+ DBG("wf_lm75: creating %s device at address 0x%02x\n",
+ ds1775 ? "ds1775" : "lm75", addr);
+
+ lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
+ if (lm == NULL)
+ return NULL;
+
+ /* 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"))
+ lm->sens.name = "hd-temp";
+ else if (!strcmp(loc, "Incoming Air Temp"))
+ lm->sens.name = "incoming-air-temp";
+ else if (!strcmp(loc, "ODD Temp"))
+ lm->sens.name = "optical-drive-temp";
+ else if (!strcmp(loc, "HD Temp"))
+ lm->sens.name = "hard-drive-temp";
+ else
+ goto fail;
+
+ lm->inited = 0;
+ lm->sens.ops = &wf_lm75_ops;
+ lm->ds1775 = ds1775;
+ lm->i2c.addr = (addr >> 1) & 0x7f;
+ lm->i2c.adapter = adapter;
+ lm->i2c.driver = &wf_lm75_driver;
+ strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1);
+
+ rc = i2c_attach_client(&lm->i2c);
+ if (rc) {
+ printk(KERN_ERR "windfarm: failed to attach %s %s to i2c,"
+ " err %d\n", ds1775 ? "ds1775" : "lm75",
+ lm->i2c.name, rc);
+ goto fail;
+ }
+
+ if (wf_register_sensor(&lm->sens)) {
+ i2c_detach_client(&lm->i2c);
+ goto fail;
+ }
+
+ return lm;
+ fail:
+ kfree(lm);
+ return NULL;
+}
+
+static int wf_lm75_attach(struct i2c_adapter *adapter)
+{
+ struct device_node *busnode, *dev;
+ struct pmac_i2c_bus *bus;
+
+ DBG("wf_lm75: adapter %s detected\n", adapter->name);
+
+ bus = pmac_i2c_adapter_to_bus(adapter);
+ if (bus == NULL)
+ return -ENODEV;
+ busnode = pmac_i2c_get_bus_node(bus);
+
+ DBG("wf_lm75: bus found, looking for device...\n");
+
+ /* Now look for lm75(s) in there */
+ for (dev = NULL;
+ (dev = of_get_next_child(busnode, dev)) != NULL;) {
+ const char *loc =
+ of_get_property(dev, "hwsensor-location", NULL);
+ u8 addr;
+
+ /* We must re-match the adapter in order to properly check
+ * the channel on multibus setups
+ */
+ if (!pmac_i2c_match_adapter(dev, adapter))
+ continue;
+ addr = pmac_i2c_get_dev_addr(dev);
+ if (loc == NULL || addr == 0)
+ continue;
+ /* real lm75 */
+ if (of_device_is_compatible(dev, "lm75"))
+ wf_lm75_create(adapter, addr, 0, loc);
+ /* ds1775 (compatible, better resolution */
+ else if (of_device_is_compatible(dev, "ds1775"))
+ wf_lm75_create(adapter, addr, 1, loc);
+ }
+ return 0;
+}
+
+static int wf_lm75_detach(struct i2c_client *client)
+{
+ struct wf_lm75_sensor *lm = i2c_to_lm75(client);
+
+ DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
+
+ /* Mark client detached */
+ lm->i2c.adapter = NULL;
+
+ /* release sensor */
+ wf_unregister_sensor(&lm->sens);
+
+ return 0;
+}
+
+static int __init wf_lm75_sensor_init(void)
+{
+ /* Don't register on old machines that use therm_pm72 for now */
+ if (machine_is_compatible("PowerMac7,2") ||
+ machine_is_compatible("PowerMac7,3") ||
+ machine_is_compatible("RackMac3,1"))
+ return -ENODEV;
+ return i2c_add_driver(&wf_lm75_driver);
+}
+
+static void __exit wf_lm75_sensor_exit(void)
+{
+ i2c_del_driver(&wf_lm75_driver);
+}
+
+
+module_init(wf_lm75_sensor_init);
+module_exit(wf_lm75_sensor_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/macintosh/windfarm_max6690_sensor.c b/drivers/macintosh/windfarm_max6690_sensor.c
new file mode 100644
index 0000000..e207a90
--- /dev/null
+++ b/drivers/macintosh/windfarm_max6690_sensor.c
@@ -0,0 +1,187 @@
+/*
+ * 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 "0.2"
+
+/* 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)
+#define i2c_to_6690(x) container_of((x), struct wf_6690_sensor, i2c)
+
+static int wf_max6690_attach(struct i2c_adapter *adapter);
+static int wf_max6690_detach(struct i2c_client *client);
+
+static struct i2c_driver wf_max6690_driver = {
+ .driver = {
+ .name = "wf_max6690",
+ },
+ .attach_adapter = wf_max6690_attach,
+ .detach_client = wf_max6690_detach,
+};
+
+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.adapter == 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);
+
+ if (max->i2c.adapter) {
+ i2c_detach_client(&max->i2c);
+ max->i2c.adapter = NULL;
+ }
+ kfree(max);
+}
+
+static struct wf_sensor_ops wf_max6690_ops = {
+ .get_value = wf_max6690_get,
+ .release = wf_max6690_release,
+ .owner = THIS_MODULE,
+};
+
+static void wf_max6690_create(struct i2c_adapter *adapter, u8 addr,
+ const char *loc)
+{
+ struct wf_6690_sensor *max;
+ char *name;
+
+ max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL);
+ if (max == NULL) {
+ printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor %s: "
+ "no memory\n", loc);
+ return;
+ }
+
+ if (!strcmp(loc, "BACKSIDE"))
+ name = "backside-temp";
+ else if (!strcmp(loc, "NB Ambient"))
+ name = "north-bridge-temp";
+ else if (!strcmp(loc, "GPU Ambient"))
+ name = "gpu-temp";
+ else
+ goto fail;
+
+ max->sens.ops = &wf_max6690_ops;
+ max->sens.name = name;
+ max->i2c.addr = addr >> 1;
+ max->i2c.adapter = adapter;
+ max->i2c.driver = &wf_max6690_driver;
+ strncpy(max->i2c.name, name, I2C_NAME_SIZE-1);
+
+ if (i2c_attach_client(&max->i2c)) {
+ printk(KERN_ERR "windfarm: failed to attach MAX6690 sensor\n");
+ goto fail;
+ }
+
+ if (wf_register_sensor(&max->sens)) {
+ i2c_detach_client(&max->i2c);
+ goto fail;
+ }
+
+ return;
+
+ fail:
+ kfree(max);
+}
+
+static int wf_max6690_attach(struct i2c_adapter *adapter)
+{
+ struct device_node *busnode, *dev = NULL;
+ struct pmac_i2c_bus *bus;
+ const char *loc;
+
+ bus = pmac_i2c_adapter_to_bus(adapter);
+ if (bus == NULL)
+ return -ENODEV;
+ busnode = pmac_i2c_get_bus_node(bus);
+
+ while ((dev = of_get_next_child(busnode, dev)) != NULL) {
+ u8 addr;
+
+ /* We must re-match the adapter in order to properly check
+ * the channel on multibus setups
+ */
+ if (!pmac_i2c_match_adapter(dev, adapter))
+ continue;
+ if (!of_device_is_compatible(dev, "max6690"))
+ continue;
+ addr = pmac_i2c_get_dev_addr(dev);
+ loc = of_get_property(dev, "hwsensor-location", NULL);
+ if (loc == NULL || addr == 0)
+ continue;
+ printk("found max6690, loc=%s addr=0x%02x\n", loc, addr);
+ wf_max6690_create(adapter, addr, loc);
+ }
+
+ return 0;
+}
+
+static int wf_max6690_detach(struct i2c_client *client)
+{
+ struct wf_6690_sensor *max = i2c_to_6690(client);
+
+ max->i2c.adapter = NULL;
+ wf_unregister_sensor(&max->sens);
+
+ return 0;
+}
+
+static int __init wf_max6690_sensor_init(void)
+{
+ /* Don't register on old machines that use therm_pm72 for now */
+ if (machine_is_compatible("PowerMac7,2") ||
+ machine_is_compatible("PowerMac7,3") ||
+ machine_is_compatible("RackMac3,1"))
+ return -ENODEV;
+ return i2c_add_driver(&wf_max6690_driver);
+}
+
+static void __exit wf_max6690_sensor_exit(void)
+{
+ i2c_del_driver(&wf_max6690_driver);
+}
+
+module_init(wf_max6690_sensor_init);
+module_exit(wf_max6690_sensor_exit);
+
+MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
+MODULE_DESCRIPTION("MAX6690 sensor objects for PowerMac thermal control");
+MODULE_LICENSE("GPL");
diff --git a/drivers/macintosh/windfarm_pid.c b/drivers/macintosh/windfarm_pid.c
new file mode 100644
index 0000000..f10efb2
--- /dev/null
+++ b/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/drivers/macintosh/windfarm_pid.h b/drivers/macintosh/windfarm_pid.h
new file mode 100644
index 0000000..bbccc22
--- /dev/null
+++ b/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/drivers/macintosh/windfarm_pm112.c b/drivers/macintosh/windfarm_pm112.c
new file mode 100644
index 0000000..73d695d
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm112.c
@@ -0,0 +1,714 @@
+/*
+ * 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 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];
+
+ /* 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;
+
+ /*
+ * 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);
+ 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 __devexit 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 = __devexit_p(wf_pm112_remove),
+ .driver = {
+ .name = "windfarm",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wf_pm112_init(void)
+{
+ struct device_node *cpu;
+
+ if (!machine_is_compatible("PowerMac11,2"))
+ return -ENODEV;
+
+ /* Count the number of CPU cores */
+ nr_cores = 0;
+ for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; )
+ ++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/drivers/macintosh/windfarm_pm121.c b/drivers/macintosh/windfarm_pm121.c
new file mode 100644
index 0000000..66ec4fb
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm121.c
@@ -0,0 +1,1040 @@
+/*
+ * 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/system.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 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 max(new_setpoint, max(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;
+ pid_param.min = control->ops->get_min(control);
+ pid_param.max = control->ops->get_max(control);
+
+ 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->name);
+
+ 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;
+ }
+
+ /* 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 (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
+ wf_clear_overtemp();
+}
+
+
+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 __devexit pm121_remove(struct platform_device *ddev)
+{
+ wf_unregister_client(&pm121_events);
+ return 0;
+}
+
+static struct platform_driver pm121_driver = {
+ .probe = pm121_probe,
+ .remove = __devexit_p(pm121_remove),
+ .driver = {
+ .name = "windfarm",
+ .bus = &platform_bus_type,
+ },
+};
+
+
+static int __init pm121_init(void)
+{
+ int rc = -ENODEV;
+
+ if (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/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c
new file mode 100644
index 0000000..abbe206
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm81.c
@@ -0,0 +1,813 @@
+/*
+ * 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/system.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;
+
+/*
+ * ****** 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 Sytem 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 = fan_system->ops->get_min(fan_system);
+ pid_param.max = fan_system->ops->get_max(fan_system);
+ if (fan_hd) {
+ pid_param.min =
+ max(pid_param.min,fan_hd->ops->get_min(fan_hd));
+ pid_param.max =
+ min(pid_param.max,fan_hd->ops->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 = sensor_hd_temp->ops->get_value(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 = fan_system->ops->set_value(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 = fan_hd->ops->set_value(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 = fan_cpu_main->ops->get_min(fan_cpu_main);
+ pid_param.max = fan_cpu_main->ops->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 = sensor_cpu_temp->ops->get_value(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 = sensor_cpu_power->ops->get_value(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 = fan_cpu_main->ops->set_value(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;
+ }
+
+ /* 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 (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
+ wf_clear_overtemp();
+}
+
+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 __devexit 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 */
+ if (wf_smu_sys_fans)
+ kfree(wf_smu_sys_fans);
+ if (wf_smu_cpu_fans)
+ kfree(wf_smu_cpu_fans);
+
+ return 0;
+}
+
+static struct platform_driver wf_smu_driver = {
+ .probe = wf_smu_probe,
+ .remove = __devexit_p(wf_smu_remove),
+ .driver = {
+ .name = "windfarm",
+ .owner = THIS_MODULE,
+ },
+};
+
+
+static int __init wf_smu_init(void)
+{
+ int rc = -ENODEV;
+
+ if (machine_is_compatible("PowerMac8,1") ||
+ 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/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c
new file mode 100644
index 0000000..764c525
--- /dev/null
+++ b/drivers/macintosh/windfarm_pm91.c
@@ -0,0 +1,745 @@
+/*
+ * 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/system.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;
+
+/* 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 = fan_cpu_main->ops->get_min(fan_cpu_main);
+ pid_param.max = fan_cpu_main->ops->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 = sensor_cpu_temp->ops->get_value(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 = sensor_cpu_power->ops->get_value(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 = fan_cpu_main->ops->set_value(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 = fan_cpu_second->ops->set_value(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 = fan_cpu_main->ops->set_value(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 = fan_hd->ops->get_min(fan_hd);
+ param.max = fan_hd->ops->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 = sensor_hd_temp->ops->get_value(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 = fan_hd->ops->set_value(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 = fan_slots->ops->get_min(fan_slots);
+ param.max = fan_slots->ops->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 = sensor_slots_power->ops->get_value(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 = fan_slots->ops->set_value(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;
+ }
+
+ /* 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 (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
+ wf_clear_overtemp();
+}
+
+
+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 __devexit 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 */
+ if (wf_smu_slots_fans)
+ kfree(wf_smu_cpu_fans);
+ if (wf_smu_drive_fans)
+ kfree(wf_smu_cpu_fans);
+ if (wf_smu_cpu_fans)
+ kfree(wf_smu_cpu_fans);
+
+ return 0;
+}
+
+static struct platform_driver wf_smu_driver = {
+ .probe = wf_smu_probe,
+ .remove = __devexit_p(wf_smu_remove),
+ .driver = {
+ .name = "windfarm",
+ .owner = THIS_MODULE,
+ },
+};
+
+
+static int __init wf_smu_init(void)
+{
+ int rc = -ENODEV;
+
+ if (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/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c
new file mode 100644
index 0000000..961fa0e
--- /dev/null
+++ b/drivers/macintosh/windfarm_smu_controls.c
@@ -0,0 +1,327 @@
+/*
+ * 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/system.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 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, "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/drivers/macintosh/windfarm_smu_sat.c b/drivers/macintosh/windfarm_smu_sat.c
new file mode 100644
index 0000000..7f2be4b
--- /dev/null
+++ b/drivers/macintosh/windfarm_smu_sat.c
@@ -0,0 +1,395 @@
+/*
+ * 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 "0.2"
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DBG(args...) printk(args)
+#else
+#define DBG(args...) do { } while(0)
+#endif
+
+/* If the cache is older than 800ms we'll refetch it */
+#define MAX_AGE msecs_to_jiffies(800)
+
+struct wf_sat {
+ int nr;
+ atomic_t refcnt;
+ struct mutex mutex;
+ unsigned long last_read; /* jiffies when cache last updated */
+ u8 cache[16];
+ struct i2c_client i2c;
+ struct device_node *node;
+};
+
+static struct wf_sat *sats[2];
+
+struct wf_sat_sensor {
+ 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)
+#define i2c_to_sat(c) container_of(c, struct wf_sat, i2c)
+
+static int wf_sat_attach(struct i2c_adapter *adapter);
+static int wf_sat_detach(struct i2c_client *client);
+
+static struct i2c_driver wf_sat_driver = {
+ .driver = {
+ .name = "wf_smu_sat",
+ },
+ .attach_adapter = wf_sat_attach,
+ .detach_client = wf_sat_detach,
+};
+
+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;
+ }
+
+ len = i2c_smbus_read_word_data(&sat->i2c, 9);
+ if (len < 0) {
+ printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");
+ return NULL;
+ }
+ 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];
+ }
+#ifdef DEBUG
+ DBG(KERN_DEBUG "sat %d partition %x:", sat_id, id);
+ for (i = 0; i < len; ++i)
+ DBG(" %x", buf[i]);
+ DBG("\n");
+#endif
+
+ 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;
+ DBG(KERN_DEBUG "wf_sat_get: data is");
+ for (i = 0; i < 16; ++i)
+ DBG(" %.2x", sat->cache[i]);
+ DBG("\n");
+ }
+#endif
+ return 0;
+}
+
+static int wf_sat_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.adapter == 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 wf_sensor *sr)
+{
+ struct wf_sat_sensor *sens = wf_to_sat(sr);
+ struct wf_sat *sat = sens->sat;
+
+ if (atomic_dec_and_test(&sat->refcnt)) {
+ if (sat->i2c.adapter) {
+ i2c_detach_client(&sat->i2c);
+ sat->i2c.adapter = NULL;
+ }
+ if (sat->nr >= 0)
+ sats[sat->nr] = NULL;
+ kfree(sat);
+ }
+ kfree(sens);
+}
+
+static struct wf_sensor_ops wf_sat_ops = {
+ .get_value = wf_sat_get,
+ .release = wf_sat_release,
+ .owner = THIS_MODULE,
+};
+
+static void wf_sat_create(struct i2c_adapter *adapter, struct device_node *dev)
+{
+ struct wf_sat *sat;
+ struct wf_sat_sensor *sens;
+ const u32 *reg;
+ const char *loc, *type;
+ u8 addr, chip, core;
+ struct device_node *child;
+ int shift, cpu, index;
+ char *name;
+ int vsens[2], isens[2];
+
+ reg = of_get_property(dev, "reg", NULL);
+ if (reg == NULL)
+ return;
+ addr = *reg;
+ DBG(KERN_DEBUG "wf_sat: creating sat at address %x\n", addr);
+
+ sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);
+ if (sat == NULL)
+ return;
+ sat->nr = -1;
+ sat->node = of_node_get(dev);
+ atomic_set(&sat->refcnt, 0);
+ mutex_init(&sat->mutex);
+ sat->i2c.addr = (addr >> 1) & 0x7f;
+ sat->i2c.adapter = adapter;
+ sat->i2c.driver = &wf_sat_driver;
+ strncpy(sat->i2c.name, "smu-sat", I2C_NAME_SIZE-1);
+
+ if (i2c_attach_client(&sat->i2c)) {
+ printk(KERN_ERR "windfarm: failed to attach smu-sat to i2c\n");
+ goto fail;
+ }
+
+ 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 %s\n", loc, child->full_name);
+ 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;
+ atomic_inc(&sat->refcnt);
+ sens->sens.ops = &wf_sat_ops;
+ sens->sens.name = (char *) (sens + 1);
+ snprintf(sens->sens.name, 16, "%s-%d", name, cpu);
+
+ if (wf_register_sensor(&sens->sens)) {
+ atomic_dec(&sat->refcnt);
+ kfree(sens);
+ }
+ }
+
+ /* 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;
+ atomic_inc(&sat->refcnt);
+ sens->sens.ops = &wf_sat_ops;
+ sens->sens.name = (char *) (sens + 1);
+ snprintf(sens->sens.name, 16, "cpu-power-%d", cpu);
+
+ if (wf_register_sensor(&sens->sens)) {
+ atomic_dec(&sat->refcnt);
+ kfree(sens);
+ }
+ }
+
+ if (sat->nr >= 0)
+ sats[sat->nr] = sat;
+
+ return;
+
+ fail:
+ kfree(sat);
+}
+
+static int wf_sat_attach(struct i2c_adapter *adapter)
+{
+ struct device_node *busnode, *dev = NULL;
+ struct pmac_i2c_bus *bus;
+
+ bus = pmac_i2c_adapter_to_bus(adapter);
+ if (bus == NULL)
+ return -ENODEV;
+ busnode = pmac_i2c_get_bus_node(bus);
+
+ while ((dev = of_get_next_child(busnode, dev)) != NULL)
+ if (of_device_is_compatible(dev, "smu-sat"))
+ wf_sat_create(adapter, dev);
+ return 0;
+}
+
+static int wf_sat_detach(struct i2c_client *client)
+{
+ struct wf_sat *sat = i2c_to_sat(client);
+
+ /* XXX TODO */
+
+ sat->i2c.adapter = NULL;
+ return 0;
+}
+
+static int __init sat_sensors_init(void)
+{
+ return i2c_add_driver(&wf_sat_driver);
+}
+
+#if 0 /* uncomment when module_exit() below is uncommented */
+static void __exit sat_sensors_exit(void)
+{
+ i2c_del_driver(&wf_sat_driver);
+}
+#endif
+
+module_init(sat_sensors_init);
+/*module_exit(sat_sensors_exit); Uncomment when cleanup is implemented */
+
+MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
+MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control");
+MODULE_LICENSE("GPL");
diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c
new file mode 100644
index 0000000..9c567b9
--- /dev/null
+++ b/drivers/macintosh/windfarm_smu_sensors.c
@@ -0,0 +1,483 @@
+/*
+ * 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/system.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 struct wf_sensor_ops smu_cputemp_ops = {
+ .get_value = smu_cputemp_get,
+ .release = smu_ads_release,
+ .owner = THIS_MODULE,
+};
+static struct wf_sensor_ops smu_cpuamp_ops = {
+ .get_value = smu_cpuamp_get,
+ .release = smu_ads_release,
+ .owner = THIS_MODULE,
+};
+static struct wf_sensor_ops smu_cpuvolt_ops = {
+ .get_value = smu_cpuvolt_get,
+ .release = smu_ads_release,
+ .owner = THIS_MODULE,
+};
+static 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 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 ((machine_is_compatible("PowerMac8,1") ||
+ machine_is_compatible("PowerMac8,2") ||
+ 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");
+
OpenPOWER on IntegriCloud