patch-2.1.54 linux/drivers/char/h8.c
Next file: linux/drivers/char/h8.h
Previous file: linux/drivers/char/acquirewdt.c
Back to the patch index
Back to the overall index
- Lines: 1273
- Date:
Sat Sep 6 10:16:07 1997
- Orig file:
v2.1.53/linux/drivers/char/h8.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.1.53/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