162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* envctrl.c: Temperature and Fan monitoring on Machines providing it. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) 562306a36Sopenharmony_ci * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com) 662306a36Sopenharmony_ci * VT - The implementation is to support Sun Microelectronics (SME) platform 762306a36Sopenharmony_ci * environment monitoring. SME platforms use pcf8584 as the i2c bus 862306a36Sopenharmony_ci * controller to access pcf8591 (8-bit A/D and D/A converter) and 962306a36Sopenharmony_ci * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface). 1062306a36Sopenharmony_ci * At board level, it follows SME Firmware I2C Specification. Reference: 1162306a36Sopenharmony_ci * http://www-eu2.semiconductors.com/pip/PCF8584P 1262306a36Sopenharmony_ci * http://www-eu2.semiconductors.com/pip/PCF8574AP 1362306a36Sopenharmony_ci * http://www-eu2.semiconductors.com/pip/PCF8591P 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * EB - Added support for CP1500 Global Address and PS/Voltage monitoring. 1662306a36Sopenharmony_ci * Eric Brower <ebrower@usa.net> 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * DB - Audit every copy_to_user in envctrl_read. 1962306a36Sopenharmony_ci * Daniele Bellucci <bellucda@tiscali.it> 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <linux/module.h> 2362306a36Sopenharmony_ci#include <linux/kthread.h> 2462306a36Sopenharmony_ci#include <linux/delay.h> 2562306a36Sopenharmony_ci#include <linux/ioport.h> 2662306a36Sopenharmony_ci#include <linux/miscdevice.h> 2762306a36Sopenharmony_ci#include <linux/kmod.h> 2862306a36Sopenharmony_ci#include <linux/reboot.h> 2962306a36Sopenharmony_ci#include <linux/slab.h> 3062306a36Sopenharmony_ci#include <linux/of.h> 3162306a36Sopenharmony_ci#include <linux/platform_device.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/uaccess.h> 3462306a36Sopenharmony_ci#include <asm/envctrl.h> 3562306a36Sopenharmony_ci#include <asm/io.h> 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define DRIVER_NAME "envctrl" 3862306a36Sopenharmony_ci#define PFX DRIVER_NAME ": " 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define PCF8584_ADDRESS 0x55 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define CONTROL_PIN 0x80 4362306a36Sopenharmony_ci#define CONTROL_ES0 0x40 4462306a36Sopenharmony_ci#define CONTROL_ES1 0x20 4562306a36Sopenharmony_ci#define CONTROL_ES2 0x10 4662306a36Sopenharmony_ci#define CONTROL_ENI 0x08 4762306a36Sopenharmony_ci#define CONTROL_STA 0x04 4862306a36Sopenharmony_ci#define CONTROL_STO 0x02 4962306a36Sopenharmony_ci#define CONTROL_ACK 0x01 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define STATUS_PIN 0x80 5262306a36Sopenharmony_ci#define STATUS_STS 0x20 5362306a36Sopenharmony_ci#define STATUS_BER 0x10 5462306a36Sopenharmony_ci#define STATUS_LRB 0x08 5562306a36Sopenharmony_ci#define STATUS_AD0 0x08 5662306a36Sopenharmony_ci#define STATUS_AAB 0x04 5762306a36Sopenharmony_ci#define STATUS_LAB 0x02 5862306a36Sopenharmony_ci#define STATUS_BB 0x01 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* 6162306a36Sopenharmony_ci * CLK Mode Register. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci#define BUS_CLK_90 0x00 6462306a36Sopenharmony_ci#define BUS_CLK_45 0x01 6562306a36Sopenharmony_ci#define BUS_CLK_11 0x02 6662306a36Sopenharmony_ci#define BUS_CLK_1_5 0x03 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define CLK_3 0x00 6962306a36Sopenharmony_ci#define CLK_4_43 0x10 7062306a36Sopenharmony_ci#define CLK_6 0x14 7162306a36Sopenharmony_ci#define CLK_8 0x18 7262306a36Sopenharmony_ci#define CLK_12 0x1c 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci#define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */ 7562306a36Sopenharmony_ci#define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */ 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci/* Monitor type of i2c child device. 7862306a36Sopenharmony_ci * Firmware definitions. 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_ci#define PCF8584_MAX_CHANNELS 8 8162306a36Sopenharmony_ci#define PCF8584_GLOBALADDR_TYPE 6 /* global address monitor */ 8262306a36Sopenharmony_ci#define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */ 8362306a36Sopenharmony_ci#define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */ 8462306a36Sopenharmony_ci#define PCF8584_TEMP_TYPE 1 /* temperature monitor*/ 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* Monitor type of i2c child device. 8762306a36Sopenharmony_ci * Driver definitions. 8862306a36Sopenharmony_ci */ 8962306a36Sopenharmony_ci#define ENVCTRL_NOMON 0 9062306a36Sopenharmony_ci#define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */ 9162306a36Sopenharmony_ci#define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */ 9262306a36Sopenharmony_ci#define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */ 9362306a36Sopenharmony_ci#define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperature */ 9462306a36Sopenharmony_ci /* monitor */ 9562306a36Sopenharmony_ci#define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */ 9662306a36Sopenharmony_ci#define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */ 9762306a36Sopenharmony_ci#define ENVCTRL_SCSITEMP_MON 7 /* scsi temperature */ 9862306a36Sopenharmony_ci#define ENVCTRL_GLOBALADDR_MON 8 /* global address */ 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* Child device type. 10162306a36Sopenharmony_ci * Driver definitions. 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_ci#define I2C_ADC 0 /* pcf8591 */ 10462306a36Sopenharmony_ci#define I2C_GPIO 1 /* pcf8571 */ 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/* Data read from child device may need to decode 10762306a36Sopenharmony_ci * through a data table and a scale. 10862306a36Sopenharmony_ci * Translation type as defined by firmware. 10962306a36Sopenharmony_ci */ 11062306a36Sopenharmony_ci#define ENVCTRL_TRANSLATE_NO 0 11162306a36Sopenharmony_ci#define ENVCTRL_TRANSLATE_PARTIAL 1 11262306a36Sopenharmony_ci#define ENVCTRL_TRANSLATE_COMBINED 2 11362306a36Sopenharmony_ci#define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */ 11462306a36Sopenharmony_ci#define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */ 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci/* Driver miscellaneous definitions. */ 11762306a36Sopenharmony_ci#define ENVCTRL_MAX_CPU 4 11862306a36Sopenharmony_ci#define CHANNEL_DESC_SZ 256 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci/* Mask values for combined GlobalAddress/PowerStatus node */ 12162306a36Sopenharmony_ci#define ENVCTRL_GLOBALADDR_ADDR_MASK 0x1F 12262306a36Sopenharmony_ci#define ENVCTRL_GLOBALADDR_PSTAT_MASK 0x60 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci/* Node 0x70 ignored on CompactPCI CP1400/1500 platforms 12562306a36Sopenharmony_ci * (see envctrl_init_i2c_child) 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_ci#define ENVCTRL_CPCI_IGNORED_NODE 0x70 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci#define PCF8584_DATA 0x00 13062306a36Sopenharmony_ci#define PCF8584_CSR 0x01 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS. 13362306a36Sopenharmony_ci * Property of a port or channel as defined by the firmware. 13462306a36Sopenharmony_ci */ 13562306a36Sopenharmony_cistruct pcf8584_channel { 13662306a36Sopenharmony_ci unsigned char chnl_no; 13762306a36Sopenharmony_ci unsigned char io_direction; 13862306a36Sopenharmony_ci unsigned char type; 13962306a36Sopenharmony_ci unsigned char last; 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/* Each child device may have one or more tables of bytes to help decode 14362306a36Sopenharmony_ci * data. Table property as defined by the firmware. 14462306a36Sopenharmony_ci */ 14562306a36Sopenharmony_cistruct pcf8584_tblprop { 14662306a36Sopenharmony_ci unsigned int type; 14762306a36Sopenharmony_ci unsigned int scale; 14862306a36Sopenharmony_ci unsigned int offset; /* offset from the beginning of the table */ 14962306a36Sopenharmony_ci unsigned int size; 15062306a36Sopenharmony_ci}; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/* i2c child */ 15362306a36Sopenharmony_cistruct i2c_child_t { 15462306a36Sopenharmony_ci /* Either ADC or GPIO. */ 15562306a36Sopenharmony_ci unsigned char i2ctype; 15662306a36Sopenharmony_ci unsigned long addr; 15762306a36Sopenharmony_ci struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS]; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* Channel info. */ 16062306a36Sopenharmony_ci unsigned int total_chnls; /* Number of monitor channels. */ 16162306a36Sopenharmony_ci unsigned char fan_mask; /* Byte mask for fan status channels. */ 16262306a36Sopenharmony_ci unsigned char voltage_mask; /* Byte mask for voltage status channels. */ 16362306a36Sopenharmony_ci struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS]; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Properties of all monitor channels. */ 16662306a36Sopenharmony_ci unsigned int total_tbls; /* Number of monitor tables. */ 16762306a36Sopenharmony_ci char *tables; /* Pointer to table(s). */ 16862306a36Sopenharmony_ci char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */ 16962306a36Sopenharmony_ci char mon_type[PCF8584_MAX_CHANNELS]; 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic void __iomem *i2c; 17362306a36Sopenharmony_cistatic struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2]; 17462306a36Sopenharmony_cistatic unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; 17562306a36Sopenharmony_cistatic unsigned int warning_temperature = 0; 17662306a36Sopenharmony_cistatic unsigned int shutdown_temperature = 0; 17762306a36Sopenharmony_cistatic char read_cpu; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci/* Forward declarations. */ 18062306a36Sopenharmony_cistatic struct i2c_child_t *envctrl_get_i2c_child(unsigned char); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci/* Function Description: Test the PIN bit (Pending Interrupt Not) 18362306a36Sopenharmony_ci * to test when serial transmission is completed . 18462306a36Sopenharmony_ci * Return : None. 18562306a36Sopenharmony_ci */ 18662306a36Sopenharmony_cistatic void envtrl_i2c_test_pin(void) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci int limit = 1000000; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci while (--limit > 0) { 19162306a36Sopenharmony_ci if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN)) 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci udelay(1); 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (limit <= 0) 19762306a36Sopenharmony_ci printk(KERN_INFO PFX "Pin status will not clear.\n"); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci/* Function Description: Test busy bit. 20162306a36Sopenharmony_ci * Return : None. 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_cistatic void envctrl_i2c_test_bb(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci int limit = 1000000; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci while (--limit > 0) { 20862306a36Sopenharmony_ci /* Busy bit 0 means busy. */ 20962306a36Sopenharmony_ci if (readb(i2c + PCF8584_CSR) & STATUS_BB) 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci udelay(1); 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (limit <= 0) 21562306a36Sopenharmony_ci printk(KERN_INFO PFX "Busy bit will not clear.\n"); 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci/* Function Description: Send the address for a read access. 21962306a36Sopenharmony_ci * Return : 0 if not acknowledged, otherwise acknowledged. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic int envctrl_i2c_read_addr(unsigned char addr) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci envctrl_i2c_test_bb(); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* Load address. */ 22662306a36Sopenharmony_ci writeb(addr + 1, i2c + PCF8584_DATA); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci envctrl_i2c_test_bb(); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci writeb(OBD_SEND_START, i2c + PCF8584_CSR); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci /* Wait for PIN. */ 23362306a36Sopenharmony_ci envtrl_i2c_test_pin(); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* CSR 0 means acknowledged. */ 23662306a36Sopenharmony_ci if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) { 23762306a36Sopenharmony_ci return readb(i2c + PCF8584_DATA); 23862306a36Sopenharmony_ci } else { 23962306a36Sopenharmony_ci writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); 24062306a36Sopenharmony_ci return 0; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/* Function Description: Send the address for write mode. 24562306a36Sopenharmony_ci * Return : None. 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_cistatic void envctrl_i2c_write_addr(unsigned char addr) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci envctrl_i2c_test_bb(); 25062306a36Sopenharmony_ci writeb(addr, i2c + PCF8584_DATA); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* Generate Start condition. */ 25362306a36Sopenharmony_ci writeb(OBD_SEND_START, i2c + PCF8584_CSR); 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci/* Function Description: Read 1 byte of data from addr 25762306a36Sopenharmony_ci * set by envctrl_i2c_read_addr() 25862306a36Sopenharmony_ci * Return : Data from address set by envctrl_i2c_read_addr(). 25962306a36Sopenharmony_ci */ 26062306a36Sopenharmony_cistatic unsigned char envctrl_i2c_read_data(void) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci envtrl_i2c_test_pin(); 26362306a36Sopenharmony_ci writeb(CONTROL_ES0, i2c + PCF8584_CSR); /* Send neg ack. */ 26462306a36Sopenharmony_ci return readb(i2c + PCF8584_DATA); 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci/* Function Description: Instruct the device which port to read data from. 26862306a36Sopenharmony_ci * Return : None. 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_cistatic void envctrl_i2c_write_data(unsigned char port) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci envtrl_i2c_test_pin(); 27362306a36Sopenharmony_ci writeb(port, i2c + PCF8584_DATA); 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci/* Function Description: Generate Stop condition after last byte is sent. 27762306a36Sopenharmony_ci * Return : None. 27862306a36Sopenharmony_ci */ 27962306a36Sopenharmony_cistatic void envctrl_i2c_stop(void) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci envtrl_i2c_test_pin(); 28262306a36Sopenharmony_ci writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci/* Function Description: Read adc device. 28662306a36Sopenharmony_ci * Return : Data at address and port. 28762306a36Sopenharmony_ci */ 28862306a36Sopenharmony_cistatic unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci /* Send address. */ 29162306a36Sopenharmony_ci envctrl_i2c_write_addr(addr); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci /* Setup port to read. */ 29462306a36Sopenharmony_ci envctrl_i2c_write_data(port); 29562306a36Sopenharmony_ci envctrl_i2c_stop(); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* Read port. */ 29862306a36Sopenharmony_ci envctrl_i2c_read_addr(addr); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* Do a single byte read and send stop. */ 30162306a36Sopenharmony_ci envctrl_i2c_read_data(); 30262306a36Sopenharmony_ci envctrl_i2c_stop(); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return readb(i2c + PCF8584_DATA); 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci/* Function Description: Read gpio device. 30862306a36Sopenharmony_ci * Return : Data at address. 30962306a36Sopenharmony_ci */ 31062306a36Sopenharmony_cistatic unsigned char envctrl_i2c_read_8574(unsigned char addr) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci unsigned char rd; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci envctrl_i2c_read_addr(addr); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* Do a single byte read and send stop. */ 31762306a36Sopenharmony_ci rd = envctrl_i2c_read_data(); 31862306a36Sopenharmony_ci envctrl_i2c_stop(); 31962306a36Sopenharmony_ci return rd; 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci/* Function Description: Decode data read from an adc device using firmware 32362306a36Sopenharmony_ci * table. 32462306a36Sopenharmony_ci * Return: Number of read bytes. Data is stored in bufdata in ascii format. 32562306a36Sopenharmony_ci */ 32662306a36Sopenharmony_cistatic int envctrl_i2c_data_translate(unsigned char data, int translate_type, 32762306a36Sopenharmony_ci int scale, char *tbl, char *bufdata) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci int len = 0; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci switch (translate_type) { 33262306a36Sopenharmony_ci case ENVCTRL_TRANSLATE_NO: 33362306a36Sopenharmony_ci /* No decode necessary. */ 33462306a36Sopenharmony_ci len = 1; 33562306a36Sopenharmony_ci bufdata[0] = data; 33662306a36Sopenharmony_ci break; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci case ENVCTRL_TRANSLATE_FULL: 33962306a36Sopenharmony_ci /* Decode this way: data = table[data]. */ 34062306a36Sopenharmony_ci len = 1; 34162306a36Sopenharmony_ci bufdata[0] = tbl[data]; 34262306a36Sopenharmony_ci break; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci case ENVCTRL_TRANSLATE_SCALE: 34562306a36Sopenharmony_ci /* Decode this way: data = table[data]/scale */ 34662306a36Sopenharmony_ci sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale)); 34762306a36Sopenharmony_ci len = strlen(bufdata); 34862306a36Sopenharmony_ci bufdata[len - 1] = bufdata[len - 2]; 34962306a36Sopenharmony_ci bufdata[len - 2] = '.'; 35062306a36Sopenharmony_ci break; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci default: 35362306a36Sopenharmony_ci break; 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci return len; 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci/* Function Description: Read cpu-related data such as cpu temperature, voltage. 36062306a36Sopenharmony_ci * Return: Number of read bytes. Data is stored in bufdata in ascii format. 36162306a36Sopenharmony_ci */ 36262306a36Sopenharmony_cistatic int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild, 36362306a36Sopenharmony_ci char mon_type, unsigned char *bufdata) 36462306a36Sopenharmony_ci{ 36562306a36Sopenharmony_ci unsigned char data; 36662306a36Sopenharmony_ci int i, j = -1; 36762306a36Sopenharmony_ci char *tbl; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci /* Find the right monitor type and channel. */ 37062306a36Sopenharmony_ci for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { 37162306a36Sopenharmony_ci if (pchild->mon_type[i] == mon_type) { 37262306a36Sopenharmony_ci if (++j == cpu) { 37362306a36Sopenharmony_ci break; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if (j != cpu) 37962306a36Sopenharmony_ci return 0; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci /* Read data from address and port. */ 38262306a36Sopenharmony_ci data = envctrl_i2c_read_8591((unsigned char)pchild->addr, 38362306a36Sopenharmony_ci (unsigned char)pchild->chnl_array[i].chnl_no); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* Find decoding table. */ 38662306a36Sopenharmony_ci tbl = pchild->tables + pchild->tblprop_array[i].offset; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, 38962306a36Sopenharmony_ci pchild->tblprop_array[i].scale, 39062306a36Sopenharmony_ci tbl, bufdata); 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci/* Function Description: Read noncpu-related data such as motherboard 39462306a36Sopenharmony_ci * temperature. 39562306a36Sopenharmony_ci * Return: Number of read bytes. Data is stored in bufdata in ascii format. 39662306a36Sopenharmony_ci */ 39762306a36Sopenharmony_cistatic int envctrl_read_noncpu_info(struct i2c_child_t *pchild, 39862306a36Sopenharmony_ci char mon_type, unsigned char *bufdata) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci unsigned char data; 40162306a36Sopenharmony_ci int i; 40262306a36Sopenharmony_ci char *tbl = NULL; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { 40562306a36Sopenharmony_ci if (pchild->mon_type[i] == mon_type) 40662306a36Sopenharmony_ci break; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci if (i >= PCF8584_MAX_CHANNELS) 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci /* Read data from address and port. */ 41362306a36Sopenharmony_ci data = envctrl_i2c_read_8591((unsigned char)pchild->addr, 41462306a36Sopenharmony_ci (unsigned char)pchild->chnl_array[i].chnl_no); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* Find decoding table. */ 41762306a36Sopenharmony_ci tbl = pchild->tables + pchild->tblprop_array[i].offset; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, 42062306a36Sopenharmony_ci pchild->tblprop_array[i].scale, 42162306a36Sopenharmony_ci tbl, bufdata); 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci/* Function Description: Read fan status. 42562306a36Sopenharmony_ci * Return : Always 1 byte. Status stored in bufdata. 42662306a36Sopenharmony_ci */ 42762306a36Sopenharmony_cistatic int envctrl_i2c_fan_status(struct i2c_child_t *pchild, 42862306a36Sopenharmony_ci unsigned char data, 42962306a36Sopenharmony_ci char *bufdata) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci unsigned char tmp, ret = 0; 43262306a36Sopenharmony_ci int i, j = 0; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci tmp = data & pchild->fan_mask; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (tmp == pchild->fan_mask) { 43762306a36Sopenharmony_ci /* All bits are on. All fans are functioning. */ 43862306a36Sopenharmony_ci ret = ENVCTRL_ALL_FANS_GOOD; 43962306a36Sopenharmony_ci } else if (tmp == 0) { 44062306a36Sopenharmony_ci /* No bits are on. No fans are functioning. */ 44162306a36Sopenharmony_ci ret = ENVCTRL_ALL_FANS_BAD; 44262306a36Sopenharmony_ci } else { 44362306a36Sopenharmony_ci /* Go through all channels, mark 'on' the matched bits. 44462306a36Sopenharmony_ci * Notice that fan_mask may have discontiguous bits but 44562306a36Sopenharmony_ci * return mask are always contiguous. For example if we 44662306a36Sopenharmony_ci * monitor 4 fans at channels 0,1,2,4, the return mask 44762306a36Sopenharmony_ci * should be 00010000 if only fan at channel 4 is working. 44862306a36Sopenharmony_ci */ 44962306a36Sopenharmony_ci for (i = 0; i < PCF8584_MAX_CHANNELS;i++) { 45062306a36Sopenharmony_ci if (pchild->fan_mask & chnls_mask[i]) { 45162306a36Sopenharmony_ci if (!(chnls_mask[i] & tmp)) 45262306a36Sopenharmony_ci ret |= chnls_mask[j]; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci j++; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci bufdata[0] = ret; 46062306a36Sopenharmony_ci return 1; 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci/* Function Description: Read global addressing line. 46462306a36Sopenharmony_ci * Return : Always 1 byte. Status stored in bufdata. 46562306a36Sopenharmony_ci */ 46662306a36Sopenharmony_cistatic int envctrl_i2c_globaladdr(struct i2c_child_t *pchild, 46762306a36Sopenharmony_ci unsigned char data, 46862306a36Sopenharmony_ci char *bufdata) 46962306a36Sopenharmony_ci{ 47062306a36Sopenharmony_ci /* Translatation table is not necessary, as global 47162306a36Sopenharmony_ci * addr is the integer value of the GA# bits. 47262306a36Sopenharmony_ci * 47362306a36Sopenharmony_ci * NOTE: MSB is documented as zero, but I see it as '1' always.... 47462306a36Sopenharmony_ci * 47562306a36Sopenharmony_ci * ----------------------------------------------- 47662306a36Sopenharmony_ci * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 | 47762306a36Sopenharmony_ci * ----------------------------------------------- 47862306a36Sopenharmony_ci * GA0 - GA4 integer value of Global Address (backplane slot#) 47962306a36Sopenharmony_ci * DEG 0 = cPCI Power supply output is starting to degrade 48062306a36Sopenharmony_ci * 1 = cPCI Power supply output is OK 48162306a36Sopenharmony_ci * FAL 0 = cPCI Power supply has failed 48262306a36Sopenharmony_ci * 1 = cPCI Power supply output is OK 48362306a36Sopenharmony_ci */ 48462306a36Sopenharmony_ci bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK); 48562306a36Sopenharmony_ci return 1; 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci/* Function Description: Read standard voltage and power supply status. 48962306a36Sopenharmony_ci * Return : Always 1 byte. Status stored in bufdata. 49062306a36Sopenharmony_ci */ 49162306a36Sopenharmony_cistatic unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild, 49262306a36Sopenharmony_ci unsigned char data, 49362306a36Sopenharmony_ci char *bufdata) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci unsigned char tmp, ret = 0; 49662306a36Sopenharmony_ci int i, j = 0; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci tmp = data & pchild->voltage_mask; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci /* Two channels are used to monitor voltage and power supply. */ 50162306a36Sopenharmony_ci if (tmp == pchild->voltage_mask) { 50262306a36Sopenharmony_ci /* All bits are on. Voltage and power supply are okay. */ 50362306a36Sopenharmony_ci ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD; 50462306a36Sopenharmony_ci } else if (tmp == 0) { 50562306a36Sopenharmony_ci /* All bits are off. Voltage and power supply are bad */ 50662306a36Sopenharmony_ci ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD; 50762306a36Sopenharmony_ci } else { 50862306a36Sopenharmony_ci /* Either voltage or power supply has problem. */ 50962306a36Sopenharmony_ci for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { 51062306a36Sopenharmony_ci if (pchild->voltage_mask & chnls_mask[i]) { 51162306a36Sopenharmony_ci j++; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* Break out when there is a mismatch. */ 51462306a36Sopenharmony_ci if (!(chnls_mask[i] & tmp)) 51562306a36Sopenharmony_ci break; 51662306a36Sopenharmony_ci } 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci /* Make a wish that hardware will always use the 52062306a36Sopenharmony_ci * first channel for voltage and the second for 52162306a36Sopenharmony_ci * power supply. 52262306a36Sopenharmony_ci */ 52362306a36Sopenharmony_ci if (j == 1) 52462306a36Sopenharmony_ci ret = ENVCTRL_VOLTAGE_BAD; 52562306a36Sopenharmony_ci else 52662306a36Sopenharmony_ci ret = ENVCTRL_POWERSUPPLY_BAD; 52762306a36Sopenharmony_ci } 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci bufdata[0] = ret; 53062306a36Sopenharmony_ci return 1; 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci/* Function Description: Read a byte from /dev/envctrl. Mapped to user read(). 53462306a36Sopenharmony_ci * Return: Number of read bytes. 0 for error. 53562306a36Sopenharmony_ci */ 53662306a36Sopenharmony_cistatic ssize_t 53762306a36Sopenharmony_cienvctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) 53862306a36Sopenharmony_ci{ 53962306a36Sopenharmony_ci struct i2c_child_t *pchild; 54062306a36Sopenharmony_ci unsigned char data[10]; 54162306a36Sopenharmony_ci int ret = 0; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* Get the type of read as decided in ioctl() call. 54462306a36Sopenharmony_ci * Find the appropriate i2c child. 54562306a36Sopenharmony_ci * Get the data and put back to the user buffer. 54662306a36Sopenharmony_ci */ 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci switch ((int)(long)file->private_data) { 54962306a36Sopenharmony_ci case ENVCTRL_RD_WARNING_TEMPERATURE: 55062306a36Sopenharmony_ci if (warning_temperature == 0) 55162306a36Sopenharmony_ci return 0; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci data[0] = (unsigned char)(warning_temperature); 55462306a36Sopenharmony_ci ret = 1; 55562306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 55662306a36Sopenharmony_ci ret = -EFAULT; 55762306a36Sopenharmony_ci break; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: 56062306a36Sopenharmony_ci if (shutdown_temperature == 0) 56162306a36Sopenharmony_ci return 0; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci data[0] = (unsigned char)(shutdown_temperature); 56462306a36Sopenharmony_ci ret = 1; 56562306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 56662306a36Sopenharmony_ci ret = -EFAULT; 56762306a36Sopenharmony_ci break; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci case ENVCTRL_RD_MTHRBD_TEMPERATURE: 57062306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON))) 57162306a36Sopenharmony_ci return 0; 57262306a36Sopenharmony_ci ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data); 57362306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 57462306a36Sopenharmony_ci ret = -EFAULT; 57562306a36Sopenharmony_ci break; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci case ENVCTRL_RD_CPU_TEMPERATURE: 57862306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) 57962306a36Sopenharmony_ci return 0; 58062306a36Sopenharmony_ci ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci /* Reset cpu to the default cpu0. */ 58362306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 58462306a36Sopenharmony_ci ret = -EFAULT; 58562306a36Sopenharmony_ci break; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci case ENVCTRL_RD_CPU_VOLTAGE: 58862306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON))) 58962306a36Sopenharmony_ci return 0; 59062306a36Sopenharmony_ci ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci /* Reset cpu to the default cpu0. */ 59362306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 59462306a36Sopenharmony_ci ret = -EFAULT; 59562306a36Sopenharmony_ci break; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci case ENVCTRL_RD_SCSI_TEMPERATURE: 59862306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON))) 59962306a36Sopenharmony_ci return 0; 60062306a36Sopenharmony_ci ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data); 60162306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 60262306a36Sopenharmony_ci ret = -EFAULT; 60362306a36Sopenharmony_ci break; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci case ENVCTRL_RD_ETHERNET_TEMPERATURE: 60662306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON))) 60762306a36Sopenharmony_ci return 0; 60862306a36Sopenharmony_ci ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data); 60962306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 61062306a36Sopenharmony_ci ret = -EFAULT; 61162306a36Sopenharmony_ci break; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci case ENVCTRL_RD_FAN_STATUS: 61462306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON))) 61562306a36Sopenharmony_ci return 0; 61662306a36Sopenharmony_ci data[0] = envctrl_i2c_read_8574(pchild->addr); 61762306a36Sopenharmony_ci ret = envctrl_i2c_fan_status(pchild,data[0], data); 61862306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 61962306a36Sopenharmony_ci ret = -EFAULT; 62062306a36Sopenharmony_ci break; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci case ENVCTRL_RD_GLOBALADDRESS: 62362306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) 62462306a36Sopenharmony_ci return 0; 62562306a36Sopenharmony_ci data[0] = envctrl_i2c_read_8574(pchild->addr); 62662306a36Sopenharmony_ci ret = envctrl_i2c_globaladdr(pchild, data[0], data); 62762306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 62862306a36Sopenharmony_ci ret = -EFAULT; 62962306a36Sopenharmony_ci break; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci case ENVCTRL_RD_VOLTAGE_STATUS: 63262306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON))) 63362306a36Sopenharmony_ci /* If voltage monitor not present, check for CPCI equivalent */ 63462306a36Sopenharmony_ci if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) 63562306a36Sopenharmony_ci return 0; 63662306a36Sopenharmony_ci data[0] = envctrl_i2c_read_8574(pchild->addr); 63762306a36Sopenharmony_ci ret = envctrl_i2c_voltage_status(pchild, data[0], data); 63862306a36Sopenharmony_ci if (copy_to_user(buf, data, ret)) 63962306a36Sopenharmony_ci ret = -EFAULT; 64062306a36Sopenharmony_ci break; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci default: 64362306a36Sopenharmony_ci break; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci } 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci return ret; 64862306a36Sopenharmony_ci} 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci/* Function Description: Command what to read. Mapped to user ioctl(). 65162306a36Sopenharmony_ci * Return: Gives 0 for implemented commands, -EINVAL otherwise. 65262306a36Sopenharmony_ci */ 65362306a36Sopenharmony_cistatic long 65462306a36Sopenharmony_cienvctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 65562306a36Sopenharmony_ci{ 65662306a36Sopenharmony_ci char __user *infobuf; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci switch (cmd) { 65962306a36Sopenharmony_ci case ENVCTRL_RD_WARNING_TEMPERATURE: 66062306a36Sopenharmony_ci case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: 66162306a36Sopenharmony_ci case ENVCTRL_RD_MTHRBD_TEMPERATURE: 66262306a36Sopenharmony_ci case ENVCTRL_RD_FAN_STATUS: 66362306a36Sopenharmony_ci case ENVCTRL_RD_VOLTAGE_STATUS: 66462306a36Sopenharmony_ci case ENVCTRL_RD_ETHERNET_TEMPERATURE: 66562306a36Sopenharmony_ci case ENVCTRL_RD_SCSI_TEMPERATURE: 66662306a36Sopenharmony_ci case ENVCTRL_RD_GLOBALADDRESS: 66762306a36Sopenharmony_ci file->private_data = (void *)(long)cmd; 66862306a36Sopenharmony_ci break; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci case ENVCTRL_RD_CPU_TEMPERATURE: 67162306a36Sopenharmony_ci case ENVCTRL_RD_CPU_VOLTAGE: 67262306a36Sopenharmony_ci /* Check to see if application passes in any cpu number, 67362306a36Sopenharmony_ci * the default is cpu0. 67462306a36Sopenharmony_ci */ 67562306a36Sopenharmony_ci infobuf = (char __user *) arg; 67662306a36Sopenharmony_ci if (infobuf == NULL) { 67762306a36Sopenharmony_ci read_cpu = 0; 67862306a36Sopenharmony_ci }else { 67962306a36Sopenharmony_ci get_user(read_cpu, infobuf); 68062306a36Sopenharmony_ci } 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci /* Save the command for use when reading. */ 68362306a36Sopenharmony_ci file->private_data = (void *)(long)cmd; 68462306a36Sopenharmony_ci break; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci default: 68762306a36Sopenharmony_ci return -EINVAL; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci return 0; 69162306a36Sopenharmony_ci} 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci/* Function Description: open device. Mapped to user open(). 69462306a36Sopenharmony_ci * Return: Always 0. 69562306a36Sopenharmony_ci */ 69662306a36Sopenharmony_cistatic int 69762306a36Sopenharmony_cienvctrl_open(struct inode *inode, struct file *file) 69862306a36Sopenharmony_ci{ 69962306a36Sopenharmony_ci file->private_data = NULL; 70062306a36Sopenharmony_ci return 0; 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci/* Function Description: Open device. Mapped to user close(). 70462306a36Sopenharmony_ci * Return: Always 0. 70562306a36Sopenharmony_ci */ 70662306a36Sopenharmony_cistatic int 70762306a36Sopenharmony_cienvctrl_release(struct inode *inode, struct file *file) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci return 0; 71062306a36Sopenharmony_ci} 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_cistatic const struct file_operations envctrl_fops = { 71362306a36Sopenharmony_ci .owner = THIS_MODULE, 71462306a36Sopenharmony_ci .read = envctrl_read, 71562306a36Sopenharmony_ci .unlocked_ioctl = envctrl_ioctl, 71662306a36Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 71762306a36Sopenharmony_ci .open = envctrl_open, 71862306a36Sopenharmony_ci .release = envctrl_release, 71962306a36Sopenharmony_ci .llseek = noop_llseek, 72062306a36Sopenharmony_ci}; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_cistatic struct miscdevice envctrl_dev = { 72362306a36Sopenharmony_ci ENVCTRL_MINOR, 72462306a36Sopenharmony_ci "envctrl", 72562306a36Sopenharmony_ci &envctrl_fops 72662306a36Sopenharmony_ci}; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci/* Function Description: Set monitor type based on firmware description. 72962306a36Sopenharmony_ci * Return: None. 73062306a36Sopenharmony_ci */ 73162306a36Sopenharmony_cistatic void envctrl_set_mon(struct i2c_child_t *pchild, 73262306a36Sopenharmony_ci const char *chnl_desc, 73362306a36Sopenharmony_ci int chnl_no) 73462306a36Sopenharmony_ci{ 73562306a36Sopenharmony_ci /* Firmware only has temperature type. It does not distinguish 73662306a36Sopenharmony_ci * different kinds of temperatures. We use channel description 73762306a36Sopenharmony_ci * to disinguish them. 73862306a36Sopenharmony_ci */ 73962306a36Sopenharmony_ci if (!(strcmp(chnl_desc,"temp,cpu")) || 74062306a36Sopenharmony_ci !(strcmp(chnl_desc,"temp,cpu0")) || 74162306a36Sopenharmony_ci !(strcmp(chnl_desc,"temp,cpu1")) || 74262306a36Sopenharmony_ci !(strcmp(chnl_desc,"temp,cpu2")) || 74362306a36Sopenharmony_ci !(strcmp(chnl_desc,"temp,cpu3"))) 74462306a36Sopenharmony_ci pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON; 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci if (!(strcmp(chnl_desc,"vddcore,cpu0")) || 74762306a36Sopenharmony_ci !(strcmp(chnl_desc,"vddcore,cpu1")) || 74862306a36Sopenharmony_ci !(strcmp(chnl_desc,"vddcore,cpu2")) || 74962306a36Sopenharmony_ci !(strcmp(chnl_desc,"vddcore,cpu3"))) 75062306a36Sopenharmony_ci pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON; 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci if (!(strcmp(chnl_desc,"temp,motherboard"))) 75362306a36Sopenharmony_ci pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON; 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci if (!(strcmp(chnl_desc,"temp,scsi"))) 75662306a36Sopenharmony_ci pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON; 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci if (!(strcmp(chnl_desc,"temp,ethernet"))) 75962306a36Sopenharmony_ci pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON; 76062306a36Sopenharmony_ci} 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci/* Function Description: Initialize monitor channel with channel desc, 76362306a36Sopenharmony_ci * decoding tables, monitor type, optional properties. 76462306a36Sopenharmony_ci * Return: None. 76562306a36Sopenharmony_ci */ 76662306a36Sopenharmony_cistatic void envctrl_init_adc(struct i2c_child_t *pchild, struct device_node *dp) 76762306a36Sopenharmony_ci{ 76862306a36Sopenharmony_ci int i = 0, len; 76962306a36Sopenharmony_ci const char *pos; 77062306a36Sopenharmony_ci const unsigned int *pval; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* Firmware describe channels into a stream separated by a '\0'. */ 77362306a36Sopenharmony_ci pos = of_get_property(dp, "channels-description", &len); 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci while (len > 0) { 77662306a36Sopenharmony_ci int l = strlen(pos) + 1; 77762306a36Sopenharmony_ci envctrl_set_mon(pchild, pos, i++); 77862306a36Sopenharmony_ci len -= l; 77962306a36Sopenharmony_ci pos += l; 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci /* Get optional properties. */ 78362306a36Sopenharmony_ci pval = of_get_property(dp, "warning-temp", NULL); 78462306a36Sopenharmony_ci if (pval) 78562306a36Sopenharmony_ci warning_temperature = *pval; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci pval = of_get_property(dp, "shutdown-temp", NULL); 78862306a36Sopenharmony_ci if (pval) 78962306a36Sopenharmony_ci shutdown_temperature = *pval; 79062306a36Sopenharmony_ci} 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci/* Function Description: Initialize child device monitoring fan status. 79362306a36Sopenharmony_ci * Return: None. 79462306a36Sopenharmony_ci */ 79562306a36Sopenharmony_cistatic void envctrl_init_fanstat(struct i2c_child_t *pchild) 79662306a36Sopenharmony_ci{ 79762306a36Sopenharmony_ci int i; 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci /* Go through all channels and set up the mask. */ 80062306a36Sopenharmony_ci for (i = 0; i < pchild->total_chnls; i++) 80162306a36Sopenharmony_ci pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci /* We only need to know if this child has fan status monitored. 80462306a36Sopenharmony_ci * We don't care which channels since we have the mask already. 80562306a36Sopenharmony_ci */ 80662306a36Sopenharmony_ci pchild->mon_type[0] = ENVCTRL_FANSTAT_MON; 80762306a36Sopenharmony_ci} 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci/* Function Description: Initialize child device for global addressing line. 81062306a36Sopenharmony_ci * Return: None. 81162306a36Sopenharmony_ci */ 81262306a36Sopenharmony_cistatic void envctrl_init_globaladdr(struct i2c_child_t *pchild) 81362306a36Sopenharmony_ci{ 81462306a36Sopenharmony_ci int i; 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci /* Voltage/PowerSupply monitoring is piggybacked 81762306a36Sopenharmony_ci * with Global Address on CompactPCI. See comments 81862306a36Sopenharmony_ci * within envctrl_i2c_globaladdr for bit assignments. 81962306a36Sopenharmony_ci * 82062306a36Sopenharmony_ci * The mask is created here by assigning mask bits to each 82162306a36Sopenharmony_ci * bit position that represents PCF8584_VOLTAGE_TYPE data. 82262306a36Sopenharmony_ci * Channel numbers are not consecutive within the globaladdr 82362306a36Sopenharmony_ci * node (why?), so we use the actual counter value as chnls_mask 82462306a36Sopenharmony_ci * index instead of the chnl_array[x].chnl_no value. 82562306a36Sopenharmony_ci * 82662306a36Sopenharmony_ci * NOTE: This loop could be replaced with a constant representing 82762306a36Sopenharmony_ci * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK). 82862306a36Sopenharmony_ci */ 82962306a36Sopenharmony_ci for (i = 0; i < pchild->total_chnls; i++) { 83062306a36Sopenharmony_ci if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) { 83162306a36Sopenharmony_ci pchild->voltage_mask |= chnls_mask[i]; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci } 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci /* We only need to know if this child has global addressing 83662306a36Sopenharmony_ci * line monitored. We don't care which channels since we know 83762306a36Sopenharmony_ci * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK). 83862306a36Sopenharmony_ci */ 83962306a36Sopenharmony_ci pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON; 84062306a36Sopenharmony_ci} 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci/* Initialize child device monitoring voltage status. */ 84362306a36Sopenharmony_cistatic void envctrl_init_voltage_status(struct i2c_child_t *pchild) 84462306a36Sopenharmony_ci{ 84562306a36Sopenharmony_ci int i; 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_ci /* Go through all channels and set up the mask. */ 84862306a36Sopenharmony_ci for (i = 0; i < pchild->total_chnls; i++) 84962306a36Sopenharmony_ci pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci /* We only need to know if this child has voltage status monitored. 85262306a36Sopenharmony_ci * We don't care which channels since we have the mask already. 85362306a36Sopenharmony_ci */ 85462306a36Sopenharmony_ci pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON; 85562306a36Sopenharmony_ci} 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci/* Function Description: Initialize i2c child device. 85862306a36Sopenharmony_ci * Return: None. 85962306a36Sopenharmony_ci */ 86062306a36Sopenharmony_cistatic void envctrl_init_i2c_child(struct device_node *dp, 86162306a36Sopenharmony_ci struct i2c_child_t *pchild) 86262306a36Sopenharmony_ci{ 86362306a36Sopenharmony_ci int len, i, tbls_size = 0; 86462306a36Sopenharmony_ci const void *pval; 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci /* Get device address. */ 86762306a36Sopenharmony_ci pval = of_get_property(dp, "reg", &len); 86862306a36Sopenharmony_ci memcpy(&pchild->addr, pval, len); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci /* Get tables property. Read firmware temperature tables. */ 87162306a36Sopenharmony_ci pval = of_get_property(dp, "translation", &len); 87262306a36Sopenharmony_ci if (pval && len > 0) { 87362306a36Sopenharmony_ci memcpy(pchild->tblprop_array, pval, len); 87462306a36Sopenharmony_ci pchild->total_tbls = len / sizeof(struct pcf8584_tblprop); 87562306a36Sopenharmony_ci for (i = 0; i < pchild->total_tbls; i++) { 87662306a36Sopenharmony_ci if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) { 87762306a36Sopenharmony_ci tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset; 87862306a36Sopenharmony_ci } 87962306a36Sopenharmony_ci } 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci pchild->tables = kmalloc(tbls_size, GFP_KERNEL); 88262306a36Sopenharmony_ci if (pchild->tables == NULL){ 88362306a36Sopenharmony_ci printk(KERN_ERR PFX "Failed to allocate table.\n"); 88462306a36Sopenharmony_ci return; 88562306a36Sopenharmony_ci } 88662306a36Sopenharmony_ci pval = of_get_property(dp, "tables", &len); 88762306a36Sopenharmony_ci if (!pval || len <= 0) { 88862306a36Sopenharmony_ci printk(KERN_ERR PFX "Failed to get table.\n"); 88962306a36Sopenharmony_ci return; 89062306a36Sopenharmony_ci } 89162306a36Sopenharmony_ci memcpy(pchild->tables, pval, len); 89262306a36Sopenharmony_ci } 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci /* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04) 89562306a36Sopenharmony_ci * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is 89662306a36Sopenharmony_ci * "For Factory Use Only." 89762306a36Sopenharmony_ci * 89862306a36Sopenharmony_ci * We ignore the node on these platforms by assigning the 89962306a36Sopenharmony_ci * 'NULL' monitor type. 90062306a36Sopenharmony_ci */ 90162306a36Sopenharmony_ci if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) { 90262306a36Sopenharmony_ci struct device_node *root_node; 90362306a36Sopenharmony_ci int len; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci root_node = of_find_node_by_path("/"); 90662306a36Sopenharmony_ci if (of_node_name_eq(root_node, "SUNW,UltraSPARC-IIi-cEngine")) { 90762306a36Sopenharmony_ci for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) { 90862306a36Sopenharmony_ci pchild->mon_type[len] = ENVCTRL_NOMON; 90962306a36Sopenharmony_ci } 91062306a36Sopenharmony_ci of_node_put(root_node); 91162306a36Sopenharmony_ci return; 91262306a36Sopenharmony_ci } 91362306a36Sopenharmony_ci of_node_put(root_node); 91462306a36Sopenharmony_ci } 91562306a36Sopenharmony_ci 91662306a36Sopenharmony_ci /* Get the monitor channels. */ 91762306a36Sopenharmony_ci pval = of_get_property(dp, "channels-in-use", &len); 91862306a36Sopenharmony_ci memcpy(pchild->chnl_array, pval, len); 91962306a36Sopenharmony_ci pchild->total_chnls = len / sizeof(struct pcf8584_channel); 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci for (i = 0; i < pchild->total_chnls; i++) { 92262306a36Sopenharmony_ci switch (pchild->chnl_array[i].type) { 92362306a36Sopenharmony_ci case PCF8584_TEMP_TYPE: 92462306a36Sopenharmony_ci envctrl_init_adc(pchild, dp); 92562306a36Sopenharmony_ci break; 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci case PCF8584_GLOBALADDR_TYPE: 92862306a36Sopenharmony_ci envctrl_init_globaladdr(pchild); 92962306a36Sopenharmony_ci i = pchild->total_chnls; 93062306a36Sopenharmony_ci break; 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci case PCF8584_FANSTAT_TYPE: 93362306a36Sopenharmony_ci envctrl_init_fanstat(pchild); 93462306a36Sopenharmony_ci i = pchild->total_chnls; 93562306a36Sopenharmony_ci break; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci case PCF8584_VOLTAGE_TYPE: 93862306a36Sopenharmony_ci if (pchild->i2ctype == I2C_ADC) { 93962306a36Sopenharmony_ci envctrl_init_adc(pchild,dp); 94062306a36Sopenharmony_ci } else { 94162306a36Sopenharmony_ci envctrl_init_voltage_status(pchild); 94262306a36Sopenharmony_ci } 94362306a36Sopenharmony_ci i = pchild->total_chnls; 94462306a36Sopenharmony_ci break; 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci default: 94762306a36Sopenharmony_ci break; 94862306a36Sopenharmony_ci } 94962306a36Sopenharmony_ci } 95062306a36Sopenharmony_ci} 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci/* Function Description: Search the child device list for a device. 95362306a36Sopenharmony_ci * Return : The i2c child if found. NULL otherwise. 95462306a36Sopenharmony_ci */ 95562306a36Sopenharmony_cistatic struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type) 95662306a36Sopenharmony_ci{ 95762306a36Sopenharmony_ci int i, j; 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) { 96062306a36Sopenharmony_ci for (j = 0; j < PCF8584_MAX_CHANNELS; j++) { 96162306a36Sopenharmony_ci if (i2c_childlist[i].mon_type[j] == mon_type) { 96262306a36Sopenharmony_ci return (struct i2c_child_t *)(&(i2c_childlist[i])); 96362306a36Sopenharmony_ci } 96462306a36Sopenharmony_ci } 96562306a36Sopenharmony_ci } 96662306a36Sopenharmony_ci return NULL; 96762306a36Sopenharmony_ci} 96862306a36Sopenharmony_ci 96962306a36Sopenharmony_cistatic void envctrl_do_shutdown(void) 97062306a36Sopenharmony_ci{ 97162306a36Sopenharmony_ci static int inprog = 0; 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci if (inprog != 0) 97462306a36Sopenharmony_ci return; 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci inprog = 1; 97762306a36Sopenharmony_ci printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n"); 97862306a36Sopenharmony_ci orderly_poweroff(true); 97962306a36Sopenharmony_ci} 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_cistatic struct task_struct *kenvctrld_task; 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_cistatic int kenvctrld(void *__unused) 98462306a36Sopenharmony_ci{ 98562306a36Sopenharmony_ci int poll_interval; 98662306a36Sopenharmony_ci int whichcpu; 98762306a36Sopenharmony_ci char tempbuf[10]; 98862306a36Sopenharmony_ci struct i2c_child_t *cputemp; 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_ci if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) { 99162306a36Sopenharmony_ci printk(KERN_ERR PFX 99262306a36Sopenharmony_ci "kenvctrld unable to monitor CPU temp-- exiting\n"); 99362306a36Sopenharmony_ci return -ENODEV; 99462306a36Sopenharmony_ci } 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci poll_interval = 5000; /* TODO env_mon_interval */ 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci printk(KERN_INFO PFX "%s starting...\n", current->comm); 99962306a36Sopenharmony_ci for (;;) { 100062306a36Sopenharmony_ci msleep_interruptible(poll_interval); 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci if (kthread_should_stop()) 100362306a36Sopenharmony_ci break; 100462306a36Sopenharmony_ci 100562306a36Sopenharmony_ci for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) { 100662306a36Sopenharmony_ci if (0 < envctrl_read_cpu_info(whichcpu, cputemp, 100762306a36Sopenharmony_ci ENVCTRL_CPUTEMP_MON, 100862306a36Sopenharmony_ci tempbuf)) { 100962306a36Sopenharmony_ci if (tempbuf[0] >= shutdown_temperature) { 101062306a36Sopenharmony_ci printk(KERN_CRIT 101162306a36Sopenharmony_ci "%s: WARNING: CPU%i temperature %i C meets or exceeds "\ 101262306a36Sopenharmony_ci "shutdown threshold %i C\n", 101362306a36Sopenharmony_ci current->comm, whichcpu, 101462306a36Sopenharmony_ci tempbuf[0], shutdown_temperature); 101562306a36Sopenharmony_ci envctrl_do_shutdown(); 101662306a36Sopenharmony_ci } 101762306a36Sopenharmony_ci } 101862306a36Sopenharmony_ci } 101962306a36Sopenharmony_ci } 102062306a36Sopenharmony_ci printk(KERN_INFO PFX "%s exiting...\n", current->comm); 102162306a36Sopenharmony_ci return 0; 102262306a36Sopenharmony_ci} 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_cistatic int envctrl_probe(struct platform_device *op) 102562306a36Sopenharmony_ci{ 102662306a36Sopenharmony_ci struct device_node *dp; 102762306a36Sopenharmony_ci int index, err; 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci if (i2c) 103062306a36Sopenharmony_ci return -EINVAL; 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_ci i2c = of_ioremap(&op->resource[0], 0, 0x2, DRIVER_NAME); 103362306a36Sopenharmony_ci if (!i2c) 103462306a36Sopenharmony_ci return -ENOMEM; 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci index = 0; 103762306a36Sopenharmony_ci dp = op->dev.of_node->child; 103862306a36Sopenharmony_ci while (dp) { 103962306a36Sopenharmony_ci if (of_node_name_eq(dp, "gpio")) { 104062306a36Sopenharmony_ci i2c_childlist[index].i2ctype = I2C_GPIO; 104162306a36Sopenharmony_ci envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); 104262306a36Sopenharmony_ci } else if (of_node_name_eq(dp, "adc")) { 104362306a36Sopenharmony_ci i2c_childlist[index].i2ctype = I2C_ADC; 104462306a36Sopenharmony_ci envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); 104562306a36Sopenharmony_ci } 104662306a36Sopenharmony_ci 104762306a36Sopenharmony_ci dp = dp->sibling; 104862306a36Sopenharmony_ci } 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_ci /* Set device address. */ 105162306a36Sopenharmony_ci writeb(CONTROL_PIN, i2c + PCF8584_CSR); 105262306a36Sopenharmony_ci writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA); 105362306a36Sopenharmony_ci 105462306a36Sopenharmony_ci /* Set system clock and SCL frequencies. */ 105562306a36Sopenharmony_ci writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR); 105662306a36Sopenharmony_ci writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA); 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci /* Enable serial interface. */ 105962306a36Sopenharmony_ci writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR); 106062306a36Sopenharmony_ci udelay(200); 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci /* Register the device as a minor miscellaneous device. */ 106362306a36Sopenharmony_ci err = misc_register(&envctrl_dev); 106462306a36Sopenharmony_ci if (err) { 106562306a36Sopenharmony_ci printk(KERN_ERR PFX "Unable to get misc minor %d\n", 106662306a36Sopenharmony_ci envctrl_dev.minor); 106762306a36Sopenharmony_ci goto out_iounmap; 106862306a36Sopenharmony_ci } 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci /* Note above traversal routine post-incremented 'i' to accommodate 107162306a36Sopenharmony_ci * a next child device, so we decrement before reverse-traversal of 107262306a36Sopenharmony_ci * child devices. 107362306a36Sopenharmony_ci */ 107462306a36Sopenharmony_ci printk(KERN_INFO PFX "Initialized "); 107562306a36Sopenharmony_ci for (--index; index >= 0; --index) { 107662306a36Sopenharmony_ci printk("[%s 0x%lx]%s", 107762306a36Sopenharmony_ci (I2C_ADC == i2c_childlist[index].i2ctype) ? "adc" : 107862306a36Sopenharmony_ci ((I2C_GPIO == i2c_childlist[index].i2ctype) ? "gpio" : "unknown"), 107962306a36Sopenharmony_ci i2c_childlist[index].addr, (0 == index) ? "\n" : " "); 108062306a36Sopenharmony_ci } 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); 108362306a36Sopenharmony_ci if (IS_ERR(kenvctrld_task)) { 108462306a36Sopenharmony_ci err = PTR_ERR(kenvctrld_task); 108562306a36Sopenharmony_ci goto out_deregister; 108662306a36Sopenharmony_ci } 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci return 0; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ciout_deregister: 109162306a36Sopenharmony_ci misc_deregister(&envctrl_dev); 109262306a36Sopenharmony_ciout_iounmap: 109362306a36Sopenharmony_ci of_iounmap(&op->resource[0], i2c, 0x2); 109462306a36Sopenharmony_ci for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) 109562306a36Sopenharmony_ci kfree(i2c_childlist[index].tables); 109662306a36Sopenharmony_ci 109762306a36Sopenharmony_ci return err; 109862306a36Sopenharmony_ci} 109962306a36Sopenharmony_ci 110062306a36Sopenharmony_cistatic int envctrl_remove(struct platform_device *op) 110162306a36Sopenharmony_ci{ 110262306a36Sopenharmony_ci int index; 110362306a36Sopenharmony_ci 110462306a36Sopenharmony_ci kthread_stop(kenvctrld_task); 110562306a36Sopenharmony_ci 110662306a36Sopenharmony_ci of_iounmap(&op->resource[0], i2c, 0x2); 110762306a36Sopenharmony_ci misc_deregister(&envctrl_dev); 110862306a36Sopenharmony_ci 110962306a36Sopenharmony_ci for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) 111062306a36Sopenharmony_ci kfree(i2c_childlist[index].tables); 111162306a36Sopenharmony_ci 111262306a36Sopenharmony_ci return 0; 111362306a36Sopenharmony_ci} 111462306a36Sopenharmony_ci 111562306a36Sopenharmony_cistatic const struct of_device_id envctrl_match[] = { 111662306a36Sopenharmony_ci { 111762306a36Sopenharmony_ci .name = "i2c", 111862306a36Sopenharmony_ci .compatible = "i2cpcf,8584", 111962306a36Sopenharmony_ci }, 112062306a36Sopenharmony_ci {}, 112162306a36Sopenharmony_ci}; 112262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, envctrl_match); 112362306a36Sopenharmony_ci 112462306a36Sopenharmony_cistatic struct platform_driver envctrl_driver = { 112562306a36Sopenharmony_ci .driver = { 112662306a36Sopenharmony_ci .name = DRIVER_NAME, 112762306a36Sopenharmony_ci .of_match_table = envctrl_match, 112862306a36Sopenharmony_ci }, 112962306a36Sopenharmony_ci .probe = envctrl_probe, 113062306a36Sopenharmony_ci .remove = envctrl_remove, 113162306a36Sopenharmony_ci}; 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_cimodule_platform_driver(envctrl_driver); 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1136