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

Next file: linux/drivers/md/lvm-fs.c
Previous file: linux/drivers/macintosh/via-cuda.c
Back to the patch index
Back to the overall index

diff -Naur -X /home/marcelo/lib/dontdiff linux.orig/drivers/macintosh/via-pmu.c linux/drivers/macintosh/via-pmu.c
@@ -9,9 +9,9 @@
  * and the RTC (real time clock) chip.
  *
  * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
+ * Copyright (C) 2001 Benjamin Herrenschmidt
  * 
- * todo: - Check this driver for smp safety (new Core99 motherboards).
- *       - Cleanup synchro between VIA interrupt and GPIO-based PMU
+ * todo: - Cleanup synchro between VIA interrupt and GPIO-based PMU
  *         interrupt.
  *
  *
@@ -45,10 +45,12 @@
 #include <asm/sections.h>
 #include <asm/irq.h>
 #include <asm/hardirq.h>
-#include <asm/feature.h>
+#include <asm/pmac_feature.h>
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
+#include <asm/sections.h>
 #include <asm/cputable.h>
+#include <asm/time.h>
 #ifdef CONFIG_PMAC_BACKLIGHT
 #include <asm/backlight.h>
 #endif
@@ -56,9 +58,13 @@
 /* Some compile options */
 #undef SUSPEND_USES_PMU
 #define DEBUG_SLEEP
+#undef HACKED_PCI_SAVE
 
 /* Misc minor number allocated for /dev/pmu */
-#define PMU_MINOR	154
+#define PMU_MINOR		154
+
+/* How many iterations between battery polls */
+#define BATTERY_POLLING_COUNT	2
 
 static volatile unsigned char *via;
 
@@ -108,7 +114,7 @@
 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 interrupt_data[256]; /* Made bigger: I've been told that might happen */
 static unsigned char *reply_ptr;
 static int data_index;
 static int data_len;
@@ -126,9 +132,26 @@
 static int pmu_version;
 static int drop_interrupts;
 #ifdef CONFIG_PMAC_PBOOK
+static int option_lid_wakeup = 1;
 static int sleep_in_progress;
+static int can_sleep;
+#endif /* CONFIG_PMAC_PBOOK */
+
+static struct proc_dir_entry *proc_pmu_root;
+static struct proc_dir_entry *proc_pmu_info;
+static struct proc_dir_entry *proc_pmu_options;
+
+#ifdef CONFIG_PMAC_PBOOK
+int pmu_battery_count;
+int pmu_cur_battery;
+unsigned int pmu_power_flags;
+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];
 #endif /* CONFIG_PMAC_PBOOK */
 
+int __fake_sleep;
 int asleep;
 struct notifier_block *sleep_notifier_list;
 
@@ -153,15 +176,22 @@
 static void pmu_done(struct adb_request *req);
 static void pmu_handle_data(unsigned char *data, int len,
 			    struct pt_regs *regs);
-static void set_volume(int level);
 static void gpio1_interrupt(int irq, void *arg, struct pt_regs *regs);
+static int proc_get_info(char *page, char **start, off_t off,
+			  int count, int *eof, void *data);
 #ifdef CONFIG_PMAC_BACKLIGHT
 static int pmu_set_backlight_level(int level, void* data);
 static int pmu_set_backlight_enable(int on, int level, void* data);
 #endif /* CONFIG_PMAC_BACKLIGHT */
 #ifdef CONFIG_PMAC_PBOOK
 static void pmu_pass_intr(unsigned char *data, int len);
-#endif
+static int proc_get_batt(char *page, char **start, off_t off,
+			int count, int *eof, void *data);
+#endif /* CONFIG_PMAC_PBOOK */
+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 *buffer,
+			unsigned long count, void *data);
 
 #ifdef CONFIG_ADB
 struct adb_driver via_pmu_driver = {
@@ -309,6 +339,10 @@
 	} else
 		pmu_kind = PMU_UNKNOWN;
 
+#ifdef CONFIG_PMAC_PBOOK
+	if (pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0)
+		can_sleep = 1;
+#endif /* CONFIG_PMAC_PBOOK */
 	via = (volatile unsigned char *) ioremap(vias->addrs->address, 0x2000);
 
 	out_8(&via[IER], IER_CLR | 0x7f);	/* disable all intrs */
@@ -362,9 +396,14 @@
 	if (vias == NULL)
 		return -ENODEV;
 
+	request_OF_resource(vias, 0, NULL);
+
 	bright_req_1.complete = 1;
 	bright_req_2.complete = 1;
 	bright_req_3.complete = 1;
+#ifdef CONFIG_PMAC_PBOOK
+	batt_req.complete = 1;
+#endif
 
 	if (request_irq(vias->intrs[0].line, via_pmu_interrupt, 0, "VIA-PMU",
 			(void *)0)) {
@@ -388,6 +427,46 @@
 	register_backlight_controller(&pmu_backlight_controller, NULL, "pmu");
 #endif /* CONFIG_PMAC_BACKLIGHT */
 
+#ifdef CONFIG_PMAC_PBOOK
+  	if (machine_is_compatible("AAPL,3400/2400") ||
+  		machine_is_compatible("AAPL,3500"))
+		pmu_battery_count = 1;
+	else if (machine_is_compatible("AAPL,PowerBook1998") ||
+		machine_is_compatible("PowerBook1,1"))
+		pmu_battery_count = 2;
+	else {
+		struct device_node* prim = find_devices("power-mgt");
+		u32 *prim_info = NULL;
+		if (prim)
+			prim_info = (u32 *)get_property(prim, "prim-info", NULL);
+		if (prim_info) {
+			/* Other stuffs here yet unknown */
+			pmu_battery_count = (prim_info[6] >> 16) & 0xff;
+		}
+	}
+#endif /* CONFIG_PMAC_PBOOK */
+	/* Create /proc/pmu */
+	proc_pmu_root = proc_mkdir("pmu", 0);
+	if (proc_pmu_root) {
+		int i;
+		proc_pmu_info = create_proc_read_entry("info", 0, proc_pmu_root,
+					proc_get_info, NULL);
+#ifdef CONFIG_PMAC_PBOOK
+		for (i=0; i<pmu_battery_count; i++) {
+			char title[16];
+			sprintf(title, "battery_%d", i);
+			proc_pmu_batt[i] = create_proc_read_entry(title, 0, proc_pmu_root,
+						proc_get_batt, (void *)i);
+		}
+#endif /* CONFIG_PMAC_PBOOK */
+		proc_pmu_options = create_proc_entry("options", 0600, proc_pmu_root);
+		if (proc_pmu_options) {
+			proc_pmu_options->nlink = 1;
+			proc_pmu_options->read_proc = proc_read_options;
+			proc_pmu_options->write_proc = proc_write_options;
+		}
+	}
+
 	/* 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
@@ -448,8 +527,8 @@
 	pmu_request(&req, NULL, 1, PMU_GET_VERSION);
 	while (!req.complete)
 		pmu_poll();
-	if (req.reply_len > 1)
-		pmu_version = req.reply[1];
+	if (req.reply_len > 0)
+		pmu_version = req.reply[0];
 
 	return 1;
 }
@@ -460,6 +539,263 @@
 	return pmu_kind;
 }
 
+#ifdef CONFIG_PMAC_PBOOK
+
+/* 
+ * WARNING ! This code probably needs some debugging... -- BenH.
+ */
+static void __pmac
+done_battery_state_ohare(struct adb_request* req)
+{
+	unsigned int bat_flags = 0;
+	int current = 0;
+	unsigned int capa, max, voltage, time;
+	int lrange[] = { 0, 275, 850, 1680, 2325, 
+				2765, 3160, 3500, 3830, 4115, 
+				4360, 4585, 4795, 4990, 5170, 
+				5340, 5510, 5710, 5930, 6150, 
+				6370, 6500
+				};
+	
+	if (req->reply[0] & 0x01)
+		pmu_power_flags |= PMU_PWR_AC_PRESENT;
+	else
+		pmu_power_flags &= ~PMU_PWR_AC_PRESENT;
+
+	if (req->reply[0] & 0x04) {
+		int vb, i, j, charge, pcharge;
+		bat_flags |= PMU_BATT_PRESENT;
+		vb = (req->reply[1] << 8) | req->reply[2];
+		voltage = ((vb * 2650) + 726650)/100;
+		current = *((signed char *)&req->reply[5]);
+		if ((req->reply[0] & 0x01) == 0 && (current > 200))
+			vb += (current - 200) * 15;
+		else if (req->reply[0] & 0x02)
+			vb = (vb - 10) * 100;
+	  	i = (33000 - vb) / 10;
+	  	j = i - (i % 100);
+	  	if (j <= 0)
+	       		charge = 0;
+	  	else if (j >= 21)
+	       		charge = 650000;
+	  	else
+			charge = (lrange[j + 1] - lrange[j]) * (i - j) + (lrange[j] * 100);
+		charge = (1000 - charge / 650) / 10;
+		if (req->reply[0] & 0x40) {
+			pcharge = (req->reply[6] << 8) + req->reply[7];
+			if (pcharge > 6500)
+				pcharge = 6500;
+			pcharge *= 100;
+			pcharge = (1000 - pcharge / 650) / 10;
+			if (pcharge < charge)
+				charge = pcharge;
+		}
+		capa = charge;
+		max = 100;
+		time = (charge * 274) / current;
+		current = -current;
+		
+	} else
+		capa = max = current = voltage = time = 0;
+
+	if ((req->reply[0] & 0x02) && (current > 0))
+		bat_flags |= PMU_BATT_CHARGING;
+	if (req->reply[0] & 0x04) /* CHECK THIS ONE */
+		bat_flags |= PMU_BATT_PRESENT;
+
+	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].current = current;
+	pmu_batteries[pmu_cur_battery].voltage = voltage;
+	pmu_batteries[pmu_cur_battery].time_remaining = time;
+}
+
+static void __pmac
+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 = 0;
+	int current;
+	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;
+
+
+	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];
+				current = *((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];
+				current = *((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;
+		}
+	} else
+		capa = max = current = voltage = 0;
+
+	if ((req->reply[1] & 0x01) && (current > 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].current = current;
+	pmu_batteries[pmu_cur_battery].voltage = voltage;
+	if (current) {
+		if ((req->reply[1] & 0x01) && (current > 0))
+			pmu_batteries[pmu_cur_battery].time_remaining
+				= ((max-capa) * 3600) / current;
+		else
+			pmu_batteries[pmu_cur_battery].time_remaining
+				= (capa * 3600) / (-current);
+	} else
+		pmu_batteries[pmu_cur_battery].time_remaining = 0;
+
+	pmu_cur_battery = (pmu_cur_battery + 1) % pmu_battery_count;
+}
+
+static void __pmac
+query_battery_state(void)
+{
+	if (!batt_req.complete)
+		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);
+}
+
+#endif /* CONFIG_PMAC_PBOOK */
+
+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);
+#ifdef CONFIG_PMAC_PBOOK
+	p += sprintf(p, "AC Power               : %d\n",
+		((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0));
+	p += sprintf(p, "Battery count          : %d\n", pmu_battery_count);
+#endif /* CONFIG_PMAC_PBOOK */
+
+	return p - page;
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+static int
+proc_get_batt(char *page, char **start, off_t off,
+		int count, int *eof, void *data)
+{
+	int batnum = (int)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].current);
+	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;
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
+static int
+proc_read_options(char *page, char **start, off_t off,
+			int count, int *eof, void *data)
+{
+	char *p = page;
+
+#ifdef CONFIG_PMAC_PBOOK
+	if (pmu_kind == PMU_KEYLARGO_BASED && can_sleep)
+		p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup);
+#endif /* CONFIG_PMAC_PBOOK */
+
+	return p - page;
+}
+			
+static int
+proc_write_options(struct file *file, const char *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++;
+#ifdef CONFIG_PMAC_PBOOK
+	if (pmu_kind == PMU_KEYLARGO_BASED && can_sleep)
+		if (!strcmp(label, "lid_wakeup"))
+			option_lid_wakeup = ((*val) == '1');
+#endif /* CONFIG_PMAC_PBOOK */
+	return fcount;
+}
+
 #ifdef CONFIG_ADB
 /* Send an ADB command */
 static int __openfirmware
@@ -622,11 +958,7 @@
 	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_len = 0;
 	req->reply_expected = 0;
 	return pmu_queue_request(req);
 }
@@ -835,27 +1167,22 @@
 	spin_lock_irqsave(&pmu_lock, flags);
 	++disable_poll;
 		
-	while ((intr = in_8(&via[IFR])) != 0) {
+	for (;;) {
+		intr = in_8(&via[IFR]) & (SR_INT | CB1_INT);
+		if (intr == 0)
+			break;
 		if (++nloop > 1000) {
 			printk(KERN_DEBUG "PMU: stuck in intr loop, "
-			       "intr=%x pmu_state=%d\n", intr, pmu_state);
+			       "intr=%x, ier=%x pmu_state=%d\n",
+			       intr, in_8(&via[IER]), pmu_state);
 			break;
 		}
 		out_8(&via[IFR], intr);
 		if (intr & SR_INT)
 			pmu_sr_intr(regs);
-		else if (intr & CB1_INT)
+		if (intr & CB1_INT)
 			adb_int_pending = 1;
 	}
-	/* 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 && !pmu_suspended && (in_8(gpio_reg + 0x9) & 0x02) == 0)
-		adb_int_pending = 1;
-#endif
 
 	if (pmu_state == idle) {
 		if (adb_int_pending) {
@@ -866,9 +1193,8 @@
 			wait_for_ack();
 			send_byte(PMU_INT_ACK);
 			adb_int_pending = 0;
-		} else if (current_req) {
+		} else if (current_req)
 			pmu_start();
-		}
 	}
 	
 	--disable_poll;
@@ -878,8 +1204,10 @@
 static void __openfirmware
 gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
 {
-	adb_int_pending = 1;
-	via_pmu_interrupt(0, 0, 0);
+	if ((in_8(gpio_reg + 0x9) & 0x02) == 0) {
+		adb_int_pending = 1;
+		via_pmu_interrupt(0, 0, 0);
+	}
 }
 
 static void __openfirmware
@@ -1040,16 +1368,25 @@
 				adb_input(data+1, len-1, regs, 1);
 #endif /* CONFIG_ADB */		
 		}
-	} else if (data[0] == 0x08 && len == 3) {
-		/* sound/brightness buttons pressed */
+	} else {
+		/* Sound/brightness button pressed */
+		if ((data[0] & PMU_INT_SNDBRT) && len == 3) {
 #ifdef CONFIG_PMAC_BACKLIGHT
-		set_backlight_level(data[1] >> 4);
+			set_backlight_level(data[1] >> 4);
 #endif
-		set_volume(data[2]);
-	} else {
+		}
 #ifdef CONFIG_PMAC_PBOOK
-		pmu_pass_intr(data, len);
-#endif
+		/* Environement or tick interrupt, query batteries */
+		if (pmu_battery_count && (data[0] & PMU_INT_TICK)) {
+			if ((--query_batt_timer) == 0) {
+				query_battery_state();
+				query_batt_timer = BATTERY_POLLING_COUNT;
+			}
+		} else if (pmu_battery_count && (data[0] & PMU_INT_ENVIRONMENT))
+			query_battery_state();
+ 		if (data[0])
+			pmu_pass_intr(data, len);
+#endif /* CONFIG_PMAC_PBOOK */
 	}
 }
 
@@ -1116,11 +1453,6 @@
 		pmu_poll();
 }
 
-static void __openfirmware
-set_volume(int level)
-{
-}
-
 void __openfirmware
 pmu_restart(void)
 {
@@ -1266,10 +1598,14 @@
  * PCI devices which may get powered off when we sleep.
  */
 static struct pci_save {
+#ifndef HACKED_PCI_SAVE
 	u16	command;
 	u16	cache_lat;
 	u16	intr;
 	u32	rom_address;
+#else
+	u32	config[16];
+#endif	
 } *pbook_pci_saves;
 static int n_pbook_pci_saves;
 
@@ -1293,14 +1629,24 @@
 		return;
 
 	pci_for_each_dev(pd) {
+#ifndef HACKED_PCI_SAVE
 		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);
+#else
+		int i;
+		for (i=1;i<16;i++)
+			pci_read_config_dword(pd, i<<4, &ps->config[i]);
+#endif
 		++ps;
 	}
 }
 
+/* For this to work, we must take care of a few things: If gmac was enabled
+ * during boot, it will be in the pci dev list. If it's disabled at this point
+ * (and it will probably be), then you can't access it's config space.
+ */
 static void __openfirmware
 pbook_pci_restore(void)
 {
@@ -1310,7 +1656,13 @@
 	int j;
 
 	pci_for_each_dev(pd) {
+#ifdef HACKED_PCI_SAVE
+		int i;
 		ps++;
+		for (i=2;i<16;i++)
+			pci_write_config_dword(pd, i<<4, ps->config[i]);
+		pci_write_config_dword(pd, 4, ps->config[1]);
+#else
 		if (ps->command == 0)
 			continue;
 		pci_read_config_word(pd, PCI_COMMAND, &cmd);
@@ -1330,8 +1682,8 @@
 				ps->intr);
 			pci_write_config_word(pd, PCI_COMMAND, ps->command);
 			break;
-			/* other header types not restored at present */
 		}
+#endif	
 	}
 }
 
@@ -1370,7 +1722,7 @@
 	}
 	mdelay(50);
 }
-#endif /* DEBUG_SLEEP */
+#endif
 
 /*
  * Put the powerbook to sleep.
@@ -1403,6 +1755,15 @@
 	out_8(&via[IER], IER_SET | SR_INT | CB1_INT);
 }
 
+static inline void wakeup_decrementer(void)
+{
+	set_dec(tb_ticks_per_jiffy);
+	/* No currently-supported powerbook has a 601,
+	 * so use get_tbl, not native
+	 */
+	last_jiffy_stamp(0) = tb_last_stamp = get_tbl();
+}
+
 #define	GRACKLE_PM	(1<<7)
 #define GRACKLE_DOZE	(1<<5)
 #define	GRACKLE_NAP	(1<<4)
@@ -1436,6 +1797,10 @@
 	 * vmalloc's are done before actual sleep of block drivers */
 	fsync_dev(0);
 
+	/* Give the disks a little time to actually finish writing */
+	for (wait = jiffies + (HZ/2); time_before(jiffies, wait); )
+		mb();
+
 	/* 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) {
@@ -1443,13 +1808,9 @@
 		return -EBUSY;
 	}
 
-	/* Give the disks a little time to actually finish writing */
-	for (wait = jiffies + (HZ/2); 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)
+		!bright_req_3.complete || !batt_req.complete)
 		pmu_poll();
 	
 	/* Turn off various things. Darwin does some retry tests here... */
@@ -1495,7 +1856,7 @@
 	/* The VIA is supposed not to be restored correctly*/
 	save_via_state();
 	/* We shut down some HW */
-	feature_prepare_for_sleep();
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
 
 	pci_read_config_word(grackle, 0x70, &pmcr1);
 	/* Apparently, MacOS uses NAP mode for Grackle ??? */
@@ -1512,7 +1873,7 @@
 	pci_write_config_word(grackle, 0x70, pmcr1);
 
 	/* Make sure the PMU is idle */
-	feature_wake_up();
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
 	restore_via_state();
 	
 	/* Restore L2 cache */
@@ -1522,11 +1883,6 @@
 	/* Restore userland MMU context */
 	set_context(current->active_mm->context, current->active_mm->pgd);
 
-	/* 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)
@@ -1558,6 +1914,10 @@
 	/* Leave some time for HW to settle down */
 	mdelay(100);
 
+	/* Restart jiffies & scheduling */
+	wakeup_decrementer();
+	sti();
+
 	/* Notify drivers */
 	broadcast_wake();
 
@@ -1571,7 +1931,7 @@
 	struct adb_request req;
 	int ret, timeout;
 	
-	if (!feature_can_sleep()) {
+	if (!can_sleep) {
 		printk(KERN_ERR "Sleep mode not supported on this machine\n");
 		return -ENOSYS;
 	}
@@ -1591,20 +1951,19 @@
 	 * vmalloc's are done before actual sleep of block drivers */
 	fsync_dev(0);
 
+	/* Give the disks a little time to actually finish writing */
+	for (wait = jiffies + HZ; time_before(jiffies, wait); )
+		mb();
+
 	/* 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)
+		!bright_req_3.complete || !batt_req.complete)
 		pmu_poll();
 
 	/* Tell PMU what events will wake us up */
@@ -1614,7 +1973,8 @@
 		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);
+		0, PMU_PWR_WAKEUP_KEY |
+		(option_lid_wakeup ? PMU_PWR_WAKEUP_LID_OPEN : 0));
 	while (!req.complete)
 		pmu_poll();
 		
@@ -1651,10 +2011,12 @@
 	/* 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_state != idle)
-		pmu_poll();
+	if (!__fake_sleep) {
+		/* Ask the PMU to put us to sleep */
+		pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T');
+		while (!req.complete && pmu_state != idle)
+			pmu_poll();
+	}
 
 	out_8(&via[B], in_8(&via[B]) | TREQ);
 	wait_for_ack();
@@ -1666,13 +2028,16 @@
 	 * 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();
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
 
 	/* Call low-level ASM sleep handler */
-	low_sleep_handler();
+	if (__fake_sleep)
+		mdelay(5000);
+	else
+		low_sleep_handler();
 
 	/* Restore Apple core ASICs state */
-	feature_wake_up();
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
 
 	/* Restore VIA */
 	restore_via_state();
@@ -1694,11 +2059,6 @@
 	/* Restore userland MMU context */
 	set_context(current->active_mm->context, current->active_mm->pgd);
 
-	/* 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)
@@ -1725,6 +2085,10 @@
 	/* Leave some time for HW to settle down */
 	mdelay(100);
 
+	/* Restart jiffies & scheduling */
+	wakeup_decrementer();
+	sti();
+
 	/* Notify drivers */
 	broadcast_wake();
 
@@ -1766,6 +2130,10 @@
 	 * vmalloc's are done before actual sleep of block drivers */
 	fsync_dev(0);
 
+	/* Give the disks a little time to actually finish writing */
+	for (wait = jiffies + (HZ/4); time_before(jiffies, wait); )
+		mb();
+
 	/* 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) {
@@ -1773,13 +2141,9 @@
 		return -EBUSY;
 	}
 
-	/* Give the disks a little time to actually finish writing */
-	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)
+		!bright_req_3.complete || !batt_req.complete)
 		pmu_poll();
 
 	/* Disable all interrupts except pmu */
@@ -1811,6 +2175,8 @@
 	while (!sleep_req.complete)
 		mb();
 
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1);
+
 	/* displacement-flush the L2 cache - necessary? */
 	for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000)
 		i = *(volatile int *)p;
@@ -1825,20 +2191,23 @@
 
 	/* OK, we're awake again, start restoring things */
 	out_be32(mem_ctrl_sleep, 0x3f);
+	pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0);
 	pbook_pci_restore();
 
 	/* wait for the PMU interrupt sequence to complete */
 	while (asleep)
 		mb();
 
-	/* Re-enable DEC interrupts and kick DEC */
-	asm volatile("mtdec %0" : : "r" (0x7fffffff));
-	sti();
-	asm volatile("mtdec %0" : : "r" (0x10000000));
-
 	/* reenable interrupts */
 	pmac_sleep_restore_intrs();
 
+	/* Leave some time for HW to settle down */
+	mdelay(100);
+
+	/* Restart jiffies & scheduling */
+	wakeup_decrementer();
+	sti();
+
 	/* Notify drivers */
 	broadcast_wake();
 
@@ -1877,6 +2246,7 @@
 	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;
@@ -1887,6 +2257,7 @@
 			pp->rb_put = i;
 			wake_up_interruptible(&pp->wait);
 		}
+		spin_unlock(&pp->lock);
 	}
 	spin_unlock_irqrestore(&all_pvt_lock, flags);
 }
@@ -1914,6 +2285,7 @@
 {
 	struct pmu_private *pp = file->private_data;
 	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
 	int ret;
 
 	if (count < 1 || pp == 0)
@@ -1922,38 +2294,41 @@
 	if (ret)
 		return ret;
 
+	spin_lock_irqsave(&pp->lock, flags);
 	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;
+			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;
 		}
-		spin_unlock(&pp->lock);
 		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;
 }
 
@@ -1967,14 +2342,15 @@
 {
 	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(&pp->lock);
+	spin_lock_irqsave(&pp->lock, flags);
 	if (pp->rb_get != pp->rb_put)
 		mask |= POLLIN;
-	spin_unlock(&pp->lock);
+	spin_unlock_irqrestore(&pp->lock, flags);
 	return mask;
 }
 
@@ -2025,7 +2401,7 @@
 		sleep_in_progress = 0;
 		return error;
 	case PMU_IOC_CAN_SLEEP:
-		return put_user(feature_can_sleep(), (__u32 *)arg);
+		return put_user((u32)can_sleep, (__u32 *)arg);
 
 #ifdef CONFIG_PMAC_BACKLIGHT
 	/* Backlight should have its own device or go via
@@ -2155,5 +2531,8 @@
 EXPORT_SYMBOL(pmu_register_sleep_notifier);
 EXPORT_SYMBOL(pmu_unregister_sleep_notifier);
 EXPORT_SYMBOL(pmu_enable_irled);
+EXPORT_SYMBOL(pmu_battery_count);
+EXPORT_SYMBOL(pmu_batteries);
+EXPORT_SYMBOL(pmu_power_flags);
 #endif /* CONFIG_PMAC_PBOOK */
 

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