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

Next file: linux/drivers/net/3c501.c
Previous file: linux/drivers/macintosh/nvram.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.42/linux/drivers/macintosh/via-pmu.c linux/drivers/macintosh/via-pmu.c
@@ -9,6 +9,12 @@
  * 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.
+ *
+ *
  */
 #include <stdarg.h>
 #include <linux/config.h>
@@ -91,13 +97,16 @@
 static unsigned char *reply_ptr;
 static int data_index;
 static int data_len;
-static int adb_int_pending;
+static volatile int adb_int_pending;
 static int pmu_adb_flags;
 static int adb_dev_map = 0;
 static struct adb_request bright_req_1, bright_req_2, bright_req_3;
 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 unsigned char *gpio_reg = NULL;
+static int gpio_irq;
 
 int asleep;
 struct notifier_block *sleep_notifier_list;
@@ -118,6 +127,7 @@
 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);
 #ifdef CONFIG_PMAC_PBOOK
 static void pmu_pass_intr(unsigned char *data, int len);
 #endif
@@ -191,6 +201,7 @@
 	"PowerBook 2400/3400/3500(G3)",
 	"PowerBook G3 Series",
 	"1999 PowerBook G3",
+	"Core99 (iBook/iMac/G4)"
 };
 
 int __openfirmware
@@ -203,9 +214,6 @@
 		return 0;
 	if (vias->next != 0)
 		printk(KERN_WARNING "Warning: only using 1st via-pmu\n");
-	
-	feature_set(vias, FEATURE_VIA_enable);
-
 #if 0
 	{ int i;
 
@@ -218,13 +226,16 @@
 	printk("\n"); }
 #endif
 
-	if (vias->n_addrs != 1 || vias->n_intrs != 1) {
+	if (vias->n_addrs < 1 || vias->n_intrs < 1) {
 		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 0;
 	}
 
+	pmu_has_adb = 1;
+	pmu_has_backlight = 1;
+
 	if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0)
 	    || device_is_compatible(vias->parent, "ohare")))
 		pmu_kind = PMU_OHARE_BASED;
@@ -232,12 +243,17 @@
 		pmu_kind = PMU_PADDINGTON_BASED;
 	else if (device_is_compatible(vias->parent, "heathrow"))
 		pmu_kind = PMU_HEATHROW_BASED;
-	else
+	else if (device_is_compatible(vias->parent, "Keylargo")) {
+		pmu_kind = PMU_KEYLARGO_BASED;
+		pmu_has_adb = (find_type_devices("adb") != NULL);
+		pmu_has_backlight = (find_type_devices("backlight") != NULL);
+	} else
 		pmu_kind = PMU_UNKNOWN;
 
 	via = (volatile unsigned char *) ioremap(vias->addrs->address, 0x2000);
 
 	out_8(&via[IER], IER_CLR | 0x7f);	/* disable all intrs */
+	out_8(&via[IFR], 0x7f);			/* clear IFR */
 
 	pmu_state = idle;
 
@@ -289,6 +305,23 @@
 		return;
 	}
 
+	if (pmu_kind == PMU_KEYLARGO_BASED) {
+		struct device_node *gpio, *gpiop;
+
+		gpiop = find_devices("gpio");
+		if (gpiop && gpiop->n_addrs) {
+			gpio_reg = ioremap(gpiop->addrs->address, 0x10);
+			gpio = find_devices("extint-gpio1");
+			if (gpio && gpio->parent == gpiop && gpio->n_intrs) {
+				gpio_irq = gpio->intrs[0].line;
+				if (request_irq(gpio_irq, gpio1_interrupt, 0,
+			   			"GPIO1/ADB", (void *)0))
+				    printk(KERN_ERR "pmu: can't get irq %d (GPIO1)\n",
+						gpio->intrs[0].line);
+			}
+		}
+	}
+
 	/* Enable interrupts */
 	out_8(&via[IER], IER_SET | SR_INT | CB1_INT);
 
@@ -395,6 +428,8 @@
 		}
 		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;
@@ -425,7 +460,7 @@
 {
 	struct adb_request req;
 
-	if ((vias == NULL) || (!pmu_fully_inited))
+	if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb)
 		return -ENXIO;
 
 	if (devs) {
@@ -447,10 +482,9 @@
 pmu_adb_reset_bus(void)
 {
 	struct adb_request req;
-	long timeout;
 	int save_autopoll = adb_dev_map;
 
-	if ((vias == NULL) || (!pmu_fully_inited))
+	if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb)
 		return -ENXIO;
 
 	/* anyone got a better idea?? */
@@ -460,27 +494,17 @@
 	req.done = NULL;
 	req.data[0] = PMU_ADB_CMD;
 	req.data[1] = 0;
-	req.data[2] = 3; /* ADB_BUSRESET ??? */
+	req.data[2] = ADB_BUSRESET; /* 3 ??? */
 	req.data[3] = 0;
 	req.data[4] = 0;
 	req.reply_len = 0;
 	req.reply_expected = 1;
-	if (pmu_queue_request(&req) != 0)
-	{
+	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_adb_autopoll(save_autopoll);
@@ -558,6 +582,8 @@
 	return 0;
 }
 
+/* New PMU seems to be very sensitive to those timings, so we make sure
+ * PCI is flushed immediately */
 static void __openfirmware
 send_byte(int x)
 {
@@ -566,6 +592,7 @@
 	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 void __openfirmware
@@ -575,10 +602,11 @@
 
 	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);
+	out_8(&v[B], in_8(&v[B]) & ~TREQ);
+	(void)in_8(&v[B]);
 }
 
-static int disable_poll;
+static volatile int disable_poll;
 
 static void __openfirmware
 pmu_start()
@@ -616,7 +644,8 @@
 		return;
 	save_flags(flags);
 	cli();
-	if (via[IFR] & (SR_INT | CB1_INT))
+	if ((via[IFR] & (SR_INT | CB1_INT)) ||
+		(gpio_reg && (in_8(gpio_reg + 0x9) & 0x02) == 0))
 		via_pmu_interrupt(0, 0, 0);
 	restore_flags(flags);
 }
@@ -626,7 +655,12 @@
 {
 	int intr;
 	int nloop = 0;
+	unsigned long flags;
 
+	/* 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();
 	++disable_poll;
 	while ((intr = in_8(&via[IFR])) != 0) {
 		if (++nloop > 1000) {
@@ -645,6 +679,9 @@
 			out_8(&via[IFR], intr);
 		}
 	}
+	if (gpio_reg && (in_8(gpio_reg + 0x9) & 0x02) == 0)
+		adb_int_pending = 1;
+
 	if (pmu_state == idle) {
 		if (adb_int_pending) {
 			pmu_state = intack;
@@ -655,6 +692,13 @@
 		}
 	}
 	--disable_poll;
+	restore_flags(flags);
+}
+
+static void __openfirmware
+gpio1_interrupt(int irq, void *arg, struct pt_regs *regs)
+{
+	via_pmu_interrupt(0, 0, 0);
 }
 
 static void __openfirmware
@@ -668,9 +712,17 @@
 		out_8(&via[IFR], SR_INT);
 		return;
 	}
-	if (via[B] & TACK)
+	/* This one seems to appear with PMU99. According to OF methods,
+	 * the protocol didn't change...
+	 */
+	if (via[B] & TACK) {
+		while ((in_8(&via[B]) & TACK) != 0)
+			;
+#if 0
 		printk(KERN_ERR "PMU: sr_intr but ack still high! (%x)\n",
 		       via[B]);
+#endif
+	}
 
 	/* reset TREQ and wait for TACK to go high */
 	out_8(&via[B], in_8(&via[B]) | TREQ);
@@ -832,7 +884,7 @@
 {
 	struct adb_request req;
 
-	if (vias == NULL)
+	if ((vias == NULL) || !pmu_has_backlight)
 		return;
 		
 	/* first call: get current backlight value */
@@ -853,6 +905,7 @@
 			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
@@ -883,7 +936,7 @@
 {
 	int bright;
 
-	if (vias == NULL)
+	if ((vias == NULL) || !pmu_has_backlight)
 		return ;
 
 	backlight_level = level;
@@ -963,6 +1016,12 @@
 		;
 }
 
+int
+pmu_present(void)
+{
+	return via != 0;
+}
+
 #ifdef CONFIG_PMAC_PBOOK
 
 static LIST_HEAD(sleep_notifiers);
@@ -995,7 +1054,7 @@
 
 /* Sleep is broadcast last-to-first */
 static int
-broadcast_sleep(int when, int can_cancel)
+broadcast_sleep(int when, int fallback)
 {
 	int ret = PBOOK_SLEEP_OK;
 	struct list_head *list;
@@ -1005,8 +1064,13 @@
 	     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))
+		if (ret != PBOOK_SLEEP_OK) {
+			for (; list != &sleep_notifiers; list = list->next) {
+				current = list_entry(list, struct pmu_sleep_notifier, list);
+				current->notifier_call(current, fallback);
+			}
 			return ret;
+		}
 	}
 	return ret;
 }
@@ -1101,6 +1165,24 @@
 	}
 }
 
+#if 0
+/* N.B. This doesn't work on the 3400 */
+void pmu_blink(int n)
+{
+	struct adb_request 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);
+	}
+	udelay(50000);
+}
+#endif
+
 /*
  * Put the powerbook to sleep.
  */
@@ -1127,19 +1209,27 @@
 		macio_base = (unsigned long)
 			ioremap(macio->addrs[0].address, 0x40);
 
+	/* 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. */
+	 * 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);
 
-	/* Notify device drivers */
-	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+	/* 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) {
-		broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
-		printk("pmu: sleep rejected\n");
+		printk("pmu: sleep failed\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); )
@@ -1191,6 +1281,10 @@
 	pmcr1 &= ~(GRACKLE_PM|GRACKLE_DOZE|GRACKLE_SLEEP|GRACKLE_NAP); 
 	grackle_pcibios_write_config_word(0, 0, 0x70, pmcr1);
 
+	/* 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,
@@ -1202,8 +1296,8 @@
 
 	/* Restore L2 cache */
 	if (save_l2cr)
-		_set_L2CR(save_l2cr | 0x200000); /* set invalidate bit */
-	
+ 		_set_L2CR(save_l2cr | 0x200000); /* set invalidate bit */
+
 	/* reenable interrupts */
 	sleep_restore_intrs();
 
@@ -1223,19 +1317,27 @@
 	unsigned long p, wait;
 	struct adb_request sleep_req;
 
+	/* 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. */
+	 * 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);
 
-	/* Notify device drivers */
-	ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, 1);
+	/* 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) {
-		broadcast_sleep(PBOOK_SLEEP_REJECT, 0);
-		printk("pmu: sleep rejected\n");
+		printk("pmu: sleep failed\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); )
@@ -1462,33 +1564,35 @@
 			error = powerbook_sleep_G3();
 			break;
 		default:
-			error = ENOSYS;
+			error = -ENOSYS;
 		}
 		return error;
 	case PMU_IOC_GET_BACKLIGHT:
+		if (!pmu_has_backlight)
+			return -ENOSYS;
 		return put_user(backlight_level, (__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);
 		return error;
 	case PMU_IOC_GET_MODEL:
 	    	return put_user(pmu_kind, (__u32 *)arg);
+	case PMU_IOC_HAS_ADB:
+		return put_user(pmu_has_adb, (__u32 *)arg);
 	}
 	return -EINVAL;
 }
 
 static struct file_operations pmu_device_fops = {
-	NULL,		/* no seek */
-	pmu_read,
-	pmu_write,
-	NULL,		/* no readdir */
-	pmu_fpoll,
-	pmu_ioctl,
-	NULL,		/* no mmap */
-	pmu_open,
-	NULL,		/* flush */
-	pmu_release,
+	read:		pmu_read,
+	write:		pmu_write,
+	poll:		pmu_fpoll,
+	ioctl:		pmu_ioctl,
+	open:		pmu_open,
+	release:	pmu_release,
 };
 
 static struct miscdevice pmu_device = {
@@ -1502,3 +1606,70 @@
 }
 #endif /* CONFIG_PMAC_PBOOK */
 
+#if 0
+static inline void polled_handshake(volatile unsigned char *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 *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 *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 *v = via;
+
+	req->complete = 1;
+	c = req->data[0];
+	l = pmu_data_len[c][0];
+	if (l >= 0 && req->nbytes != l + 1)
+		return -EINVAL;
+
+	save_flags(flags); cli();
+	while (pmu_state != idle)
+		pmu_poll();
+
+	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);
+
+	restore_flags(flags);
+	return 0;
+}
+#endif /* 0 */

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