/* * Hitachi H8/337 Microcontroller driver * * The H8 is used to deal with the power and thermal environment * of a system. * * Fixes: * June 1999, AV added releasing /proc/driver/h8 * Feb 2000, Borislav Deianov * changed queues to use list.h instead of lists.h */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __KERNEL_SYSCALLS__ #include #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. */ static int h8_init(void); static int h8_display_blank(void); static int h8_display_unblank(void); static void h8_intr(int irq, void *dev_id, struct pt_regs *regs); static int h8_get_info(char *, char **, off_t, int); /* * 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 union intr_buf intrbuf; static int intr_buf_ptr; static union intr_buf xx; static 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) static unsigned int h8_base = H8_BASE_ADDR; static unsigned int h8_irq = H8_IRQ; static unsigned int h8_state = H8_IDLE; static unsigned int h8_index = -1; static unsigned int h8_enabled = 0; static LIST_HEAD(h8_actq); static LIST_HEAD(h8_cmdq); static LIST_HEAD(h8_freeq); /* * Globals used in thermal control of Alphabook1. */ static int cpu_speed_divisor = -1; static int h8_event_mask = 0; static DECLARE_WAIT_QUEUE_HEAD(h8_monitor_wait); static unsigned int h8_command_mask = 0; static int h8_uthermal_threshold = DEFAULT_UTHERMAL_THRESHOLD; static int h8_uthermal_window = UTH_HYSTERESIS; static int h8_debug = 0xfffffdfc; static int h8_ldamp = MHZ_115; static int h8_udamp = MHZ_57; static u_char h8_current_temp = 0; static u_char h8_system_temp = 0; static int h8_sync_channel = 0; static DECLARE_WAIT_QUEUE_HEAD(h8_sync_wait); static int h8_init_performed; /* CPU speeds and clock divisor values */ static 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 = list_entry(h8_actq.next, h8_cmd_q_t, 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 (!list_empty(&h8_actq)) 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; list_del(&qp->link); h8_cmd_done (qp); /* More commands to send over? */ if (!list_empty(&h8_cmdq)) 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; } static void __exit h8_cleanup (void) { remove_proc_entry("driver/h8", NULL); release_region(h8_base, 8); free_irq(h8_irq, NULL); } static int __init h8_init(void) { if(request_irq(h8_irq, h8_intr, SA_INTERRUPT, "h8", NULL)) { printk(KERN_ERR "H8: error: IRQ %d is not free\n", h8_irq); return -EIO; } printk(KERN_INFO "H8 at 0x%x IRQ %d\n", h8_base, h8_irq); create_proc_info_entry("driver/h8", 0, NULL, h8_get_info); request_region(h8_base, 8, "h8"); h8_alloc_queues(); h8_hw_init(); kernel_thread(h8_monitor_thread, NULL, 0); return 0; } module_init(h8_init); module_exit(h8_cleanup); static void __init 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; } static int h8_get_info(char *buf, char **start, off_t fpos, int length) { #ifdef CONFIG_PROC_FS 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; #else return 0; #endif } /* Called from console driver -- must make sure h8_enabled. */ static 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. */ static 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; } static 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(KERN_ERR "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 */ list_add(&qp[i].link, &h8_freeq); } 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 (list_empty(&h8_freeq)) { 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 = list_entry(h8_freeq.next, h8_cmd_q_t, link); list_del(&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(); /* XXX this actually puts it at the start of cmd queue, bug? */ list_add(&qp->link, &h8_cmdq); 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 (!list_empty(&h8_actq)) { Dprintk("h8_start_new_cmd: inconsistency: IDLE with non-empty active queue!\n"); restore_flags(flags); return; } if (list_empty(&h8_cmdq)) { 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 = list_entry(h8_cmdq.next, h8_cmd_q_t, link); list_del(&qp->link); /* XXX should this go to the end of the active queue? */ list_add(&qp->link, &h8_actq); 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 = list_entry(h8_actq.next, h8_cmd_q_t, 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(KERN_DEBUG "h8_read_event_status: value 0x%x\n", intrbuf.word); /* * Power related items */ if (intrbuf.word & H8_DC_CHANGE) { if(h8_debug & 0x4) printk(KERN_DEBUG "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(KERN_CRIT "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(KERN_DEBUG "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(KERN_DEBUG "h8_read_event_status: DOCKING_STATION_STATUS\n"); /* read_ext_status */ } if (intrbuf.word & H8_EXT_BATT_STATUS) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: EXT_BATT_STATUS\n"); } if (intrbuf.word & H8_EXT_BATT_CHARGE_STATE) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: EXT_BATT_CHARGE_STATE\n"); } if (intrbuf.word & H8_BATT_CHANGE_OVER) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: BATT_CHANGE_OVER\n"); } if (intrbuf.word & H8_WATCHDOG) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: WATCHDOG\n"); /* nop */ } if (intrbuf.word & H8_SHUTDOWN) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: SHUTDOWN\n"); /* nop */ } if (intrbuf.word & H8_KEYBOARD) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: KEYBOARD\n"); /* nop */ } if (intrbuf.word & H8_EXT_MOUSE_OR_CASE_SWITCH) { if(h8_debug & 0x4) printk(KERN_DEBUG "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(KERN_DEBUG "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(KERN_DEBUG "h8_read_event_status: INT_BATT_CHARGE_STATE\n"); /* nop - happens often */ } if (intrbuf.word & H8_INT_BATT_STATUS) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: INT_BATT_STATUS\n"); } if (intrbuf.word & H8_INT_BATT_CHARGE_THRESHOLD) { if(h8_debug & 0x4) printk(KERN_DEBUG "h8_read_event_status: INT_BATT_CHARGE_THRESHOLD\n"); /* nop - happens often */ } if (intrbuf.word & H8_EXT_BATT_LOW) { if(h8_debug & 0x4) printk(KERN_DEBUG "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. */ static void h8_cmd_done(h8_cmd_q_t *qp) { /* what to do */ switch (qp->cmdbuf[0]) { case H8_SYNC: if (h8_debug & 0x40000) printk(KERN_DEBUG "H8: Sync command done - byte returned was 0x%x\n", qp->rcvbuf[0]); list_add(&qp->link, &h8_freeq); break; case H8_RD_SN: case H8_RD_ENET_ADDR: printk(KERN_DEBUG "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]); list_add(&qp->link, &h8_freeq); break; case H8_RD_HW_VER: case H8_RD_MIC_VER: case H8_RD_MAX_TEMP: printk(KERN_DEBUG "H8: Max recorded CPU temp %d, Sys temp %d\n", qp->rcvbuf[0], qp->rcvbuf[1]); list_add(&qp->link, &h8_freeq); break; case H8_RD_MIN_TEMP: printk(KERN_DEBUG "H8: Min recorded CPU temp %d, Sys temp %d\n", qp->rcvbuf[0], qp->rcvbuf[1]); list_add(&qp->link, &h8_freeq); 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); list_add(&qp->link, &h8_freeq); break; case H8_RD_SYS_VARIENT: case H8_RD_PWR_ON_CYCLES: printk(KERN_DEBUG " H8: RD_PWR_ON_CYCLES command done\n"); break; case H8_RD_PWR_ON_SECS: printk(KERN_DEBUG "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); list_add(&qp->link, &h8_freeq); 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(KERN_DEBUG "H8: Read int batt status cmd done - returned was %x %x %x\n", qp->rcvbuf[0], qp->rcvbuf[1], qp->rcvbuf[2]); list_add(&qp->link, &h8_freeq); 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(KERN_DEBUG "H8: Device control cmd done - byte returned was 0x%x\n", qp->rcvbuf[0]); } list_add(&qp->link, &h8_freeq); 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(KERN_DEBUG "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]); } list_add(&qp->link, &h8_freeq); 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 (KERN_DEBUG "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(KERN_INFO "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(KERN_DEBUG "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(KERN_WARNING "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(KERN_WARNING "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(KERN_DEBUG "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(KERN_DEBUG "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); } MODULE_LICENSE("GPL"); EXPORT_NO_SYMBOLS;