/*
 * 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>
#include <linux/poll.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 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 */
        NULL,
        NULL,           /* write */
        NULL,           /* readdir */
        NULL,
        NULL,
        NULL,           /* mmap */
        NULL,
	NULL,		/* flush */
        NULL,
        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(&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(&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

/* 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 address: 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 sped 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);
}
