patch-2.3.43 linux/drivers/macintosh/mediabay.c

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

diff -u --recursive --new-file v2.3.42/linux/drivers/macintosh/mediabay.c linux/drivers/macintosh/mediabay.c
@@ -38,6 +38,14 @@
 #endif
 
 #undef MB_USE_INTERRUPTS
+#undef MB_DEBUG
+#define MB_IGNORE_SIGNALS
+
+#ifdef MB_DEBUG
+#define MBDBG(fmt, arg...)	printk(KERN_INFO fmt , ## arg)
+#else
+#define MBDBG(fmt, arg...)	do { } while (0)
+#endif
 
 struct media_bay_hw {
 	unsigned char	b0;
@@ -49,17 +57,16 @@
 struct media_bay_info {
 	volatile struct media_bay_hw*	addr;
 	int				content_id;
-	int				previous_id;
-	int				ready;
+	int				state;
 	int				last_value;
 	int				value_count;
-	int				reset_timer;
+	int				timer;
 	struct device_node*		dev_node;
 #ifdef CONFIG_BLK_DEV_IDE
 	unsigned long			cd_base;
 	int 				cd_index;
 	int				cd_irq;
-	int				cd_timer;
+	int				cd_retry;
 #endif
 };
 
@@ -73,31 +80,79 @@
 #ifdef CONFIG_BLK_DEV_IDE
 /* check the busy bit in the media-bay ide interface
    (assumes the media-bay contains an ide device) */
+//#define MB_IDE_READY(i)	((inb(media_bays[i].cd_base + 0x70) & 0xc0) == 0x40)
 #define MB_IDE_READY(i)	((inb(media_bays[i].cd_base + 0x70) & 0x80) == 0)
 #endif
 
+/* Note: All delays are not in milliseconds and converted to HZ relative
+ * values by the macro below
+ */
+#define MS_TO_HZ(ms)	((ms * HZ) / 1000)
+
 /*
  * Consider the media-bay ID value stable if it is the same for
- * this many consecutive samples (at intervals of 1/HZ seconds).
+ * this number of milliseconds
  */
-#define MB_STABLE_COUNT	4
+#define MB_STABLE_DELAY	40
+
+/* Wait after powering up the media bay this delay in ms
+ * timeout bumped for some powerbooks
+ */
+#define MB_POWER_DELAY	200
 
 /*
  * Hold the media-bay reset signal true for this many ticks
  * after a device is inserted before releasing it.
  */
-#define MB_RESET_COUNT	40
+#define MB_RESET_DELAY	40
+
+/*
+ * Wait this long after the reset signal is released and before doing
+ * further operations. After this delay, the IDE reset signal is released
+ * too for an IDE device
+ */
+#define MB_SETUP_DELAY	100
 
 /*
  * Wait this many ticks after an IDE device (e.g. CD-ROM) is inserted
- * (or until the device is ready) before registering the IDE interface.
+ * (or until the device is ready) before waiting for busy bit to disappear
+ */
+#define MB_IDE_WAIT	1000
+
+/*
+ * Timeout waiting for busy bit of an IDE device to go down
+ */
+#define MB_IDE_TIMEOUT	5000
+
+/*
+ * Max retries of the full power up/down sequence for an IDE device
+ */
+#define MAX_CD_RETRIES	3
+
+/*
+ * States of a media bay
  */
-#define MB_IDE_WAIT	1500
+enum {
+	mb_empty = 0,		/* Idle */
+	mb_powering_up,		/* power bit set, waiting MB_POWER_DELAY */
+	mb_enabling_bay,	/* enable bits set, waiting MB_RESET_DELAY */
+	mb_resetting,		/* reset bit unset, waiting MB_SETUP_DELAY */
+	mb_ide_resetting,	/* IDE reset bit unser, waiting MB_IDE_WAIT */
+	mb_ide_waiting,		/* Waiting for BUSY bit to go away until MB_IDE_TIMEOUT */
+	mb_up,			/* Media bay full */
+	mb_powering_down	/* Powering down (avoid too fast down/up) */
+};
 
 static void poll_media_bay(int which);
 static void set_media_bay(int which, int id);
+static void set_mb_power(int which, int onoff);
+static void media_bay_step(int i);
 static int media_bay_task(void *);
 
+#ifdef MB_USE_INTERRUPTS
+static void media_bay_intr(int irq, void *devid, struct pt_regs *regs);
+#endif
+
 /*
  * It seems that the bit for the media-bay interrupt in the IRQ_LEVEL
  * register is always set when there is something in the media bay.
@@ -113,8 +168,7 @@
 	struct device_node *np;
 	int		n,i;
 	
-	for (i=0; i<MAX_BAYS; i++)
-	{
+	for (i=0; i<MAX_BAYS; i++) {
 		memset((char *)&media_bays[i], 0, sizeof(struct media_bay_info));
 		media_bays[i].content_id	= -1;
 #ifdef CONFIG_BLK_DEV_IDE
@@ -124,40 +178,43 @@
 	
 	np = find_devices("media-bay");
 	n = 0;
-	while(np && (n<MAX_BAYS))
-	{
+	while(np && (n<MAX_BAYS)) {
 		if (np->n_addrs == 0)
 			continue;
 		media_bays[n].addr = (volatile struct media_bay_hw *)
 			ioremap(np->addrs[0].address, sizeof(struct media_bay_hw));
 
 #ifdef MB_USE_INTERRUPTS
-		if (np->n_intrs == 0)
-		{
+		if (np->n_intrs == 0) {
 			printk(KERN_ERR "media bay %d has no irq\n",n);
 			continue;
 		}
 		
-		if (request_irq(np_intrs[0].line, media_bay_intr, 0, "Media bay", NULL))
-		{
-			printk(KERN_ERR "Couldn't get IRQ %d for media bay %d\n", irq, n);
+		if (request_irq(np->intrs[0].line, media_bay_intr, 0, "Media bay", (void *)n)) {
+			printk(KERN_ERR "Couldn't get IRQ %d for media bay %d\n",
+				np->intrs[0].line, n);
 			continue;
 		}
 #endif	
 		media_bay_count++;
 	
-		set_media_bay(n, MB_CONTENTS(n));
-		if (media_bays[n].content_id != MB_NO) {
-			feature_clear(media_bays[n].dev_node, FEATURE_Mediabay_reset);
-			udelay(500);
-		}
-		media_bays[n].ready		= 1;
-		media_bays[n].previous_id	= media_bays[n].content_id;
-		media_bays[n].reset_timer	= 0;
 		media_bays[n].dev_node		= np;
-#ifdef CONFIG_BLK_DEV_IDE
-		media_bays[n].cd_timer		= 0;
-#endif
+
+		/* Force an immediate detect */
+		set_mb_power(n,0);
+		mdelay(MB_POWER_DELAY);
+		out_8(&media_bays[n].addr->contents, 0x70);
+		mdelay(MB_STABLE_DELAY);
+		media_bays[n].content_id = MB_NO;
+		media_bays[n].last_value = MB_CONTENTS(n);
+		media_bays[n].value_count = MS_TO_HZ(MB_STABLE_DELAY);
+		media_bays[n].state = mb_empty;
+		do {
+			mdelay(1000/HZ);
+			media_bay_step(n);
+		} while((media_bays[n].state != mb_empty) &&
+			(media_bays[n].state != mb_up));
+		
 		n++;
 		np=np->next;
 	}
@@ -174,17 +231,66 @@
 	}
 }
 
-#if 0
-static void
+#ifdef MB_USE_INTERRUPTS
+static void __pmac
 media_bay_intr(int irq, void *devid, struct pt_regs *regs)
 {
-	int id = MB_CONTENTS();
-
-	if (id == MB_NO)
-		set_media_bay(id);
 }
 #endif
 
+static void __pmac
+set_mb_power(int which, int onoff)
+{
+	volatile struct media_bay_info*	mb = &media_bays[which];
+
+	if (onoff) {
+		feature_set(mb->dev_node, FEATURE_Mediabay_power);
+		udelay(10);
+		feature_set(mb->dev_node, FEATURE_Mediabay_reset);
+		udelay(10);
+		mb->state = mb_powering_up;
+		MBDBG("mediabay%d: powering up\n", which);
+	} else {
+		feature_clear(mb->dev_node, FEATURE_Mediabay_floppy_enable);
+		feature_clear(mb->dev_node, FEATURE_Mediabay_IDE_enable);
+		feature_clear(mb->dev_node, FEATURE_Mediabay_PCI_enable);
+		feature_clear(mb->dev_node, FEATURE_SWIM3_enable);
+		feature_clear(mb->dev_node, FEATURE_Mediabay_power);
+		mb->state = mb_powering_down;
+		MBDBG("mediabay%d: powering down\n", which);
+	}
+	mb->timer = MS_TO_HZ(MB_POWER_DELAY);
+}
+
+static void __pmac
+set_media_bay(int which, int id)
+{
+	volatile struct media_bay_info* bay;
+
+	bay = &media_bays[which];
+	
+	switch (id) {
+	case MB_CD:
+		feature_set(bay->dev_node, FEATURE_Mediabay_IDE_enable);
+		udelay(10);
+		feature_set(bay->dev_node, FEATURE_Mediabay_IDE_reset);
+		printk(KERN_INFO "media bay %d contains a CD-ROM drive\n", which);
+		break;
+	case MB_FD:
+	case MB_FD1:
+		feature_set(bay->dev_node, FEATURE_Mediabay_floppy_enable);
+		feature_set(bay->dev_node, FEATURE_SWIM3_enable);
+		printk(KERN_INFO "media bay %d contains a floppy disk drive\n", which);
+		break;
+	case MB_NO:
+		break;
+	default:
+		printk(KERN_INFO "media bay %d contains an unknown device (%d)\n",
+		       which, id);
+		break;
+	}
+}
+
 int __pmac
 check_media_bay(struct device_node *which_bay, int what)
 {
@@ -194,7 +300,7 @@
 	for (i=0; i<media_bay_count; i++)
 		if (which_bay == media_bays[i].dev_node)
 		{
-			if ((what == media_bays[i].content_id) && media_bays[i].ready)
+			if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
 				return 0;
 			media_bays[i].cd_index = -1;
 			return -EINVAL;
@@ -212,7 +318,7 @@
 	for (i=0; i<media_bay_count; i++)
 		if (base == media_bays[i].cd_base)
 		{
-			if ((what == media_bays[i].content_id) && media_bays[i].ready)
+			if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
 				return 0;
 			media_bays[i].cd_index = -1;
 			return -EINVAL;
@@ -232,17 +338,135 @@
 	for (i=0; i<media_bay_count; i++)
 		if (which_bay == media_bays[i].dev_node)
 		{
+			int timeout = 5000;
+			
  			media_bays[i].cd_base	= base;
 			media_bays[i].cd_irq	= irq;
-			media_bays[i].cd_index	= index;
+
+			if ((MB_CD != media_bays[i].content_id) || media_bays[i].state != mb_up)
+				return 0;
+				
 			printk(KERN_DEBUG "Registered ide %d for media bay %d\n", index, i);			
-			return 0;
+			do {
+				if (MB_IDE_READY(i)) {
+					media_bays[i].cd_index	= index;
+					return 0;
+				}
+				mdelay(1);
+			} while(--timeout);
+			printk(KERN_DEBUG "Timeout waiting IDE in bay %d\n", i);			
+			return -ENODEV;
 		} 
 #endif
 	
 	return -ENODEV;
 }
 
+static void __pmac
+media_bay_step(int i)
+{
+	volatile struct media_bay_info* bay = &media_bays[i];
+
+	/* We don't poll when powering down */
+	if (bay->state != mb_powering_down)
+	    poll_media_bay(i);
+
+	/* If timer expired or polling IDE busy, run state machine */
+	if ((bay->state != mb_ide_waiting) && (bay->timer != 0) && ((--bay->timer) != 0))
+	    return;
+
+	switch(bay->state) {
+	case mb_powering_up:
+	    	set_media_bay(i, bay->last_value);
+	    	bay->timer = MS_TO_HZ(MB_RESET_DELAY);
+	    	bay->state = mb_enabling_bay;
+		MBDBG("mediabay%d: enabling (kind:%d)\n", i, bay->content_id);
+		break;
+	case mb_enabling_bay:
+	    	feature_clear(bay->dev_node, FEATURE_Mediabay_reset);
+	    	bay->timer = MS_TO_HZ(MB_SETUP_DELAY);
+	    	bay->state = mb_resetting;
+		MBDBG("mediabay%d: waiting reset (kind:%d)\n", i, bay->content_id);
+	    	break;
+	    
+	case mb_resetting:
+		if (bay->content_id != MB_CD) {
+			MBDBG("mediabay%d: bay is up (kind:%d)\n", i, bay->content_id);
+			bay->state = mb_up;
+			break;
+	    	}
+#ifdef CONFIG_BLK_DEV_IDE
+		MBDBG("mediabay%d: waiting IDE reset (kind:%d)\n", i, bay->content_id);
+	    	feature_clear(bay->dev_node, FEATURE_Mediabay_IDE_reset);
+	    	bay->timer = MS_TO_HZ(MB_IDE_WAIT);
+	    	bay->state = mb_ide_resetting;
+#else
+		printk(KERN_DEBUG "media-bay %d is ide (not compiled in kernel)\n", i);
+		set_mb_power(i, 0);
+#endif // #ifdef CONFIG_BLK_DEV_IDE
+	    	break;
+	    
+#ifdef CONFIG_BLK_DEV_IDE
+	case mb_ide_resetting:
+	    	bay->timer = MS_TO_HZ(MB_IDE_TIMEOUT);
+	    	bay->state = mb_ide_waiting;
+		MBDBG("mediabay%d: waiting IDE ready (kind:%d)\n", i, bay->content_id);
+	    	break;
+	    
+	case mb_ide_waiting:
+	    	if (bay->cd_base == 0) {
+			bay->timer = 0;
+			bay->state = mb_up;
+			MBDBG("mediabay%d: up before IDE init\n", i);
+			break;
+	    	} else if (MB_IDE_READY(i)) {
+			bay->timer = 0;
+			bay->state = mb_up;
+			if (bay->cd_index < 0)
+				bay->cd_index = ide_register(bay->cd_base, 0, bay->cd_irq);
+			if (bay->cd_index == -1) {
+				/* We eventually do a retry */
+				bay->cd_retry++;
+				printk("IDE register error\n");
+				set_mb_power(i, 0);
+			} else {
+				printk(KERN_DEBUG "media-bay %d is ide %d\n", i, bay->cd_index);
+				MBDBG("mediabay %d IDE ready\n", i);
+			}
+			break;
+	    	}
+	    	if (bay->timer == 0) {
+			printk("\nIDE Timeout in bay %d !\n", i);
+			MBDBG("mediabay%d: nIDE Timeout !\n", i);
+			set_mb_power(i, 0);
+	    	}
+		break;
+#endif // #ifdef CONFIG_BLK_DEV_IDE
+
+	case mb_powering_down:
+	    	bay->state = mb_empty;
+#ifdef CONFIG_BLK_DEV_IDE
+    	        if (bay->cd_index >= 0) {
+			printk(KERN_DEBUG "Unregistering mb %d ide, index:%d\n", i,
+			       bay->cd_index);
+			ide_unregister(bay->cd_index);
+			bay->cd_index = -1;
+		}
+	    	if (bay->cd_retry) {
+			if (bay->cd_retry > MAX_CD_RETRIES) {
+				/* Should add an error sound (sort of beep in dmasound) */
+				printk("\nmedia-bay %d, IDE device badly inserted or unrecognised\n", i);
+			} else {
+				/* Force a new power down/up sequence */
+				bay->content_id = MB_NO;
+			}
+	    	}
+#endif	    
+		MBDBG("mediabay%d: end of power down\n", i);
+	    	break;
+	}
+}
+
 /*
  * This procedure runs as a kernel thread to poll the media bay
  * once each tick and register and unregister the IDE interface
@@ -252,123 +476,57 @@
 int __pmac
 media_bay_task(void *x)
 {
-	volatile struct media_bay_info* bay;
 	int	i = 0;
 	
 	strcpy(current->comm, "media-bay");
-	for (;;)
-	{
-		bay = &media_bays[i];
-		poll_media_bay(i);
-		if (bay->content_id != bay->previous_id) {
-			bay->reset_timer = (bay->content_id != MB_NO) ?
-				MB_RESET_COUNT: 0;
-			bay->ready = 0;
-#ifdef CONFIG_BLK_DEV_IDE
-			bay->cd_timer = 0;
-			if (bay->content_id != MB_CD && bay->cd_index >= 0) {
-				printk(KERN_DEBUG "Unregistering mb %d ide, index:%d\n", i, bay->cd_index);
-				ide_unregister(bay->cd_index);
-				bay->cd_index = -1;
-			}
-#endif
-		} else if (bay->reset_timer) {
-			if (--bay->reset_timer == 0) {
- 				feature_clear(bay->dev_node, FEATURE_Mediabay_reset);
-				bay->ready = 1;
-#ifdef CONFIG_BLK_DEV_IDE
-				bay->cd_timer = 0;
-				if (bay->content_id == MB_CD && bay->cd_base != 0)
-					bay->cd_timer = MB_IDE_WAIT;
-#endif
-			}
-#ifdef CONFIG_BLK_DEV_IDE
-		} else if (bay->cd_timer && (--bay->cd_timer == 0 || MB_IDE_READY(i))
-			   && bay->cd_index < 0) {
-			bay->cd_timer = 0;
-			printk(KERN_DEBUG "Registering IDE, base:0x%08lx, irq:%d\n", bay->cd_base, bay->cd_irq);
-			printk("\n");
-			bay->cd_index = ide_register(bay->cd_base, 0, bay->cd_irq);
-			if (bay->cd_index == -1)
-				printk("\nCD-ROM badly inserted. Remove it and try again !\n");
-			else
-				printk(KERN_DEBUG "media-bay %d is ide %d\n", i, bay->cd_index);
+#ifdef MB_IGNORE_SIGNALS
+	sigfillset(&current->blocked);
 #endif
-		}
 
-		bay->previous_id = bay->content_id;
+	for (;;) {
+	    media_bay_step(i);
+
+	    if (++i >= media_bay_count) {
+		i = 0;
 		current->state = TASK_INTERRUPTIBLE;
 		schedule_timeout(1);
 		if (signal_pending(current))
 			return 0;
-		i = (i+1)%media_bay_count;
+	    }
 	}
 }
 
 void __pmac
 poll_media_bay(int which)
 {
+	volatile struct media_bay_info* bay = &media_bays[which];
 	int id = MB_CONTENTS(which);
 
-	if (id == media_bays[which].last_value) {
-	    if (id != media_bays[which].content_id
-	        && ++media_bays[which].value_count >= MB_STABLE_COUNT) {
+	if (id == bay->last_value) {
+	    if (id != bay->content_id
+	        && ++bay->value_count >= MS_TO_HZ(MB_STABLE_DELAY)) {
 	        /* If the device type changes without going thru "MB_NO", we force
 	           a pass by "MB_NO" to make sure things are properly reset */
-	        if ((id != MB_NO) && (media_bays[which].content_id != MB_NO)) {
-		    set_media_bay(which, MB_NO);
-		    udelay(500);
+	        if ((id != MB_NO) && (bay->content_id != MB_NO)) {
+	            id = MB_NO;
+		    MBDBG("mediabay%d: forcing MB_NO\n", which);
+		}
+		MBDBG("mediabay%d: switching to %d\n", which, id);
+		set_mb_power(which, id != MB_NO);
+		bay->content_id = id;
+		if (id == MB_NO) {
+#ifdef CONFIG_BLK_DEV_IDE
+		    bay->cd_retry = 0;
+#endif
+		    printk(KERN_INFO "media bay %d is empty\n", which);
 		}
- 		set_media_bay(which, id);
  	    }
 	} else {
-		media_bays[which].last_value = id;
-		media_bays[which].value_count = 0;
+		bay->last_value = id;
+		bay->value_count = 0;
 	}
 }
 
-static void __pmac
-set_media_bay(int which, int id)
-{
-	volatile struct media_bay_info* bay;
-
-	bay = &media_bays[which];
-	
-	bay->content_id = id;
-	bay->last_value = id;
-	
-	switch (id) {
-	case MB_CD:
-		feature_set(bay->dev_node, FEATURE_Mediabay_enable);
-		feature_set(bay->dev_node, FEATURE_Mediabay_IDE_enable);
-		udelay(500);
-		feature_set(bay->dev_node, FEATURE_CD_power);
-		printk(KERN_INFO "media bay %d contains a CD-ROM drive\n", which);
-		break;
-	case MB_FD:
-		feature_set(bay->dev_node, FEATURE_Mediabay_enable);
-		feature_set(bay->dev_node, FEATURE_Mediabay_floppy_enable);
-		feature_set(bay->dev_node, FEATURE_SWIM3_enable);
-		printk(KERN_INFO "media bay %d contains a floppy disk drive\n", which);
-		break;
-	case MB_NO:
-		feature_clear(bay->dev_node, FEATURE_CD_power);
-		feature_clear(bay->dev_node, FEATURE_Mediabay_enable);
-		feature_clear(bay->dev_node, FEATURE_Mediabay_floppy_enable);
-		feature_clear(bay->dev_node, FEATURE_Mediabay_IDE_enable);
-		feature_clear(bay->dev_node, FEATURE_SWIM3_enable);
-		feature_set(bay->dev_node, FEATURE_Mediabay_reset);
-		printk(KERN_INFO "media bay %d is empty\n", which);
-		break;
-	default:
-		feature_set(bay->dev_node, FEATURE_Mediabay_enable);
-		printk(KERN_INFO "media bay %d contains an unknown device (%d)\n",
-		       which, id);
-		break;
-	}
-	
-	udelay(500);
-}
 
 #ifdef CONFIG_PMAC_PBOOK
 /*
@@ -388,42 +546,34 @@
 	case PBOOK_SLEEP_NOW:
 		for (i=0; i<media_bay_count; i++) {
 			bay = &media_bays[i];
-			feature_clear(bay->dev_node, FEATURE_Mediabay_enable);
-			feature_clear(bay->dev_node, FEATURE_Mediabay_IDE_enable);
-			feature_clear(bay->dev_node, FEATURE_SWIM3_enable);
-			feature_clear(bay->dev_node, FEATURE_Mediabay_floppy_enable);
-			feature_set(bay->dev_node, FEATURE_Mediabay_reset);
-			feature_clear(bay->dev_node, FEATURE_CD_power);
-			out_8(&media_bays[i].addr->contents, 0x70);
+			set_mb_power(i, 0);
+			mdelay(10);
+			out_8(&bay->addr->contents, 0x70);
 		}
 		break;
 	case PBOOK_WAKE:
 		for (i=0; i<media_bay_count; i++) {
 			bay = &media_bays[i];
-			feature_set(bay->dev_node, FEATURE_Mediabay_enable);
-			/* I suppose this is enough delay to stabilize MB_CONTENT ... */
-			mdelay(10);
-			/* We re-enable the bay using it's previous content only if
-			   it did not change */
-			if (MB_CONTENTS(i) == bay->content_id) {
-				set_media_bay(i, bay->content_id);
-				if (bay->content_id != MB_NO) {
-					mdelay(400);
-					/* Clear the bay reset */
-					feature_clear(bay->dev_node, FEATURE_Mediabay_reset);
-					/* This small delay makes sure the device has time
-					   to assert the BUSY bit (used by IDE sleep) */
-					udelay(100);
-					/* We reset the state machine timers in case we were in the
-					   middle of a wait loop */
-					if (bay->reset_timer)
-						bay->reset_timer = MB_RESET_COUNT;
-#ifdef CONFIG_BLK_DEV_IDE
-					if (bay->cd_timer)
-						bay->cd_timer = MB_IDE_WAIT;
-#endif
-				}
-			}
+			/* We re-enable the bay using it's previous content
+			   only if it did not change. Note those bozo timings, they seem
+			   to help the 3400 get it right
+			 */
+			mdelay(MB_STABLE_DELAY);
+			out_8(&bay->addr->contents, 0x70);
+			mdelay(MB_STABLE_DELAY);
+			if (MB_CONTENTS(i) != bay->content_id)
+				continue;
+			set_mb_power(i, 1);
+			mdelay(MB_POWER_DELAY);
+			media_bays[i].last_value = bay->content_id;
+			media_bays[i].value_count = MS_TO_HZ(MB_STABLE_DELAY);
+			media_bays[i].timer = 0;
+			media_bays[i].cd_retry = 0;
+			do {
+				mdelay(1000/HZ);
+				media_bay_step(i);
+			} while((media_bays[i].state != mb_empty) &&
+				(media_bays[i].state != mb_up));
 		}
 		break;
 	}

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