blob: 611d2b5d435b029643c730535521f03e36c8dc49 [file] [log] [blame]
/*
* 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 <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/linkage.h>
#include <linux/stat.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
#include <linux/list.h>
#include <linux/ioport.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/slab.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.
*/
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;