109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26b8c90f2SDavid S. Miller /* envctrl.c: Temperature and Fan monitoring on Machines providing it.
31da177e4SLinus Torvalds *
41da177e4SLinus Torvalds * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be)
51da177e4SLinus Torvalds * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com)
61da177e4SLinus Torvalds * VT - The implementation is to support Sun Microelectronics (SME) platform
71da177e4SLinus Torvalds * environment monitoring. SME platforms use pcf8584 as the i2c bus
81da177e4SLinus Torvalds * controller to access pcf8591 (8-bit A/D and D/A converter) and
91da177e4SLinus Torvalds * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface).
101da177e4SLinus Torvalds * At board level, it follows SME Firmware I2C Specification. Reference:
111da177e4SLinus Torvalds * http://www-eu2.semiconductors.com/pip/PCF8584P
121da177e4SLinus Torvalds * http://www-eu2.semiconductors.com/pip/PCF8574AP
131da177e4SLinus Torvalds * http://www-eu2.semiconductors.com/pip/PCF8591P
141da177e4SLinus Torvalds *
151da177e4SLinus Torvalds * EB - Added support for CP1500 Global Address and PS/Voltage monitoring.
161da177e4SLinus Torvalds * Eric Brower <ebrower@usa.net>
171da177e4SLinus Torvalds *
181da177e4SLinus Torvalds * DB - Audit every copy_to_user in envctrl_read.
191da177e4SLinus Torvalds * Daniele Bellucci <bellucda@tiscali.it>
201da177e4SLinus Torvalds */
211da177e4SLinus Torvalds
221da177e4SLinus Torvalds #include <linux/module.h>
23218b29e0SChristoph Hellwig #include <linux/kthread.h>
241da177e4SLinus Torvalds #include <linux/delay.h>
251da177e4SLinus Torvalds #include <linux/ioport.h>
261da177e4SLinus Torvalds #include <linux/miscdevice.h>
27872ec648SDavid S. Miller #include <linux/kmod.h>
2810a0a8d4SJeremy Fitzhardinge #include <linux/reboot.h>
295a0e3ad6STejun Heo #include <linux/slab.h>
306b8c90f2SDavid S. Miller #include <linux/of.h>
31*878f2774SRob Herring #include <linux/platform_device.h>
321da177e4SLinus Torvalds
337c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
341da177e4SLinus Torvalds #include <asm/envctrl.h>
35e1a39fbbSDavid S. Miller #include <asm/io.h>
361da177e4SLinus Torvalds
376b8c90f2SDavid S. Miller #define DRIVER_NAME "envctrl"
386b8c90f2SDavid S. Miller #define PFX DRIVER_NAME ": "
396b8c90f2SDavid S. Miller
401da177e4SLinus Torvalds #define PCF8584_ADDRESS 0x55
411da177e4SLinus Torvalds
421da177e4SLinus Torvalds #define CONTROL_PIN 0x80
431da177e4SLinus Torvalds #define CONTROL_ES0 0x40
441da177e4SLinus Torvalds #define CONTROL_ES1 0x20
451da177e4SLinus Torvalds #define CONTROL_ES2 0x10
461da177e4SLinus Torvalds #define CONTROL_ENI 0x08
471da177e4SLinus Torvalds #define CONTROL_STA 0x04
481da177e4SLinus Torvalds #define CONTROL_STO 0x02
491da177e4SLinus Torvalds #define CONTROL_ACK 0x01
501da177e4SLinus Torvalds
511da177e4SLinus Torvalds #define STATUS_PIN 0x80
521da177e4SLinus Torvalds #define STATUS_STS 0x20
531da177e4SLinus Torvalds #define STATUS_BER 0x10
541da177e4SLinus Torvalds #define STATUS_LRB 0x08
551da177e4SLinus Torvalds #define STATUS_AD0 0x08
561da177e4SLinus Torvalds #define STATUS_AAB 0x04
571da177e4SLinus Torvalds #define STATUS_LAB 0x02
581da177e4SLinus Torvalds #define STATUS_BB 0x01
591da177e4SLinus Torvalds
601da177e4SLinus Torvalds /*
611da177e4SLinus Torvalds * CLK Mode Register.
621da177e4SLinus Torvalds */
631da177e4SLinus Torvalds #define BUS_CLK_90 0x00
641da177e4SLinus Torvalds #define BUS_CLK_45 0x01
651da177e4SLinus Torvalds #define BUS_CLK_11 0x02
661da177e4SLinus Torvalds #define BUS_CLK_1_5 0x03
671da177e4SLinus Torvalds
681da177e4SLinus Torvalds #define CLK_3 0x00
691da177e4SLinus Torvalds #define CLK_4_43 0x10
701da177e4SLinus Torvalds #define CLK_6 0x14
711da177e4SLinus Torvalds #define CLK_8 0x18
721da177e4SLinus Torvalds #define CLK_12 0x1c
731da177e4SLinus Torvalds
741da177e4SLinus Torvalds #define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */
751da177e4SLinus Torvalds #define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */
761da177e4SLinus Torvalds
771da177e4SLinus Torvalds /* Monitor type of i2c child device.
781da177e4SLinus Torvalds * Firmware definitions.
791da177e4SLinus Torvalds */
801da177e4SLinus Torvalds #define PCF8584_MAX_CHANNELS 8
811da177e4SLinus Torvalds #define PCF8584_GLOBALADDR_TYPE 6 /* global address monitor */
821da177e4SLinus Torvalds #define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */
831da177e4SLinus Torvalds #define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */
841da177e4SLinus Torvalds #define PCF8584_TEMP_TYPE 1 /* temperature monitor*/
851da177e4SLinus Torvalds
861da177e4SLinus Torvalds /* Monitor type of i2c child device.
871da177e4SLinus Torvalds * Driver definitions.
881da177e4SLinus Torvalds */
891da177e4SLinus Torvalds #define ENVCTRL_NOMON 0
901da177e4SLinus Torvalds #define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */
911da177e4SLinus Torvalds #define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */
921da177e4SLinus Torvalds #define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */
93af901ca1SAndré Goddard Rosa #define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperature */
941da177e4SLinus Torvalds /* monitor */
951da177e4SLinus Torvalds #define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */
961da177e4SLinus Torvalds #define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */
97af901ca1SAndré Goddard Rosa #define ENVCTRL_SCSITEMP_MON 7 /* scsi temperature */
981da177e4SLinus Torvalds #define ENVCTRL_GLOBALADDR_MON 8 /* global address */
991da177e4SLinus Torvalds
1001da177e4SLinus Torvalds /* Child device type.
1011da177e4SLinus Torvalds * Driver definitions.
1021da177e4SLinus Torvalds */
1031da177e4SLinus Torvalds #define I2C_ADC 0 /* pcf8591 */
1041da177e4SLinus Torvalds #define I2C_GPIO 1 /* pcf8571 */
1051da177e4SLinus Torvalds
1061da177e4SLinus Torvalds /* Data read from child device may need to decode
1071da177e4SLinus Torvalds * through a data table and a scale.
1081da177e4SLinus Torvalds * Translation type as defined by firmware.
1091da177e4SLinus Torvalds */
1101da177e4SLinus Torvalds #define ENVCTRL_TRANSLATE_NO 0
1111da177e4SLinus Torvalds #define ENVCTRL_TRANSLATE_PARTIAL 1
1121da177e4SLinus Torvalds #define ENVCTRL_TRANSLATE_COMBINED 2
1131da177e4SLinus Torvalds #define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */
1141da177e4SLinus Torvalds #define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */
1151da177e4SLinus Torvalds
1161da177e4SLinus Torvalds /* Driver miscellaneous definitions. */
1171da177e4SLinus Torvalds #define ENVCTRL_MAX_CPU 4
1181da177e4SLinus Torvalds #define CHANNEL_DESC_SZ 256
1191da177e4SLinus Torvalds
1201da177e4SLinus Torvalds /* Mask values for combined GlobalAddress/PowerStatus node */
1211da177e4SLinus Torvalds #define ENVCTRL_GLOBALADDR_ADDR_MASK 0x1F
1221da177e4SLinus Torvalds #define ENVCTRL_GLOBALADDR_PSTAT_MASK 0x60
1231da177e4SLinus Torvalds
1241da177e4SLinus Torvalds /* Node 0x70 ignored on CompactPCI CP1400/1500 platforms
1251da177e4SLinus Torvalds * (see envctrl_init_i2c_child)
1261da177e4SLinus Torvalds */
1271da177e4SLinus Torvalds #define ENVCTRL_CPCI_IGNORED_NODE 0x70
1281da177e4SLinus Torvalds
1291da177e4SLinus Torvalds #define PCF8584_DATA 0x00
1301da177e4SLinus Torvalds #define PCF8584_CSR 0x01
1311da177e4SLinus Torvalds
1321da177e4SLinus Torvalds /* Each child device can be monitored by up to PCF8584_MAX_CHANNELS.
1331da177e4SLinus Torvalds * Property of a port or channel as defined by the firmware.
1341da177e4SLinus Torvalds */
1351da177e4SLinus Torvalds struct pcf8584_channel {
1361da177e4SLinus Torvalds unsigned char chnl_no;
1371da177e4SLinus Torvalds unsigned char io_direction;
1381da177e4SLinus Torvalds unsigned char type;
1391da177e4SLinus Torvalds unsigned char last;
1401da177e4SLinus Torvalds };
1411da177e4SLinus Torvalds
1421da177e4SLinus Torvalds /* Each child device may have one or more tables of bytes to help decode
1431da177e4SLinus Torvalds * data. Table property as defined by the firmware.
1441da177e4SLinus Torvalds */
1451da177e4SLinus Torvalds struct pcf8584_tblprop {
1461da177e4SLinus Torvalds unsigned int type;
1471da177e4SLinus Torvalds unsigned int scale;
1481da177e4SLinus Torvalds unsigned int offset; /* offset from the beginning of the table */
1491da177e4SLinus Torvalds unsigned int size;
1501da177e4SLinus Torvalds };
1511da177e4SLinus Torvalds
1521da177e4SLinus Torvalds /* i2c child */
1531da177e4SLinus Torvalds struct i2c_child_t {
1541da177e4SLinus Torvalds /* Either ADC or GPIO. */
1551da177e4SLinus Torvalds unsigned char i2ctype;
1561da177e4SLinus Torvalds unsigned long addr;
1571da177e4SLinus Torvalds struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS];
1581da177e4SLinus Torvalds
1591da177e4SLinus Torvalds /* Channel info. */
1601da177e4SLinus Torvalds unsigned int total_chnls; /* Number of monitor channels. */
1611da177e4SLinus Torvalds unsigned char fan_mask; /* Byte mask for fan status channels. */
1621da177e4SLinus Torvalds unsigned char voltage_mask; /* Byte mask for voltage status channels. */
1631da177e4SLinus Torvalds struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS];
1641da177e4SLinus Torvalds
1651da177e4SLinus Torvalds /* Properties of all monitor channels. */
1661da177e4SLinus Torvalds unsigned int total_tbls; /* Number of monitor tables. */
1671da177e4SLinus Torvalds char *tables; /* Pointer to table(s). */
1681da177e4SLinus Torvalds char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */
1691da177e4SLinus Torvalds char mon_type[PCF8584_MAX_CHANNELS];
1701da177e4SLinus Torvalds };
1711da177e4SLinus Torvalds
1721da177e4SLinus Torvalds static void __iomem *i2c;
1731da177e4SLinus Torvalds static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2];
1741da177e4SLinus Torvalds static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
1751da177e4SLinus Torvalds static unsigned int warning_temperature = 0;
1761da177e4SLinus Torvalds static unsigned int shutdown_temperature = 0;
1771da177e4SLinus Torvalds static char read_cpu;
1781da177e4SLinus Torvalds
1791da177e4SLinus Torvalds /* Forward declarations. */
1801da177e4SLinus Torvalds static struct i2c_child_t *envctrl_get_i2c_child(unsigned char);
1811da177e4SLinus Torvalds
1821da177e4SLinus Torvalds /* Function Description: Test the PIN bit (Pending Interrupt Not)
1831da177e4SLinus Torvalds * to test when serial transmission is completed .
1841da177e4SLinus Torvalds * Return : None.
1851da177e4SLinus Torvalds */
envtrl_i2c_test_pin(void)1861da177e4SLinus Torvalds static void envtrl_i2c_test_pin(void)
1871da177e4SLinus Torvalds {
1881da177e4SLinus Torvalds int limit = 1000000;
1891da177e4SLinus Torvalds
1901da177e4SLinus Torvalds while (--limit > 0) {
1911da177e4SLinus Torvalds if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN))
1921da177e4SLinus Torvalds break;
1931da177e4SLinus Torvalds udelay(1);
1941da177e4SLinus Torvalds }
1951da177e4SLinus Torvalds
1961da177e4SLinus Torvalds if (limit <= 0)
1976b8c90f2SDavid S. Miller printk(KERN_INFO PFX "Pin status will not clear.\n");
1981da177e4SLinus Torvalds }
1991da177e4SLinus Torvalds
2001da177e4SLinus Torvalds /* Function Description: Test busy bit.
2011da177e4SLinus Torvalds * Return : None.
2021da177e4SLinus Torvalds */
envctrl_i2c_test_bb(void)2031da177e4SLinus Torvalds static void envctrl_i2c_test_bb(void)
2041da177e4SLinus Torvalds {
2051da177e4SLinus Torvalds int limit = 1000000;
2061da177e4SLinus Torvalds
2071da177e4SLinus Torvalds while (--limit > 0) {
2081da177e4SLinus Torvalds /* Busy bit 0 means busy. */
2091da177e4SLinus Torvalds if (readb(i2c + PCF8584_CSR) & STATUS_BB)
2101da177e4SLinus Torvalds break;
2111da177e4SLinus Torvalds udelay(1);
2121da177e4SLinus Torvalds }
2131da177e4SLinus Torvalds
2141da177e4SLinus Torvalds if (limit <= 0)
2156b8c90f2SDavid S. Miller printk(KERN_INFO PFX "Busy bit will not clear.\n");
2161da177e4SLinus Torvalds }
2171da177e4SLinus Torvalds
2181da177e4SLinus Torvalds /* Function Description: Send the address for a read access.
2191da177e4SLinus Torvalds * Return : 0 if not acknowledged, otherwise acknowledged.
2201da177e4SLinus Torvalds */
envctrl_i2c_read_addr(unsigned char addr)2211da177e4SLinus Torvalds static int envctrl_i2c_read_addr(unsigned char addr)
2221da177e4SLinus Torvalds {
2231da177e4SLinus Torvalds envctrl_i2c_test_bb();
2241da177e4SLinus Torvalds
2251da177e4SLinus Torvalds /* Load address. */
2261da177e4SLinus Torvalds writeb(addr + 1, i2c + PCF8584_DATA);
2271da177e4SLinus Torvalds
2281da177e4SLinus Torvalds envctrl_i2c_test_bb();
2291da177e4SLinus Torvalds
2301da177e4SLinus Torvalds writeb(OBD_SEND_START, i2c + PCF8584_CSR);
2311da177e4SLinus Torvalds
2321da177e4SLinus Torvalds /* Wait for PIN. */
2331da177e4SLinus Torvalds envtrl_i2c_test_pin();
2341da177e4SLinus Torvalds
2351da177e4SLinus Torvalds /* CSR 0 means acknowledged. */
2361da177e4SLinus Torvalds if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) {
2371da177e4SLinus Torvalds return readb(i2c + PCF8584_DATA);
2381da177e4SLinus Torvalds } else {
2391da177e4SLinus Torvalds writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
2401da177e4SLinus Torvalds return 0;
2411da177e4SLinus Torvalds }
2421da177e4SLinus Torvalds }
2431da177e4SLinus Torvalds
2441da177e4SLinus Torvalds /* Function Description: Send the address for write mode.
2451da177e4SLinus Torvalds * Return : None.
2461da177e4SLinus Torvalds */
envctrl_i2c_write_addr(unsigned char addr)2471da177e4SLinus Torvalds static void envctrl_i2c_write_addr(unsigned char addr)
2481da177e4SLinus Torvalds {
2491da177e4SLinus Torvalds envctrl_i2c_test_bb();
2501da177e4SLinus Torvalds writeb(addr, i2c + PCF8584_DATA);
2511da177e4SLinus Torvalds
2521da177e4SLinus Torvalds /* Generate Start condition. */
2531da177e4SLinus Torvalds writeb(OBD_SEND_START, i2c + PCF8584_CSR);
2541da177e4SLinus Torvalds }
2551da177e4SLinus Torvalds
2561da177e4SLinus Torvalds /* Function Description: Read 1 byte of data from addr
2571da177e4SLinus Torvalds * set by envctrl_i2c_read_addr()
2581da177e4SLinus Torvalds * Return : Data from address set by envctrl_i2c_read_addr().
2591da177e4SLinus Torvalds */
envctrl_i2c_read_data(void)2601da177e4SLinus Torvalds static unsigned char envctrl_i2c_read_data(void)
2611da177e4SLinus Torvalds {
2621da177e4SLinus Torvalds envtrl_i2c_test_pin();
2631da177e4SLinus Torvalds writeb(CONTROL_ES0, i2c + PCF8584_CSR); /* Send neg ack. */
2641da177e4SLinus Torvalds return readb(i2c + PCF8584_DATA);
2651da177e4SLinus Torvalds }
2661da177e4SLinus Torvalds
2671da177e4SLinus Torvalds /* Function Description: Instruct the device which port to read data from.
2681da177e4SLinus Torvalds * Return : None.
2691da177e4SLinus Torvalds */
envctrl_i2c_write_data(unsigned char port)2701da177e4SLinus Torvalds static void envctrl_i2c_write_data(unsigned char port)
2711da177e4SLinus Torvalds {
2721da177e4SLinus Torvalds envtrl_i2c_test_pin();
2731da177e4SLinus Torvalds writeb(port, i2c + PCF8584_DATA);
2741da177e4SLinus Torvalds }
2751da177e4SLinus Torvalds
2761da177e4SLinus Torvalds /* Function Description: Generate Stop condition after last byte is sent.
2771da177e4SLinus Torvalds * Return : None.
2781da177e4SLinus Torvalds */
envctrl_i2c_stop(void)2791da177e4SLinus Torvalds static void envctrl_i2c_stop(void)
2801da177e4SLinus Torvalds {
2811da177e4SLinus Torvalds envtrl_i2c_test_pin();
2821da177e4SLinus Torvalds writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
2831da177e4SLinus Torvalds }
2841da177e4SLinus Torvalds
2851da177e4SLinus Torvalds /* Function Description: Read adc device.
2861da177e4SLinus Torvalds * Return : Data at address and port.
2871da177e4SLinus Torvalds */
envctrl_i2c_read_8591(unsigned char addr,unsigned char port)2881da177e4SLinus Torvalds static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port)
2891da177e4SLinus Torvalds {
2901da177e4SLinus Torvalds /* Send address. */
2911da177e4SLinus Torvalds envctrl_i2c_write_addr(addr);
2921da177e4SLinus Torvalds
2931da177e4SLinus Torvalds /* Setup port to read. */
2941da177e4SLinus Torvalds envctrl_i2c_write_data(port);
2951da177e4SLinus Torvalds envctrl_i2c_stop();
2961da177e4SLinus Torvalds
2971da177e4SLinus Torvalds /* Read port. */
2981da177e4SLinus Torvalds envctrl_i2c_read_addr(addr);
2991da177e4SLinus Torvalds
3001da177e4SLinus Torvalds /* Do a single byte read and send stop. */
3011da177e4SLinus Torvalds envctrl_i2c_read_data();
3021da177e4SLinus Torvalds envctrl_i2c_stop();
3031da177e4SLinus Torvalds
3041da177e4SLinus Torvalds return readb(i2c + PCF8584_DATA);
3051da177e4SLinus Torvalds }
3061da177e4SLinus Torvalds
3071da177e4SLinus Torvalds /* Function Description: Read gpio device.
3081da177e4SLinus Torvalds * Return : Data at address.
3091da177e4SLinus Torvalds */
envctrl_i2c_read_8574(unsigned char addr)3101da177e4SLinus Torvalds static unsigned char envctrl_i2c_read_8574(unsigned char addr)
3111da177e4SLinus Torvalds {
3121da177e4SLinus Torvalds unsigned char rd;
3131da177e4SLinus Torvalds
3141da177e4SLinus Torvalds envctrl_i2c_read_addr(addr);
3151da177e4SLinus Torvalds
3161da177e4SLinus Torvalds /* Do a single byte read and send stop. */
3171da177e4SLinus Torvalds rd = envctrl_i2c_read_data();
3181da177e4SLinus Torvalds envctrl_i2c_stop();
3191da177e4SLinus Torvalds return rd;
3201da177e4SLinus Torvalds }
3211da177e4SLinus Torvalds
3221da177e4SLinus Torvalds /* Function Description: Decode data read from an adc device using firmware
3231da177e4SLinus Torvalds * table.
3241da177e4SLinus Torvalds * Return: Number of read bytes. Data is stored in bufdata in ascii format.
3251da177e4SLinus Torvalds */
envctrl_i2c_data_translate(unsigned char data,int translate_type,int scale,char * tbl,char * bufdata)3261da177e4SLinus Torvalds static int envctrl_i2c_data_translate(unsigned char data, int translate_type,
3271da177e4SLinus Torvalds int scale, char *tbl, char *bufdata)
3281da177e4SLinus Torvalds {
3291da177e4SLinus Torvalds int len = 0;
3301da177e4SLinus Torvalds
3311da177e4SLinus Torvalds switch (translate_type) {
3321da177e4SLinus Torvalds case ENVCTRL_TRANSLATE_NO:
3331da177e4SLinus Torvalds /* No decode necessary. */
3341da177e4SLinus Torvalds len = 1;
3351da177e4SLinus Torvalds bufdata[0] = data;
3361da177e4SLinus Torvalds break;
3371da177e4SLinus Torvalds
3381da177e4SLinus Torvalds case ENVCTRL_TRANSLATE_FULL:
3391da177e4SLinus Torvalds /* Decode this way: data = table[data]. */
3401da177e4SLinus Torvalds len = 1;
3411da177e4SLinus Torvalds bufdata[0] = tbl[data];
3421da177e4SLinus Torvalds break;
3431da177e4SLinus Torvalds
3441da177e4SLinus Torvalds case ENVCTRL_TRANSLATE_SCALE:
3451da177e4SLinus Torvalds /* Decode this way: data = table[data]/scale */
3461da177e4SLinus Torvalds sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale));
3471da177e4SLinus Torvalds len = strlen(bufdata);
3481da177e4SLinus Torvalds bufdata[len - 1] = bufdata[len - 2];
3491da177e4SLinus Torvalds bufdata[len - 2] = '.';
3501da177e4SLinus Torvalds break;
3511da177e4SLinus Torvalds
3521da177e4SLinus Torvalds default:
3531da177e4SLinus Torvalds break;
354da201161SPeter Senna Tschudin }
3551da177e4SLinus Torvalds
3561da177e4SLinus Torvalds return len;
3571da177e4SLinus Torvalds }
3581da177e4SLinus Torvalds
3591da177e4SLinus Torvalds /* Function Description: Read cpu-related data such as cpu temperature, voltage.
3601da177e4SLinus Torvalds * Return: Number of read bytes. Data is stored in bufdata in ascii format.
3611da177e4SLinus Torvalds */
envctrl_read_cpu_info(int cpu,struct i2c_child_t * pchild,char mon_type,unsigned char * bufdata)3621da177e4SLinus Torvalds static int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild,
3631da177e4SLinus Torvalds char mon_type, unsigned char *bufdata)
3641da177e4SLinus Torvalds {
3651da177e4SLinus Torvalds unsigned char data;
36651c9daecSJason A. Donenfeld int i, j = -1;
36751c9daecSJason A. Donenfeld char *tbl;
3681da177e4SLinus Torvalds
3691da177e4SLinus Torvalds /* Find the right monitor type and channel. */
3701da177e4SLinus Torvalds for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
3711da177e4SLinus Torvalds if (pchild->mon_type[i] == mon_type) {
3721da177e4SLinus Torvalds if (++j == cpu) {
3731da177e4SLinus Torvalds break;
3741da177e4SLinus Torvalds }
3751da177e4SLinus Torvalds }
3761da177e4SLinus Torvalds }
3771da177e4SLinus Torvalds
3781da177e4SLinus Torvalds if (j != cpu)
3791da177e4SLinus Torvalds return 0;
3801da177e4SLinus Torvalds
3811da177e4SLinus Torvalds /* Read data from address and port. */
3821da177e4SLinus Torvalds data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
3831da177e4SLinus Torvalds (unsigned char)pchild->chnl_array[i].chnl_no);
3841da177e4SLinus Torvalds
3851da177e4SLinus Torvalds /* Find decoding table. */
3861da177e4SLinus Torvalds tbl = pchild->tables + pchild->tblprop_array[i].offset;
3871da177e4SLinus Torvalds
3881da177e4SLinus Torvalds return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
3891da177e4SLinus Torvalds pchild->tblprop_array[i].scale,
3901da177e4SLinus Torvalds tbl, bufdata);
3911da177e4SLinus Torvalds }
3921da177e4SLinus Torvalds
3931da177e4SLinus Torvalds /* Function Description: Read noncpu-related data such as motherboard
3941da177e4SLinus Torvalds * temperature.
3951da177e4SLinus Torvalds * Return: Number of read bytes. Data is stored in bufdata in ascii format.
3961da177e4SLinus Torvalds */
envctrl_read_noncpu_info(struct i2c_child_t * pchild,char mon_type,unsigned char * bufdata)3971da177e4SLinus Torvalds static int envctrl_read_noncpu_info(struct i2c_child_t *pchild,
3981da177e4SLinus Torvalds char mon_type, unsigned char *bufdata)
3991da177e4SLinus Torvalds {
4001da177e4SLinus Torvalds unsigned char data;
4011da177e4SLinus Torvalds int i;
4021da177e4SLinus Torvalds char *tbl = NULL;
4031da177e4SLinus Torvalds
4041da177e4SLinus Torvalds for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
4051da177e4SLinus Torvalds if (pchild->mon_type[i] == mon_type)
4061da177e4SLinus Torvalds break;
4071da177e4SLinus Torvalds }
4081da177e4SLinus Torvalds
4091da177e4SLinus Torvalds if (i >= PCF8584_MAX_CHANNELS)
4101da177e4SLinus Torvalds return 0;
4111da177e4SLinus Torvalds
4121da177e4SLinus Torvalds /* Read data from address and port. */
4131da177e4SLinus Torvalds data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
4141da177e4SLinus Torvalds (unsigned char)pchild->chnl_array[i].chnl_no);
4151da177e4SLinus Torvalds
4161da177e4SLinus Torvalds /* Find decoding table. */
4171da177e4SLinus Torvalds tbl = pchild->tables + pchild->tblprop_array[i].offset;
4181da177e4SLinus Torvalds
4191da177e4SLinus Torvalds return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
4201da177e4SLinus Torvalds pchild->tblprop_array[i].scale,
4211da177e4SLinus Torvalds tbl, bufdata);
4221da177e4SLinus Torvalds }
4231da177e4SLinus Torvalds
4241da177e4SLinus Torvalds /* Function Description: Read fan status.
4251da177e4SLinus Torvalds * Return : Always 1 byte. Status stored in bufdata.
4261da177e4SLinus Torvalds */
envctrl_i2c_fan_status(struct i2c_child_t * pchild,unsigned char data,char * bufdata)4271da177e4SLinus Torvalds static int envctrl_i2c_fan_status(struct i2c_child_t *pchild,
4281da177e4SLinus Torvalds unsigned char data,
4291da177e4SLinus Torvalds char *bufdata)
4301da177e4SLinus Torvalds {
4311da177e4SLinus Torvalds unsigned char tmp, ret = 0;
4321da177e4SLinus Torvalds int i, j = 0;
4331da177e4SLinus Torvalds
4341da177e4SLinus Torvalds tmp = data & pchild->fan_mask;
4351da177e4SLinus Torvalds
4361da177e4SLinus Torvalds if (tmp == pchild->fan_mask) {
4371da177e4SLinus Torvalds /* All bits are on. All fans are functioning. */
4381da177e4SLinus Torvalds ret = ENVCTRL_ALL_FANS_GOOD;
4391da177e4SLinus Torvalds } else if (tmp == 0) {
4401da177e4SLinus Torvalds /* No bits are on. No fans are functioning. */
4411da177e4SLinus Torvalds ret = ENVCTRL_ALL_FANS_BAD;
4421da177e4SLinus Torvalds } else {
4431da177e4SLinus Torvalds /* Go through all channels, mark 'on' the matched bits.
4441da177e4SLinus Torvalds * Notice that fan_mask may have discontiguous bits but
4451da177e4SLinus Torvalds * return mask are always contiguous. For example if we
4461da177e4SLinus Torvalds * monitor 4 fans at channels 0,1,2,4, the return mask
4471da177e4SLinus Torvalds * should be 00010000 if only fan at channel 4 is working.
4481da177e4SLinus Torvalds */
4491da177e4SLinus Torvalds for (i = 0; i < PCF8584_MAX_CHANNELS;i++) {
4501da177e4SLinus Torvalds if (pchild->fan_mask & chnls_mask[i]) {
4511da177e4SLinus Torvalds if (!(chnls_mask[i] & tmp))
4521da177e4SLinus Torvalds ret |= chnls_mask[j];
4531da177e4SLinus Torvalds
4541da177e4SLinus Torvalds j++;
4551da177e4SLinus Torvalds }
4561da177e4SLinus Torvalds }
4571da177e4SLinus Torvalds }
4581da177e4SLinus Torvalds
4591da177e4SLinus Torvalds bufdata[0] = ret;
4601da177e4SLinus Torvalds return 1;
4611da177e4SLinus Torvalds }
4621da177e4SLinus Torvalds
4631da177e4SLinus Torvalds /* Function Description: Read global addressing line.
4641da177e4SLinus Torvalds * Return : Always 1 byte. Status stored in bufdata.
4651da177e4SLinus Torvalds */
envctrl_i2c_globaladdr(struct i2c_child_t * pchild,unsigned char data,char * bufdata)4661da177e4SLinus Torvalds static int envctrl_i2c_globaladdr(struct i2c_child_t *pchild,
4671da177e4SLinus Torvalds unsigned char data,
4681da177e4SLinus Torvalds char *bufdata)
4691da177e4SLinus Torvalds {
4701da177e4SLinus Torvalds /* Translatation table is not necessary, as global
4711da177e4SLinus Torvalds * addr is the integer value of the GA# bits.
4721da177e4SLinus Torvalds *
4731da177e4SLinus Torvalds * NOTE: MSB is documented as zero, but I see it as '1' always....
4741da177e4SLinus Torvalds *
4751da177e4SLinus Torvalds * -----------------------------------------------
4761da177e4SLinus Torvalds * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 |
4771da177e4SLinus Torvalds * -----------------------------------------------
4781da177e4SLinus Torvalds * GA0 - GA4 integer value of Global Address (backplane slot#)
4791da177e4SLinus Torvalds * DEG 0 = cPCI Power supply output is starting to degrade
4801da177e4SLinus Torvalds * 1 = cPCI Power supply output is OK
4811da177e4SLinus Torvalds * FAL 0 = cPCI Power supply has failed
4821da177e4SLinus Torvalds * 1 = cPCI Power supply output is OK
4831da177e4SLinus Torvalds */
4841da177e4SLinus Torvalds bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK);
4851da177e4SLinus Torvalds return 1;
4861da177e4SLinus Torvalds }
4871da177e4SLinus Torvalds
4881da177e4SLinus Torvalds /* Function Description: Read standard voltage and power supply status.
4891da177e4SLinus Torvalds * Return : Always 1 byte. Status stored in bufdata.
4901da177e4SLinus Torvalds */
envctrl_i2c_voltage_status(struct i2c_child_t * pchild,unsigned char data,char * bufdata)4911da177e4SLinus Torvalds static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild,
4921da177e4SLinus Torvalds unsigned char data,
4931da177e4SLinus Torvalds char *bufdata)
4941da177e4SLinus Torvalds {
4951da177e4SLinus Torvalds unsigned char tmp, ret = 0;
4961da177e4SLinus Torvalds int i, j = 0;
4971da177e4SLinus Torvalds
4981da177e4SLinus Torvalds tmp = data & pchild->voltage_mask;
4991da177e4SLinus Torvalds
5001da177e4SLinus Torvalds /* Two channels are used to monitor voltage and power supply. */
5011da177e4SLinus Torvalds if (tmp == pchild->voltage_mask) {
5021da177e4SLinus Torvalds /* All bits are on. Voltage and power supply are okay. */
5031da177e4SLinus Torvalds ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD;
5041da177e4SLinus Torvalds } else if (tmp == 0) {
5051da177e4SLinus Torvalds /* All bits are off. Voltage and power supply are bad */
5061da177e4SLinus Torvalds ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD;
5071da177e4SLinus Torvalds } else {
5081da177e4SLinus Torvalds /* Either voltage or power supply has problem. */
5091da177e4SLinus Torvalds for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
5101da177e4SLinus Torvalds if (pchild->voltage_mask & chnls_mask[i]) {
5111da177e4SLinus Torvalds j++;
5121da177e4SLinus Torvalds
5131da177e4SLinus Torvalds /* Break out when there is a mismatch. */
5141da177e4SLinus Torvalds if (!(chnls_mask[i] & tmp))
5151da177e4SLinus Torvalds break;
5161da177e4SLinus Torvalds }
5171da177e4SLinus Torvalds }
5181da177e4SLinus Torvalds
5191da177e4SLinus Torvalds /* Make a wish that hardware will always use the
5201da177e4SLinus Torvalds * first channel for voltage and the second for
5211da177e4SLinus Torvalds * power supply.
5221da177e4SLinus Torvalds */
5231da177e4SLinus Torvalds if (j == 1)
5241da177e4SLinus Torvalds ret = ENVCTRL_VOLTAGE_BAD;
5251da177e4SLinus Torvalds else
5261da177e4SLinus Torvalds ret = ENVCTRL_POWERSUPPLY_BAD;
5271da177e4SLinus Torvalds }
5281da177e4SLinus Torvalds
5291da177e4SLinus Torvalds bufdata[0] = ret;
5301da177e4SLinus Torvalds return 1;
5311da177e4SLinus Torvalds }
5321da177e4SLinus Torvalds
5331da177e4SLinus Torvalds /* Function Description: Read a byte from /dev/envctrl. Mapped to user read().
5341da177e4SLinus Torvalds * Return: Number of read bytes. 0 for error.
5351da177e4SLinus Torvalds */
5361da177e4SLinus Torvalds static ssize_t
envctrl_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)5371da177e4SLinus Torvalds envctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
5381da177e4SLinus Torvalds {
5391da177e4SLinus Torvalds struct i2c_child_t *pchild;
5401da177e4SLinus Torvalds unsigned char data[10];
5411da177e4SLinus Torvalds int ret = 0;
5421da177e4SLinus Torvalds
5431da177e4SLinus Torvalds /* Get the type of read as decided in ioctl() call.
5441da177e4SLinus Torvalds * Find the appropriate i2c child.
5451da177e4SLinus Torvalds * Get the data and put back to the user buffer.
5461da177e4SLinus Torvalds */
5471da177e4SLinus Torvalds
5481da177e4SLinus Torvalds switch ((int)(long)file->private_data) {
5491da177e4SLinus Torvalds case ENVCTRL_RD_WARNING_TEMPERATURE:
5501da177e4SLinus Torvalds if (warning_temperature == 0)
5511da177e4SLinus Torvalds return 0;
5521da177e4SLinus Torvalds
5531da177e4SLinus Torvalds data[0] = (unsigned char)(warning_temperature);
5541da177e4SLinus Torvalds ret = 1;
5551da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
5561da177e4SLinus Torvalds ret = -EFAULT;
5571da177e4SLinus Torvalds break;
5581da177e4SLinus Torvalds
5591da177e4SLinus Torvalds case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
5601da177e4SLinus Torvalds if (shutdown_temperature == 0)
5611da177e4SLinus Torvalds return 0;
5621da177e4SLinus Torvalds
5631da177e4SLinus Torvalds data[0] = (unsigned char)(shutdown_temperature);
5641da177e4SLinus Torvalds ret = 1;
5651da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
5661da177e4SLinus Torvalds ret = -EFAULT;
5671da177e4SLinus Torvalds break;
5681da177e4SLinus Torvalds
5691da177e4SLinus Torvalds case ENVCTRL_RD_MTHRBD_TEMPERATURE:
5701da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON)))
5711da177e4SLinus Torvalds return 0;
5721da177e4SLinus Torvalds ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data);
5731da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
5741da177e4SLinus Torvalds ret = -EFAULT;
5751da177e4SLinus Torvalds break;
5761da177e4SLinus Torvalds
5771da177e4SLinus Torvalds case ENVCTRL_RD_CPU_TEMPERATURE:
5781da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON)))
5791da177e4SLinus Torvalds return 0;
5801da177e4SLinus Torvalds ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data);
5811da177e4SLinus Torvalds
5821da177e4SLinus Torvalds /* Reset cpu to the default cpu0. */
5831da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
5841da177e4SLinus Torvalds ret = -EFAULT;
5851da177e4SLinus Torvalds break;
5861da177e4SLinus Torvalds
5871da177e4SLinus Torvalds case ENVCTRL_RD_CPU_VOLTAGE:
5881da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON)))
5891da177e4SLinus Torvalds return 0;
5901da177e4SLinus Torvalds ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data);
5911da177e4SLinus Torvalds
5921da177e4SLinus Torvalds /* Reset cpu to the default cpu0. */
5931da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
5941da177e4SLinus Torvalds ret = -EFAULT;
5951da177e4SLinus Torvalds break;
5961da177e4SLinus Torvalds
5971da177e4SLinus Torvalds case ENVCTRL_RD_SCSI_TEMPERATURE:
5981da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON)))
5991da177e4SLinus Torvalds return 0;
6001da177e4SLinus Torvalds ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data);
6011da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
6021da177e4SLinus Torvalds ret = -EFAULT;
6031da177e4SLinus Torvalds break;
6041da177e4SLinus Torvalds
6051da177e4SLinus Torvalds case ENVCTRL_RD_ETHERNET_TEMPERATURE:
6061da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON)))
6071da177e4SLinus Torvalds return 0;
6081da177e4SLinus Torvalds ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data);
6091da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
6101da177e4SLinus Torvalds ret = -EFAULT;
6111da177e4SLinus Torvalds break;
6121da177e4SLinus Torvalds
6131da177e4SLinus Torvalds case ENVCTRL_RD_FAN_STATUS:
6141da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON)))
6151da177e4SLinus Torvalds return 0;
6161da177e4SLinus Torvalds data[0] = envctrl_i2c_read_8574(pchild->addr);
6171da177e4SLinus Torvalds ret = envctrl_i2c_fan_status(pchild,data[0], data);
6181da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
6191da177e4SLinus Torvalds ret = -EFAULT;
6201da177e4SLinus Torvalds break;
6211da177e4SLinus Torvalds
6221da177e4SLinus Torvalds case ENVCTRL_RD_GLOBALADDRESS:
6231da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
6241da177e4SLinus Torvalds return 0;
6251da177e4SLinus Torvalds data[0] = envctrl_i2c_read_8574(pchild->addr);
6261da177e4SLinus Torvalds ret = envctrl_i2c_globaladdr(pchild, data[0], data);
6271da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
6281da177e4SLinus Torvalds ret = -EFAULT;
6291da177e4SLinus Torvalds break;
6301da177e4SLinus Torvalds
6311da177e4SLinus Torvalds case ENVCTRL_RD_VOLTAGE_STATUS:
6321da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON)))
6331da177e4SLinus Torvalds /* If voltage monitor not present, check for CPCI equivalent */
6341da177e4SLinus Torvalds if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
6351da177e4SLinus Torvalds return 0;
6361da177e4SLinus Torvalds data[0] = envctrl_i2c_read_8574(pchild->addr);
6371da177e4SLinus Torvalds ret = envctrl_i2c_voltage_status(pchild, data[0], data);
6381da177e4SLinus Torvalds if (copy_to_user(buf, data, ret))
6391da177e4SLinus Torvalds ret = -EFAULT;
6401da177e4SLinus Torvalds break;
6411da177e4SLinus Torvalds
6421da177e4SLinus Torvalds default:
6431da177e4SLinus Torvalds break;
6441da177e4SLinus Torvalds
645da201161SPeter Senna Tschudin }
6461da177e4SLinus Torvalds
6471da177e4SLinus Torvalds return ret;
6481da177e4SLinus Torvalds }
6491da177e4SLinus Torvalds
6501da177e4SLinus Torvalds /* Function Description: Command what to read. Mapped to user ioctl().
6511da177e4SLinus Torvalds * Return: Gives 0 for implemented commands, -EINVAL otherwise.
6521da177e4SLinus Torvalds */
6531928f8e5SChristoph Hellwig static long
envctrl_ioctl(struct file * file,unsigned int cmd,unsigned long arg)6541928f8e5SChristoph Hellwig envctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
6551da177e4SLinus Torvalds {
6561da177e4SLinus Torvalds char __user *infobuf;
6571da177e4SLinus Torvalds
6581da177e4SLinus Torvalds switch (cmd) {
6591da177e4SLinus Torvalds case ENVCTRL_RD_WARNING_TEMPERATURE:
6601da177e4SLinus Torvalds case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
6611da177e4SLinus Torvalds case ENVCTRL_RD_MTHRBD_TEMPERATURE:
6621da177e4SLinus Torvalds case ENVCTRL_RD_FAN_STATUS:
6631da177e4SLinus Torvalds case ENVCTRL_RD_VOLTAGE_STATUS:
6641da177e4SLinus Torvalds case ENVCTRL_RD_ETHERNET_TEMPERATURE:
6651da177e4SLinus Torvalds case ENVCTRL_RD_SCSI_TEMPERATURE:
6661da177e4SLinus Torvalds case ENVCTRL_RD_GLOBALADDRESS:
6671da177e4SLinus Torvalds file->private_data = (void *)(long)cmd;
6681da177e4SLinus Torvalds break;
6691da177e4SLinus Torvalds
6701da177e4SLinus Torvalds case ENVCTRL_RD_CPU_TEMPERATURE:
6711da177e4SLinus Torvalds case ENVCTRL_RD_CPU_VOLTAGE:
6721da177e4SLinus Torvalds /* Check to see if application passes in any cpu number,
6731da177e4SLinus Torvalds * the default is cpu0.
6741da177e4SLinus Torvalds */
6751da177e4SLinus Torvalds infobuf = (char __user *) arg;
6761da177e4SLinus Torvalds if (infobuf == NULL) {
6771da177e4SLinus Torvalds read_cpu = 0;
6781da177e4SLinus Torvalds }else {
6791da177e4SLinus Torvalds get_user(read_cpu, infobuf);
6801da177e4SLinus Torvalds }
6811da177e4SLinus Torvalds
6821da177e4SLinus Torvalds /* Save the command for use when reading. */
6831da177e4SLinus Torvalds file->private_data = (void *)(long)cmd;
6841da177e4SLinus Torvalds break;
6851da177e4SLinus Torvalds
6861da177e4SLinus Torvalds default:
6871da177e4SLinus Torvalds return -EINVAL;
688da201161SPeter Senna Tschudin }
6891da177e4SLinus Torvalds
6901da177e4SLinus Torvalds return 0;
6911da177e4SLinus Torvalds }
6921da177e4SLinus Torvalds
6931da177e4SLinus Torvalds /* Function Description: open device. Mapped to user open().
6941da177e4SLinus Torvalds * Return: Always 0.
6951da177e4SLinus Torvalds */
6961da177e4SLinus Torvalds static int
envctrl_open(struct inode * inode,struct file * file)6971da177e4SLinus Torvalds envctrl_open(struct inode *inode, struct file *file)
6981da177e4SLinus Torvalds {
6991da177e4SLinus Torvalds file->private_data = NULL;
7001da177e4SLinus Torvalds return 0;
7011da177e4SLinus Torvalds }
7021da177e4SLinus Torvalds
7031da177e4SLinus Torvalds /* Function Description: Open device. Mapped to user close().
7041da177e4SLinus Torvalds * Return: Always 0.
7051da177e4SLinus Torvalds */
7061da177e4SLinus Torvalds static int
envctrl_release(struct inode * inode,struct file * file)7071da177e4SLinus Torvalds envctrl_release(struct inode *inode, struct file *file)
7081da177e4SLinus Torvalds {
7091da177e4SLinus Torvalds return 0;
7101da177e4SLinus Torvalds }
7111da177e4SLinus Torvalds
71200977a59SArjan van de Ven static const struct file_operations envctrl_fops = {
7131da177e4SLinus Torvalds .owner = THIS_MODULE,
7141da177e4SLinus Torvalds .read = envctrl_read,
7151928f8e5SChristoph Hellwig .unlocked_ioctl = envctrl_ioctl,
7161832f2d8SArnd Bergmann .compat_ioctl = compat_ptr_ioctl,
7171da177e4SLinus Torvalds .open = envctrl_open,
7181da177e4SLinus Torvalds .release = envctrl_release,
7196038f373SArnd Bergmann .llseek = noop_llseek,
7201da177e4SLinus Torvalds };
7211da177e4SLinus Torvalds
7221da177e4SLinus Torvalds static struct miscdevice envctrl_dev = {
7231da177e4SLinus Torvalds ENVCTRL_MINOR,
7241da177e4SLinus Torvalds "envctrl",
7251da177e4SLinus Torvalds &envctrl_fops
7261da177e4SLinus Torvalds };
7271da177e4SLinus Torvalds
7281da177e4SLinus Torvalds /* Function Description: Set monitor type based on firmware description.
7291da177e4SLinus Torvalds * Return: None.
7301da177e4SLinus Torvalds */
envctrl_set_mon(struct i2c_child_t * pchild,const char * chnl_desc,int chnl_no)7311da177e4SLinus Torvalds static void envctrl_set_mon(struct i2c_child_t *pchild,
732ccf0dec6SStephen Rothwell const char *chnl_desc,
7331da177e4SLinus Torvalds int chnl_no)
7341da177e4SLinus Torvalds {
7351da177e4SLinus Torvalds /* Firmware only has temperature type. It does not distinguish
7361da177e4SLinus Torvalds * different kinds of temperatures. We use channel description
7371da177e4SLinus Torvalds * to disinguish them.
7381da177e4SLinus Torvalds */
7391da177e4SLinus Torvalds if (!(strcmp(chnl_desc,"temp,cpu")) ||
7401da177e4SLinus Torvalds !(strcmp(chnl_desc,"temp,cpu0")) ||
7411da177e4SLinus Torvalds !(strcmp(chnl_desc,"temp,cpu1")) ||
7421da177e4SLinus Torvalds !(strcmp(chnl_desc,"temp,cpu2")) ||
7431da177e4SLinus Torvalds !(strcmp(chnl_desc,"temp,cpu3")))
7441da177e4SLinus Torvalds pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON;
7451da177e4SLinus Torvalds
7461da177e4SLinus Torvalds if (!(strcmp(chnl_desc,"vddcore,cpu0")) ||
7471da177e4SLinus Torvalds !(strcmp(chnl_desc,"vddcore,cpu1")) ||
7481da177e4SLinus Torvalds !(strcmp(chnl_desc,"vddcore,cpu2")) ||
7491da177e4SLinus Torvalds !(strcmp(chnl_desc,"vddcore,cpu3")))
7501da177e4SLinus Torvalds pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON;
7511da177e4SLinus Torvalds
7521da177e4SLinus Torvalds if (!(strcmp(chnl_desc,"temp,motherboard")))
7531da177e4SLinus Torvalds pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON;
7541da177e4SLinus Torvalds
7551da177e4SLinus Torvalds if (!(strcmp(chnl_desc,"temp,scsi")))
7561da177e4SLinus Torvalds pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON;
7571da177e4SLinus Torvalds
7581da177e4SLinus Torvalds if (!(strcmp(chnl_desc,"temp,ethernet")))
7591da177e4SLinus Torvalds pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON;
7601da177e4SLinus Torvalds }
7611da177e4SLinus Torvalds
7621da177e4SLinus Torvalds /* Function Description: Initialize monitor channel with channel desc,
7631da177e4SLinus Torvalds * decoding tables, monitor type, optional properties.
7641da177e4SLinus Torvalds * Return: None.
7651da177e4SLinus Torvalds */
envctrl_init_adc(struct i2c_child_t * pchild,struct device_node * dp)766690c8fd3SDavid S. Miller static void envctrl_init_adc(struct i2c_child_t *pchild, struct device_node *dp)
7671da177e4SLinus Torvalds {
7681da177e4SLinus Torvalds int i = 0, len;
769ccf0dec6SStephen Rothwell const char *pos;
770ccf0dec6SStephen Rothwell const unsigned int *pval;
7711da177e4SLinus Torvalds
7721da177e4SLinus Torvalds /* Firmware describe channels into a stream separated by a '\0'. */
773690c8fd3SDavid S. Miller pos = of_get_property(dp, "channels-description", &len);
7741da177e4SLinus Torvalds
7751da177e4SLinus Torvalds while (len > 0) {
7761da177e4SLinus Torvalds int l = strlen(pos) + 1;
7771da177e4SLinus Torvalds envctrl_set_mon(pchild, pos, i++);
7781da177e4SLinus Torvalds len -= l;
7791da177e4SLinus Torvalds pos += l;
7801da177e4SLinus Torvalds }
7811da177e4SLinus Torvalds
7821da177e4SLinus Torvalds /* Get optional properties. */
783690c8fd3SDavid S. Miller pval = of_get_property(dp, "warning-temp", NULL);
784690c8fd3SDavid S. Miller if (pval)
785690c8fd3SDavid S. Miller warning_temperature = *pval;
786690c8fd3SDavid S. Miller
787690c8fd3SDavid S. Miller pval = of_get_property(dp, "shutdown-temp", NULL);
788690c8fd3SDavid S. Miller if (pval)
789690c8fd3SDavid S. Miller shutdown_temperature = *pval;
7901da177e4SLinus Torvalds }
7911da177e4SLinus Torvalds
7921da177e4SLinus Torvalds /* Function Description: Initialize child device monitoring fan status.
7931da177e4SLinus Torvalds * Return: None.
7941da177e4SLinus Torvalds */
envctrl_init_fanstat(struct i2c_child_t * pchild)7951da177e4SLinus Torvalds static void envctrl_init_fanstat(struct i2c_child_t *pchild)
7961da177e4SLinus Torvalds {
7971da177e4SLinus Torvalds int i;
7981da177e4SLinus Torvalds
7991da177e4SLinus Torvalds /* Go through all channels and set up the mask. */
8001da177e4SLinus Torvalds for (i = 0; i < pchild->total_chnls; i++)
8011da177e4SLinus Torvalds pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
8021da177e4SLinus Torvalds
8031da177e4SLinus Torvalds /* We only need to know if this child has fan status monitored.
8041da177e4SLinus Torvalds * We don't care which channels since we have the mask already.
8051da177e4SLinus Torvalds */
8061da177e4SLinus Torvalds pchild->mon_type[0] = ENVCTRL_FANSTAT_MON;
8071da177e4SLinus Torvalds }
8081da177e4SLinus Torvalds
8091da177e4SLinus Torvalds /* Function Description: Initialize child device for global addressing line.
8101da177e4SLinus Torvalds * Return: None.
8111da177e4SLinus Torvalds */
envctrl_init_globaladdr(struct i2c_child_t * pchild)8121da177e4SLinus Torvalds static void envctrl_init_globaladdr(struct i2c_child_t *pchild)
8131da177e4SLinus Torvalds {
8141da177e4SLinus Torvalds int i;
8151da177e4SLinus Torvalds
8161da177e4SLinus Torvalds /* Voltage/PowerSupply monitoring is piggybacked
8171da177e4SLinus Torvalds * with Global Address on CompactPCI. See comments
8181da177e4SLinus Torvalds * within envctrl_i2c_globaladdr for bit assignments.
8191da177e4SLinus Torvalds *
8201da177e4SLinus Torvalds * The mask is created here by assigning mask bits to each
8211da177e4SLinus Torvalds * bit position that represents PCF8584_VOLTAGE_TYPE data.
8221da177e4SLinus Torvalds * Channel numbers are not consecutive within the globaladdr
8231da177e4SLinus Torvalds * node (why?), so we use the actual counter value as chnls_mask
8241da177e4SLinus Torvalds * index instead of the chnl_array[x].chnl_no value.
8251da177e4SLinus Torvalds *
8261da177e4SLinus Torvalds * NOTE: This loop could be replaced with a constant representing
8271da177e4SLinus Torvalds * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK).
8281da177e4SLinus Torvalds */
8291da177e4SLinus Torvalds for (i = 0; i < pchild->total_chnls; i++) {
8301da177e4SLinus Torvalds if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) {
8311da177e4SLinus Torvalds pchild->voltage_mask |= chnls_mask[i];
8321da177e4SLinus Torvalds }
8331da177e4SLinus Torvalds }
8341da177e4SLinus Torvalds
8351da177e4SLinus Torvalds /* We only need to know if this child has global addressing
8361da177e4SLinus Torvalds * line monitored. We don't care which channels since we know
8371da177e4SLinus Torvalds * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK).
8381da177e4SLinus Torvalds */
8391da177e4SLinus Torvalds pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON;
8401da177e4SLinus Torvalds }
8411da177e4SLinus Torvalds
8421da177e4SLinus Torvalds /* Initialize child device monitoring voltage status. */
envctrl_init_voltage_status(struct i2c_child_t * pchild)8431da177e4SLinus Torvalds static void envctrl_init_voltage_status(struct i2c_child_t *pchild)
8441da177e4SLinus Torvalds {
8451da177e4SLinus Torvalds int i;
8461da177e4SLinus Torvalds
8471da177e4SLinus Torvalds /* Go through all channels and set up the mask. */
8481da177e4SLinus Torvalds for (i = 0; i < pchild->total_chnls; i++)
8491da177e4SLinus Torvalds pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
8501da177e4SLinus Torvalds
8511da177e4SLinus Torvalds /* We only need to know if this child has voltage status monitored.
8521da177e4SLinus Torvalds * We don't care which channels since we have the mask already.
8531da177e4SLinus Torvalds */
8541da177e4SLinus Torvalds pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON;
8551da177e4SLinus Torvalds }
8561da177e4SLinus Torvalds
8571da177e4SLinus Torvalds /* Function Description: Initialize i2c child device.
8581da177e4SLinus Torvalds * Return: None.
8591da177e4SLinus Torvalds */
envctrl_init_i2c_child(struct device_node * dp,struct i2c_child_t * pchild)8606b8c90f2SDavid S. Miller static void envctrl_init_i2c_child(struct device_node *dp,
8611da177e4SLinus Torvalds struct i2c_child_t *pchild)
8621da177e4SLinus Torvalds {
863690c8fd3SDavid S. Miller int len, i, tbls_size = 0;
864ccf0dec6SStephen Rothwell const void *pval;
8651da177e4SLinus Torvalds
8661da177e4SLinus Torvalds /* Get device address. */
867690c8fd3SDavid S. Miller pval = of_get_property(dp, "reg", &len);
868690c8fd3SDavid S. Miller memcpy(&pchild->addr, pval, len);
8691da177e4SLinus Torvalds
8701da177e4SLinus Torvalds /* Get tables property. Read firmware temperature tables. */
871690c8fd3SDavid S. Miller pval = of_get_property(dp, "translation", &len);
872690c8fd3SDavid S. Miller if (pval && len > 0) {
873690c8fd3SDavid S. Miller memcpy(pchild->tblprop_array, pval, len);
8741da177e4SLinus Torvalds pchild->total_tbls = len / sizeof(struct pcf8584_tblprop);
8751da177e4SLinus Torvalds for (i = 0; i < pchild->total_tbls; i++) {
8761da177e4SLinus Torvalds if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) {
8771da177e4SLinus Torvalds tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset;
8781da177e4SLinus Torvalds }
8791da177e4SLinus Torvalds }
8801da177e4SLinus Torvalds
8811da177e4SLinus Torvalds pchild->tables = kmalloc(tbls_size, GFP_KERNEL);
8821da177e4SLinus Torvalds if (pchild->tables == NULL){
8836b8c90f2SDavid S. Miller printk(KERN_ERR PFX "Failed to allocate table.\n");
8841da177e4SLinus Torvalds return;
8851da177e4SLinus Torvalds }
886690c8fd3SDavid S. Miller pval = of_get_property(dp, "tables", &len);
887690c8fd3SDavid S. Miller if (!pval || len <= 0) {
8886b8c90f2SDavid S. Miller printk(KERN_ERR PFX "Failed to get table.\n");
8891da177e4SLinus Torvalds return;
8901da177e4SLinus Torvalds }
891690c8fd3SDavid S. Miller memcpy(pchild->tables, pval, len);
8921da177e4SLinus Torvalds }
8931da177e4SLinus Torvalds
8941da177e4SLinus Torvalds /* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04)
8951da177e4SLinus Torvalds * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is
8961da177e4SLinus Torvalds * "For Factory Use Only."
8971da177e4SLinus Torvalds *
8981da177e4SLinus Torvalds * We ignore the node on these platforms by assigning the
8991da177e4SLinus Torvalds * 'NULL' monitor type.
9001da177e4SLinus Torvalds */
9011da177e4SLinus Torvalds if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) {
902690c8fd3SDavid S. Miller struct device_node *root_node;
9031da177e4SLinus Torvalds int len;
9041da177e4SLinus Torvalds
905690c8fd3SDavid S. Miller root_node = of_find_node_by_path("/");
90691abe6b2SRob Herring if (of_node_name_eq(root_node, "SUNW,UltraSPARC-IIi-cEngine")) {
9071da177e4SLinus Torvalds for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) {
9081da177e4SLinus Torvalds pchild->mon_type[len] = ENVCTRL_NOMON;
9091da177e4SLinus Torvalds }
9106bd520abSYangtao Li of_node_put(root_node);
9111da177e4SLinus Torvalds return;
9121da177e4SLinus Torvalds }
9136bd520abSYangtao Li of_node_put(root_node);
9141da177e4SLinus Torvalds }
9151da177e4SLinus Torvalds
9161da177e4SLinus Torvalds /* Get the monitor channels. */
917690c8fd3SDavid S. Miller pval = of_get_property(dp, "channels-in-use", &len);
918690c8fd3SDavid S. Miller memcpy(pchild->chnl_array, pval, len);
9191da177e4SLinus Torvalds pchild->total_chnls = len / sizeof(struct pcf8584_channel);
9201da177e4SLinus Torvalds
9211da177e4SLinus Torvalds for (i = 0; i < pchild->total_chnls; i++) {
9221da177e4SLinus Torvalds switch (pchild->chnl_array[i].type) {
9231da177e4SLinus Torvalds case PCF8584_TEMP_TYPE:
924690c8fd3SDavid S. Miller envctrl_init_adc(pchild, dp);
9251da177e4SLinus Torvalds break;
9261da177e4SLinus Torvalds
9271da177e4SLinus Torvalds case PCF8584_GLOBALADDR_TYPE:
9281da177e4SLinus Torvalds envctrl_init_globaladdr(pchild);
9291da177e4SLinus Torvalds i = pchild->total_chnls;
9301da177e4SLinus Torvalds break;
9311da177e4SLinus Torvalds
9321da177e4SLinus Torvalds case PCF8584_FANSTAT_TYPE:
9331da177e4SLinus Torvalds envctrl_init_fanstat(pchild);
9341da177e4SLinus Torvalds i = pchild->total_chnls;
9351da177e4SLinus Torvalds break;
9361da177e4SLinus Torvalds
9371da177e4SLinus Torvalds case PCF8584_VOLTAGE_TYPE:
9381da177e4SLinus Torvalds if (pchild->i2ctype == I2C_ADC) {
939690c8fd3SDavid S. Miller envctrl_init_adc(pchild,dp);
9401da177e4SLinus Torvalds } else {
9411da177e4SLinus Torvalds envctrl_init_voltage_status(pchild);
9421da177e4SLinus Torvalds }
9431da177e4SLinus Torvalds i = pchild->total_chnls;
9441da177e4SLinus Torvalds break;
9451da177e4SLinus Torvalds
9461da177e4SLinus Torvalds default:
9471da177e4SLinus Torvalds break;
948da201161SPeter Senna Tschudin }
9491da177e4SLinus Torvalds }
9501da177e4SLinus Torvalds }
9511da177e4SLinus Torvalds
9521da177e4SLinus Torvalds /* Function Description: Search the child device list for a device.
9531da177e4SLinus Torvalds * Return : The i2c child if found. NULL otherwise.
9541da177e4SLinus Torvalds */
envctrl_get_i2c_child(unsigned char mon_type)9551da177e4SLinus Torvalds static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type)
9561da177e4SLinus Torvalds {
9571da177e4SLinus Torvalds int i, j;
9581da177e4SLinus Torvalds
9591da177e4SLinus Torvalds for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) {
9601da177e4SLinus Torvalds for (j = 0; j < PCF8584_MAX_CHANNELS; j++) {
9611da177e4SLinus Torvalds if (i2c_childlist[i].mon_type[j] == mon_type) {
9621da177e4SLinus Torvalds return (struct i2c_child_t *)(&(i2c_childlist[i]));
9631da177e4SLinus Torvalds }
9641da177e4SLinus Torvalds }
9651da177e4SLinus Torvalds }
9661da177e4SLinus Torvalds return NULL;
9671da177e4SLinus Torvalds }
9681da177e4SLinus Torvalds
envctrl_do_shutdown(void)9691da177e4SLinus Torvalds static void envctrl_do_shutdown(void)
9701da177e4SLinus Torvalds {
9711da177e4SLinus Torvalds static int inprog = 0;
9721da177e4SLinus Torvalds
9731da177e4SLinus Torvalds if (inprog != 0)
9741da177e4SLinus Torvalds return;
9751da177e4SLinus Torvalds
9761da177e4SLinus Torvalds inprog = 1;
9771da177e4SLinus Torvalds printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n");
9787975a9b7SJoel Stanley orderly_poweroff(true);
9791da177e4SLinus Torvalds }
9801da177e4SLinus Torvalds
9811da177e4SLinus Torvalds static struct task_struct *kenvctrld_task;
9821da177e4SLinus Torvalds
kenvctrld(void * __unused)9831da177e4SLinus Torvalds static int kenvctrld(void *__unused)
9841da177e4SLinus Torvalds {
9851da177e4SLinus Torvalds int poll_interval;
9861da177e4SLinus Torvalds int whichcpu;
9871da177e4SLinus Torvalds char tempbuf[10];
9881da177e4SLinus Torvalds struct i2c_child_t *cputemp;
9891da177e4SLinus Torvalds
9901da177e4SLinus Torvalds if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) {
9916b8c90f2SDavid S. Miller printk(KERN_ERR PFX
9926b8c90f2SDavid S. Miller "kenvctrld unable to monitor CPU temp-- exiting\n");
9931da177e4SLinus Torvalds return -ENODEV;
9941da177e4SLinus Torvalds }
9951da177e4SLinus Torvalds
996cb39d263SNishanth Aravamudan poll_interval = 5000; /* TODO env_mon_interval */
9971da177e4SLinus Torvalds
9986b8c90f2SDavid S. Miller printk(KERN_INFO PFX "%s starting...\n", current->comm);
9991da177e4SLinus Torvalds for (;;) {
1000218b29e0SChristoph Hellwig msleep_interruptible(poll_interval);
1001218b29e0SChristoph Hellwig
1002218b29e0SChristoph Hellwig if (kthread_should_stop())
10031da177e4SLinus Torvalds break;
10041da177e4SLinus Torvalds
10051da177e4SLinus Torvalds for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) {
10061da177e4SLinus Torvalds if (0 < envctrl_read_cpu_info(whichcpu, cputemp,
10071da177e4SLinus Torvalds ENVCTRL_CPUTEMP_MON,
10081da177e4SLinus Torvalds tempbuf)) {
10091da177e4SLinus Torvalds if (tempbuf[0] >= shutdown_temperature) {
10101da177e4SLinus Torvalds printk(KERN_CRIT
10111da177e4SLinus Torvalds "%s: WARNING: CPU%i temperature %i C meets or exceeds "\
10121da177e4SLinus Torvalds "shutdown threshold %i C\n",
10131da177e4SLinus Torvalds current->comm, whichcpu,
10141da177e4SLinus Torvalds tempbuf[0], shutdown_temperature);
10151da177e4SLinus Torvalds envctrl_do_shutdown();
10161da177e4SLinus Torvalds }
10171da177e4SLinus Torvalds }
10181da177e4SLinus Torvalds }
10191da177e4SLinus Torvalds }
10206b8c90f2SDavid S. Miller printk(KERN_INFO PFX "%s exiting...\n", current->comm);
10211da177e4SLinus Torvalds return 0;
10221da177e4SLinus Torvalds }
10231da177e4SLinus Torvalds
envctrl_probe(struct platform_device * op)1024082a2004SGreg Kroah-Hartman static int envctrl_probe(struct platform_device *op)
10251da177e4SLinus Torvalds {
10266b8c90f2SDavid S. Miller struct device_node *dp;
10276b8c90f2SDavid S. Miller int index, err;
10281da177e4SLinus Torvalds
10296b8c90f2SDavid S. Miller if (i2c)
10306b8c90f2SDavid S. Miller return -EINVAL;
10316b8c90f2SDavid S. Miller
10326b8c90f2SDavid S. Miller i2c = of_ioremap(&op->resource[0], 0, 0x2, DRIVER_NAME);
10336b8c90f2SDavid S. Miller if (!i2c)
10346b8c90f2SDavid S. Miller return -ENOMEM;
10356b8c90f2SDavid S. Miller
10366b8c90f2SDavid S. Miller index = 0;
103761c7a080SGrant Likely dp = op->dev.of_node->child;
10386b8c90f2SDavid S. Miller while (dp) {
103991abe6b2SRob Herring if (of_node_name_eq(dp, "gpio")) {
10406b8c90f2SDavid S. Miller i2c_childlist[index].i2ctype = I2C_GPIO;
10416b8c90f2SDavid S. Miller envctrl_init_i2c_child(dp, &(i2c_childlist[index++]));
104291abe6b2SRob Herring } else if (of_node_name_eq(dp, "adc")) {
10436b8c90f2SDavid S. Miller i2c_childlist[index].i2ctype = I2C_ADC;
10446b8c90f2SDavid S. Miller envctrl_init_i2c_child(dp, &(i2c_childlist[index++]));
10451da177e4SLinus Torvalds }
10461da177e4SLinus Torvalds
10476b8c90f2SDavid S. Miller dp = dp->sibling;
10481da177e4SLinus Torvalds }
10491da177e4SLinus Torvalds
10501da177e4SLinus Torvalds /* Set device address. */
10511da177e4SLinus Torvalds writeb(CONTROL_PIN, i2c + PCF8584_CSR);
10521da177e4SLinus Torvalds writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA);
10531da177e4SLinus Torvalds
10541da177e4SLinus Torvalds /* Set system clock and SCL frequencies. */
10551da177e4SLinus Torvalds writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR);
10561da177e4SLinus Torvalds writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA);
10571da177e4SLinus Torvalds
10581da177e4SLinus Torvalds /* Enable serial interface. */
10591da177e4SLinus Torvalds writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR);
10601da177e4SLinus Torvalds udelay(200);
10611da177e4SLinus Torvalds
10621da177e4SLinus Torvalds /* Register the device as a minor miscellaneous device. */
10631da177e4SLinus Torvalds err = misc_register(&envctrl_dev);
10641da177e4SLinus Torvalds if (err) {
10656b8c90f2SDavid S. Miller printk(KERN_ERR PFX "Unable to get misc minor %d\n",
10661da177e4SLinus Torvalds envctrl_dev.minor);
10671da177e4SLinus Torvalds goto out_iounmap;
10681da177e4SLinus Torvalds }
10691da177e4SLinus Torvalds
10701da177e4SLinus Torvalds /* Note above traversal routine post-incremented 'i' to accommodate
10711da177e4SLinus Torvalds * a next child device, so we decrement before reverse-traversal of
10721da177e4SLinus Torvalds * child devices.
10731da177e4SLinus Torvalds */
10746b8c90f2SDavid S. Miller printk(KERN_INFO PFX "Initialized ");
10756b8c90f2SDavid S. Miller for (--index; index >= 0; --index) {
10761da177e4SLinus Torvalds printk("[%s 0x%lx]%s",
10776b8c90f2SDavid S. Miller (I2C_ADC == i2c_childlist[index].i2ctype) ? "adc" :
10786b8c90f2SDavid S. Miller ((I2C_GPIO == i2c_childlist[index].i2ctype) ? "gpio" : "unknown"),
10796b8c90f2SDavid S. Miller i2c_childlist[index].addr, (0 == index) ? "\n" : " ");
10801da177e4SLinus Torvalds }
10811da177e4SLinus Torvalds
1082218b29e0SChristoph Hellwig kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
1083218b29e0SChristoph Hellwig if (IS_ERR(kenvctrld_task)) {
108438c1844bSDavid S. Miller err = PTR_ERR(kenvctrld_task);
10851da177e4SLinus Torvalds goto out_deregister;
1086218b29e0SChristoph Hellwig }
10871da177e4SLinus Torvalds
10881da177e4SLinus Torvalds return 0;
10891da177e4SLinus Torvalds
10901da177e4SLinus Torvalds out_deregister:
10911da177e4SLinus Torvalds misc_deregister(&envctrl_dev);
10921da177e4SLinus Torvalds out_iounmap:
10936b8c90f2SDavid S. Miller of_iounmap(&op->resource[0], i2c, 0x2);
10946b8c90f2SDavid S. Miller for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++)
10956b8c90f2SDavid S. Miller kfree(i2c_childlist[index].tables);
10966044ec88SJesper Juhl
10971da177e4SLinus Torvalds return err;
10981da177e4SLinus Torvalds }
10991da177e4SLinus Torvalds
envctrl_remove(struct platform_device * op)1100082a2004SGreg Kroah-Hartman static int envctrl_remove(struct platform_device *op)
11011da177e4SLinus Torvalds {
11026b8c90f2SDavid S. Miller int index;
11031da177e4SLinus Torvalds
1104218b29e0SChristoph Hellwig kthread_stop(kenvctrld_task);
11051da177e4SLinus Torvalds
11066b8c90f2SDavid S. Miller of_iounmap(&op->resource[0], i2c, 0x2);
11071da177e4SLinus Torvalds misc_deregister(&envctrl_dev);
11081da177e4SLinus Torvalds
11096b8c90f2SDavid S. Miller for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++)
11106b8c90f2SDavid S. Miller kfree(i2c_childlist[index].tables);
11116b8c90f2SDavid S. Miller
11126b8c90f2SDavid S. Miller return 0;
11136b8c90f2SDavid S. Miller }
11146b8c90f2SDavid S. Miller
1115fd098316SDavid S. Miller static const struct of_device_id envctrl_match[] = {
11166b8c90f2SDavid S. Miller {
11176b8c90f2SDavid S. Miller .name = "i2c",
11186b8c90f2SDavid S. Miller .compatible = "i2cpcf,8584",
11196b8c90f2SDavid S. Miller },
11206b8c90f2SDavid S. Miller {},
11216b8c90f2SDavid S. Miller };
11226b8c90f2SDavid S. Miller MODULE_DEVICE_TABLE(of, envctrl_match);
11236b8c90f2SDavid S. Miller
11244ebb24f7SGrant Likely static struct platform_driver envctrl_driver = {
11254018294bSGrant Likely .driver = {
11266b8c90f2SDavid S. Miller .name = DRIVER_NAME,
11274018294bSGrant Likely .of_match_table = envctrl_match,
11284018294bSGrant Likely },
11296b8c90f2SDavid S. Miller .probe = envctrl_probe,
1130082a2004SGreg Kroah-Hartman .remove = envctrl_remove,
11316b8c90f2SDavid S. Miller };
11326b8c90f2SDavid S. Miller
1133dbf2b92dSAxel Lin module_platform_driver(envctrl_driver);
11346b8c90f2SDavid S. Miller
11351da177e4SLinus Torvalds MODULE_LICENSE("GPL");
1136