patch-2.2.16 linux/drivers/sbus/char/envctrl.c
Next file: linux/drivers/sbus/char/sab82532.c
Previous file: linux/drivers/sbus/audio/audio.c
Back to the patch index
Back to the overall index
- Lines: 1124
- Date:
Wed Jun 7 14:26:43 2000
- Orig file:
v2.2.15/linux/drivers/sbus/char/envctrl.c
- Orig date:
Mon Nov 16 10:37:28 1998
diff -urN v2.2.15/linux/drivers/sbus/char/envctrl.c linux/drivers/sbus/char/envctrl.c
@@ -1,7 +1,17 @@
-/* $Id: envctrl.c,v 1.9 1998/11/06 07:38:20 ecd Exp $
+/* $Id: envctrl.c,v 1.9.2.1 2000/05/02 04:23:33 davem Exp $
* envctrl.c: Temperature and Fan monitoring on Machines providing it.
*
* Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be)
+ * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com)
+ * VT - The implementation is to support Sun Microelectronics (SME) platform
+ * environment monitoring. SME platforms use pcf8584 as the i2c bus
+ * controller to access pcf8591 (8-bit A/D and D/A converter) and
+ * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface).
+ * At board level, it follows SME Firmware I2C Specification. Reference:
+ * http://www-eu2.semiconductors.com/pip/PCF8584P
+ * http://www-eu2.semiconductors.com/pip/PCF8574AP
+ * http://www-eu2.semiconductors.com/pip/PCF8591P
+ *
*/
#include <linux/config.h>
@@ -12,6 +22,8 @@
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
#include <asm/ebus.h>
#include <asm/uaccess.h>
@@ -19,10 +31,8 @@
#define ENVCTRL_MINOR 162
-
#undef DEBUG_BUS_SCAN
-
#define PCF8584_ADDRESS 0x55
#define CONTROL_PIN 0x80
@@ -57,229 +67,599 @@
#define CLK_8 0x18
#define CLK_12 0x1c
+#define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */
+#define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */
-#define I2C_WRITE 0x00
-#define I2C_READ 0x01
+/* Monitor type of i2c child device.
+ * Firmware definitions.
+ */
+#define PCF8584_MAX_CHANNELS 8
+#define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */
+#define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */
+#define PCF8584_TEMP_TYPE 1 /* temperature monitor*/
-struct pcf8584_reg
-{
- __volatile__ unsigned char data;
- __volatile__ unsigned char csr;
-};
+/* Monitor type of i2c child device.
+ * Driver definitions.
+ */
+#define ENVCTRL_NOMON 0
+#define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */
+#define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */
+#define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */
+#define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperarture */
+ /* monitor */
+#define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */
+#define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */
+#define ENVCTRL_SCSITEMP_MON 7 /* scsi temperarture */
-static struct pcf8584_reg *i2c;
+/* Child device type.
+ * Driver definitions.
+ */
+#define I2C_ADC 0 /* pcf8591 */
+#define I2C_GPIO 1 /* pcf8571 */
+/* Data read from child device may need to decode
+ * through a data table and a scale.
+ * Translation type as defined by firmware.
+ */
+#define ENVCTRL_TRANSLATE_NO 0
+#define ENVCTRL_TRANSLATE_PARTIAL 1
+#define ENVCTRL_TRANSLATE_COMBINED 2
+#define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */
+#define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */
+
+/* Driver miscellaneous definitions. */
+#define ENVCTRL_MAX_CPU 4
+#define CHANNEL_DESC_SZ 256
+
+struct pcf8584_reg {
+ unsigned char data;
+ unsigned char csr;
+};
-#ifdef DEBUG_BUS_SCAN
-struct i2c_addr_map {
- unsigned char addr;
- unsigned char mask;
- char *name;
+/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS.
+ * Property of a port or channel as defined by the firmware.
+ */
+struct pcf8584_channel {
+ unsigned char chnl_no;
+ unsigned char io_direction;
+ unsigned char type;
+ unsigned char last;
};
-static struct i2c_addr_map devmap[] = {
- { 0x38, 0x78, "PCF8574A" },
- { 0x20, 0x78, "TDA8444" },
- { 0x48, 0x78, "PCF8591" },
+/* Each child device may have one or more tables of bytes to help decode
+ * data. Table property as defined by the firmware.
+ */
+struct pcf8584_tblprop {
+ unsigned int type;
+ unsigned int scale;
+ unsigned int offset; /* offset from the beginning of the table */
+ unsigned int size;
};
-#define NR_DEVMAP (sizeof(devmap) / sizeof(devmap[0]))
-#endif
-static __inline__ int
-PUT_DATA(__volatile__ unsigned char *data, char *buffer, int user)
+/* i2c child */
+struct i2c_child_t {
+ /* Either ADC or GPIO. */
+ unsigned char i2ctype;
+ unsigned long addr;
+ struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS];
+
+ /* Channel info. */
+ unsigned int total_chnls; /* Number of monitor channels. */
+ unsigned char fan_mask; /* Byte mask for fan status channels. */
+ unsigned char voltage_mask; /* Byte mask for voltage status channels. */
+ struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS];
+
+ /* Properties of all monitor channels. */
+ unsigned int total_tbls; /* Number of monitor tables. */
+ char *tables; /* Pointer to table(s). */
+ char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */
+ char mon_type[PCF8584_MAX_CHANNELS];
+};
+
+static struct pcf8584_reg *i2c = NULL;
+static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2];
+static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+static unsigned int warning_temperature = 0;
+static unsigned int shutdown_temperature = 0;
+static char read_cpu;
+
+/* Forward declarations. */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char);
+
+/* Function description: Read a byte from an i2c controller register.
+ * Return: A byte from the passed in address.
+ */
+static inline unsigned char envctrl_readb(unsigned char *p)
{
- if (user) {
- if (put_user(*data, buffer))
- return -EFAULT;
- } else {
- *buffer = *data;
- }
- return 0;
+ unsigned char result;
+
+ __asm__ __volatile__("lduba [%1] %2, %0"
+ : "=r" (result)
+ : "r" (p), "i" (ASI_PHYS_BYPASS_EC_E));
+ return result;
}
-static __inline__ int
-GET_DATA(__volatile__ unsigned char *data, const char *buffer, int user)
+/* Function description: Write a byte to an i2c controller register.
+ * Return: Nothing.
+ */
+static inline void envctrl_writeb(unsigned char val, unsigned char *p)
{
- if (user) {
- if (get_user(*data, buffer))
- return -EFAULT;
- } else {
- *data = *buffer;
- }
- return 0;
+ __asm__ __volatile__("stba %0, [%1] %2"
+ : /* no outputs */
+ : "r" (val), "r" (p), "i" (ASI_PHYS_BYPASS_EC_E));
}
-static int
-i2c_read(unsigned char dev, char *buffer, int len, int user)
+/* Function Description: Test the PIN bit (Pending Interrupt Not)
+ * to test when serial transmission is completed .
+ * Return : None.
+ */
+static void envtrl_i2c_test_pin(void)
{
- unsigned char dummy;
- unsigned char stat;
- int error = -ENODEV;
- int count = 0;
+ int limit = 1000000;
- i2c->data = (dev << 1) | I2C_READ;
-
- while (!(i2c->csr & STATUS_BB))
+ while (--limit > 0) {
+ if(!(envctrl_readb(&i2c->csr) & STATUS_PIN))
+ break;
udelay(1);
+ }
- i2c->csr = CONTROL_PIN | CONTROL_ES0 | CONTROL_STA | CONTROL_ACK;
+ if (limit <= 0)
+ printk(KERN_INFO "envctrl: Pin status will not clear.\n");
+}
- do {
+/* Function Description: Test busy bit.
+ * Return : None.
+ */
+static void envctrl_i2c_test_bb(void)
+{
+ int limit = 1000000;
+
+ while (--limit > 0) {
+ /* Busy bit 0 means busy. */
+ if (envctrl_readb(&i2c->csr) & STATUS_BB)
+ break;
udelay(1);
- while ((stat = i2c->csr) & STATUS_PIN)
- udelay(1);
+ }
+
+ if (limit <= 0)
+ printk(KERN_INFO "envctrl: Busy bit will not clear.\n");
+}
- if (stat & STATUS_LRB)
- goto stop;
+/* Function Description: Send the adress for a read access.
+ * Return : 0 if not acknowledged, otherwise acknowledged.
+ */
+static int envctrl_i2c_read_addr(unsigned char addr)
+{
+ envctrl_i2c_test_bb();
- error = 0;
- if (len == 0) {
- count--;
- break;
- }
+ /* Load address. */
+ envctrl_writeb(addr + 1, &i2c->data);
- if (count == (len - 1))
- break;
+ envctrl_i2c_test_bb();
- if (count++ > 0) {
- error = PUT_DATA(&i2c->data, buffer++, user);
- if (error)
- break;
- } else
- dummy = i2c->data;
- } while (1);
-
- i2c->csr = CONTROL_ES0;
- if (!error && (count++ > 0))
- error = PUT_DATA(&i2c->data, buffer++, user);
- else
- dummy = i2c->data;
+ envctrl_writeb(OBD_SEND_START, &i2c->csr);
- udelay(1);
- while ((stat = i2c->csr) & STATUS_PIN)
- udelay(1);
+ /* Wait for PIN. */
+ envtrl_i2c_test_pin();
-stop:
- i2c->csr = CONTROL_PIN | CONTROL_ES0 | CONTROL_STO | CONTROL_ACK;
- if (!error && (count++ > 0))
- error = PUT_DATA(&i2c->data, buffer++, user);
- else
- dummy = i2c->data;
-
- if (error)
- return error;
- return count - 1;
+ /* CSR 0 means acknowledged. */
+ if (!(envctrl_readb(&i2c->csr) & STATUS_LRB)) {
+ return envctrl_readb(&i2c->data);
+ } else {
+ envctrl_writeb(OBD_SEND_STOP, &i2c->csr);
+ return 0;
+ }
}
-static int
-i2c_write(unsigned char dev, const char *buffer, int len, int user)
+/* Function Description: Send the adress for write mode.
+ * Return : None.
+ */
+static void envctrl_i2c_write_addr(unsigned char addr)
{
- int error = -ENODEV;
- int count = 0;
+ envctrl_i2c_test_bb();
+ envctrl_writeb(addr, &i2c->data);
- while (!(i2c->csr & STATUS_BB))
- udelay(1);
+ /* Generate Start condition. */
+ envctrl_writeb(OBD_SEND_START, &i2c->csr);
+}
- i2c->data = (dev << 1) | I2C_WRITE;
- i2c->csr = CONTROL_PIN | CONTROL_ES0 | CONTROL_STA | CONTROL_ACK;
+/* Function Description: Read 1 byte of data from addr
+ * set by envctrl_i2c_read_addr()
+ * Return : Data from address set by envctrl_i2c_read_addr().
+ */
+static unsigned char envctrl_i2c_read_data(void)
+{
+ envtrl_i2c_test_pin();
+ envctrl_writeb(CONTROL_ES0, &i2c->csr); /* Send neg ack. */
+ return envctrl_readb(&i2c->data);
+}
- do {
- unsigned char stat;
+/* Function Description: Instruct the device which port to read data from.
+ * Return : None.
+ */
+static void envctrl_i2c_write_data(unsigned char port)
+{
+ envtrl_i2c_test_pin();
+ envctrl_writeb(port, &i2c->data);
+}
- udelay(1);
- while ((stat = i2c->csr) & STATUS_PIN)
- udelay(1);
+/* Function Description: Generate Stop condition after last byte is sent.
+ * Return : None.
+ */
+static void envctrl_i2c_stop(void)
+{
+ envtrl_i2c_test_pin();
+ envctrl_writeb(OBD_SEND_STOP, &i2c->csr);
+}
- if (stat & STATUS_LRB)
- break;
+/* Function Description: Read adc device.
+ * Return : Data at address and port.
+ */
+static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port)
+{
+ /* Send address. */
+ envctrl_i2c_write_addr(addr);
- error = count;
- if (count == len)
- break;
+ /* Setup port to read. */
+ envctrl_i2c_write_data(port);
+ envctrl_i2c_stop();
- error = GET_DATA(&i2c->data, buffer++, user);
- if (error)
- break;
+ /* Read port. */
+ envctrl_i2c_read_addr(addr);
- count++;
- } while (1);
+ /* Do a single byte read and send stop. */
+ envctrl_i2c_read_data();
+ envctrl_i2c_stop();
- i2c->csr = CONTROL_PIN | CONTROL_ES0 | CONTROL_STO | CONTROL_ACK;
- return error;
+ return envctrl_readb(&i2c->data);
}
-__initfunc(static int i2c_scan_bus(void))
+/* Function Description: Read gpio device.
+ * Return : Data at address.
+ */
+static unsigned char envctrl_i2c_read_8574(unsigned char addr)
{
- unsigned char dev;
- int count = 0;
-
- for (dev = 1; dev < 128; dev++) {
- if (i2c_read(dev, 0, 0, 0) == 0) {
-#ifdef DEBUG_BUS_SCAN
- int i;
- for (i = 0; i < NR_DEVMAP; i++)
- if ((dev & devmap[i].mask) == devmap[i].addr)
- break;
- printk("envctrl: i2c device at %02x: %s\n", dev,
- i < NR_DEVMAP ? devmap[i].name : "unknown");
-#endif
- count++;
+ unsigned char rd;
+
+ envctrl_i2c_read_addr(addr);
+
+ /* Do a single byte read and send stop. */
+ rd = envctrl_i2c_read_data();
+ envctrl_i2c_stop();
+
+ return rd;
+}
+
+/* Function Description: Decode data read from an adc device using firmware
+ * table.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_i2c_data_translate(unsigned char data, int translate_type,
+ int scale, char *tbl, char *bufdata)
+{
+ int len = 0;
+
+ switch (translate_type) {
+ case ENVCTRL_TRANSLATE_NO:
+ /* No decode necessary. */
+ len = 1;
+ bufdata[0] = data;
+ break;
+
+ case ENVCTRL_TRANSLATE_FULL:
+ /* Decode this way: data = table[data]. */
+ len = 1;
+ bufdata[0] = tbl[data];
+ break;
+
+ case ENVCTRL_TRANSLATE_SCALE:
+ /* Decode this way: data = table[data]/scale */
+ sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale));
+ len = strlen(bufdata);
+ bufdata[len - 1] = bufdata[len - 2];
+ bufdata[len - 2] = '.';
+ break;
+
+ default:
+ break;
+ };
+
+ return len;
+}
+
+/* Function Description: Read cpu-related data such as cpu temperature, voltage.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_cpu_info(struct i2c_child_t *pchild,
+ char mon_type, unsigned char *bufdata)
+{
+ unsigned char data;
+ int i;
+ char *tbl, j = -1;
+
+ /* Find the right monitor type and channel. */
+ for (i = 0; i< PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->mon_type[i] == mon_type) {
+ if (++j == read_cpu) {
+ break;
+ }
}
}
- if (!count) {
- printk("%s: no devices found\n", __FUNCTION__);
- return -ENODEV;
- }
- return 0;
+
+ if (j != read_cpu)
+ return 0;
+
+ /* Read data from address and port. */
+ data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+ (unsigned char)pchild->chnl_array[i].chnl_no);
+
+ /* Find decoding table. */
+ tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+ return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+ pchild->tblprop_array[i].scale,
+ tbl, bufdata);
}
-static loff_t
-envctrl_llseek(struct file *file, loff_t offset, int type)
+/* Function Description: Read noncpu-related data such as motherboard
+ * temperature.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_noncpu_info(struct i2c_child_t *pchild,
+ char mon_type, unsigned char *bufdata)
{
- return -ESPIPE;
+ unsigned char data;
+ int i;
+ char *tbl = NULL;
+
+ for (i = 0; i< PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->mon_type[i] == mon_type) {
+ break;
+ }
+ }
+
+ if (i >= PCF8584_MAX_CHANNELS)
+ return 0;
+
+ /* Read data from address and port. */
+ data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+ (unsigned char)pchild->chnl_array[i].chnl_no);
+
+ /* Find decoding table. */
+ tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+ return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+ pchild->tblprop_array[i].scale,
+ tbl, bufdata);
}
-static ssize_t
-envctrl_read(struct file *file, char *buf, size_t count, loff_t *ppos)
-{
- unsigned long addr = (unsigned long)file->private_data;
+/* Function Description: Read fan status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static int envctrl_i2c_fan_status(struct i2c_child_t *pchild,
+ unsigned char data,
+ char *bufdata)
+{
+ unsigned char tmp, ret = 0;
+ int i, j = 0;
+
+ tmp = data & pchild->fan_mask;
+
+ if (tmp == pchild->fan_mask) {
+ /* All bits are on. All fans are functioning. */
+ ret = ENVCTRL_ALL_FANS_GOOD;
+ } else if (tmp == 0) {
+ /* No bits are on. No fans are functioning. */
+ ret = ENVCTRL_ALL_FANS_BAD;
+ } else {
+ /* Go through all channels, mark 'on' the matched bits.
+ * Notice that fan_mask may have discontiguous bits but
+ * return mask are always contiguous. For example if we
+ * monitor 4 fans at channels 0,1,2,4, the return mask
+ * should be 00010000 if only fan at channel 4 is working.
+ */
+ for (i = 0; i < PCF8584_MAX_CHANNELS;i++) {
+ if (pchild->fan_mask & chnls_mask[i]) {
+ if (!(chnls_mask[i] & tmp)) {
+ ret |= chnls_mask[j];
+ }
+ j++;
+ }
+ }
+ }
- return i2c_read(addr, buf, count, 1);
+ bufdata[0] = ret;
+ return 1;
}
+/* Function Description: Read voltage and power supply status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild,
+ unsigned char data,
+ char *bufdata)
+{
+ unsigned char tmp, ret = 0;
+ int i, j = 0;
+
+ tmp = data & pchild->voltage_mask;
+
+ /* Two channels are used to monitor voltage and power supply. */
+ if (tmp == pchild->voltage_mask) {
+ /* All bits are on. Voltage and power supply are okay. */
+ ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD;
+ } else if (tmp == 0) {
+ /* All bits are off. Voltage and power supply are bad */
+ ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD;
+ } else {
+ /* Either voltage or power supply has problem. */
+ for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->voltage_mask & chnls_mask[i]) {
+ j++;
+
+ /* Break out when there is a mismatch. */
+ if (!(chnls_mask[i] & tmp))
+ break;
+ }
+ }
+
+ /* Make a wish that hardware will always use the
+ * first channel for voltage and the second for
+ * power supply.
+ */
+ if (j == 1)
+ ret = ENVCTRL_VOLTAGE_BAD;
+ else
+ ret = ENVCTRL_POWERSUPPLY_BAD;
+ }
+
+ bufdata[0] = ret;
+ return 1;
+}
+
+/* Function Description: Read a byte from /dev/envctrl. Mapped to user read().
+ * Return: Number of read bytes. 0 for error.
+ */
static ssize_t
-envctrl_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
+envctrl_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
- unsigned long addr = (unsigned long)file->private_data;
+ struct i2c_child_t *pchild;
+ unsigned char data[10];
+ int ret = 0;
+
+ /* Get the type of read as decided in ioctl() call.
+ * Find the appropriate i2c child.
+ * Get the data and put back to the user buffer.
+ */
+
+ switch ((int)(long)file->private_data) {
+ case ENVCTRL_RD_WARNING_TEMPERATURE:
+ if (warning_temperature == 0)
+ return 0;
+
+ data[0] = (unsigned char)(warning_temperature);
+ ret = 1;
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+ if (shutdown_temperature == 0)
+ return 0;
+
+ data[0] = (unsigned char)(shutdown_temperature);
+ ret = 1;
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data);
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_CPU_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON)))
+ return 0;
+ ret = envctrl_read_cpu_info(pchild, ENVCTRL_CPUTEMP_MON, data);
+
+ /* Reset cpu to the default cpu0. */
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_CPU_VOLTAGE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON)))
+ return 0;
+ ret = envctrl_read_cpu_info(pchild, ENVCTRL_CPUVOLTAGE_MON, data);
+
+ /* Reset cpu to the default cpu0. */
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_SCSI_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data);
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data);
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_FAN_STATUS:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON)))
+ return 0;
+ data[0] = envctrl_i2c_read_8574(pchild->addr);
+ ret = envctrl_i2c_fan_status(pchild,data[0], data);
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ case ENVCTRL_RD_VOLTAGE_STATUS:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON)))
+ return 0;
+ data[0] = envctrl_i2c_read_8574(pchild->addr);
+ ret = envctrl_i2c_voltage_status(pchild, data[0], data);
+ copy_to_user((unsigned char*)buf, data, ret);
+ break;
+
+ default:
+ break;
+
+ };
- return i2c_write(addr, buf, count, 1);
+ return ret;
}
+/* Function Description: Command what to read. Mapped to user ioctl().
+ * Return: Gives 0 for implemented commands, -EINVAL otherwise.
+ */
static int
envctrl_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
- unsigned long data;
- int addr;
+ char *infobuf;
switch (cmd) {
- case I2CIOCSADR:
- if (get_user(addr, (int *)arg))
- return -EFAULT;
- data = addr & 0x7f;
- file->private_data = (void *)data;
- break;
- case I2CIOCGADR:
- addr = (unsigned long)file->private_data;
- if (put_user(addr, (int *)arg))
- return -EFAULT;
- break;
- default:
- return -EINVAL;
- }
+ case ENVCTRL_RD_WARNING_TEMPERATURE:
+ case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+ case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+ case ENVCTRL_RD_FAN_STATUS:
+ case ENVCTRL_RD_VOLTAGE_STATUS:
+ case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+ case ENVCTRL_RD_SCSI_TEMPERATURE:
+ file->private_data = (void *)(long)cmd;
+ break;
+
+ case ENVCTRL_RD_CPU_TEMPERATURE:
+ case ENVCTRL_RD_CPU_VOLTAGE:
+ /* Check to see if application passes in any cpu number,
+ * the default is cpu0.
+ */
+ infobuf = (char *) arg;
+ if (infobuf == NULL) {
+ read_cpu = 0;
+ }else {
+ get_user(read_cpu, infobuf);
+ }
+
+ /* Save the command for use when reading. */
+ file->private_data = (void *)(long)cmd;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
return 0;
}
+/* Function Description: open device. Mapped to user open().
+ * Return: Always 0.
+ */
static int
envctrl_open(struct inode *inode, struct file *file)
{
@@ -288,6 +668,9 @@
return 0;
}
+/* Function Description: Open device. Mapped to user close().
+ * Return: Always 0.
+ */
static int
envctrl_release(struct inode *inode, struct file *file)
{
@@ -296,11 +679,11 @@
}
static struct file_operations envctrl_fops = {
- envctrl_llseek,
+ NULL,
envctrl_read,
- envctrl_write,
+ NULL,
NULL, /* readdir */
- NULL, /* poll */
+ NULL, /* poll */
envctrl_ioctl,
NULL, /* mmap */
envctrl_open,
@@ -314,6 +697,189 @@
&envctrl_fops
};
+/* Function Description: Set monitor type based on firmware description.
+ * Return: None.
+ */
+static void envctrl_set_mon(struct i2c_child_t *pchild,
+ char *chnl_desc,
+ int chnl_no)
+{
+ /* Firmware only has temperature type. It does not distinguish
+ * different kinds of temperatures. We use channel description
+ * to disinguish them.
+ */
+ if (!(strcmp(chnl_desc,"temp,cpu")) ||
+ !(strcmp(chnl_desc,"temp,cpu0")) ||
+ !(strcmp(chnl_desc,"temp,cpu1")) ||
+ !(strcmp(chnl_desc,"temp,cpu2")) ||
+ !(strcmp(chnl_desc,"temp,cpu3")))
+ pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON;
+
+ if (!(strcmp(chnl_desc,"vddcore,cpu0")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu1")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu2")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu3")))
+ pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON;
+
+ if (!(strcmp(chnl_desc,"temp,motherboard")))
+ pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON;
+
+ if (!(strcmp(chnl_desc,"temp,scsi")))
+ pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON;
+
+ if (!(strcmp(chnl_desc,"temp,ethernet")))
+ pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON;
+
+ if (!(strcmp(chnl_desc,"temp,ethernet")))
+ pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON;
+}
+
+/* Function Description: Initialize monitor channel with channel desc,
+ * decoding tables, monitor type, optional properties.
+ * Return: None.
+ */
+static void envctrl_init_adc(struct i2c_child_t *pchild, int node)
+{
+ char chnls_desc[CHANNEL_DESC_SZ];
+ int i, len, j = 0;
+ char *ptr;
+
+ /* Firmware describe channels into a stream separated by a '\0'.
+ * Replace all '\0' with a space.
+ */
+ len = prom_getproperty(node, "channels-description", chnls_desc,
+ CHANNEL_DESC_SZ);
+ for (i = 0; i < len; i++) {
+ if (chnls_desc[i] == '\0')
+ chnls_desc[i] = ' ';
+ }
+
+ ptr = strtok(chnls_desc, " ");
+ while (ptr != NULL) {
+ envctrl_set_mon(pchild, ptr, j);
+ ptr = strtok(NULL, " ");
+ j++;
+ }
+
+ /* Get optional properties. */
+ len = prom_getproperty(node, "warning-temp", (char *)&warning_temperature,
+ sizeof(warning_temperature));
+ len = prom_getproperty(node, "shutdown-temp", (char *)&shutdown_temperature,
+ sizeof(shutdown_temperature));
+}
+
+/* Function Description: Initialize child device monitoring fan status.
+ * Return: None.
+ */
+static void envctrl_init_fanstat(struct i2c_child_t *pchild)
+{
+ int i;
+
+ /* Go through all channels and set up the mask. */
+ for (i = 0; i < pchild->total_chnls; i++)
+ pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+ /* We only need to know if this child has fan status monitored.
+ * We dont care which channels since we have the mask already.
+ */
+ pchild->mon_type[0] = ENVCTRL_FANSTAT_MON;
+}
+
+/* Initialize child device monitoring voltage status. */
+static void envctrl_init_voltage_status(struct i2c_child_t *pchild)
+{
+ int i;
+
+ /* Go through all channels and set up the mask. */
+ for (i = 0; i < pchild->total_chnls; i++)
+ pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+ /* We only need to know if this child has voltage status monitored.
+ * We dont care which channels since we have the mask already.
+ */
+ pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON;
+}
+
+/* Function Description: Initialize i2c child device.
+ * Return: None.
+ */
+static void envctrl_init_i2c_child(struct linux_ebus_child *edev_child,
+ struct i2c_child_t *pchild)
+{
+ int node, len, i, tbls_size = 0;
+
+ node = edev_child->prom_node;
+
+ /* Get device address. */
+ len = prom_getproperty(node, "reg", (char *)&(pchild->addr), sizeof(pchild->addr));
+
+ /* Get tables property. Read firmware temperature tables. */
+ len = prom_getproperty(node, "translation", (char *)pchild->tblprop_array,
+ PCF8584_MAX_CHANNELS*sizeof(struct pcf8584_tblprop));
+ if (len > 0) {
+ pchild->total_tbls = len / sizeof(struct pcf8584_tblprop);
+ for (i = 0; i < pchild->total_tbls; i++) {
+ if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) {
+ tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset;
+ }
+ }
+
+ pchild->tables = kmalloc(tbls_size, GFP_KERNEL);
+ len = prom_getproperty(node, "tables", (char *)pchild->tables, tbls_size);
+ if (len <= 0) {
+ printk("envctrl: Failed to get table.\n");
+ return;
+ }
+ }
+
+ /* Get the monitor channels. */
+ len = prom_getproperty(node, "channels-in-use", (char *)pchild->chnl_array,
+ PCF8584_MAX_CHANNELS*sizeof(struct pcf8584_channel));
+ pchild->total_chnls = len / sizeof(struct pcf8584_channel);
+
+ for (i = 0; i < pchild->total_chnls; i++) {
+ switch (pchild->chnl_array[i].type) {
+ case PCF8584_TEMP_TYPE:
+ envctrl_init_adc(pchild, node);
+ break;
+
+ case PCF8584_FANSTAT_TYPE:
+ envctrl_init_fanstat(pchild);
+ i = pchild->total_chnls;
+ break;
+
+ case PCF8584_VOLTAGE_TYPE:
+ if (pchild->i2ctype == I2C_ADC) {
+ envctrl_init_adc(pchild,node);
+ } else {
+ envctrl_init_voltage_status(pchild);
+ }
+ i = pchild->total_chnls;
+ break;
+
+ default:
+ break;
+ };
+ }
+}
+
+/* Function Description: Search the child device list for a device.
+ * Return : The i2c child if found. NULL otherwise.
+ */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type)
+{
+ int i, j;
+
+ for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) {
+ for (j = 0; j < PCF8584_MAX_CHANNELS; j++) {
+ if ( i2c_childlist[i].mon_type[j] == mon_type) {
+ return (struct i2c_child_t*)(&(i2c_childlist[i]));
+ }
+ }
+ }
+ return NULL;
+}
+
#ifdef MODULE
int init_module(void)
#else
@@ -321,61 +887,77 @@
#endif
{
#ifdef CONFIG_PCI
- struct linux_ebus *ebus;
- struct linux_ebus_device *edev = 0;
- int err;
-
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+ struct linux_ebus_child *edev_child = NULL;
+ int i = 0;
+
+ /* Traverse through ebus and ebus device list for i2c device and
+ * adc and gpio nodes.
+ */
for_each_ebus(ebus) {
for_each_ebusdev(edev, ebus) {
- if (!strcmp(edev->prom_name, "SUNW,envctrl"))
- goto ebus_done;
- if (!strcmp(edev->prom_name, "SUNW,rasctrl"))
- goto ebus_done;
+ if (!strcmp(edev->prom_name, "i2c")) {
+ /* In the 2.3.x version we can replace this with just
+ * an "ioremap()" call, kill envctrl_{readb,writeb}() and
+ * replace such calls with just a normal {readb,writeb}(). -DaveM
+ */
+ i2c = (struct pcf8584_reg *) __pa(edev->base_address[0]);
+ for_each_edevchild(edev, edev_child) {
+ if (!strcmp("gpio", edev_child->prom_name)) {
+ i2c_childlist[i].i2ctype = I2C_GPIO;
+ envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+ }
+ if (!strcmp("adc", edev_child->prom_name)) {
+ i2c_childlist[i].i2ctype = I2C_ADC;
+ envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+ }
+ }
+ goto done;
+ }
}
}
-ebus_done:
- if (!edev) {
- printk("%s: ebus device not found\n", __FUNCTION__);
- return -ENODEV;
- }
- if (check_region(edev->base_address[0], sizeof(*i2c))) {
- printk("%s: Can't get region %lx, %d\n",
- __FUNCTION__, edev->base_address[0], (int)sizeof(*i2c));
+done:
+ if (!edev) {
+ printk("envctrl: I2C device not found.\n");
return -ENODEV;
}
- i2c = (struct pcf8584_reg *)edev->base_address[0];
-
- request_region((unsigned long)i2c, sizeof(*i2c), "i2c");
-
- i2c->csr = CONTROL_PIN;
- i2c->data = PCF8584_ADDRESS;
- i2c->csr = CONTROL_PIN | CONTROL_ES1;
- i2c->data = CLK_4_43 | BUS_CLK_90;
- i2c->csr = CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK;
- mdelay(10);
+ /* Set device address. */
+ envctrl_writeb(CONTROL_PIN, &i2c->csr);
+ envctrl_writeb(PCF8584_ADDRESS, &i2c->data);
+
+ /* Set system clock and SCL frequencies. */
+ envctrl_writeb(CONTROL_PIN | CONTROL_ES1, &i2c->csr);
+ envctrl_writeb(CLK_4_43 | BUS_CLK_90, &i2c->data);
+
+ /* Enable serial interface. */
+ envctrl_writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, &i2c->csr);
+ udelay(200);
+ /* Register the device as a minor miscellaneous device. */
if (misc_register(&envctrl_dev)) {
- printk("%s: unable to get misc minor %d\n",
- __FUNCTION__, envctrl_dev.minor);
- release_region((unsigned long)i2c, sizeof(*i2c));
+ printk("envctrl: Unable to get misc minor %d\n",
+ envctrl_dev.minor);
}
- err = i2c_scan_bus();
- if (err)
- release_region((unsigned long)i2c, sizeof(*i2c));
- return err;
+ return 0;
#else
return -ENODEV;
#endif
}
-
#ifdef MODULE
void cleanup_module(void)
{
+ int i;
+
misc_deregister(&envctrl_dev);
- release_region((unsigned long)i2c, sizeof(*i2c));
+
+ for (i = 0; i < ENVCTRL_MAX_CPU * 2; i++) {
+ if (i2c_childlist[i].tables)
+ kfree(i2c_childlist[i].tables);
+ }
}
#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)