patch-2.2.18 linux/drivers/macintosh/via-pmu.c

Next file: linux/drivers/misc/parport_pc.c
Previous file: linux/drivers/macintosh/nvram.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/drivers/macintosh/via-pmu.c linux/drivers/macintosh/via-pmu.c
@@ -3,19 +3,17 @@
  *
  * 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
+ * battery charging and system power on the PowerBooks and new
+ * "Core99" Apple machines.
+ * The PMU may 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.
- * 
- * todo: - Check this driver for smp safety (new Core99 motherboards).
- *       - Cleanup synchro between VIA interrupt and GPIO-based PMU
- *         interrupt.
- *
- *
+ * Copyright (C) 1998 Paul Mackerras, Fabio Riccardi
+ *                    and Benjamin Herrenschmidt
+ *                    
  */
+ 
 #include <stdarg.h>
 #include <linux/config.h>
 #include <linux/types.h>
@@ -40,6 +38,10 @@
 #include <asm/feature.h>
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
+#include <asm/backlight.h>
+
+#define DEBUG_SLEEP
+#define PMU_CORE99_SLEEP	/* Doesn't work yet (almost there...) */
 
 /* Misc minor number allocated for /dev/pmu */
 #define PMU_MINOR	154
@@ -103,9 +105,11 @@
 static struct device_node *vias;
 static int pmu_kind = PMU_UNKNOWN;
 static int pmu_fully_inited = 0;
-static int pmu_has_adb, pmu_has_backlight;
+static int pmu_has_adb;
 static unsigned char *gpio_reg = NULL;
 static int gpio_irq = -1;
+static int pmu_suspended = 0;
+static spinlock_t pmu_lock;
 
 int asleep;
 
@@ -124,6 +128,8 @@
 			    struct pt_regs *regs);
 static void set_volume(int level);
 static void gpio1_interrupt(int irq, void *arg, struct pt_regs *regs);
+static int pmu_set_backlight_level(int level, void* data);
+static int pmu_set_backlight_enable(int on, int level, void* data);
 #ifdef CONFIG_PMAC_PBOOK
 static void pmu_pass_intr(unsigned char *data, int len);
 #endif
@@ -137,8 +143,10 @@
 };
 
 extern void low_sleep_handler(void);
-extern void sleep_save_intrs(int);
-extern void sleep_restore_intrs(void);
+extern void pmac_sleep_save_intrs(int);
+extern void pmac_sleep_restore_intrs(void);
+extern void openpic_sleep_save_intrs(void);
+extern void openpic_sleep_restore_intrs(void);
 
 extern int grackle_pcibios_read_config_word(unsigned char bus,
 	unsigned char dev_fn, unsigned char offset, unsigned short *val);
@@ -146,12 +154,18 @@
 extern int grackle_pcibios_write_config_word(unsigned char bus,
 	unsigned char dev_fn, unsigned char offset, unsigned short val);
 
+#ifdef DEBUG_SLEEP
+int pmu_polled_request(struct adb_request *req);
+int pmu_wink(struct adb_request *req);
+#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.
+ *   -1 if it will send a length byte
  */
 static s8 pmu_data_len[256][2] __openfirmwaredata = {
 /*	   0	   1	   2	   3	   4	   5	   6	   7  */
@@ -197,6 +211,11 @@
 	"Core99"
 };
 
+static struct backlight_controller pmu_backlight_controller = {
+	pmu_set_backlight_enable,
+	pmu_set_backlight_level
+};
+
 int __openfirmware
 find_via_pmu()
 {
@@ -226,8 +245,9 @@
 			return 0;
 	}
 
+	spin_lock_init(&pmu_lock);
+
 	pmu_has_adb = 1;
-	pmu_has_backlight = 1;
 
 	if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0)
 	    || device_is_compatible(vias->parent, "ohare")))
@@ -241,7 +261,6 @@
 
 		pmu_kind = PMU_KEYLARGO_BASED;
 		pmu_has_adb = (find_type_devices("adb") != NULL);
-		pmu_has_backlight = 0; /* Not driven by PMU */
 
 		gpiop = find_devices("gpio");
 		if (gpiop && gpiop->n_addrs) {
@@ -301,7 +320,7 @@
 	pmu_fully_inited = 1;
 
 	/* Enable backlight */
-	pmu_enable_backlight(1);
+	register_backlight_controller(&pmu_backlight_controller, NULL, "pmu");
 
 	/* Make sure PMU settle down before continuing */
 	do {
@@ -318,7 +337,7 @@
 	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, 0xff);
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0xfc);
 	timeout =  100000;
 	while (!req.complete) {
 		if (--timeout < 0) {
@@ -349,7 +368,7 @@
 		while (!req.complete)
 			pmu_poll();
 	}
-
+		
 	return 1;
 }
 
@@ -553,8 +572,8 @@
 	req->next = 0;
 	req->sent = 0;
 	req->complete = 0;
-	save_flags(flags); cli();
 
+	spin_lock_irqsave(&pmu_lock, flags);
 	if (current_req != 0) {
 		last_req->next = req;
 		last_req = req;
@@ -565,7 +584,7 @@
 			pmu_start();
 	}
 
-	restore_flags(flags);
+	spin_unlock_irqrestore(&pmu_lock, flags);
 	return 0;
 }
 
@@ -639,35 +658,90 @@
 void __openfirmware
 pmu_poll()
 {
-	int ie;
-
+	if (!via)
+		return;
 	if (disable_poll)
 		return;
-	ie = _disable_interrupts();
-	if ((via[IFR] & (SR_INT | CB1_INT)) ||
-		(gpio_reg && (in_8(gpio_reg + 0x9) & 0x02) == 0))
+	/* Kicks ADB read when PMU is suspended */
+	if (pmu_suspended)
+		adb_int_pending = 1;
+	do {
 		via_pmu_interrupt(0, 0, 0);
-	_enable_interrupts(ie);
+	} while (pmu_suspended && (adb_int_pending || pmu_state != idle
+		|| req_awaiting_reply));
 }
 
-/* This function loops until the PMU is idle, to avoid spurrious shutdowns
- * when prom.c scrollscreen or xmon spends too much time without interupts
- * while some PMU communication is going on
+/* 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 __openfirmware
-pmu_safe_poll(void)
+pmu_suspend(void)
 {
-	int ie;
+#ifdef SUSPEND_USES_PMU
+	struct adb_request *req;
+#endif
+	unsigned long flags;
 
-	if (!via || disable_poll)
+	if (!via)
 		return;
+	
+	spin_lock_irqsave(&pmu_lock, flags);
+	pmu_suspended++;
+	if (pmu_suspended != 1) {
+		spin_unlock_irqrestore(&pmu_lock, flags);
+		return;
+	}
+
 	do {
-		ie = _disable_interrupts();
-		if ((via[IFR] & (SR_INT | CB1_INT)) ||
-			(gpio_reg && (in_8(gpio_reg + 0x9) & 0x02) == 0))
-			via_pmu_interrupt(0, 0, 0);
-		_enable_interrupts(ie);
-	} while (adb_int_pending || pmu_state != idle);
+		spin_unlock(&pmu_lock);
+		via_pmu_interrupt(0, 0, 0);
+		spin_lock(&pmu_lock);
+		if (!adb_int_pending && pmu_state == idle && !req_awaiting_reply) {
+#ifdef SUSPEND_USES_PMU
+			pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0);
+			spin_unlock_irqrestore(&pmu_lock, flags);
+			while(!req.complete)
+				pmu_poll();
+#else /* SUSPEND_USES_PMU */
+			if (gpio_irq >= 0)
+				disable_irq(gpio_irq);
+			out_8(&via[IER], CB1_INT | IER_CLR);
+			spin_unlock_irqrestore(&pmu_lock, flags);
+#endif /* SUSPEND_USES_PMU */
+			break;
+		}
+	} while (1);
+}
+
+void __openfirmware
+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;
+#ifdef SUSPEND_USES_PMU
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0xfc);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+	while(!req.complete)
+		pmu_poll();
+#else /* SUSPEND_USES_PMU */
+	if (gpio_irq >= 0)
+		enable_irq(gpio_irq);
+	out_8(&via[IER], CB1_INT | IER_SET);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+	pmu_poll();
+#endif /* SUSPEND_USES_PMU */
 }
 
 static void __openfirmware
@@ -680,8 +754,9 @@
 	/* Currently, we use brute-force cli() for syncing with GPIO
 	 * interrupt. I'll make this smarter later, along with some
 	 * spinlocks for SMP */
-	save_flags(flags);cli();
+	spin_lock_irqsave(&pmu_lock, flags);
 	++disable_poll;
+	
 	while ((intr = in_8(&via[IFR])) != 0) {
 		if (++nloop > 1000) {
 			printk(KERN_DEBUG "PMU: stuck in intr loop, "
@@ -699,9 +774,15 @@
 			out_8(&via[IFR], intr);
 		}
 	}
+	/* This is not necessary except if synchronous ADB requests are done
+	 * with interrupts off, which should not happen. Since I'm not sure
+	 * this "wiring" will remain, I'm commenting it out for now. Please do
+	 * not remove. -- BenH.
+	 */
+#if 0
 	if (gpio_reg && (in_8(gpio_reg + 0x9) & 0x02) == 0)
 		adb_int_pending = 1;
-
+#endif
 	if (pmu_state == idle) {
 		if (adb_int_pending) {
 			pmu_state = intack;
@@ -713,16 +794,17 @@
 		}
 	}
 	--disable_poll;
-	restore_flags(flags);
+	spin_unlock_irqrestore(&pmu_lock, flags);
 }
 
 
 static void __openfirmware
 gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
 {
+	adb_int_pending = 1;
 	via_pmu_interrupt(0, 0, 0);
 }
-
+ 
 static void __openfirmware
 pmu_sr_intr(struct pt_regs *regs)
 {
@@ -749,7 +831,7 @@
 	/* if reading grab the byte, and reset the interrupt */
 	if (pmu_state == reading || pmu_state == reading_intr)
 		bite = in_8(&via[SR]);
-
+	
 	out_8(&via[IFR], SR_INT);
 
 	switch (pmu_state) {
@@ -771,8 +853,11 @@
 			current_req = req->next;
 			if (req->reply_expected)
 				req_awaiting_reply = req;
-			else
+			else {
+				spin_unlock(&pmu_lock);
 				pmu_done(req);
+				spin_lock(&pmu_lock);
+			}
 		} else {
 			pmu_state = reading;
 			data_index = 0;
@@ -797,7 +882,8 @@
 				printk(KERN_ERR "PMU: bad reply len %d\n",
 				       bite);
 		} else {
-			reply_ptr[data_index++] = bite;
+			if (data_index < 32)
+				reply_ptr[data_index++] = bite;
 		}
 		if (data_index < data_len) {
 			recv_byte();
@@ -810,7 +896,9 @@
 			req = current_req;
 			current_req = req->next;
 			req->reply_len += data_index;
+			spin_unlock(&pmu_lock);
 			pmu_done(req);
+			spin_lock(&pmu_lock);
 		}
 		pmu_state = idle;
 
@@ -877,7 +965,7 @@
 		}
 	} else if (data[0] == 0x08 && len == 3) {
 		/* sound/brightness buttons pressed */
-		pmu_set_brightness(data[1] >> 3);
+		set_backlight_level(data[1] >> 4);
 		set_volume(data[2]);
 	} else {
 #ifdef CONFIG_PMAC_PBOOK
@@ -886,53 +974,22 @@
 	}
 }
 
-int backlight_level = -1;
-int backlight_enabled = 0;
-
-#define LEVEL_TO_BRIGHT(lev)	((lev) < 1? 0x7f: 0x4a - ((lev) << 1))
-
-void __openfirmware
-pmu_enable_backlight(int on)
+static int backlight_to_bright[] = {
+	0x7f, 0x46, 0x42, 0x3e, 0x3a, 0x36, 0x32, 0x2e,
+	0x2a, 0x26, 0x22, 0x1e, 0x1a, 0x16, 0x12, 0x0e
+};
+ 
+static int __openfirmware
+pmu_set_backlight_enable(int on, int level, void* data)
 {
 	struct adb_request req;
+	
+	if (vias == NULL)
+		return -ENODEV;
 
-	if ((vias == NULL) || !pmu_has_backlight)
-		return;
-
-	/* first call: get current backlight value */
-	if (on && backlight_level < 0) {
-		switch (pmu_kind) {
-		case PMU_OHARE_BASED:
-			pmu_request(&req, NULL, 2, 0xd9, 0);
-			while (!req.complete)
-				pmu_poll();
-			backlight_level = req.reply[1] >> 3;
-			break;
-		case PMU_HEATHROW_BASED:
-			/* We cannot use nvram_read_byte here (not yet initialized) */
-			pmu_request(&req, NULL, 3, PMU_READ_NVRAM, 0x14, 0xe);
-			while (!req.complete)
-				pmu_poll();
-			backlight_level = req.reply[1];
-			printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", backlight_level);
-			break;
-		case PMU_PADDINGTON_BASED:
-		case PMU_KEYLARGO_BASED:
-			/* the G3 PB 1999 has a backlight node
-			   and chrp-structured nvram */
-			/* XXX should read macos's "blkt" property in nvram
-			   for this node.  For now this ensures that the
-			   backlight doesn't go off as soon as linux boots. */
-			backlight_level = 20;
-			break;
-		default:
-		        backlight_enabled = 0;
-		        return;
-		}
-	}
 	if (on) {
 		pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
-			    LEVEL_TO_BRIGHT(backlight_level));
+			    backlight_to_bright[level]);
 		while (!req.complete)
 			pmu_poll();
 	}
@@ -940,34 +997,26 @@
 		    PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
 	while (!req.complete)
 		pmu_poll();
-	backlight_enabled = on;
+
+	return 0;
 }
 
-void __openfirmware
-pmu_set_brightness(int level)
+static int __openfirmware
+pmu_set_backlight_level(int level, void* data)
 {
-	int bright;
+	if (vias == NULL)
+		return -ENODEV;
 
-	if ((vias == NULL) || !pmu_has_backlight)
-		return ;
+	if (!bright_req_1.complete)
+		return -EAGAIN;
+	pmu_request(&bright_req_1, NULL, 2, PMU_BACKLIGHT_BRIGHT,
+		backlight_to_bright[level]);
+	if (!bright_req_2.complete)
+		return -EAGAIN;
+	pmu_request(&bright_req_2, NULL, 2, PMU_POWER_CTRL, PMU_POW_BACKLIGHT
+		| (level > BACKLIGHT_OFF ? PMU_POW_ON : PMU_POW_OFF));
 
-	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));
-
-	/* XXX nvram address is hard-coded and looks ok on wallstreet, please
-	   test on your machine. Note that newer MacOS system software may break
-	   the nvram layout. */
-	if ((pmu_kind == PMU_HEATHROW_BASED) && bright_req_3.complete)
-		pmu_request(&bright_req_3, NULL, 4, PMU_WRITE_NVRAM,
-			    0x14, 0xe, level);
+	return 0;
 }
 
 void __openfirmware
@@ -1083,7 +1132,7 @@
 			       when, current, current->notifier_call);
 			for (; list != &sleep_notifiers; list = list->next) {
 				current = list_entry(list, struct pmu_sleep_notifier, list);
-				current->notifier_call(current, fallback);
+					current->notifier_call(current, fallback);
 			}
 			return ret;
 		}
@@ -1178,21 +1227,40 @@
 	}
 }
 
-#if 0
+#ifdef DEBUG_SLEEP
 /* N.B. This doesn't work on the 3400 */
-void pmu_blink(int n)
+void
+pmu_blink(int n)
 {
 	struct adb_request req;
 
+	memset(&req, 0, sizeof(req));
+	
 	for (; n > 0; --n) {
-		pmu_request(&req, NULL, 4, 0xee, 4, 0, 1);
-		while (!req.complete) pmu_poll();
-		udelay(50000);
-		pmu_request(&req, NULL, 4, 0xee, 4, 0, 0);
-		while (!req.complete) pmu_poll();
-		udelay(50000);
+		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);
 	}
-	udelay(50000);
+	mdelay(50);
 }
 #endif
 
@@ -1200,7 +1268,33 @@
  * Put the powerbook to sleep.
  */
  
-#define FEATURE_CTRL(base)	((unsigned int *)(base + 0x38))
+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)
@@ -1208,20 +1302,12 @@
 
 int __openfirmware powerbook_sleep_G3(void)
 {
-	int ret;
 	unsigned long save_l2cr;
-	unsigned long save_fcr;
 	unsigned long wait;
 	unsigned short pmcr1;
-	struct adb_request sleep_req;
-	struct device_node *macio;
-	unsigned long macio_base = 0;
-
-	macio = find_devices("mac-io");
-	if (macio != 0 && macio->n_addrs > 0)
-		macio_base = (unsigned long)
-			ioremap(macio->addrs[0].address, 0x40);
-
+	struct adb_request req;
+	int ret, timeout;
+	
 	/* Notify device drivers */
 	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, PBOOK_SLEEP_REJECT);
 	if (ret != PBOOK_SLEEP_OK) {
@@ -1245,31 +1331,57 @@
 	}
 
 	/* Give the disks a little time to actually finish writing */
-	for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
+	for (wait = jiffies + HZ; time_before(jiffies, wait); )
 		mb();
 
-	/* Disable all interrupts except pmu */
-	sleep_save_intrs(vias->intrs[0].line);
+	/* Wait for completion of async backlight requests */
+	while (!bright_req_1.complete || !bright_req_2.complete || !bright_req_3.complete)
+		pmu_poll();
+	
+	/* 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);
+	while (!req.complete)
+		pmu_poll();
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+		PMU_POW_OFF|PMU_POW_BACKLIGHT|PMU_POW_IRLED|PMU_POW_MEDIABAY);
+	while (!req.complete)
+		pmu_poll();
+
+	/* Disable all interrupts */
+	pmac_sleep_save_intrs(-1);
+
+	/* Make sure the PMU is idle */
+	while (pmu_state != idle)
+		pmu_poll();
 
 	/* Make sure the decrementer won't interrupt us */
 	asm volatile("mtdec %0" : : "r" (0x7fffffff));
-#if 0
-	/* Save the state of PCI config space for some slots */
-	pbook_pci_save();
-#endif
+	/* Make sure any pending DEC interrupt occuring while we did
+	 * the above didn't re-enable the DEC */
+	mb();
+	asm volatile("mtdec %0" : : "r" (0x7fffffff));
+	
+	/* Giveup the FPU */
+	if (current->tss.regs && (current->tss.regs->msr & MSR_FP) != 0)
+		giveup_fpu(current);
+
+	/* We can now disable MSR_EE */
+	cli();
+
 	/* For 750, save backside cache setting and disable it */
 	save_l2cr = _get_L2CR();	/* (returns 0 if not 750) */
 	if (save_l2cr)
 		_set_L2CR(0);
 
-	if (macio_base != 0) {
-		save_fcr = in_le32(FEATURE_CTRL(macio_base));
-		/* Check if this is still valid on older powerbooks */
-		out_le32(FEATURE_CTRL(macio_base), save_fcr & ~(0x00000140UL));
-	}
+	/* Ask the PMU to put us to sleep */
+	pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+	while (!req.complete)
+		pmu_poll();
 
-	if (current->tss.regs && (current->tss.regs->msr & MSR_FP) != 0)
-		giveup_fpu(current);
+	/* The VIA is supposed not to be restored correctly*/
+	save_via_state();
+	/* We shut down some HW */
+	feature_prepare_for_sleep();
 
 	grackle_pcibios_read_config_word(0,0,0x70,&pmcr1);
 	/* Apparently, MacOS uses NAP mode for Grackle ??? */
@@ -1277,15 +1389,6 @@
 	pmcr1 |= GRACKLE_PM|GRACKLE_NAP;
 	grackle_pcibios_write_config_word(0, 0, 0x70, pmcr1);
 
-	/* Ask the PMU to put us to sleep */
-	pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
-	while (!sleep_req.complete)
-		mb();
-
-	cli();
-	while (pmu_state != idle)
-		pmu_poll();
-
 	/* Call low-level ASM sleep handler */
 	low_sleep_handler();
 
@@ -1293,26 +1396,201 @@
 	grackle_pcibios_read_config_word(0, 0, 0x70, &pmcr1);
 	pmcr1 &= ~(GRACKLE_PM|GRACKLE_DOZE|GRACKLE_SLEEP|GRACKLE_NAP); 
 	grackle_pcibios_write_config_word(0, 0, 0x70, pmcr1);
+	
+	/* Restore things */
+	feature_wake_up();
+	restore_via_state();
+	
+	/* Restore L2 cache */
+	if (save_l2cr)
+ 		_set_L2CR(save_l2cr);
+	
+	/* Restore userland MMU context */
+	set_context(current->mm->context);
+
+	/* Re-enable DEC interrupts and kick DEC */
+	asm volatile("mtdec %0" : : "r" (0x7fffffff));
+	sti();
+	asm volatile("mtdec %0" : : "r" (0x10000000));
+
+	/* Power things up */
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0xfc);
+	while (!req.complete)
+		pmu_poll();
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL0,
+			PMU_POW0_ON|PMU_POW0_HARD_DRIVE);
+	while (!req.complete)
+		pmu_poll();
+	pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
+			PMU_POW_ON|PMU_POW_BACKLIGHT|PMU_POW_CHARGER|PMU_POW_IRLED|PMU_POW_MEDIABAY);
+	while (!req.complete)
+		pmu_poll();
+
+	/* ack all pending interrupts */
+	timeout = 100000;
+	interrupt_data[0] = 1;
+	while (interrupt_data[0] || pmu_state != idle) {
+		if (--timeout < 0)
+			break;
+		if (pmu_state == idle)
+			adb_int_pending = 1;
+		via_pmu_interrupt(0, 0, 0);
+		udelay(10);
+	}
+
+	/* reenable interrupt controller */
+	pmac_sleep_restore_intrs();
+
+	/* Leave some time for HW to settle down */
+	mdelay(100);
+
+	/* Notify drivers */
+	broadcast_wake();
+
+	return 0;
+}
+
+#ifdef PMU_CORE99_SLEEP
+
+/* Not finished yet */
+int __openfirmware powerbook_sleep_Core99(void)
+{
+	unsigned long save_l2cr;
+	unsigned long wait;
+	struct adb_request req;
+	int ret, timeout;
+
+	/* Notify device drivers */
+	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, PBOOK_SLEEP_REJECT);
+	if (ret != PBOOK_SLEEP_OK) {
+		printk("pmu: sleep rejected\n");
+		return -EBUSY;
+	}
+
+	/* Sync the disks. */
+	/* XXX It would be nice to have some way to ensure that
+	 * nobody is dirtying any new buffers while we wait.
+	 * BenH: Moved to _after_ sleep request and changed video
+	 * drivers to vmalloc() during sleep request. This way, all
+	 * vmalloc's are done before actual sleep of block drivers */
+	fsync_dev(0);
+
+	/* Sleep can fail now. May not be very robust but useful for debugging */
+	ret = broadcast_sleep(PBOOK_SLEEP_NOW, PBOOK_WAKE);
+	if (ret != PBOOK_SLEEP_OK) {
+		printk("pmu: sleep failed\n");
+		return -EBUSY;
+	}
+
+	/* Give the disks a little time to actually finish writing */
+	for (wait = jiffies + HZ; time_before(jiffies, wait); )
+		mb();
+
+	/* Wait for completion of async backlight requests */
+	while (!bright_req_1.complete || !bright_req_2.complete || !bright_req_3.complete)
+		pmu_poll();
+
+	/* Tell PMU what events will wake us up */
+	pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_CLR_WAKEUP_EVENTS,
+		0xff, 0xff);
+	while (!req.complete)
+		pmu_poll();
+	pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_SET_WAKEUP_EVENTS,
+		0, PMU_PWR_WAKEUP_KEY | PMU_PWR_WAKEUP_LID_OPEN);
+	while (!req.complete)
+		pmu_poll();
+
+	/* Save & disable all interrupts */
+	openpic_sleep_save_intrs();
 
 	/* Make sure the PMU is idle */
 	while (pmu_state != idle)
 		pmu_poll();
 
-	sti();
-#if 0
-	/* According to someone from Apple, this should not be needed,
-	   at least not for all devices. Let's keep it for now until we
-	   have something that works. */
-	pbook_pci_restore();
-#endif
-	set_context(current->mm->context);
+	/* Make sure the decrementer won't interrupt us */
+	asm volatile("mtdec %0" : : "r" (0x7fffffff));
+	/* Make sure any pending DEC interrupt occuring while we did
+	 * the above didn't re-enable the DEC */
+	mb();
+	asm volatile("mtdec %0" : : "r" (0x7fffffff));
+
+	/* Giveup the FPU */
+	if (current->tss.regs && (current->tss.regs->msr & MSR_FP) != 0)
+		giveup_fpu(current);
+
+	/* We can now disable MSR_EE */
+	cli();
+
+	/* For 750, save backside cache setting and disable it */
+	save_l2cr = _get_L2CR();	/* (returns 0 if not 750) */
+	if (save_l2cr)
+		_set_L2CR(0);
+
+	/* Save the state of PCI config space for some slots */
+	// pbook_pci_save();
+
+	/* Ask the PMU to put us to sleep */
+	pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+	while (!req.complete)
+		pmu_poll();
+
+	/* 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.
+	 */
+	feature_prepare_for_sleep();
+
+	/* Call low-level ASM sleep handler */
+	low_sleep_handler();
 
+	/* Restore things */
+	feature_wake_up();
+
+	// Don't restore PCI for now, it crashes. Maybe unnecessary on pbook
+	// pbook_pci_restore();
+
+	restore_via_state();
+		
 	/* Restore L2 cache */
 	if (save_l2cr)
- 		_set_L2CR(save_l2cr | 0x200000); /* set invalidate bit */
+ 		_set_L2CR(save_l2cr);
+	
+	/* Restore userland MMU context */
+	set_context(current->mm->context);
 
-	/* reenable interrupts */
-	sleep_restore_intrs();
+	/* Re-enable DEC interrupts and kick DEC */
+	asm volatile("mtdec %0" : : "r" (0x7fffffff));
+	sti();
+	asm volatile("mtdec %0" : : "r" (0x10000000));
+	
+	/* Tell PMU we are ready */
+	pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2);
+	while (!req.complete)
+		pmu_poll();
+	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0xfc);
+	while (!req.complete)
+		pmu_poll();
+		
+	/* ack all pending interrupts */
+	timeout = 100000;
+	interrupt_data[0] = 1;
+	while (interrupt_data[0] || pmu_state != idle) {
+		if (--timeout < 0)
+			break;
+		if (pmu_state == idle)
+			adb_int_pending = 1;
+		via_pmu_interrupt(0, 0, 0);
+		udelay(10);
+	}
+
+	/* reenable interrupt controller */
+	openpic_sleep_restore_intrs();
+
+	/* Leave some time for HW to settle down */
+	mdelay(100);
 
 	/* Notify drivers */
 	broadcast_wake();
@@ -1320,6 +1598,8 @@
 	return 0;
 }
 
+#endif
+
 #define PB3400_MEM_CTRL		((unsigned int *)0xf8000070)
 
 int __openfirmware powerbook_sleep_3400(void)
@@ -1328,7 +1608,7 @@
 	unsigned long msr;
 	unsigned int hid0;
 	unsigned long p, wait;
-	struct adb_request sleep_req;
+	struct adb_request req;
 
 	/* Notify device drivers */
 	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, PBOOK_SLEEP_REJECT);
@@ -1356,8 +1636,12 @@
 	for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
 		mb();
 
+	/* Wait for completion of async backlight requests */
+	while (!bright_req_1.complete || !bright_req_2.complete || !bright_req_3.complete)
+		pmu_poll();
+
 	/* Disable all interrupts except pmu */
-	sleep_save_intrs(vias->intrs[0].line);
+	pmac_sleep_save_intrs(vias->intrs[0].line);
 
 	/* Make sure the decrementer won't interrupt us */
 	asm volatile("mtdec %0" : : "r" (0x7fffffff));
@@ -1377,8 +1661,8 @@
 	}
 
 	/* Ask the PMU to put us to sleep */
-	pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
-	while (!sleep_req.complete)
+	pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+	while (!req.complete)
 		mb();
 
 	/* displacement-flush the L2 cache - necessary? */
@@ -1387,9 +1671,9 @@
 	asleep = 1;
 
 	/* Put the CPU into sleep mode */
-	asm volatile("mfspr %0,1008" : "=r" (hid0) :);
+	hid0 = _get_HID0();
 	hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP;
-	asm volatile("mtspr 1008,%0" : : "r" (hid0));
+	_set_HID0(hid0);
 	save_flags(msr);
 	msr |= MSR_POW | MSR_EE;
 	restore_flags(msr);
@@ -1404,7 +1688,7 @@
 		mb();
 
 	/* reenable interrupts */
-	sleep_restore_intrs();
+	pmac_sleep_restore_intrs();
 
 	/* Notify drivers */
 	broadcast_wake();
@@ -1576,20 +1860,24 @@
 		case PMU_PADDINGTON_BASED:
 			error = powerbook_sleep_G3();
 			break;
+#ifdef PMU_CORE99_SLEEP
+		case PMU_KEYLARGO_BASED:
+			error = powerbook_sleep_Core99();
+			break;
+#endif			
 		default:
 			error = -ENOSYS;
 		}
 		return error;
 	case PMU_IOC_GET_BACKLIGHT:
-		if (!pmu_has_backlight)
-			return -ENOSYS;
-		return put_user(backlight_level, (__u32 *)arg);
+		error = get_backlight_level();
+		if (error < 0)
+			return error;
+		return put_user(error, (__u32 *)arg);
 	case PMU_IOC_SET_BACKLIGHT:
-		if (!pmu_has_backlight)
-			return -ENOSYS;
 		error = get_user(value, (__u32 *)arg);
 		if (!error)
-			pmu_set_brightness(value);
+			error = set_backlight_level(value);
 		return error;
 	case PMU_IOC_GET_MODEL:
 	    	return put_user(pmu_kind, (__u32 *)arg);
@@ -1623,7 +1911,7 @@
 }
 #endif /* CONFIG_PMAC_PBOOK */
 
-#if 0
+#ifdef DEBUG_SLEEP
 static inline void polled_handshake(volatile unsigned char *via)
 {
 	via[B] &= ~TREQ; eieio();
@@ -1689,4 +1977,4 @@
 	restore_flags(flags);
 	return 0;
 }
-#endif /* 0 */
+#endif /* DEBUG_SLEEP */

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