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