patch-2.0.34 linux/drivers/char/h8.c

Next file: linux/drivers/char/h8.h
Previous file: linux/drivers/char/console.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.0.33/linux/drivers/char/h8.c linux/drivers/char/h8.c
@@ -0,0 +1,1272 @@
+/*
+ */
+/*
+ * Hitachi H8/337 Microcontroller driver
+ *
+ * The H8 is used to deal with the power and thermal environment
+ * of a system.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <asm/system.h>
+#include <asm/segment.h>
+#include <asm/io.h>
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+#include <linux/timer.h>
+#include <linux/fcntl.h>
+#include <linux/malloc.h>
+#include <linux/linkage.h>
+#ifdef CONFIG_PROC_FS
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+#endif
+#include <linux/miscdevice.h>
+#include <linux/lists.h>
+#include <linux/ioport.h>
+
+#define __KERNEL_SYSCALLS__
+#include <asm/unistd.h>
+
+#include "h8.h"
+
+#define DEBUG_H8
+
+#ifdef DEBUG_H8
+#define Dprintk		printk
+#else
+#define Dprintk
+#endif
+
+#define XDprintk if(h8_debug==-1)printk
+
+/*
+ * The h8 device is one of the misc char devices.
+ */
+#define H8_MINOR_DEV   140
+
+/*
+ * Forward declarations.
+ */
+int          h8_init(void);
+int          h8_display_blank(void);
+int          h8_display_unblank(void);
+
+static int   h8_open(struct inode *, struct file *);
+static void  h8_release(struct inode *, struct file *);
+static long  h8_read(struct inode *, struct file *, char *, u_long);
+static int   h8_select(struct inode *, struct file *, int, select_table *);
+static int   h8_ioctl(struct inode *, struct file *, u_int, u_long);
+
+static void  h8_intr(int irq, void *dev_id, struct pt_regs *regs);
+
+#ifdef CONFIG_PROC_FS
+static int   h8_get_info(char *, char **, off_t, int, int);
+#endif
+
+/*
+ * Support Routines.
+ */
+static void h8_hw_init(void);
+static void h8_start_new_cmd(void);
+static void h8_send_next_cmd_byte(void);
+static void h8_read_event_status(void);
+static void h8_sync(void);
+static void h8_q_cmd(u_char *, int, int);
+static void h8_cmd_done(h8_cmd_q_t *qp);
+static int  h8_alloc_queues(void);
+
+static u_long h8_get_cpu_speed(void);
+static int h8_get_curr_temp(u_char curr_temp[]);
+static void h8_get_max_temp(void);
+static void h8_get_upper_therm_thold(void);
+static void h8_set_upper_therm_thold(int);
+static int h8_get_ext_status(u_char stat_word[]);
+
+static int h8_monitor_thread(void *);
+
+static int h8_manage_therm(void);
+static void h8_set_cpu_speed(int speed_divisor);
+
+static void h8_start_monitor_timer(unsigned long secs);
+static void h8_activate_monitor(unsigned long unused);
+
+/* in arch/alpha/kernel/lca.c */
+extern void lca_clock_print(void);
+extern int  lca_get_clock(void);
+extern void lca_clock_fiddle(int);
+
+static void h8_set_event_mask(int);
+static void h8_clear_event_mask(int);
+
+/*
+ * Driver structures
+ */
+
+static struct timer_list h8_monitor_timer;
+static int h8_monitor_timer_active = 0;
+
+static char  driver_version[] = "X0.0";/* no spaces */
+
+static struct file_operations h8_fops = {
+        NULL,           /* lseek */
+        h8_read,
+        NULL,           /* write */
+        NULL,           /* readdir */
+        h8_select,
+        h8_ioctl,
+        NULL,           /* mmap */
+        h8_open,
+        h8_release,
+        NULL,           /* fsync */
+        NULL            /* fasync */
+};
+
+static struct miscdevice h8_device = {
+        H8_MINOR_DEV,
+        "h8",
+        &h8_fops
+};
+
+#ifdef CONFIG_PROC_FS
+static struct proc_dir_entry    h8_proc_entry = {
+        0, 3, "h8", S_IFREG | S_IRUGO, 1, 0, 0, 0, 0, h8_get_info
+};
+#endif
+
+union	intr_buf intrbuf;
+int	intr_buf_ptr;
+union   intr_buf xx;	
+u_char  last_temp;
+
+/*
+ * I/O Macros for register reads and writes.
+ */
+#define H8_READ(a) 	inb((a))
+#define H8_WRITE(d,a)	outb((d),(a))
+
+#define	H8_GET_STATUS	H8_READ((h8_base) + H8_STATUS_REG_OFF)
+#define H8_READ_DATA	H8_READ((h8_base) + H8_DATA_REG_OFF)
+#define WRITE_DATA(d)	H8_WRITE((d), h8_base + H8_DATA_REG_OFF)
+#define WRITE_CMD(d)	H8_WRITE((d), h8_base + H8_CMD_REG_OFF)
+
+unsigned int h8_base = H8_BASE_ADDR;
+unsigned int h8_irq = H8_IRQ;
+unsigned int h8_state = H8_IDLE;
+unsigned int h8_index = -1;
+unsigned int h8_enabled = 0;
+
+queue_head_t h8_actq, h8_cmdq, h8_freeq;
+
+/* 
+ * Globals used in thermal control of Alphabook1.
+ */
+int cpu_speed_divisor = -1;			
+int h8_event_mask = 0;			
+struct wait_queue *h8_monitor_wait = NULL;
+unsigned int h8_command_mask = 0;
+int h8_uthermal_threshold = DEFAULT_UTHERMAL_THRESHOLD;
+int h8_uthermal_window = UTH_HYSTERESIS;		      
+int h8_debug = 0xfffffdfc;
+int h8_ldamp = MHZ_115;
+int h8_udamp = MHZ_57;
+u_char h8_current_temp = 0;
+u_char h8_system_temp = 0;
+int h8_sync_channel = 0;
+struct wait_queue *h8_sync_wait = NULL;
+int h8_init_performed;
+
+/* CPU speeds and clock divisor values */
+int speed_tab[6] = {230, 153, 115, 57, 28, 14};
+  
+/*
+ * H8 interrupt handler
+ */
+static void h8_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	u_char	stat_reg, data_reg;
+	h8_cmd_q_t *qp = (h8_cmd_q_t *)QUEUE_FIRST(&h8_actq, link);
+
+	stat_reg = H8_GET_STATUS;
+	data_reg = H8_READ_DATA;
+
+	XDprintk("h8_intr: state %d status 0x%x data 0x%x\n", h8_state, stat_reg, data_reg);
+
+	switch (h8_state) {
+	  /* Response to an asynchronous event. */
+	case H8_IDLE: { /* H8_IDLE */
+	    if (stat_reg & H8_OFULL) {
+	        if (data_reg == H8_INTR) {
+		    h8_state = H8_INTR_MODE;
+		    /* Executing a command to determine what happened. */
+		    WRITE_CMD(H8_RD_EVENT_STATUS);
+		    intr_buf_ptr = 1;
+		    WRITE_CMD(H8_RD_EVENT_STATUS);
+		} else {
+		    Dprintk("h8_intr: idle stat 0x%x data 0x%x\n",
+			    stat_reg, data_reg);
+		}
+	    } else {
+	        Dprintk("h8_intr: bogus interrupt\n");
+	    }
+	    break;
+	}
+	case H8_INTR_MODE: { /* H8_INTR_MODE */
+	    XDprintk("H8 intr/intr_mode\n");
+	    if (data_reg == H8_BYTE_LEVEL_ACK) {
+	        return;
+	    } else if (data_reg == H8_CMD_ACK) {
+	        return;
+	    } else {
+	        intrbuf.byte[intr_buf_ptr] = data_reg;
+		if(!intr_buf_ptr) {
+		    h8_state = H8_IDLE;
+		    h8_read_event_status();
+		}
+		intr_buf_ptr--;
+	    }
+	    break;
+	}
+	/* Placed in this state by h8_start_new_cmd(). */
+	case H8_XMIT: { /* H8_XMIT */
+	    XDprintk("H8 intr/xmit\n");
+	    /* If a byte level acknowledgement has been received */
+	    if (data_reg == H8_BYTE_LEVEL_ACK) {
+	        XDprintk("H8 intr/xmit BYTE ACK\n");
+		qp->nacks++;
+		if (qp->nacks > qp->ncmd)
+		    if(h8_debug & 0x1)
+		        Dprintk("h8intr: bogus # of acks!\n");
+		/* 
+		 * If the number of bytes sent is less than the total 
+		 * number of bytes in the command.
+		 */ 
+		if (qp->cnt < qp->ncmd) {
+		    h8_send_next_cmd_byte();
+		}
+		return;
+		/* If the complete command has produced an acknowledgement. */
+	    } else if (data_reg == H8_CMD_ACK) {
+	        XDprintk("H8 intr/xmit CMD ACK\n");
+		/* If there are response bytes */
+		if (qp->nrsp)
+		    h8_state = H8_RCV;
+		else
+		    h8_state = H8_IDLE;
+		qp->cnt = 0;
+		return;
+		/* Error, need to start over with a clean slate. */
+	    } else if (data_reg == H8_NACK) {
+	        XDprintk("h8_intr: NACK received restarting command\n");
+		qp->nacks = 0;
+		qp->cnt = 0;
+		h8_state = H8_IDLE;
+		WRITE_CMD(H8_SYNC);
+		return;
+	    } else {
+	        Dprintk ("h8intr: xmit unknown data 0x%x \n", data_reg);
+		return;
+	    }
+	    break;
+	}
+	case H8_RESYNC: { /* H8_RESYNC */
+	    XDprintk("H8 intr/resync\n");
+	    if (data_reg == H8_BYTE_LEVEL_ACK) {
+	        return;
+	    } else if (data_reg == H8_SYNC_BYTE) {
+	        h8_state = H8_IDLE;
+		if (!QUEUE_EMPTY(&h8_actq, link))
+		    h8_send_next_cmd_byte();
+	    } else {
+	        Dprintk ("h8_intr: resync unknown data 0x%x \n", data_reg);
+		return;
+	    }
+	    break;
+	} 
+	case H8_RCV: { /* H8_RCV */
+	    XDprintk("H8 intr/rcv\n");
+	    if (qp->cnt < qp->nrsp) {
+	        qp->rcvbuf[qp->cnt] = data_reg;
+		qp->cnt++;
+		/* If command reception finished. */
+		if (qp->cnt == qp->nrsp) {
+		    h8_state = H8_IDLE;
+		    QUEUE_REMOVE(&h8_actq, qp, link);
+		    h8_cmd_done (qp);
+		    /* More commands to send over? */
+		    if (!QUEUE_EMPTY(&h8_cmdq, link))
+		        h8_start_new_cmd();
+		}
+		return;
+	    } else {
+	        Dprintk ("h8intr: rcv overflow cmd 0x%x\n", qp->cmdbuf[0]);
+	    }
+	    break;
+	}
+	default: /* default */
+	    Dprintk("H8 intr/unknown\n");
+	    break;
+	}
+	return;
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+        printk("H8 module at %X(Interrupt %d)\n", h8_base, h8_irq);
+        if(request_irq(h8_irq, h8_intr, SA_INTERRUPT, "h8", NULL))
+        {
+                printk("H8: error: IRQ %d is not free.\n", h8_irq);
+                return -EIO;
+        }
+
+        misc_register(&h8_device);
+        request_region(h8_base, 8, "h8");
+
+#ifdef CONFIG_PROC_FS
+        proc_register_dynamic(&proc_root, &h8_proc_entry);
+#endif
+
+	QUEUE_INIT(&h8_actq, link, h8_cmd_q_t *);
+	QUEUE_INIT(&h8_cmdq, link, h8_cmd_q_t *);
+	QUEUE_INIT(&h8_freeq, link, h8_cmd_q_t *);
+	h8_alloc_queues();
+
+	h8_hw_init();
+
+	kernel_thread(h8_monitor_thread, NULL, 0);
+
+        return 0;
+}
+
+void cleanup_module(void)
+{
+        misc_deregister(&h8_device);
+        release_region(h8_base, 8);
+        free_irq(h8_irq, NULL);
+}
+
+#else /* MODULE */
+
+int h8_init(void)
+{
+        if(request_irq(h8_irq, h8_intr, SA_INTERRUPT, "h8", NULL))
+        {
+                printk("H8: error: IRQ %d is not free\n", h8_irq);
+                return -EIO;
+        }
+        printk("H8 at 0x%x IRQ %d\n", h8_base, h8_irq);
+
+#ifdef CONFIG_PROC_FS
+        proc_register_dynamic(&proc_root, &h8_proc_entry);
+#endif
+
+        misc_register(&h8_device);
+        request_region(h8_base, 8, "h8");
+
+	QUEUE_INIT(&h8_actq, link, h8_cmd_q_t *);
+	QUEUE_INIT(&h8_cmdq, link, h8_cmd_q_t *);
+	QUEUE_INIT(&h8_freeq, link, h8_cmd_q_t *);
+	h8_alloc_queues();
+
+	h8_hw_init();
+
+	kernel_thread(h8_monitor_thread, NULL, 0);
+
+        return 0;
+}
+#endif /* MODULE */
+
+void h8_hw_init(void)
+{
+	u_char	buf[H8_MAX_CMD_SIZE];
+
+	/* set CPU speed to max for booting */
+	h8_set_cpu_speed(MHZ_230);
+
+	/*
+	 * Initialize the H8
+	 */
+	h8_sync();  /* activate interrupts */
+
+	/* To clear conditions left by console */
+	h8_read_event_status(); 
+
+	/* Perform a conditioning read */
+	buf[0] = H8_DEVICE_CONTROL;
+	buf[1] = 0xff;
+	buf[2] = 0x0;
+	h8_q_cmd(buf, 3, 1);
+
+	/* Turn on built-in and external mice, capture power switch */
+	buf[0] = H8_DEVICE_CONTROL;
+	buf[1] = 0x0;
+	buf[2] = H8_ENAB_INT_PTR | H8_ENAB_EXT_PTR |
+	       /*H8_DISAB_PWR_OFF_SW |*/ H8_ENAB_LOW_SPD_IND;
+	h8_q_cmd(buf, 3, 1);
+
+        h8_enabled = 1;
+	return;
+}
+
+#ifdef CONFIG_PROC_FS
+int h8_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
+{
+        char *p;
+
+        if (!h8_enabled)
+                return 0;
+        p = buf;
+
+
+        /*
+           0) Linux driver version (this will change if format changes)
+           1) 
+           2) 
+           3)
+           4)
+	*/
+            
+        p += sprintf(p, "%s \n",
+                     driver_version
+		     );
+
+        return p - buf;
+}
+#endif
+
+static long h8_read(struct inode *inode, struct file *fp, char *buf,
+		    u_long count)
+{
+	printk("h8_read: IMPDEL\n");
+	return 0;
+}
+
+static int h8_select(struct inode *inode, struct file *fp, int sel_type,
+                     select_table * wait)
+{
+	printk("h8_select: IMPDEL\n");
+	return 0;
+}
+
+static int h8_ioctl(struct inode * inode, struct file *filp,
+                    u_int cmd, u_long arg)
+{
+	printk("h8_ioctl: IMPDEL\n");
+	return 0;
+}
+
+static void h8_release(struct inode * inode, struct file * filp)
+{
+	printk("h8_release: IMPDEL\n");
+}
+
+static int h8_open(struct inode * inode, struct file * filp)
+{
+	printk("h8_open: IMPDEL\n");
+	return 0;
+}
+
+/* Called from console driver -- must make sure h8_enabled. */
+int h8_display_blank(void)
+{
+#ifdef CONFIG_H8_DISPLAY_BLANK
+        int     error;
+
+        if (!h8_enabled)
+                return 0;
+        error = h8_set_display_power_state(H8_STATE_STANDBY);
+        if (error == H8_SUCCESS)
+                return 1;
+        h8_error("set display standby", error);
+#endif
+        return 0;
+}
+
+/* Called from console driver -- must make sure h8_enabled. */
+int h8_display_unblank(void)
+{
+#ifdef CONFIG_H8_DISPLAY_BLANK
+        int error;
+
+        if (!h8_enabled)
+                return 0;
+        error = h8_set_display_power_state(H8_STATE_READY);
+        if (error == H8_SUCCESS)
+                return 1;
+        h8_error("set display ready", error);
+#endif
+        return 0;
+}
+
+int
+h8_alloc_queues(void)
+{
+        h8_cmd_q_t *qp;
+	unsigned long flags;
+        int i;
+
+        qp = (h8_cmd_q_t *)kmalloc((sizeof (h8_cmd_q_t) * H8_Q_ALLOC_AMOUNT),
+				   GFP_KERNEL);
+
+        if (!qp) {
+                printk("H8: could not allocate memory for command queue\n");
+                return(0);
+        }
+        /* add to the free queue */
+        save_flags(flags); cli();
+        for (i = 0; i < H8_Q_ALLOC_AMOUNT; i++) {
+                /* place each at front of freeq */
+                QUEUE_ENTER(&h8_freeq, &qp[i], link, h8_cmd_q_t *);
+        }
+        restore_flags(flags);
+        return (1);
+}
+
+/* 
+ * Basic means by which commands are sent to the H8.
+ */
+void
+h8_q_cmd(u_char *cmd, int cmd_size, int resp_size)
+{
+        h8_cmd_q_t      *qp;
+	unsigned long flags;
+        int             i;
+
+        /* get cmd buf */
+	save_flags(flags); cli();
+        while (QUEUE_EMPTY(&h8_freeq, link)) {
+                Dprintk("H8: need to allocate more cmd buffers\n");
+                restore_flags(flags);
+                h8_alloc_queues();
+                save_flags(flags); cli();
+        }
+        /* get first element from queue */
+        qp = (h8_cmd_q_t *)QUEUE_FIRST(&h8_freeq, link);
+        QUEUE_REMOVE(&h8_freeq, qp, link);
+
+        restore_flags(flags);
+
+        /* fill it in */
+        for (i = 0; i < cmd_size; i++)
+            qp->cmdbuf[i] = cmd[i];
+        qp->ncmd = cmd_size;
+        qp->nrsp = resp_size;
+
+        /* queue it at the end of the cmd queue */
+        save_flags(flags); cli();
+
+        QUEUE_ENTER(&h8_cmdq, qp, link, h8_cmd_q_t *);
+
+        restore_flags(flags);
+
+        h8_start_new_cmd();
+}
+
+void
+h8_start_new_cmd(void)
+{
+        unsigned long flags;
+        h8_cmd_q_t *qp;
+
+	save_flags(flags); cli();
+        if (h8_state != H8_IDLE) {
+                if (h8_debug & 0x1)
+                        Dprintk("h8_start_new_cmd: not idle\n");
+                restore_flags(flags);
+                return;
+        }
+
+        if (!QUEUE_EMPTY(&h8_actq, link)) {
+                Dprintk("h8_start_new_cmd: inconsistency: IDLE with non-empty active queue!\n");
+                restore_flags(flags);
+                return;
+        }
+
+        if (QUEUE_EMPTY(&h8_cmdq, link)) {
+                Dprintk("h8_start_new_cmd: no command to dequeue\n");
+                restore_flags(flags);
+                return;
+        }
+        /*
+         * Take first command off of the command queue and put
+         * it on the active queue.
+         */
+        qp = (h8_cmd_q_t *) QUEUE_FIRST(&h8_cmdq, link);
+        QUEUE_REMOVE(&h8_cmdq, qp, link);
+        QUEUE_ENTER(&h8_actq, qp, link, h8_cmd_q_t *);
+        h8_state = H8_XMIT;
+        if (h8_debug & 0x1)
+                Dprintk("h8_start_new_cmd: Starting a command\n");
+
+        qp->cnt = 1;
+        WRITE_CMD(qp->cmdbuf[0]);               /* Kick it off */
+
+        restore_flags(flags);
+        return;
+}
+
+void
+h8_send_next_cmd_byte(void)
+{
+        h8_cmd_q_t      *qp = (h8_cmd_q_t *)QUEUE_FIRST(&h8_actq, link);
+        int cnt;
+
+        cnt = qp->cnt;
+        qp->cnt++;
+
+        if (h8_debug & 0x1)
+                Dprintk("h8 sending next cmd byte 0x%x (0x%x)\n",
+			cnt, qp->cmdbuf[cnt]);
+
+        if (cnt) {
+                WRITE_DATA(qp->cmdbuf[cnt]);
+        } else {
+                WRITE_CMD(qp->cmdbuf[cnt]);
+        }
+        return;
+}
+
+/*
+ * Synchronize H8 communications channel for command transmission.
+ */
+void
+h8_sync(void)
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+
+        buf[0] = H8_SYNC;
+        buf[1] = H8_SYNC_BYTE;
+        h8_q_cmd(buf, 2, 1);
+}
+
+/*
+ * Responds to external interrupt. Reads event status word and 
+ * decodes type of interrupt. 
+ */
+void
+h8_read_event_status(void)
+{
+
+        if(h8_debug & 0x200)
+                printk("h8_read_event_status: value 0x%x\n", intrbuf.word);
+
+        /*
+         * Power related items
+         */
+        if (intrbuf.word & H8_DC_CHANGE) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: DC_CHANGE\n");
+                /* see if dc added or removed, set batt/dc flag, send event */
+
+                h8_set_event_mask(H8_MANAGE_BATTERY);
+                wake_up(&h8_monitor_wait);
+        }
+
+        if (intrbuf.word & H8_POWER_BUTTON) {
+                printk("Power switch pressed - please wait - preparing to power 
+off\n");
+                h8_set_event_mask(H8_POWER_BUTTON);
+                wake_up(&h8_monitor_wait);
+        }
+
+        /*
+         * Thermal related items
+         */
+        if (intrbuf.word & H8_THERMAL_THRESHOLD) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: THERMAL_THRESHOLD\n");
+                h8_set_event_mask(H8_MANAGE_UTHERM);
+                wake_up(&h8_monitor_wait);
+        }
+
+        /*
+         * nops -for now
+         */
+        if (intrbuf.word & H8_DOCKING_STATION_STATUS) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: DOCKING_STATION_STATUS\n");
+                /* read_ext_status */
+        }
+        if (intrbuf.word & H8_EXT_BATT_STATUS) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: EXT_BATT_STATUS\n");
+
+        }
+        if (intrbuf.word & H8_EXT_BATT_CHARGE_STATE) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: EXT_BATT_CHARGE_STATE\n");
+
+        }
+        if (intrbuf.word & H8_BATT_CHANGE_OVER) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: BATT_CHANGE_OVER\n");
+
+        }
+        if (intrbuf.word & H8_WATCHDOG) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: WATCHDOG\n");
+                /* nop */
+        }
+        if (intrbuf.word & H8_SHUTDOWN) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: SHUTDOWN\n");
+                /* nop */
+        }
+        if (intrbuf.word & H8_KEYBOARD) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: KEYBOARD\n");
+                /* nop */
+        }
+        if (intrbuf.word & H8_EXT_MOUSE_OR_CASE_SWITCH) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: EXT_MOUSE_OR_CASE_SWITCH\n");
+                /* read_ext_status*/
+        }
+        if (intrbuf.word & H8_INT_BATT_LOW) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: INT_BATT_LOW\n");
+                /* post event, warn user */
+        }
+        if (intrbuf.word & H8_INT_BATT_CHARGE_STATE) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: INT_BATT_CHARGE_STATE\n");
+                /* nop - happens often */
+        }
+        if (intrbuf.word & H8_INT_BATT_STATUS) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: INT_BATT_STATUS\n");
+
+        }
+        if (intrbuf.word & H8_INT_BATT_CHARGE_THRESHOLD) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: INT_BATT_CHARGE_THRESHOLD\n");
+                /* nop - happens often */
+        }
+        if (intrbuf.word & H8_EXT_BATT_LOW) {
+		if(h8_debug & 0x4)
+		    printk("h8_read_event_status: EXT_BATT_LOW\n");
+                /*if no internal, post event, warn user */
+                /* else nop */
+        }
+
+        return;
+}
+
+/*
+ * Function called when H8 has performed requested command.
+ */
+void
+h8_cmd_done(h8_cmd_q_t *qp)
+{
+
+        /* what to do */
+        switch (qp->cmdbuf[0]) {
+	case H8_SYNC:
+	    if (h8_debug & 0x40000) 
+	        printk("H8: Sync command done - byte returned was 0x%x\n", 
+		       qp->rcvbuf[0]);
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_RD_SN:
+	case H8_RD_ENET_ADDR:
+	    printk("H8: Read ethernet addr - command done - address: %x - %x - %x - %x - %x - %x \n", 
+		   qp->rcvbuf[0], qp->rcvbuf[1], qp->rcvbuf[2],
+		   qp->rcvbuf[3], qp->rcvbuf[4], qp->rcvbuf[5]);
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_RD_HW_VER:
+	case H8_RD_MIC_VER:
+	case H8_RD_MAX_TEMP:
+	    printk("H8: Max recorded CPU temp %d, Sys temp %d\n",
+		   qp->rcvbuf[0], qp->rcvbuf[1]);
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_RD_MIN_TEMP:
+	    printk("H8: Min recorded CPU temp %d, Sys temp %d\n",
+		   qp->rcvbuf[0], qp->rcvbuf[1]);
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_RD_CURR_TEMP:
+	    h8_sync_channel |= H8_RD_CURR_TEMP;
+	    xx.byte[0] = qp->rcvbuf[0];
+	    xx.byte[1] = qp->rcvbuf[1];
+	    wake_up(&h8_sync_wait); 
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *); 
+	    break;
+
+	case H8_RD_SYS_VARIENT:
+	case H8_RD_PWR_ON_CYCLES:
+	    printk(" H8: RD_PWR_ON_CYCLES command done\n");
+	    break;
+
+	case H8_RD_PWR_ON_SECS:
+	    printk("H8: RD_PWR_ON_SECS command done\n");
+	    break;
+
+	case H8_RD_RESET_STATUS:
+	case H8_RD_PWR_DN_STATUS:
+	case H8_RD_EVENT_STATUS:
+	case H8_RD_ROM_CKSM:
+	case H8_RD_EXT_STATUS:
+	    xx.byte[1] = qp->rcvbuf[0];
+	    xx.byte[0] = qp->rcvbuf[1];
+	    h8_sync_channel |= H8_GET_EXT_STATUS;
+	    wake_up(&h8_sync_wait); 
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *); 
+	    break;
+
+	case H8_RD_USER_CFG:
+	case H8_RD_INT_BATT_VOLT:
+	case H8_RD_DC_INPUT_VOLT:
+	case H8_RD_HORIZ_PTR_VOLT:
+	case H8_RD_VERT_PTR_VOLT:
+	case H8_RD_EEPROM_STATUS:
+	case H8_RD_ERR_STATUS:
+	case H8_RD_NEW_BUSY_SPEED:
+	case H8_RD_CONFIG_INTERFACE:
+	case H8_RD_INT_BATT_STATUS:
+	    printk("H8: Read int batt status cmd done - returned was %x %x %x\n",
+		   qp->rcvbuf[0], qp->rcvbuf[1], qp->rcvbuf[2]);
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_RD_EXT_BATT_STATUS:
+	case H8_RD_PWR_UP_STATUS:
+	case H8_RD_EVENT_STATUS_MASK:
+	case H8_CTL_EMU_BITPORT:
+	case H8_DEVICE_CONTROL:
+	    if(h8_debug & 0x20000) {
+	        printk("H8: Device control cmd done - byte returned was 0x%x\n",
+		       qp->rcvbuf[0]);
+	    }
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_CTL_TFT_BRT_DC:
+	case H8_CTL_WATCHDOG:
+	case H8_CTL_MIC_PROT:
+	case H8_CTL_INT_BATT_CHG:
+	case H8_CTL_EXT_BATT_CHG:
+	case H8_CTL_MARK_SPACE:
+	case H8_CTL_MOUSE_SENSITIVITY:
+	case H8_CTL_DIAG_MODE:
+	case H8_CTL_IDLE_AND_BUSY_SPDS:
+	    printk("H8: Idle and busy speed command done\n");
+	    break;
+
+	case H8_CTL_TFT_BRT_BATT:
+	case H8_CTL_UPPER_TEMP:
+	    if(h8_debug & 0x10) {
+	        XDprintk("H8: ctl upper thermal thresh cmd done - returned was %d\n",
+		       qp->rcvbuf[0]);
+	    }
+	    QUEUE_ENTER(&h8_freeq, qp, link, h8_cmd_q_t *);
+	    break;
+
+	case H8_CTL_LOWER_TEMP:
+	case H8_CTL_TEMP_CUTOUT:
+	case H8_CTL_WAKEUP:
+	case H8_CTL_CHG_THRESHOLD:
+	case H8_CTL_TURBO_MODE:
+	case H8_SET_DIAG_STATUS:
+	case H8_SOFTWARE_RESET:
+	case H8_RECAL_PTR:
+	case H8_SET_INT_BATT_PERCENT:
+	case H8_WRT_CFG_INTERFACE_REG:
+	case H8_WRT_EVENT_STATUS_MASK:
+	case H8_ENTER_POST_MODE:
+	case H8_EXIT_POST_MODE:
+	case H8_RD_EEPROM:
+	case H8_WRT_EEPROM:
+	case H8_WRT_TO_STATUS_DISP:
+	    printk("H8: Write IO status display command done\n");
+	    break;
+
+	case H8_DEFINE_SPC_CHAR:
+	case H8_DEFINE_TABLE_STRING_ENTRY:
+	case H8_PERFORM_EMU_CMD:
+	case H8_EMU_RD_REG:
+	case H8_EMU_WRT_REG:
+	case H8_EMU_RD_RAM:
+	case H8_EMU_WRT_RAM:
+	case H8_BQ_RD_REG:
+	case H8_BQ_WRT_REG:
+	case H8_PWR_OFF:
+	    printk ("H8: misc command completed\n");
+	    break;
+        }
+        return;
+}
+
+/*
+ * Retrieve the current cpu temperature and case temperature.  Provides
+ * the feedback for the thermal control algorithm.  Synchcronized via 
+ * sleep() for priority so that no other actions in the process will take
+ * place before the data becomes available.
+ */
+int
+h8_get_curr_temp(u_char curr_temp[])
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+        unsigned long flags;
+
+        memset(buf, 0, H8_MAX_CMD_SIZE); 
+        buf[0] = H8_RD_CURR_TEMP;
+
+        h8_q_cmd(buf, 1, 2);
+
+	save_flags(flags); cli();
+
+        while((h8_sync_channel & H8_RD_CURR_TEMP) == 0)
+                sleep_on(&h8_sync_wait); 
+
+        restore_flags(flags);
+
+        h8_sync_channel &= ~H8_RD_CURR_TEMP;
+        curr_temp[0] = xx.byte[0];
+        curr_temp[1] = xx.byte[1];
+        xx.word = 0;
+
+        if(h8_debug & 0x8) 
+                printk("H8: curr CPU temp %d, Sys temp %d\n",
+		       curr_temp[0], curr_temp[1]);
+        return 0;
+}
+
+static void
+h8_get_max_temp(void)
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+
+        buf[0] = H8_RD_MAX_TEMP;
+        h8_q_cmd(buf, 1, 2);
+}
+
+/*
+ * Assigns an upper limit to the value of the H8 thermal interrupt.
+ * As an example setting a value of 115 F here will cause the 
+ * interrupt to trigger when the cpu temperature reaches 115 F.
+ */
+static void
+h8_set_upper_therm_thold(int thold)
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+
+        /* write 0 to reinitialize interrupt */
+        buf[0] = H8_CTL_UPPER_TEMP;
+        buf[1] = 0x0;
+        buf[2] = 0x0;
+        h8_q_cmd(buf, 3, 1); 
+
+        /* Do it for real */
+        buf[0] = H8_CTL_UPPER_TEMP;
+        buf[1] = 0x0;
+        buf[2] = thold;
+        h8_q_cmd(buf, 3, 1); 
+}
+
+static void
+h8_get_upper_therm_thold(void)
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+
+        buf[0] = H8_CTL_UPPER_TEMP;
+        buf[1] = 0xff;
+        buf[2] = 0;
+        h8_q_cmd(buf, 3, 1); 
+}
+
+/*
+ * The external status word contains information on keyboard controller,
+ * power button, changes in external batt status, change in DC state,
+ * docking station, etc. General purpose querying use.
+ */
+int
+h8_get_ext_status(u_char stat_word[])
+{
+        u_char  buf[H8_MAX_CMD_SIZE];
+	unsigned long flags;
+
+        memset(buf, 0, H8_MAX_CMD_SIZE); 
+        buf[0] = H8_RD_EXT_STATUS;
+
+        h8_q_cmd(buf, 1, 2);
+
+	save_flags(flags); cli();
+
+        while((h8_sync_channel & H8_GET_EXT_STATUS) == 0)
+                sleep_on(&h8_sync_wait); 
+
+        restore_flags(flags);
+
+        h8_sync_channel &= ~H8_GET_EXT_STATUS;
+        stat_word[0] = xx.byte[0];
+        stat_word[1] = xx.byte[1];
+        xx.word = 0;
+
+        if(h8_debug & 0x8) 
+                printk("H8: curr ext status %x,  %x\n",
+		       stat_word[0], stat_word[1]);
+
+        return 0;
+}
+
+/*
+ * Thread attached to task 0 manages thermal/physcial state of Alphabook. 
+ * When a condition is detected by the interrupt service routine, the
+ * isr does a wakeup() on h8_monitor_wait.  The mask value is then
+ * screened for the appropriate action.
+ */
+
+int
+h8_monitor_thread(void * unused)
+{
+        u_char curr_temp[2];
+
+        /*
+         * Need a logic based safety valve here. During boot when this thread is
+         * started and the thermal interrupt is not yet initialized this logic 
+         * checks the temperature and acts accordingly.  When this path is acted
+         * upon system boot is painfully slow, however, the priority associated 
+         * with overheating is high enough to warrant this action.
+         */
+        h8_get_curr_temp(curr_temp);
+
+        printk("H8: Initial CPU temp: %d\n", curr_temp[0]);
+
+        if(curr_temp[0] >= h8_uthermal_threshold) {
+                h8_set_event_mask(H8_MANAGE_UTHERM);
+                h8_manage_therm();
+        } else {
+                /*
+                 * Arm the upper thermal limit of the H8 so that any temp in
+                 * excess will trigger the thermal control mechanism.
+                 */
+                h8_set_upper_therm_thold(h8_uthermal_threshold);
+        }
+
+        for(;;) {
+		sleep_on(&h8_monitor_wait);
+
+                if(h8_debug & 0x2)
+                        printk("h8_monitor_thread awakened, mask:%x\n",
+                                h8_event_mask);
+
+                if (h8_event_mask & (H8_MANAGE_UTHERM|H8_MANAGE_LTHERM)) {
+                        h8_manage_therm();
+                }
+
+#if 0
+                if (h8_event_mask & H8_POWER_BUTTON) {
+                        h8_system_down();
+                }
+
+		/*
+		 * If an external DC supply is removed or added make 
+		 * appropriate cpu speed adjustments.
+		 */
+                if (h8_event_mask & H8_MANAGE_BATTERY) {
+                          h8_run_level_3_manage(H8_RUN); 
+                          h8_clear_event_mask(H8_MANAGE_BATTERY);
+                }
+#endif
+        }
+}
+
+/* 
+ * Function implements the following policy. When the machine is booted
+ * the system is set to run at full clock speed. When the upper thermal
+ * threshold is reached as a result of full clock a damping factor is 
+ * applied to cool off the cpu.  The default value is one quarter clock
+ * (57 Mhz).  When as a result of this cooling a temperature lower by
+ * hmc_uthermal_window is reached, the machine is reset to a higher 
+ * speed, one half clock (115 Mhz).  One half clock is maintained until
+ * the upper thermal threshold is again reached restarting the cycle.
+ */
+
+int
+h8_manage_therm(void)
+{
+        u_char curr_temp[2];
+
+        if(h8_event_mask & H8_MANAGE_UTHERM) {
+		/* Upper thermal interrupt received, need to cool down. */
+		if(h8_debug & 0x10)
+                        printk("H8: Thermal threshold %d F reached\n",
+			       h8_uthermal_threshold);
+		h8_set_cpu_speed(h8_udamp); 
+                h8_clear_event_mask(H8_MANAGE_UTHERM);
+                h8_set_event_mask(H8_MANAGE_LTHERM);
+                /* Check again in 30 seconds for cpu temperature */
+                h8_start_monitor_timer(H8_TIMEOUT_INTERVAL); 
+        } else if (h8_event_mask & H8_MANAGE_LTHERM) {
+		/* See how cool the system has become as a result
+		   of the reduction in speed. */
+                h8_get_curr_temp(curr_temp);
+                last_temp = curr_temp[0];
+                if (curr_temp[0] < (h8_uthermal_threshold - h8_uthermal_window))
+		{
+			/* System cooling has progressed to a point
+			   that the cpu may be speeded up. */
+                        h8_set_upper_therm_thold(h8_uthermal_threshold);
+                        h8_set_cpu_speed(h8_ldamp); /* adjustable */ 
+                        if(h8_debug & 0x10)
+                            printk("H8: CPU cool, applying cpu_divisor: %d \n",
+				   h8_ldamp);
+                        h8_clear_event_mask(H8_MANAGE_LTHERM);
+                }
+		else /* Not cool enough yet, check again in 30 seconds. */
+                        h8_start_monitor_timer(H8_TIMEOUT_INTERVAL);
+        } else {
+                
+        }
+	return 0;
+}
+
+/* 
+ * Function conditions the value of global_rpb_counter before
+ * calling the primitive which causes the actual speed change.
+ */
+void
+h8_set_cpu_speed(int speed_divisor)
+{
+
+#ifdef NOT_YET
+/*
+ * global_rpb_counter is consumed by alpha_delay() in determining just
+ * how much time to delay.  It is necessary that the number of microseconds
+ * in DELAY(n) be kept consistent over a variety of cpu clock speeds.
+ * To that end global_rpb_counter is here adjusted.
+ */ 
+        
+        switch (speed_divisor) {
+                case 0:
+                        global_rpb_counter = rpb->rpb_counter * 2L;
+                        break;
+                case 1:
+                        global_rpb_counter = rpb->rpb_counter * 4L / 3L ;
+                        break;
+                case 3:
+                        global_rpb_counter = rpb->rpb_counter / 2L;
+                        break;
+                case 4:
+                        global_rpb_counter = rpb->rpb_counter / 4L;
+                        break;
+                case 5:
+                        global_rpb_counter = rpb->rpb_counter / 8L;
+                        break;
+                /* 
+                 * This case most commonly needed for cpu_speed_divisor 
+                 * of 2 which is the value assigned by the firmware. 
+                 */
+                default:
+                        global_rpb_counter = rpb->rpb_counter;
+                break;
+        }
+#endif /* NOT_YET */
+
+        if(h8_debug & 0x8)
+                printk("H8: Setting CPU speed to %d MHz\n",
+		       speed_tab[speed_divisor]); 
+
+         /* Make the actual speed change */
+        lca_clock_fiddle(speed_divisor);
+}
+
+/*
+ * Gets value stored in rpb representing cpu clock speed and adjusts this
+ * value based on the current clock speed divisor.
+ */
+u_long
+h8_get_cpu_speed(void)
+{
+        u_long speed = 0;
+        u_long counter;
+
+#ifdef NOT_YET
+        counter = rpb->rpb_counter / 1000000L;
+
+        switch (alphabook_get_clock()) {
+                case 0:
+                        speed = counter * 2L;
+                        break;
+                case 1:
+                        speed = counter * 4L / 3L ;
+                        break;
+                case 2:
+                        speed = counter;
+                        break;
+                case 3:
+                        speed = counter / 2L;
+                        break;
+                case 4:
+                        speed = counter / 4L;
+                        break;
+                case 5:
+                        speed = counter / 8L;
+                        break;
+                default:
+                break;
+        }
+        if(h8_debug & 0x8)
+                printk("H8: CPU speed current setting: %d MHz\n", speed); 
+#endif  /* NOT_YET */
+	return speed;
+}
+
+static void
+h8_activate_monitor(unsigned long unused)
+{
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	h8_monitor_timer_active = 0;
+	restore_flags(flags);
+
+	wake_up(&h8_monitor_wait);
+}
+
+static void
+h8_start_monitor_timer(unsigned long secs)
+{
+	unsigned long flags;
+
+	if (h8_monitor_timer_active)
+	    return;
+
+	save_flags(flags); cli();
+	h8_monitor_timer_active = 1;
+	restore_flags(flags);
+
+        init_timer(&h8_monitor_timer);
+        h8_monitor_timer.function = h8_activate_monitor;
+        h8_monitor_timer.expires = secs * HZ + jiffies;
+        add_timer(&h8_monitor_timer);
+}
+
+static void h8_set_event_mask(int mask)
+{
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	h8_event_mask |= mask;
+	restore_flags(flags);
+}
+
+static void h8_clear_event_mask(int mask)
+{
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	h8_event_mask &= (~mask);
+	restore_flags(flags);
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov