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

Next file: linux/drivers/misc/Config.in
Previous file: linux/drivers/macintosh/via-cuda.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.19/linux/drivers/macintosh/via-pmu.c linux/drivers/macintosh/via-pmu.c
@@ -21,10 +21,12 @@
 #include <linux/blkdev.h>
 #include <linux/pci.h>
 #include <linux/malloc.h>
+#include <linux/poll.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <linux/cuda.h>
 #include <asm/prom.h>
-#include <asm/adb.h>
-#include <asm/pmu.h>
-#include <asm/cuda.h>
+#include <asm/machdep.h>
 #include <asm/io.h>
 #include <asm/pgtable.h>
 #include <asm/system.h>
@@ -32,6 +34,7 @@
 #include <asm/irq.h>
 #include <asm/feature.h>
 #include <asm/uaccess.h>
+#include <asm/mmu_context.h>
 
 /* Misc minor number allocated for /dev/pmu */
 #define PMU_MINOR	154
@@ -70,6 +73,7 @@
 #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 enum pmu_state {
@@ -98,11 +102,13 @@
 int asleep;
 struct notifier_block *sleep_notifier_list;
 
+static int pmu_probe(void);
+static int pmu_init(void);
 static int init_pmu(void);
 static int pmu_queue_request(struct adb_request *req);
 static void pmu_start(void);
 static void via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs);
-static int pmu_adb_send_request(struct adb_request *req, int sync);
+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);
 static void send_byte(int x);
@@ -112,15 +118,31 @@
 static void pmu_handle_data(unsigned char *data, int len,
 			    struct pt_regs *regs);
 static void set_volume(int level);
+#ifdef CONFIG_PMAC_PBOOK
+static void pmu_pass_intr(unsigned char *data, int len);
+#endif
 
-static struct adb_controller	pmu_controller = {
-	ADB_VIAPMU,
-	pmu_adb_send_request,
+struct adb_driver via_pmu_driver = {
+	"PMU",
+	pmu_probe,
+	pmu_init,
+	pmu_send_request,
+	/*pmu_queue_request,*/
 	pmu_adb_autopoll,
-	pmu_adb_reset_bus,
-	pmu_poll
+	pmu_poll,
+	pmu_adb_reset_bus
 };
 
+extern void low_sleep_handler(void);
+extern void sleep_save_intrs(int);
+extern void sleep_restore_intrs(void);
+
+extern int grackle_pcibios_read_config_word(unsigned char bus,
+	unsigned char dev_fn, unsigned char offset, unsigned short *val);
+
+extern int grackle_pcibios_write_config_word(unsigned char bus,
+	unsigned char dev_fn, unsigned char offset, unsigned short val);
+
 /*
  * This table indicates for each PMU opcode:
  * - the number of data bytes to be sent with the command, or -1
@@ -164,13 +186,21 @@
 /*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",
+};
 
-void __openfirmware
+int __openfirmware
 find_via_pmu()
 {
+	if (via != 0)
+		return 1;
 	vias = find_devices("via-pmu");
 	if (vias == 0)
-		return;
+		return 0;
 	if (vias->next != 0)
 		printk(KERN_WARNING "Warning: only using 1st via-pmu\n");
 	
@@ -179,7 +209,7 @@
 #if 0
 	{ int i;
 
-	printk("via_pmu_init: node = %p, addrs =", vias->node);
+	printk("find_via_pmu: node = %p, addrs =", vias->node);
 	for (i = 0; i < vias->n_addrs; ++i)
 		printk(" %x(%x)", vias->addrs[i].address, vias->addrs[i].size);
 	printk(", intrs =");
@@ -192,12 +222,14 @@
 		printk(KERN_ERR "via-pmu: %d addresses, %d interrupts!\n",
 		       vias->n_addrs, vias->n_intrs);
 		if (vias->n_addrs < 1 || vias->n_intrs < 1)
-			return;
+			return 0;
 	}
 
 	if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0)
 	    || device_is_compatible(vias->parent, "ohare")))
 		pmu_kind = PMU_OHARE_BASED;
+	else if (device_is_compatible(vias->parent, "paddington"))
+		pmu_kind = PMU_PADDINGTON_BASED;
 	else if (device_is_compatible(vias->parent, "heathrow"))
 		pmu_kind = PMU_HEATHROW_BASED;
 	else
@@ -209,23 +241,28 @@
 
 	pmu_state = idle;
 
-	if (!init_pmu())
+	if (!init_pmu()) {
 		via = NULL;
+		return 0;
+	}
 
-	adb_controller = &pmu_controller;
+	printk(KERN_INFO "PMU driver initialized for %s\n",
+	       pbook_type[pmu_kind]);
+	sys_ctrler = SYS_CTRLER_PMU;
+	return 1;
+}
 
-	if (via)
-		printk(KERN_INFO "PMU driver initialized for %s\n",
-		    (pmu_kind == PMU_OHARE_BASED) ? "PowerBook 2400/3400/3500(G3)" :
-		    ((pmu_kind == PMU_HEATHROW_BASED) ? "PowerBook G3 Series" :
-		    "Unknown PowerBook"));
+static int __openfirmware
+pmu_probe()
+{
+	return vias == NULL? -ENODEV: 0;
 }
 
-void __openfirmware
-via_pmu_init(void)
+static int __openfirmware
+pmu_init(void)
 {
 	if (vias == NULL)
-		return;
+		return -ENXIO;
 
 	bright_req_1.complete = 1;
 	bright_req_2.complete = 1;
@@ -235,7 +272,7 @@
 			(void *)0)) {
 		printk(KERN_ERR "VIA-PMU: can't get irq %d\n",
 		       vias->intrs[0].line);
-		return;
+		return -ENXIO;
 	}
 
 	/* Enable interrupts */
@@ -245,6 +282,8 @@
 	
 	/* Enable backlight */
 	pmu_enable_backlight(1);
+
+	return 0;
 }
 
 static int __openfirmware
@@ -292,20 +331,19 @@
 
 /* Send an ADB command */
 static int __openfirmware
-pmu_adb_send_request(struct adb_request *req, int sync)
+pmu_send_request(struct adb_request *req, int sync)
 {
-    int i, ret;
+	int i, ret;
 
-    if ((vias == NULL) || (!pmu_fully_inited))
-    {
- 	req->complete = 1;
-   	return -ENXIO;
-   }
+	if ((vias == NULL) || (!pmu_fully_inited)) {
+		req->complete = 1;
+		return -ENXIO;
+	}
 
-    ret = -EINVAL;
-	
-    switch (req->data[0]) {
-    case PMU_PACKET:
+	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;
@@ -316,7 +354,7 @@
 			req->reply_len = 0;
 		ret = pmu_queue_request(req);
 		break;
-    case CUDA_PACKET:
+	case CUDA_PACKET:
 		switch (req->data[1]) {
 		case CUDA_GET_TIME:
 			if (req->nbytes != 2)
@@ -344,7 +382,7 @@
 			break;
 		}
 		break;
-    case ADB_PACKET:
+	case ADB_PACKET:
 		for (i = req->nbytes - 1; i > 1; --i)
 			req->data[i+2] = req->data[i];
 		req->data[3] = req->nbytes - 2;
@@ -356,19 +394,17 @@
 		req->reply_len = 0;
 		ret = pmu_queue_request(req);
 		break;
-    }
-    if (ret)
-    {
-    	req->complete = 1;
-    	return ret;
-    }
-    	
-    if (sync) {
-	while (!req->complete)
-		pmu_poll();
-    }
+	}
+	if (ret) {
+		req->complete = 1;
+		return ret;
+	}
+
+	if (sync)
+		while (!req->complete)
+			pmu_poll();
 
-    return 0;
+	return 0;
 }
 
 /* Enable/disable autopolling */
@@ -513,19 +549,25 @@
 static void __openfirmware
 send_byte(int x)
 {
-	out_8(&via[ACR], 0x1c);
-	out_8(&via[SR], x);
-	out_8(&via[B], via[B] & ~0x10);		/* assert TREQ */
+	volatile unsigned char *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 */
 }
 
 static void __openfirmware
 recv_byte()
 {
-	out_8(&via[ACR], 0x0c);
-	in_8(&via[SR]);		/* resets SR */
-	out_8(&via[B], via[B] & ~0x10);
+	volatile unsigned char *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]) & ~0x10);
 }
 
+static int disable_poll;
+
 static void __openfirmware
 pmu_start()
 {
@@ -545,7 +587,9 @@
 	data_len = pmu_data_len[req->data[0]][0];
 
 	/* set the shift register to shift out and send a byte */
+	++disable_poll;
 	send_byte(req->data[0]);
+	--disable_poll;
 
 out:
 	restore_flags(flags);
@@ -554,13 +598,15 @@
 void __openfirmware
 pmu_poll()
 {
-	int ie;
+	unsigned long flags;
 
-	__save_flags(ie);
-	__cli();
+	if (disable_poll)
+		return;
+	save_flags(flags);
+	cli();
 	if (via[IFR] & (SR_INT | CB1_INT))
 		via_pmu_interrupt(0, 0, 0);
-	__restore_flags(ie);
+	restore_flags(flags);
 }
 
 static void __openfirmware
@@ -569,6 +615,7 @@
 	int intr;
 	int nloop = 0;
 
+	++disable_poll;
 	while ((intr = in_8(&via[IFR])) != 0) {
 		if (++nloop > 1000) {
 			printk(KERN_DEBUG "PMU: stuck in intr loop, "
@@ -580,11 +627,9 @@
 		else if (intr & CB1_INT) {
 			adb_int_pending = 1;
 			out_8(&via[IFR], CB1_INT);
-		} else
-		{
-			/* -- Disabled printk, will happen _really_ often on
-				  PowerBooks ((CB2 interrupts) --
-			printk(KERN_DEBUG "PMU: spurrious interrupt intr=%x\n", intr); */
+		}
+		intr &= ~(SR_INT | CB1_INT);
+		if (intr != 0) {
 			out_8(&via[IFR], intr);
 		}
 	}
@@ -597,6 +642,7 @@
 			pmu_start();
 		}
 	}
+	--disable_poll;
 }
 
 static void __openfirmware
@@ -605,17 +651,17 @@
 	struct adb_request *req;
 	int bite, timeout;
 
+	if (via[B] & TREQ) {
+		printk(KERN_ERR "PMU: spurious SR intr (%x)\n", via[B]);
+		out_8(&via[IFR], SR_INT);
+		return;
+	}
 	if (via[B] & TACK)
-		printk(KERN_DEBUG "PMU: sr_intr but ack still high! (%x)\n",
+		printk(KERN_ERR "PMU: sr_intr but ack still high! (%x)\n",
 		       via[B]);
 
-	/* if reading grab the byte, and reset the interrupt */
-	if ((via[ACR] & SR_OUT) == 0)
-		bite = in_8(&via[SR]);
-	out_8(&via[IFR], SR_INT);
-
 	/* reset TREQ and wait for TACK to go high */
-	out_8(&via[B], via[B] | TREQ);
+	out_8(&via[B], in_8(&via[B]) | TREQ);
 	timeout = 3200;
 	while ((in_8(&via[B]) & TACK) == 0) {
 		if (--timeout < 0) {
@@ -625,6 +671,11 @@
 		udelay(10);
 	}
 
+	/* 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) {
 	case sending:
 		req = current_req;
@@ -707,8 +758,6 @@
 static void __openfirmware
 pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs)
 {
-	static int show_pmu_ints = 1;
-
 	asleep = 0;
 	if (len < 1) {
 		adb_int_pending = 0;
@@ -730,21 +779,34 @@
 			}
 			pmu_done(req);
 		} else {
-			adb_input(data+1, len-1, regs, 1);
+#ifdef CONFIG_XMON
+			if (len == 4 && data[1] == 0x2c) {
+				extern int xmon_wants_key, xmon_pmu_keycode;
+				if (xmon_wants_key) {
+					xmon_pmu_keycode = data[2];
+					return;
+				}
+			}
+#endif /* CONFIG_XMON */
+			/*
+			 * 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, regs, 1);
 		}
+	} else if (data[0] == 0x08 && len == 3) {
+		/* sound/brightness buttons pressed */
+		pmu_set_brightness(data[1] >> 3);
+		set_volume(data[2]);
 	} 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");
-		}
+#ifdef CONFIG_PMAC_PBOOK
+		pmu_pass_intr(data, len);
+#endif
 	}
 }
 
@@ -759,38 +821,46 @@
 	struct adb_request req;
 
 	if (vias == NULL)
-		return ;
+		return;
 		
-	if (on) {
-	    /* first call: get current backlight value */
-	    if (backlight_level < 0) {
-		switch(pmu_kind) {
-		    case PMU_OHARE_BASED:
+	/* 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;
-			printk(KERN_DEBUG "pmu: controls returned bright: %d\n", (int)req.reply[1]);
 			break;
-		    case PMU_HEATHROW_BASED:
+		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();
-			printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", (int)req.reply[1]);
 			backlight_level = req.reply[1];
+			printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", backlight_level);
 			break;
-		    default:
+		case PMU_PADDINGTON_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;
 		}
-	    }
-	    pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
-	    	LEVEL_TO_BRIGHT(backlight_level));
-	    while (!req.complete)
-		pmu_poll();
+	}
+	if (on) {
+		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));
+		    PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
 	while (!req.complete)
 		pmu_poll();
 	backlight_enabled = on;
@@ -847,7 +917,7 @@
 {
 	struct adb_request req;
 
-	__cli();
+	cli();
 	
 	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
 					PMU_INT_TICK );
@@ -866,7 +936,7 @@
 {
 	struct adb_request req;
 
-	__cli();
+	cli();
 	
 	pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB |
 					PMU_INT_TICK );
@@ -881,13 +951,69 @@
 		;
 }
 
+#ifdef CONFIG_PMAC_PBOOK
+
+static LIST_HEAD(sleep_notifiers);
+
 int
-pmu_present(void)
+pmu_register_sleep_notifier(struct pmu_sleep_notifier *n)
 {
-	return (adb_controller && (adb_controller->kind == ADB_VIAPMU) && vias);
+	struct list_head *list;
+	struct pmu_sleep_notifier *current;
+
+	for (list = sleep_notifiers.next; list != &sleep_notifiers;
+	     list = list->next) {
+		current = list_entry(list, struct pmu_sleep_notifier, list);
+		if (n->priority > current->priority)
+			break;
+	}
+	__list_add(&n->list, list->prev, list);
+	return 0;
 }
 
-#ifdef CONFIG_PMAC_PBOOK
+int
+pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* n)
+{
+	if (n->list.next == 0)
+		return -ENOENT;
+	list_del(&n->list);
+	n->list.next = 0;
+	return 0;
+}
+
+/* Sleep is broadcast last-to-first */
+static int
+broadcast_sleep(int when, int can_cancel)
+{
+	int ret = PBOOK_SLEEP_OK;
+	struct list_head *list;
+	struct pmu_sleep_notifier *current;
+
+	for (list = sleep_notifiers.prev; list != &sleep_notifiers;
+	     list = list->prev) {
+		current = list_entry(list, struct pmu_sleep_notifier, list);
+		ret = current->notifier_call(current, when);
+		if (can_cancel && (ret != PBOOK_SLEEP_OK))
+			return ret;
+	}
+	return ret;
+}
+
+/* Wake is broadcast first-to-last */
+static int
+broadcast_wake(void)
+{
+	int ret = PBOOK_SLEEP_OK;
+	struct list_head *list;
+	struct pmu_sleep_notifier *current;
+
+	for (list = sleep_notifiers.next; list != &sleep_notifiers;
+	     list = list->next) {
+		current = list_entry(list, struct pmu_sleep_notifier, list);
+		current->notifier_call(current, PBOOK_WAKE);
+	}
+	return ret;
+}
 
 /*
  * This struct is used to store config register values for
@@ -897,10 +1023,11 @@
 	u16	command;
 	u16	cache_lat;
 	u16	intr;
+	u32	rom_address;
 } *pbook_pci_saves;
 static int n_pbook_pci_saves;
 
-static inline void __openfirmware
+static void __openfirmware
 pbook_pci_save(void)
 {
 	int npci;
@@ -922,12 +1049,13 @@
 		pci_read_config_word(pd, PCI_COMMAND, &ps->command);
 		pci_read_config_word(pd, PCI_CACHE_LINE_SIZE, &ps->cache_lat);
 		pci_read_config_word(pd, PCI_INTERRUPT_LINE, &ps->intr);
+		pci_read_config_dword(pd, PCI_ROM_ADDRESS, &ps->rom_address);
 		++ps;
 		--npci;
 	}
 }
 
-static inline void __openfirmware
+static void __openfirmware
 pbook_pci_restore(void)
 {
 	u16 cmd;
@@ -948,7 +1076,7 @@
 					PCI_BASE_ADDRESS_0 + j*4,
 					pd->resource[j].start);
 			pci_write_config_dword(pd, PCI_ROM_ADDRESS,
-			       pd->resource[PCI_ROM_RESOURCE].start);
+				ps->rom_address);
 			pci_write_config_word(pd, PCI_CACHE_LINE_SIZE,
 				ps->cache_lat);
 			pci_write_config_word(pd, PCI_INTERRUPT_LINE,
@@ -963,43 +1091,147 @@
 /*
  * Put the powerbook to sleep.
  */
-#define IRQ_ENABLE	((unsigned int *)0xf3000024)
-#define MEM_CTRL	((unsigned int *)0xf8000070)
+ 
+#define FEATURE_CTRL(base)	((unsigned int *)(base + 0x38))
+#define	GRACKLE_PM	(1<<7)
+#define GRACKLE_DOZE	(1<<5)
+#define	GRACKLE_NAP	(1<<4)
+#define	GRACKLE_SLEEP	(1<<3)
+
+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;
 
-int __openfirmware powerbook_sleep(void)
+	macio = find_devices("mac-io");
+	if (macio != 0 && macio->n_addrs > 0)
+		macio_base = (unsigned long)
+			ioremap(macio->addrs[0].address, 0x40);
+
+	/* Sync the disks. */
+	/* XXX It would be nice to have some way to ensure that
+	 * nobody is dirtying any new buffers while we wait. */
+	fsync_dev(0);
+
+	/* Notify device drivers */
+	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+	if (ret != PBOOK_SLEEP_OK) {
+		broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
+		printk("pmu: sleep rejected\n");
+		return -EBUSY;
+	}
+	broadcast_sleep(PBOOK_SLEEP_NOW, 0);
+
+	/* Give the disks a little time to actually finish writing */
+	for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
+		mb();
+
+	/* Disable all interrupts except pmu */
+	sleep_save_intrs(vias->intrs[0].line);
+
+	/* 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
+	/* 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));
+	}
+
+	if (current->thread.regs && (current->thread.regs->msr & MSR_FP) != 0)
+		giveup_fpu(current);
+
+	grackle_pcibios_read_config_word(0,0,0x70,&pmcr1);
+	/* Apparently, MacOS uses NAP mode for Grackle ??? */
+	pmcr1 &= ~(GRACKLE_DOZE|GRACKLE_SLEEP); 
+	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();
+
+	/* We're awake again, stop grackle PM */
+	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);
+
+	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);
+
+	/* Restore L2 cache */
+	if (save_l2cr)
+		_set_L2CR(save_l2cr | 0x200000); /* set invalidate bit */
+	
+	/* reenable interrupts */
+	sleep_restore_intrs();
+
+	/* Notify drivers */
+	broadcast_wake();
+
+	return 0;
+}
+
+#define PB3400_MEM_CTRL		((unsigned int *)0xf8000070)
+
+int __openfirmware powerbook_sleep_3400(void)
 {
 	int ret, i, x;
-	static int save_backlight;
-	static unsigned int save_irqen;
 	unsigned long msr;
 	unsigned int hid0;
 	unsigned long p, wait;
 	struct adb_request sleep_req;
 
-	/* Notify device drivers */
-	ret = notifier_call_chain(&sleep_notifier_list, PBOOK_SLEEP, NULL);
-	if (ret & NOTIFY_STOP_MASK)
-		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. */
 	fsync_dev(0);
 
-	/* Turn off the display backlight */
-	save_backlight = backlight_enabled;
-	if (save_backlight)
-		pmu_enable_backlight(0);
+	/* Notify device drivers */
+	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+	if (ret != PBOOK_SLEEP_OK) {
+		broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
+		printk("pmu: sleep rejected\n");
+		return -EBUSY;
+	}
+	broadcast_sleep(PBOOK_SLEEP_NOW, 0);
 
 	/* Give the disks a little time to actually finish writing */
 	for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
 		mb();
 
 	/* Disable all interrupts except pmu */
-	save_irqen = in_le32(IRQ_ENABLE);
-	for (i = 0; i < 32; ++i)
-		if (i != vias->intrs[0].line && (save_irqen & (1 << i)))
-			disable_irq(i);
+	sleep_save_intrs(vias->intrs[0].line);
+
+	/* Make sure the decrementer won't interrupt us */
 	asm volatile("mtdec %0" : : "r" (0x7fffffff));
 
 	/* Save the state of PCI config space for some slots */
@@ -1008,9 +1240,9 @@
 	/* Set the memory controller to keep the memory refreshed
 	   while we're asleep */
 	for (i = 0x403f; i >= 0x4000; --i) {
-		out_be32(MEM_CTRL, i);
+		out_be32(PB3400_MEM_CTRL, i);
 		do {
-			x = (in_be32(MEM_CTRL) >> 16) & 0x3ff;
+			x = (in_be32(PB3400_MEM_CTRL) >> 16) & 0x3ff;
 		} while (x == 0);
 		if (x >= 0x100)
 			break;
@@ -1020,6 +1252,7 @@
 	pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
 	while (!sleep_req.complete)
 		mb();
+
 	/* displacement-flush the L2 cache - necessary? */
 	for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000)
 		i = *(volatile int *)p;
@@ -1035,7 +1268,7 @@
 	udelay(10);
 
 	/* OK, we're awake again, start restoring things */
-	out_be32(MEM_CTRL, 0x3f);
+	out_be32(PB3400_MEM_CTRL, 0x3f);
 	pbook_pci_restore();
 
 	/* wait for the PMU interrupt sequence to complete */
@@ -1043,21 +1276,10 @@
 		mb();
 
 	/* reenable interrupts */
-	for (i = 0; i < 32; ++i)
-		if (i != vias->intrs[0].line && (save_irqen & (1 << i)))
-			enable_irq(i);
+	sleep_restore_intrs();
 
 	/* Notify drivers */
-	notifier_call_chain(&sleep_notifier_list, PBOOK_WAKE, NULL);
-
-	/* reenable ADB autopoll */
-	pmu_adb_autopoll(adb_dev_map);
-
-	/* Turn on the screen backlight, if it was on before */
-	if (save_backlight)
-		pmu_enable_backlight(1);
-
-	/* Wait for the hard disk to spin up */
+	broadcast_wake();
 
 	return 0;
 }
@@ -1065,15 +1287,112 @@
 /*
  * Support for /dev/pmu device
  */
+#define RB_SIZE		10
+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;
+};
+
+static LIST_HEAD(all_pmu_pvt);
+static spinlock_t all_pvt_lock = SPIN_LOCK_UNLOCKED;
+
+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);
+		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_irqrestore(&all_pvt_lock, flags);
+}
+
 static int __openfirmware 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);
+	spin_lock_irqsave(&all_pvt_lock, flags);
+	list_add(&pp->list, &all_pmu_pvt);
+	spin_unlock_irqrestore(&all_pvt_lock, flags);
+	file->private_data = pp;
 	return 0;
 }
 
 static ssize_t __openfirmware pmu_read(struct file *file, char *buf,
 			size_t count, loff_t *ppos)
 {
-	return 0;
+	struct pmu_private *pp = file->private_data;
+	DECLARE_WAITQUEUE(wait, current);
+	int ret;
+
+	if (count < 1 || pp == 0)
+		return -EINVAL;
+	ret = verify_area(VERIFY_WRITE, buf, count);
+	if (ret)
+		return ret;
+
+	add_wait_queue(&pp->wait, &wait);
+	current->state = TASK_INTERRUPTIBLE;
+
+	for (;;) {
+		ret = -EAGAIN;
+		spin_lock(&pp->lock);
+		if (pp->rb_get != pp->rb_put) {
+			int i = pp->rb_get;
+			struct rb_entry *rp = &pp->rb_buf[i];
+			ret = rp->len;
+			if (ret > count)
+				ret = count;
+			if (ret > 0 && copy_to_user(buf, rp->data, ret))
+				ret = -EFAULT;
+			if (++i >= RB_SIZE)
+				i = 0;
+			pp->rb_get = i;
+		}
+		spin_unlock(&pp->lock);
+		if (ret >= 0)
+			break;
+
+		if (file->f_flags & O_NONBLOCK)
+			break;
+		ret = -ERESTARTSYS;
+		if (signal_pending(current))
+			break;
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&pp->wait, &wait);
+
+	return ret;
 }
 
 static ssize_t __openfirmware pmu_write(struct file *file, const char *buf,
@@ -1082,26 +1401,65 @@
 	return 0;
 }
 
+static unsigned int pmu_fpoll(struct file *filp, poll_table *wait)
+{
+	struct pmu_private *pp = filp->private_data;
+	unsigned int mask = 0;
+
+	if (pp == 0)
+		return 0;
+	poll_wait(filp, &pp->wait, wait);
+	spin_lock(&pp->lock);
+	if (pp->rb_get != pp->rb_put)
+		mask |= POLLIN;
+	spin_unlock(&pp->lock);
+	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 = 0;
+		spin_lock_irqsave(&all_pvt_lock, flags);
+		list_del(&pp->list);
+		spin_unlock_irqrestore(&all_pvt_lock, flags);
+		kfree(pp);
+	}
+	return 0;
+}
+
 /* Note: removed __openfirmware here since it causes link errors */
-static int /*__openfirmware*/ pmu_ioctl(struct inode * inode, struct file *filp,
+static int pmu_ioctl(struct inode * inode, struct file *filp,
 		     u_int cmd, u_long arg)
 {
 	int error;
 	__u32 value;
 
 	switch (cmd) {
-	    case PMU_IOC_SLEEP:
-	    	if (pmu_kind != PMU_OHARE_BASED)
-	    		return -ENOSYS;
-		return powerbook_sleep();
-	    case PMU_IOC_GET_BACKLIGHT:
+	case PMU_IOC_SLEEP:
+		switch (pmu_kind) {
+		case PMU_OHARE_BASED:
+			error = powerbook_sleep_3400();
+			break;
+		case PMU_HEATHROW_BASED:
+		case PMU_PADDINGTON_BASED:
+			error = powerbook_sleep_G3();
+			break;
+		default:
+			error = ENOSYS;
+		}
+		return error;
+	case PMU_IOC_GET_BACKLIGHT:
 		return put_user(backlight_level, (__u32 *)arg);
-	    case PMU_IOC_SET_BACKLIGHT:
+	case PMU_IOC_SET_BACKLIGHT:
 		error = get_user(value, (__u32 *)arg);
 		if (!error)
 			pmu_set_brightness(value);
 		return error;
-	    case PMU_IOC_GET_MODEL:
+	case PMU_IOC_GET_MODEL:
 	    	return put_user(pmu_kind, (__u32 *)arg);
 	}
 	return -EINVAL;
@@ -1112,12 +1470,12 @@
 	pmu_read,
 	pmu_write,
 	NULL,		/* no readdir */
-	NULL,		/* no poll yet */
+	pmu_fpoll,
 	pmu_ioctl,
 	NULL,		/* no mmap */
 	pmu_open,
 	NULL,		/* flush */
-	NULL		/* no release */
+	pmu_release,
 };
 
 static struct miscdevice pmu_device = {

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