patch-2.4.19 linux-2.4.19/arch/cris/drivers/gpio.c
Next file: linux-2.4.19/arch/cris/drivers/ide.c
Previous file: linux-2.4.19/arch/cris/drivers/ethernet.c
Back to the patch index
Back to the overall index
- Lines: 603
- Date:
Fri Aug 2 17:39:42 2002
- Orig file:
linux-2.4.18/arch/cris/drivers/gpio.c
- Orig date:
Mon Feb 25 11:37:52 2002
diff -urN linux-2.4.18/arch/cris/drivers/gpio.c linux-2.4.19/arch/cris/drivers/gpio.c
@@ -1,14 +1,26 @@
-/* $Id: gpio.c,v 1.12 2001/11/12 19:42:15 pkj Exp $
+/* $Id: gpio.c,v 1.15 2002/05/06 13:19:13 johana Exp $
*
* Etrax general port I/O device
*
- * Copyright (c) 1999, 2000, 2001 Axis Communications AB
+ * Copyright (c) 1999, 2000, 2001, 2002 Axis Communications AB
*
* Authors: Bjorn Wesen (initial version)
* Ola Knutsson (LED handling)
- * Johan Adolfsson (read/set directions, write)
+ * Johan Adolfsson (read/set directions, write, port G)
*
* $Log: gpio.c,v $
+ * Revision 1.15 2002/05/06 13:19:13 johana
+ * IO_SETINPUT returns mask with bit set = inputs for PA and PB as well.
+ *
+ * Revision 1.14 2002/04/12 12:01:53 johana
+ * Use global r_port_g_data_shadow.
+ * Moved gpio_init_port_g() closer to gpio_init() and marked it __init.
+ *
+ * Revision 1.13 2002/04/10 12:03:55 johana
+ * Added support for port G /dev/gpiog (minor 3).
+ * Changed indentation on switch cases.
+ * Fixed other spaces to tabs.
+ *
* Revision 1.12 2001/11/12 19:42:15 pkj
* * Corrected return values from gpio_leds_ioctl().
* * Fixed compiler warnings.
@@ -94,14 +106,17 @@
struct gpio_private {
struct gpio_private *next;
+ /* These fields are for PA and PB only */
volatile unsigned char *port, *shadow;
volatile unsigned char *dir, *dir_shadow;
unsigned char changeable_dir;
unsigned char changeable_bits;
- unsigned char highalarm, lowalarm;
unsigned char clk_mask;
unsigned char data_mask;
unsigned char write_msb;
+ unsigned char pad1, pad2, pad3;
+ /* These fields are generic */
+ unsigned long highalarm, lowalarm;
wait_queue_head_t alarm_wq;
int minor;
};
@@ -110,10 +125,17 @@
static struct gpio_private *alarmlist = 0;
-#define NUM_PORTS 2
-static volatile unsigned char *ports[2] = { R_PORT_PA_DATA, R_PORT_PB_DATA };
-static volatile unsigned char *shads[2] = {
- &port_pa_data_shadow, &port_pb_data_shadow };
+/* Port A and B use 8 bit access, but Port G is 32 bit */
+#define NUM_PORTS (GPIO_MINOR_B+1)
+
+static volatile unsigned char *ports[NUM_PORTS] = {
+ R_PORT_PA_DATA,
+ R_PORT_PB_DATA,
+};
+static volatile unsigned char *shads[NUM_PORTS] = {
+ &port_pa_data_shadow,
+ &port_pb_data_shadow
+};
/* What direction bits that are user changeable 1=changeable*/
#ifndef CONFIG_ETRAX_PA_CHANGEABLE_DIR
@@ -131,17 +153,36 @@
#endif
-static unsigned char changeable_dir[2] = { CONFIG_ETRAX_PA_CHANGEABLE_DIR,
- CONFIG_ETRAX_PB_CHANGEABLE_DIR };
-static unsigned char changeable_bits[2] = { CONFIG_ETRAX_PA_CHANGEABLE_BITS,
- CONFIG_ETRAX_PB_CHANGEABLE_BITS };
+static unsigned char changeable_dir[NUM_PORTS] = {
+ CONFIG_ETRAX_PA_CHANGEABLE_DIR,
+ CONFIG_ETRAX_PB_CHANGEABLE_DIR
+};
+static unsigned char changeable_bits[NUM_PORTS] = {
+ CONFIG_ETRAX_PA_CHANGEABLE_BITS,
+ CONFIG_ETRAX_PB_CHANGEABLE_BITS
+};
-static volatile unsigned char *dir[2] = { R_PORT_PA_DIR, R_PORT_PB_DIR };
+static volatile unsigned char *dir[NUM_PORTS] = {
+ R_PORT_PA_DIR,
+ R_PORT_PB_DIR
+};
+
+static volatile unsigned char *dir_shadow[NUM_PORTS] = {
+ &port_pa_dir_shadow,
+ &port_pb_dir_shadow
+};
+
+/* Port G is 32 bit, handle it special, some bits are both inputs
+ and outputs at the same time, only some of the bits can change direction
+ and some of them in groups of 8 bit. */
+static unsigned long changeable_dir_g;
+static unsigned long dir_g_in_bits;
+static unsigned long dir_g_out_bits;
+static unsigned long dir_g_shadow; /* 1=output */
+
+#define USE_PORTS(priv) ((priv)->minor <= GPIO_MINOR_B)
-static volatile unsigned char *dir_shadow[2] = {
- &port_pa_dir_shadow, &port_pb_dir_shadow };
-#define LEDS 2
static unsigned int
gpio_poll(struct file *filp,
@@ -149,10 +190,10 @@
{
/* TODO poll on alarms! */
#if 0
- if (!ANYTHING_WANTED) {
+ if (!ANYTHING_WANTED) {
D(printk("gpio_select sleeping task\n"));
- select_wait(&gpio_wq, table);
- return 0;
+ select_wait(&gpio_wq, table);
+ return 0;
}
D(printk("gpio_select ready\n"));
#endif
@@ -166,6 +207,10 @@
unsigned char data, clk_mask, data_mask, write_msb;
unsigned long flags;
ssize_t retval = count;
+ if (priv->minor !=GPIO_MINOR_A && priv->minor != GPIO_MINOR_B) {
+ return -EFAULT;
+ }
+
if (verify_area(VERIFY_READ, buf, count)) {
return -EFAULT;
}
@@ -210,13 +255,15 @@
return retval;
}
+
+
static int
gpio_open(struct inode *inode, struct file *filp)
{
struct gpio_private *priv;
int p = MINOR(inode->i_rdev);
- if (p >= NUM_PORTS && p != LEDS)
+ if (p > GPIO_MINOR_LAST)
return -EINVAL;
priv = (struct gpio_private *)kmalloc(sizeof(struct gpio_private),
@@ -231,13 +278,21 @@
priv->next = alarmlist;
alarmlist = priv;
- priv->port = ports[p];
- priv->shadow = shads[p];
-
- priv->changeable_dir = changeable_dir[p];
- priv->changeable_bits = changeable_bits[p];
- priv->dir = dir[p];
- priv->dir_shadow = dir_shadow[p];
+ if (USE_PORTS(priv)) { /* A and B */
+ priv->port = ports[p];
+ priv->shadow = shads[p];
+ priv->dir = dir[p];
+ priv->dir_shadow = dir_shadow[p];
+ priv->changeable_dir = changeable_dir[p];
+ priv->changeable_bits = changeable_bits[p];
+ } else {
+ priv->port = NULL;
+ priv->shadow = NULL;
+ priv->dir = NULL;
+ priv->dir_shadow = NULL;
+ priv->changeable_dir = 0;
+ priv->changeable_bits = 0;
+ }
priv->highalarm = 0;
priv->lowalarm = 0;
@@ -289,87 +344,189 @@
}
switch (_IOC_NR(cmd)) {
- case IO_READBITS:
- // read the port
+ case IO_READBITS:
+ // read the port
+ if (USE_PORTS(priv)) {
return *priv->port;
- case IO_SETBITS:
- save_flags(flags); cli();
- // set changeable bits with a 1 in arg
+ } else if (priv->minor == GPIO_MINOR_G) {
+ return *R_PORT_G_DATA;
+ }
+ break;
+ case IO_SETBITS:
+ save_flags(flags); cli();
+ // set changeable bits with a 1 in arg
+ if (USE_PORTS(priv)) {
*priv->port = *priv->shadow |=
((unsigned char)arg & priv->changeable_bits);
- restore_flags(flags);
- break;
- case IO_CLRBITS:
- save_flags(flags); cli();
- // clear changeable bits with a 1 in arg
+ } else if (priv->minor == GPIO_MINOR_G) {
+ *R_PORT_G_DATA = port_g_data_shadow |= (arg & dir_g_out_bits);
+ }
+ restore_flags(flags);
+ break;
+ case IO_CLRBITS:
+ save_flags(flags); cli();
+ // clear changeable bits with a 1 in arg
+ if (USE_PORTS(priv)) {
*priv->port = *priv->shadow &=
- ~((unsigned char)arg & priv->changeable_bits);
- restore_flags(flags);
- break;
- case IO_HIGHALARM:
- // set alarm when bits with 1 in arg go high
- priv->highalarm |= (unsigned char)arg;
- break;
- case IO_LOWALARM:
- // set alarm when bits with 1 in arg go low
- priv->lowalarm |= (unsigned char)arg;
- break;
- case IO_CLRALARM:
- // clear alarm for bits with 1 in arg
- priv->highalarm &= ~(unsigned char)arg;
- priv->lowalarm &= ~(unsigned char)arg;
- break;
- case IO_READDIR:
- /* Read direction 0=input 1=output */
+ ~((unsigned char)arg & priv->changeable_bits);
+ } else if (priv->minor == GPIO_MINOR_G) {
+ *R_PORT_G_DATA = port_g_data_shadow &= ~((unsigned long)arg & dir_g_out_bits);
+ }
+ restore_flags(flags);
+ break;
+ case IO_HIGHALARM:
+ // set alarm when bits with 1 in arg go high
+ priv->highalarm |= arg;
+ break;
+ case IO_LOWALARM:
+ // set alarm when bits with 1 in arg go low
+ priv->lowalarm |= arg;
+ break;
+ case IO_CLRALARM:
+ // clear alarm for bits with 1 in arg
+ priv->highalarm &= ~arg;
+ priv->lowalarm &= ~arg;
+ break;
+ case IO_READDIR:
+ /* Read direction 0=input 1=output */
+ if (USE_PORTS(priv)) {
return *priv->dir_shadow;
- case IO_SETINPUT:
+ } else if (priv->minor == GPIO_MINOR_G) {
+ /* Note: Some bits are both in and out,
+ * Those that are dual is set hare as well.
+ */
+ return dir_g_shadow | dir_g_out_bits;
+ }
+ case IO_SETINPUT:
+ /* Set direction 0=unchanged 1=input,
+ * return mask with 1=input
+ */
+ if (USE_PORTS(priv)) {
save_flags(flags); cli();
- /* Set direction 0=unchanged 1=input */
*priv->dir = *priv->dir_shadow &=
~((unsigned char)arg & priv->changeable_dir);
restore_flags(flags);
- return *priv->dir_shadow;
- case IO_SETOUTPUT:
+ return ~(*priv->dir_shadow);
+ } else if (priv->minor == GPIO_MINOR_G) {
+ /* We must fiddle with R_GEN_CONFIG to change dir */
+ if (((arg & dir_g_in_bits) != arg) &&
+ (arg & changeable_dir_g)) {
+ arg &= changeable_dir_g;
+ /* Clear bits in genconfig to set to input */
+ if (arg & (1<<0)) {
+ genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG,g0dir);
+ dir_g_in_bits |= (1<<0);
+ dir_g_out_bits &= ~(1<<0);
+ }
+ if ((arg & 0x0000FF00) == 0x0000FF00) {
+ genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG,g8_15dir);
+ dir_g_in_bits |= 0x0000FF00;
+ dir_g_out_bits &= ~0x0000FF00;
+ }
+ if ((arg & 0x00FF0000) == 0x00FF0000) {
+ genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG,g16_23dir);
+ dir_g_in_bits |= 0x00FF0000;
+ dir_g_out_bits &= ~0x00FF0000;
+ }
+ if (arg & (1<<24)) {
+ genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG,g24dir);
+ dir_g_in_bits |= (1<<24);
+ dir_g_out_bits &= ~(1<<24);
+ }
+ printk("gpio: SETINPUT on port G set "
+ "genconfig to 0x%08lX "
+ "in_bits: 0x%08lX "
+ "out_bits: 0x%08lX\n",
+ (unsigned long)genconfig_shadow,
+ dir_g_in_bits, dir_g_out_bits);
+ *R_GEN_CONFIG = genconfig_shadow;
+ /* Must be a >120 ns delay before writing this again */
+
+ }
+ return dir_g_in_bits;
+ }
+ return 0;
+
+ case IO_SETOUTPUT:
+ /* Set direction 0=unchanged 1=output,
+ * return mask with 1=output
+ */
+ if (USE_PORTS(priv)) {
save_flags(flags); cli();
- /* Set direction 0=unchanged 1=output */
*priv->dir = *priv->dir_shadow |=
((unsigned char)arg & priv->changeable_dir);
restore_flags(flags);
return *priv->dir_shadow;
- case IO_SHUTDOWN:
- SOFT_SHUTDOWN();
- break;
- case IO_GET_PWR_BT:
+ } else if (priv->minor == GPIO_MINOR_G) {
+ /* We must fiddle with R_GEN_CONFIG to change dir */
+ if (((arg & dir_g_out_bits) != arg) &&
+ (arg & changeable_dir_g)) {
+ /* Set bits in genconfig to set to output */
+ if (arg & (1<<0)) {
+ genconfig_shadow |= IO_MASK(R_GEN_CONFIG,g0dir);
+ dir_g_out_bits |= (1<<0);
+ dir_g_in_bits &= ~(1<<0);
+ }
+ if ((arg & 0x0000FF00) == 0x0000FF00) {
+ genconfig_shadow |= IO_MASK(R_GEN_CONFIG,g8_15dir);
+ dir_g_out_bits |= 0x0000FF00;
+ dir_g_in_bits &= ~0x0000FF00;
+ }
+ if ((arg & 0x00FF0000) == 0x00FF0000) {
+ genconfig_shadow |= IO_MASK(R_GEN_CONFIG,g16_23dir);
+ dir_g_out_bits |= 0x00FF0000;
+ dir_g_in_bits &= ~0x00FF0000;
+ }
+ if (arg & (1<<24)) {
+ genconfig_shadow |= IO_MASK(R_GEN_CONFIG,g24dir);
+ dir_g_out_bits |= (1<<24);
+ dir_g_in_bits &= ~(1<<24);
+ }
+ printk("gpio: SETOUTPUT on port G set "
+ "genconfig to 0x%08lX "
+ "in_bits: 0x%08lX "
+ "out_bits: 0x%08lX\n",
+ (unsigned long)genconfig_shadow,
+ dir_g_in_bits, dir_g_out_bits);
+ *R_GEN_CONFIG = genconfig_shadow;
+ /* Must be a >120 ns delay before writing this again */
+ }
+ return dir_g_out_bits;
+ }
+ return 0;
+ case IO_SHUTDOWN:
+ SOFT_SHUTDOWN();
+ break;
+ case IO_GET_PWR_BT:
#if defined (CONFIG_ETRAX_SOFT_SHUTDOWN)
- return (*R_PORT_G_DATA &
- ( 1 << CONFIG_ETRAX_POWERBUTTON_BIT));
+ return (*R_PORT_G_DATA & ( 1 << CONFIG_ETRAX_POWERBUTTON_BIT));
#else
- return 0;
+ return 0;
#endif
- break;
- case IO_CFG_WRITE_MODE:
- priv->clk_mask = arg & 0xFF;
- priv->data_mask = (arg >> 8) & 0xFF;
- priv->write_msb = (arg >> 16) & 0x01;
- /* Check if we're allowed to change the bits and
- * the direction is correct
- */
- if (!((priv->clk_mask & priv->changeable_bits) &&
- (priv->data_mask & priv->changeable_bits) &&
- (priv->clk_mask & *priv->dir_shadow) &&
- (priv->data_mask & *priv->dir_shadow)))
- {
- priv->clk_mask = 0;
- priv->data_mask = 0;
- return -EPERM;
- }
- break;
- default:
- if (priv->minor == LEDS)
- return gpio_leds_ioctl(cmd, arg);
- else
- return -EINVAL;
- }
+ break;
+ case IO_CFG_WRITE_MODE:
+ priv->clk_mask = arg & 0xFF;
+ priv->data_mask = (arg >> 8) & 0xFF;
+ priv->write_msb = (arg >> 16) & 0x01;
+ /* Check if we're allowed to change the bits and
+ * the direction is correct
+ */
+ if (!((priv->clk_mask & priv->changeable_bits) &&
+ (priv->data_mask & priv->changeable_bits) &&
+ (priv->clk_mask & *priv->dir_shadow) &&
+ (priv->data_mask & *priv->dir_shadow)))
+ {
+ priv->clk_mask = 0;
+ priv->data_mask = 0;
+ return -EPERM;
+ }
+ break;
+ default:
+ if (priv->minor == GPIO_MINOR_LEDS)
+ return gpio_leds_ioctl(cmd, arg);
+ else
+ return -EINVAL;
+ } /* switch */
return 0;
}
@@ -381,24 +538,24 @@
unsigned char red;
switch (_IOC_NR(cmd)) {
- case IO_LEDACTIVE_SET:
- green = ((unsigned char) arg) & 1;
- red = (((unsigned char) arg) >> 1) & 1;
- LED_ACTIVE_SET_G(green);
- LED_ACTIVE_SET_R(red);
- break;
-
- case IO_LED_SETBIT:
- LED_BIT_SET(arg);
- break;
-
- case IO_LED_CLRBIT:
- LED_BIT_CLR(arg);
- break;
+ case IO_LEDACTIVE_SET:
+ green = ((unsigned char) arg) & 1;
+ red = (((unsigned char) arg) >> 1) & 1;
+ LED_ACTIVE_SET_G(green);
+ LED_ACTIVE_SET_R(red);
+ break;
+
+ case IO_LED_SETBIT:
+ LED_BIT_SET(arg);
+ break;
+
+ case IO_LED_CLRBIT:
+ LED_BIT_CLR(arg);
+ break;
- default:
- return -EINVAL;
- }
+ default:
+ return -EINVAL;
+ } /* switch */
return 0;
}
@@ -412,6 +569,92 @@
release: gpio_release,
};
+
+static void __init gpio_init_port_g(void)
+{
+#define GROUPA (0x0000FF3F)
+#define GROUPB (1<<6 | 1<<7)
+#define GROUPC (1<<30 | 1<<31)
+#define GROUPD (0x3FFF0000)
+#define GROUPD_LOW (0x00FF0000)
+ unsigned long used_in_bits = 0;
+ unsigned long used_out_bits = 0;
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, scsi0, select)){
+ used_in_bits |= GROUPA | GROUPB | 0 | 0;
+ used_out_bits |= GROUPA | GROUPB | 0 | 0;
+ }
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, ata, select)) {
+ used_in_bits |= GROUPA | GROUPB | GROUPC | (GROUPD & ~(1<<25|1<<26));
+ used_out_bits |= GROUPA | GROUPB | GROUPC | GROUPD;
+ }
+
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, par0, select)) {
+ used_in_bits |= (GROUPA & ~(1<<0)) | 0 | 0 | 0;
+ used_out_bits |= (GROUPA & ~(1<<0)) | 0 | 0 | 0;
+ }
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, ser2, select)) {
+ used_in_bits |= 0 | GROUPB | 0 | 0;
+ used_out_bits |= 0 | GROUPB | 0 | 0;
+ }
+ /* mio same as shared RAM ? */
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, mio, select)) {
+ used_in_bits |= (GROUPA & ~(1<<0)) | 0 |0 |GROUPD_LOW;
+ used_out_bits |= (GROUPA & ~(1<<0|1<<1|1<<2)) | 0 |0 |GROUPD_LOW;
+ }
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, scsi1, select)) {
+ used_in_bits |= 0 | 0 | GROUPC | GROUPD;
+ used_out_bits |= 0 | 0 | GROUPC | GROUPD;
+ }
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, scsi0w, select)) {
+ used_in_bits |= GROUPA | GROUPB | 0 | (GROUPD_LOW | 1<<24);
+ used_out_bits |= GROUPA | GROUPB | 0 | (GROUPD_LOW | 1<<24 | 1<<25|1<<26);
+ }
+
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, par1, select)) {
+ used_in_bits |= 0 | 0 | 0 | (GROUPD & ~(1<<24));
+ used_out_bits |= 0 | 0 | 0 | (GROUPD & ~(1<<24));
+ }
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, ser3, select)) {
+ used_in_bits |= 0 | 0 | GROUPC | 0;
+ used_out_bits |= 0 | 0 | GROUPC | 0;
+ }
+ /* mio same as shared RAM-W? */
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, mio_w, select)) {
+ used_in_bits |= (GROUPA & ~(1<<0)) | 0 | 0 |GROUPD_LOW;
+ used_out_bits |= (GROUPA & ~(1<<0|1<<1|1<<2)) | 0 | 0 |GROUPD_LOW;
+ }
+ /* TODO: USB p2, parw, sync ser3? */
+
+ /* Initialise the dir_g_shadow etc. depending on genconfig */
+ /* 0=input 1=output */
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, g0dir, out))
+ dir_g_shadow |= (1 << 0);
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, g8_15dir, out))
+ dir_g_shadow |= 0x0000FF00;
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, g16_23dir, out))
+ dir_g_shadow |= 0x00FF0000;
+ if (genconfig_shadow & IO_STATE(R_GEN_CONFIG, g24dir, out))
+ dir_g_shadow |= (1 << 24);
+
+ dir_g_in_bits = ~used_in_bits;
+ dir_g_out_bits = ~used_out_bits;
+
+ changeable_dir_g = 0x01FFFF01; /* all that can change dir */
+ changeable_dir_g &= dir_g_out_bits;
+ changeable_dir_g &= dir_g_in_bits;
+ /* Correct the bits that can change direction */
+ dir_g_out_bits &= ~changeable_dir_g;
+ dir_g_out_bits |= dir_g_shadow;
+ dir_g_in_bits &= ~changeable_dir_g;
+ dir_g_in_bits |= (~dir_g_shadow & changeable_dir_g);
+
+
+ printk("GPIO port G: in_bits: 0x%08lX out_bits: 0x%08lX val: %08lX\n",
+ dir_g_in_bits, dir_g_out_bits, (unsigned long)*R_PORT_G_DATA);
+ printk("GPIO port G: dir: %08lX changeable: %08lX\n",
+ dir_g_shadow, changeable_dir_g);
+}
+
/* main driver initialization routine, called from mem.c */
static __init int
@@ -431,13 +674,13 @@
return res;
}
- /* Clear all leds */
+ /* Clear all leds */
#if defined (CONFIG_ETRAX_CSP0_LEDS) || defined (CONFIG_ETRAX_PA_LEDS) || defined (CONFIG_ETRAX_PB_LEDS)
init_ioremap();
LED_NETWORK_SET(0);
LED_ACTIVE_SET(0);
LED_DISK_READ(0);
- LED_DISK_WRITE(0);
+ LED_DISK_WRITE(0);
#if defined (CONFIG_ETRAX_CSP0_LEDS)
for (i = 0; i < 32; i++) {
@@ -446,8 +689,8 @@
#endif
#endif
-
- printk("ETRAX 100LX GPIO driver v2.2, (c) 2001 Axis Communications AB\n");
+ gpio_init_port_g();
+ printk("ETRAX 100LX GPIO driver v2.3, (c) 2001, 2002 Axis Communications AB\n");
return res;
}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)