Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (26 commits) hwmon: (w83627ehf) Better fix for negative temperature values hwmon: (w83627ehf) Uninline is_word_sized hwmon: (lm75) Document why clones are not detected hwmon: (w83627ehf) Move fan pins check to a separate function hwmon: (w83627ehf) Skip reading unused voltage registers hwmon: (lm75) Add support for Analog Devices ADT75 hwmon: (pmbus_core) Simplify sign extensions hwmon: (pmbus) Add support for Lineage Power DC-DC converters hwmon: (pmbus/ltc2978) Add support for LTC3880 to LTC2978 driver hwmon: (pmbus/ltc2978) Explicit driver for LTC2978 hwmon: (pmbus) Add support for TEMP2 peak attributes hwmon: AD7314 driver (ported from IIO) hwmon: (pmbus) Add support for Intersil power management chips hwmon: (pmbus) Always call _pmbus_read_byte in core driver hwmon: (pmbus) Replace EINVAL return codes with more appropriate errors hwmon: (pmbus) Provide more documentation hwmon/f71882fg: Make the decision wether to register fan attr. per fan hwmon/f71882fg: Add a f71882fg_create_fan_sysfs_files helper function hwmon/f71882fg: Make all fan/pwm attr tables 2 dimensional hwmon: (exynos4_tmu) Remove IRQF_DISABLED ...
This commit is contained in:
		
				commit
				
					
						3cb603284b
					
				
			
		
					 30 changed files with 2755 additions and 331 deletions
				
			
		
							
								
								
									
										25
									
								
								Documentation/hwmon/ad7314
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Documentation/hwmon/ad7314
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					Kernel driver ad7314
 | 
				
			||||||
 | 
					====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Supported chips:
 | 
				
			||||||
 | 
					   * Analog Devices AD7314
 | 
				
			||||||
 | 
					     Prefix: 'ad7314'
 | 
				
			||||||
 | 
					     Datasheet: Publicly available at Analog Devices website.
 | 
				
			||||||
 | 
					   * Analog Devices ADT7301
 | 
				
			||||||
 | 
					     Prefix: 'adt7301'
 | 
				
			||||||
 | 
					     Datasheet: Publicly available at Analog Devices website.
 | 
				
			||||||
 | 
					   * Analog Devices ADT7302
 | 
				
			||||||
 | 
					     Prefix: 'adt7302'
 | 
				
			||||||
 | 
					     Datasheet: Publicly available at Analog Devices website.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Description
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Driver supports the above parts.  The ad7314 has a 10 bit
 | 
				
			||||||
 | 
					sensor with 1lsb = 0.25 degrees centigrade. The adt7301 and
 | 
				
			||||||
 | 
					adt7302 have 14 bit sensors with 1lsb = 0.03125 degrees centigrade.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Notes
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Currently power down mode is not supported.
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,10 @@ Supported chips:
 | 
				
			||||||
    Prefix: 'adm1275'
 | 
					    Prefix: 'adm1275'
 | 
				
			||||||
    Addresses scanned: -
 | 
					    Addresses scanned: -
 | 
				
			||||||
    Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1275.pdf
 | 
					    Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1275.pdf
 | 
				
			||||||
 | 
					  * Analog Devices ADM1276
 | 
				
			||||||
 | 
					    Prefix: 'adm1276'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1276.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
 | 
					Author: Guenter Roeck <guenter.roeck@ericsson.com>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +17,13 @@ Author: Guenter Roeck <guenter.roeck@ericsson.com>
 | 
				
			||||||
Description
 | 
					Description
 | 
				
			||||||
-----------
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This driver supports hardware montoring for Analog Devices ADM1275 Hot-Swap
 | 
					This driver supports hardware montoring for Analog Devices ADM1275 and ADM1276
 | 
				
			||||||
Controller and Digital Power Monitor.
 | 
					Hot-Swap Controller and Digital Power Monitor.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The ADM1275 is a hot-swap controller that allows a circuit board to be removed
 | 
					ADM1275 and ADM1276 are hot-swap controllers that allow a circuit board to be
 | 
				
			||||||
from or inserted into a live backplane. It also features current and voltage
 | 
					removed from or inserted into a live backplane. They also feature current and
 | 
				
			||||||
readback via an integrated 12-bit analog-to-digital converter (ADC), accessed
 | 
					voltage readback via an integrated 12-bit analog-to-digital converter (ADC),
 | 
				
			||||||
using a PMBus. interface.
 | 
					accessed using a PMBus interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The driver is a client driver to the core PMBus driver. Please see
 | 
					The driver is a client driver to the core PMBus driver. Please see
 | 
				
			||||||
Documentation/hwmon/pmbus for details on PMBus client drivers.
 | 
					Documentation/hwmon/pmbus for details on PMBus client drivers.
 | 
				
			||||||
| 
						 | 
					@ -48,17 +52,25 @@ attributes are write-only, all other attributes are read-only.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
in1_label		"vin1" or "vout1" depending on chip variant and
 | 
					in1_label		"vin1" or "vout1" depending on chip variant and
 | 
				
			||||||
			configuration.
 | 
								configuration.
 | 
				
			||||||
in1_input		Measured voltage. From READ_VOUT register.
 | 
					in1_input		Measured voltage.
 | 
				
			||||||
in1_min			Minumum Voltage. From VOUT_UV_WARN_LIMIT register.
 | 
					in1_min			Minumum Voltage.
 | 
				
			||||||
in1_max			Maximum voltage. From VOUT_OV_WARN_LIMIT register.
 | 
					in1_max			Maximum voltage.
 | 
				
			||||||
in1_min_alarm		Voltage low alarm. From VOLTAGE_UV_WARNING status.
 | 
					in1_min_alarm		Voltage low alarm.
 | 
				
			||||||
in1_max_alarm		Voltage high alarm. From VOLTAGE_OV_WARNING status.
 | 
					in1_max_alarm		Voltage high alarm.
 | 
				
			||||||
in1_highest		Historical maximum voltage.
 | 
					in1_highest		Historical maximum voltage.
 | 
				
			||||||
in1_reset_history	Write any value to reset history.
 | 
					in1_reset_history	Write any value to reset history.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
curr1_label		"iout1"
 | 
					curr1_label		"iout1"
 | 
				
			||||||
curr1_input		Measured current. From READ_IOUT register.
 | 
					curr1_input		Measured current.
 | 
				
			||||||
curr1_max		Maximum current. From IOUT_OC_WARN_LIMIT register.
 | 
					curr1_max		Maximum current.
 | 
				
			||||||
curr1_max_alarm		Current high alarm. From IOUT_OC_WARN_LIMIT register.
 | 
					curr1_max_alarm		Current high alarm.
 | 
				
			||||||
 | 
					curr1_lcrit		Critical minimum current. Depending on the chip
 | 
				
			||||||
 | 
								configuration, either curr1_lcrit or curr1_crit is
 | 
				
			||||||
 | 
								supported, but not both.
 | 
				
			||||||
 | 
					curr1_lcrit_alarm	Critical current low alarm.
 | 
				
			||||||
 | 
					curr1_crit		Critical maximum current. Depending on the chip
 | 
				
			||||||
 | 
								configuration, either curr1_lcrit or curr1_crit is
 | 
				
			||||||
 | 
								supported, but not both.
 | 
				
			||||||
 | 
					curr1_crit_alarm	Critical current high alarm.
 | 
				
			||||||
curr1_highest		Historical maximum current.
 | 
					curr1_highest		Historical maximum current.
 | 
				
			||||||
curr1_reset_history	Write any value to reset history.
 | 
					curr1_reset_history	Write any value to reset history.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										81
									
								
								Documentation/hwmon/exynos4_tmu
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Documentation/hwmon/exynos4_tmu
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,81 @@
 | 
				
			||||||
 | 
					Kernel driver exynos4_tmu
 | 
				
			||||||
 | 
					=================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Supported chips:
 | 
				
			||||||
 | 
					* ARM SAMSUNG EXYNOS4 series of SoC
 | 
				
			||||||
 | 
					  Prefix: 'exynos4-tmu'
 | 
				
			||||||
 | 
					  Datasheet: Not publicly available
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Authors: Donggeun Kim <dg77.kim@samsung.com>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Description
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The chip only exposes the measured 8-bit temperature code value
 | 
				
			||||||
 | 
					through a register.
 | 
				
			||||||
 | 
					Temperature can be taken from the temperature code.
 | 
				
			||||||
 | 
					There are three equations converting from temperature to temperature code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The three equations are:
 | 
				
			||||||
 | 
					  1. Two point trimming
 | 
				
			||||||
 | 
						Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  2. One point trimming
 | 
				
			||||||
 | 
						Tc = T + TI1 - 25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  3. No trimming
 | 
				
			||||||
 | 
						Tc = T + 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Tc: Temperature code, T: Temperature,
 | 
				
			||||||
 | 
					  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
 | 
				
			||||||
 | 
					       Temperature code measured at 25 degree Celsius which is unchanged
 | 
				
			||||||
 | 
					  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
 | 
				
			||||||
 | 
					       Temperature code measured at 85 degree Celsius which is unchanged
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
 | 
				
			||||||
 | 
					when temperature exceeds pre-defined levels.
 | 
				
			||||||
 | 
					The maximum number of configurable threshold is four.
 | 
				
			||||||
 | 
					The threshold levels are defined as follows:
 | 
				
			||||||
 | 
					  Level_0: current temperature > trigger_level_0 + threshold
 | 
				
			||||||
 | 
					  Level_1: current temperature > trigger_level_1 + threshold
 | 
				
			||||||
 | 
					  Level_2: current temperature > trigger_level_2 + threshold
 | 
				
			||||||
 | 
					  Level_3: current temperature > trigger_level_3 + threshold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  The threshold and each trigger_level are set
 | 
				
			||||||
 | 
					  through the corresponding registers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When an interrupt occurs, this driver notify user space of
 | 
				
			||||||
 | 
					one of four threshold levels for the interrupt
 | 
				
			||||||
 | 
					through kobject_uevent_env and sysfs_notify functions.
 | 
				
			||||||
 | 
					Although an interrupt condition for level_0 can be set,
 | 
				
			||||||
 | 
					it is not notified to user space through sysfs_notify function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sysfs Interface
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					name		name of the temperature sensor
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_input	temperature
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_max	temperature for level_1 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_crit	temperature for level_2 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_emergency	temperature for level_3 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_max_alarm	alarm for level_1 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_crit_alarm
 | 
				
			||||||
 | 
							alarm for level_2 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp1_emergency_alarm
 | 
				
			||||||
 | 
							alarm for level_3 interrupt
 | 
				
			||||||
 | 
							RO
 | 
				
			||||||
| 
						 | 
					@ -12,26 +12,46 @@ Supported chips:
 | 
				
			||||||
    Addresses scanned: I2C 0x48 - 0x4f
 | 
					    Addresses scanned: I2C 0x48 - 0x4f
 | 
				
			||||||
    Datasheet: Publicly available at the National Semiconductor website
 | 
					    Datasheet: Publicly available at the National Semiconductor website
 | 
				
			||||||
               http://www.national.com/
 | 
					               http://www.national.com/
 | 
				
			||||||
  * Dallas Semiconductor DS75
 | 
					  * Dallas Semiconductor DS75, DS1775
 | 
				
			||||||
    Prefix: 'lm75'
 | 
					    Prefixes: 'ds75', 'ds1775'
 | 
				
			||||||
    Addresses scanned: I2C 0x48 - 0x4f
 | 
					    Addresses scanned: none
 | 
				
			||||||
    Datasheet: Publicly available at the Dallas Semiconductor website
 | 
					 | 
				
			||||||
               http://www.maxim-ic.com/
 | 
					 | 
				
			||||||
  * Dallas Semiconductor DS1775
 | 
					 | 
				
			||||||
    Prefix: 'lm75'
 | 
					 | 
				
			||||||
    Addresses scanned: I2C 0x48 - 0x4f
 | 
					 | 
				
			||||||
    Datasheet: Publicly available at the Dallas Semiconductor website
 | 
					    Datasheet: Publicly available at the Dallas Semiconductor website
 | 
				
			||||||
               http://www.maxim-ic.com/
 | 
					               http://www.maxim-ic.com/
 | 
				
			||||||
  * Maxim MAX6625, MAX6626
 | 
					  * Maxim MAX6625, MAX6626
 | 
				
			||||||
    Prefix: 'lm75'
 | 
					    Prefixes: 'max6625', 'max6626'
 | 
				
			||||||
    Addresses scanned: I2C 0x48 - 0x4b
 | 
					    Addresses scanned: none
 | 
				
			||||||
    Datasheet: Publicly available at the Maxim website
 | 
					    Datasheet: Publicly available at the Maxim website
 | 
				
			||||||
               http://www.maxim-ic.com/
 | 
					               http://www.maxim-ic.com/
 | 
				
			||||||
  * Microchip (TelCom) TCN75
 | 
					  * Microchip (TelCom) TCN75
 | 
				
			||||||
    Prefix: 'lm75'
 | 
					    Prefix: 'lm75'
 | 
				
			||||||
    Addresses scanned: I2C 0x48 - 0x4f
 | 
					    Addresses scanned: none
 | 
				
			||||||
    Datasheet: Publicly available at the Microchip website
 | 
					    Datasheet: Publicly available at the Microchip website
 | 
				
			||||||
               http://www.microchip.com/
 | 
					               http://www.microchip.com/
 | 
				
			||||||
 | 
					  * Microchip MCP9800, MCP9801, MCP9802, MCP9803
 | 
				
			||||||
 | 
					    Prefix: 'mcp980x'
 | 
				
			||||||
 | 
					    Addresses scanned: none
 | 
				
			||||||
 | 
					    Datasheet: Publicly available at the Microchip website
 | 
				
			||||||
 | 
					               http://www.microchip.com/
 | 
				
			||||||
 | 
					  * Analog Devices ADT75
 | 
				
			||||||
 | 
					    Prefix: 'adt75'
 | 
				
			||||||
 | 
					    Addresses scanned: none
 | 
				
			||||||
 | 
					    Datasheet: Publicly available at the Analog Devices website
 | 
				
			||||||
 | 
					               http://www.analog.com/adt75
 | 
				
			||||||
 | 
					  * ST Microelectronics STDS75
 | 
				
			||||||
 | 
					    Prefix: 'stds75'
 | 
				
			||||||
 | 
					    Addresses scanned: none
 | 
				
			||||||
 | 
					    Datasheet: Publicly available at the ST website
 | 
				
			||||||
 | 
					               http://www.st.com/internet/analog/product/121769.jsp
 | 
				
			||||||
 | 
					  * Texas Instruments TMP100, TMP101, TMP105, TMP75, TMP175, TMP275
 | 
				
			||||||
 | 
					    Prefixes: 'tmp100', 'tmp101', 'tmp105', 'tmp175', 'tmp75', 'tmp275'
 | 
				
			||||||
 | 
					    Addresses scanned: none
 | 
				
			||||||
 | 
					    Datasheet: Publicly available at the Texas Instruments website
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp100
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp101
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp105
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp75
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp175
 | 
				
			||||||
 | 
					               http://www.ti.com/product/tmp275
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Author: Frodo Looijaard <frodol@dds.nl>
 | 
					Author: Frodo Looijaard <frodol@dds.nl>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,21 +70,16 @@ range of -55 to +125 degrees.
 | 
				
			||||||
The LM75 only updates its values each 1.5 seconds; reading it more often
 | 
					The LM75 only updates its values each 1.5 seconds; reading it more often
 | 
				
			||||||
will do no harm, but will return 'old' values.
 | 
					will do no harm, but will return 'old' values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The LM75 is usually used in combination with LM78-like chips, to measure
 | 
					The original LM75 was typically used in combination with LM78-like chips
 | 
				
			||||||
the temperature of the processor(s).
 | 
					on PC motherboards, to measure the temperature of the processor(s). Clones
 | 
				
			||||||
 | 
					are now used in various embedded designs.
 | 
				
			||||||
The DS75, DS1775, MAX6625, and MAX6626 are supported as well.
 | 
					 | 
				
			||||||
They are not distinguished from an LM75. While most of these chips
 | 
					 | 
				
			||||||
have three additional bits of accuracy (12 vs. 9 for the LM75),
 | 
					 | 
				
			||||||
the additional bits are not supported. Not only that, but these chips will
 | 
					 | 
				
			||||||
not be detected if not in 9-bit precision mode (use the force parameter if
 | 
					 | 
				
			||||||
needed).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The TCN75 is supported as well, and is not distinguished from an LM75.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
The LM75 is essentially an industry standard; there may be other
 | 
					The LM75 is essentially an industry standard; there may be other
 | 
				
			||||||
LM75 clones not listed here, with or without various enhancements,
 | 
					LM75 clones not listed here, with or without various enhancements,
 | 
				
			||||||
that are supported.
 | 
					that are supported. The clones are not detected by the driver, unless
 | 
				
			||||||
 | 
					they reproduce the exact register tricks of the original LM75, and must
 | 
				
			||||||
 | 
					therefore be instantiated explicitly. The specific enhancements (such as
 | 
				
			||||||
 | 
					higher resolution) are not currently supported by the driver.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The LM77 is not supported, contrary to what we pretended for a long time.
 | 
					The LM77 is not supported, contrary to what we pretended for a long time.
 | 
				
			||||||
Both chips are simply not compatible, value encoding differs.
 | 
					Both chips are simply not compatible, value encoding differs.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										103
									
								
								Documentation/hwmon/ltc2978
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								Documentation/hwmon/ltc2978
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					Kernel driver ltc2978
 | 
				
			||||||
 | 
					=====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Supported chips:
 | 
				
			||||||
 | 
					  * Linear Technology LTC2978
 | 
				
			||||||
 | 
					    Prefix: 'ltc2978'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://cds.linear.com/docs/Datasheet/2978fa.pdf
 | 
				
			||||||
 | 
					  * Linear Technology LTC3880
 | 
				
			||||||
 | 
					    Prefix: 'ltc3880'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://cds.linear.com/docs/Datasheet/3880f.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Author: Guenter Roeck <guenter.roeck@ericsson.com>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Description
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The LTC2978 is an octal power supply monitor, supervisor, sequencer and
 | 
				
			||||||
 | 
					margin controller. The LTC3880 is a dual, PolyPhase DC/DC synchronous
 | 
				
			||||||
 | 
					step-down switching regulator controller.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage Notes
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This driver does not probe for PMBus devices. You will have to instantiate
 | 
				
			||||||
 | 
					devices explicitly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example: the following commands will load the driver for an LTC2978 at address
 | 
				
			||||||
 | 
					0x60 on I2C bus #1:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# modprobe ltc2978
 | 
				
			||||||
 | 
					# echo ltc2978 0x60 > /sys/bus/i2c/devices/i2c-1/new_device
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sysfs attributes
 | 
				
			||||||
 | 
					----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in1_label		"vin"
 | 
				
			||||||
 | 
					in1_input		Measured input voltage.
 | 
				
			||||||
 | 
					in1_min			Minimum input voltage.
 | 
				
			||||||
 | 
					in1_max			Maximum input voltage.
 | 
				
			||||||
 | 
					in1_lcrit		Critical minimum input voltage.
 | 
				
			||||||
 | 
					in1_crit		Critical maximum input voltage.
 | 
				
			||||||
 | 
					in1_min_alarm		Input voltage low alarm.
 | 
				
			||||||
 | 
					in1_max_alarm		Input voltage high alarm.
 | 
				
			||||||
 | 
					in1_lcrit_alarm		Input voltage critical low alarm.
 | 
				
			||||||
 | 
					in1_crit_alarm		Input voltage critical high alarm.
 | 
				
			||||||
 | 
					in1_lowest		Lowest input voltage. LTC2978 only.
 | 
				
			||||||
 | 
					in1_highest		Highest input voltage.
 | 
				
			||||||
 | 
					in1_reset_history	Reset history. Writing into this attribute will reset
 | 
				
			||||||
 | 
								history for all attributes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in[2-9]_label		"vout[1-8]". Channels 3 to 9 on LTC2978 only.
 | 
				
			||||||
 | 
					in[2-9]_input		Measured output voltage.
 | 
				
			||||||
 | 
					in[2-9]_min		Minimum output voltage.
 | 
				
			||||||
 | 
					in[2-9]_max		Maximum output voltage.
 | 
				
			||||||
 | 
					in[2-9]_lcrit		Critical minimum output voltage.
 | 
				
			||||||
 | 
					in[2-9]_crit		Critical maximum output voltage.
 | 
				
			||||||
 | 
					in[2-9]_min_alarm	Output voltage low alarm.
 | 
				
			||||||
 | 
					in[2-9]_max_alarm	Output voltage high alarm.
 | 
				
			||||||
 | 
					in[2-9]_lcrit_alarm	Output voltage critical low alarm.
 | 
				
			||||||
 | 
					in[2-9]_crit_alarm	Output voltage critical high alarm.
 | 
				
			||||||
 | 
					in[2-9]_lowest		Lowest output voltage. LTC2978 only.
 | 
				
			||||||
 | 
					in[2-9]_highest		Lowest output voltage.
 | 
				
			||||||
 | 
					in[2-9]_reset_history	Reset history. Writing into this attribute will reset
 | 
				
			||||||
 | 
								history for all attributes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp[1-3]_input		Measured temperature.
 | 
				
			||||||
 | 
								On LTC2978, only one temperature measurement is
 | 
				
			||||||
 | 
								supported and reflects the internal temperature.
 | 
				
			||||||
 | 
								On LTC3880, temp1 and temp2 report external
 | 
				
			||||||
 | 
								temperatures, and temp3 reports the internal
 | 
				
			||||||
 | 
								temperature.
 | 
				
			||||||
 | 
					temp[1-3]_min		Mimimum temperature.
 | 
				
			||||||
 | 
					temp[1-3]_max		Maximum temperature.
 | 
				
			||||||
 | 
					temp[1-3]_lcrit		Critical low temperature.
 | 
				
			||||||
 | 
					temp[1-3]_crit		Critical high temperature.
 | 
				
			||||||
 | 
					temp[1-3]_min_alarm	Chip temperature low alarm.
 | 
				
			||||||
 | 
					temp[1-3]_max_alarm	Chip temperature high alarm.
 | 
				
			||||||
 | 
					temp[1-3]_lcrit_alarm	Chip temperature critical low alarm.
 | 
				
			||||||
 | 
					temp[1-3]_crit_alarm	Chip temperature critical high alarm.
 | 
				
			||||||
 | 
					temp[1-3]_lowest	Lowest measured temperature. LTC2978 only.
 | 
				
			||||||
 | 
					temp[1-3]_highest	Highest measured temperature.
 | 
				
			||||||
 | 
					temp[1-3]_reset_history	Reset history. Writing into this attribute will reset
 | 
				
			||||||
 | 
								history for all attributes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					power[1-2]_label	"pout[1-2]". LTC3880 only.
 | 
				
			||||||
 | 
					power[1-2]_input	Measured power.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					curr1_label		"iin". LTC3880 only.
 | 
				
			||||||
 | 
					curr1_input		Measured input current.
 | 
				
			||||||
 | 
					curr1_max		Maximum input current.
 | 
				
			||||||
 | 
					curr1_max_alarm		Input current high alarm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					curr[2-3]_label		"iout[1-2]". LTC3880 only.
 | 
				
			||||||
 | 
					curr[2-3]_input		Measured input current.
 | 
				
			||||||
 | 
					curr[2-3]_max		Maximum input current.
 | 
				
			||||||
 | 
					curr[2-3]_crit		Critical input current.
 | 
				
			||||||
 | 
					curr[2-3]_max_alarm	Input current high alarm.
 | 
				
			||||||
 | 
					curr[2-3]_crit_alarm	Input current critical high alarm.
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,6 @@ Supported chips:
 | 
				
			||||||
    Addresses scanned: -
 | 
					    Addresses scanned: -
 | 
				
			||||||
    Datasheet:
 | 
					    Datasheet:
 | 
				
			||||||
 http://archive.ericsson.net/service/internet/picov/get?DocNo=28701-EN/LZT146395
 | 
					 http://archive.ericsson.net/service/internet/picov/get?DocNo=28701-EN/LZT146395
 | 
				
			||||||
  * Linear Technology LTC2978
 | 
					 | 
				
			||||||
    Octal PMBus Power Supply Monitor and Controller
 | 
					 | 
				
			||||||
    Prefix: 'ltc2978'
 | 
					 | 
				
			||||||
    Addresses scanned: -
 | 
					 | 
				
			||||||
    Datasheet: http://cds.linear.com/docs/Datasheet/2978fa.pdf
 | 
					 | 
				
			||||||
  * ON Semiconductor ADP4000, NCP4200, NCP4208
 | 
					  * ON Semiconductor ADP4000, NCP4200, NCP4208
 | 
				
			||||||
    Prefixes: 'adp4000', 'ncp4200', 'ncp4208'
 | 
					    Prefixes: 'adp4000', 'ncp4200', 'ncp4208'
 | 
				
			||||||
    Addresses scanned: -
 | 
					    Addresses scanned: -
 | 
				
			||||||
| 
						 | 
					@ -20,6 +15,14 @@ Supported chips:
 | 
				
			||||||
	http://www.onsemi.com/pub_link/Collateral/ADP4000-D.PDF
 | 
						http://www.onsemi.com/pub_link/Collateral/ADP4000-D.PDF
 | 
				
			||||||
	http://www.onsemi.com/pub_link/Collateral/NCP4200-D.PDF
 | 
						http://www.onsemi.com/pub_link/Collateral/NCP4200-D.PDF
 | 
				
			||||||
	http://www.onsemi.com/pub_link/Collateral/JUNE%202009-%20REV.%200.PDF
 | 
						http://www.onsemi.com/pub_link/Collateral/JUNE%202009-%20REV.%200.PDF
 | 
				
			||||||
 | 
					  * Lineage Power
 | 
				
			||||||
 | 
					    Prefixes: 'pdt003', 'pdt006', 'pdt012', 'udt020'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheets:
 | 
				
			||||||
 | 
						http://www.lineagepower.com/oem/pdf/PDT003A0X.pdf
 | 
				
			||||||
 | 
						http://www.lineagepower.com/oem/pdf/PDT006A0X.pdf
 | 
				
			||||||
 | 
						http://www.lineagepower.com/oem/pdf/PDT012A0X.pdf
 | 
				
			||||||
 | 
						http://www.lineagepower.com/oem/pdf/UDT020A0X.pdf
 | 
				
			||||||
  * Generic PMBus devices
 | 
					  * Generic PMBus devices
 | 
				
			||||||
    Prefix: 'pmbus'
 | 
					    Prefix: 'pmbus'
 | 
				
			||||||
    Addresses scanned: -
 | 
					    Addresses scanned: -
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										283
									
								
								Documentation/hwmon/pmbus-core
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								Documentation/hwmon/pmbus-core
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,283 @@
 | 
				
			||||||
 | 
					PMBus core driver and internal API
 | 
				
			||||||
 | 
					==================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Introduction
 | 
				
			||||||
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[from pmbus.org] The Power Management Bus (PMBus) is an open standard
 | 
				
			||||||
 | 
					power-management protocol with a fully defined command language that facilitates
 | 
				
			||||||
 | 
					communication with power converters and other devices in a power system. The
 | 
				
			||||||
 | 
					protocol is implemented over the industry-standard SMBus serial interface and
 | 
				
			||||||
 | 
					enables programming, control, and real-time monitoring of compliant power
 | 
				
			||||||
 | 
					conversion products. This flexible and highly versatile standard allows for
 | 
				
			||||||
 | 
					communication between devices based on both analog and digital technologies, and
 | 
				
			||||||
 | 
					provides true interoperability which will reduce design complexity and shorten
 | 
				
			||||||
 | 
					time to market for power system designers. Pioneered by leading power supply and
 | 
				
			||||||
 | 
					semiconductor companies, this open power system standard is maintained and
 | 
				
			||||||
 | 
					promoted by the PMBus Implementers Forum (PMBus-IF), comprising 30+ adopters
 | 
				
			||||||
 | 
					with the objective to provide support to, and facilitate adoption among, users.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unfortunately, while PMBus commands are standardized, there are no mandatory
 | 
				
			||||||
 | 
					commands, and manufacturers can add as many non-standard commands as they like.
 | 
				
			||||||
 | 
					Also, different PMBUs devices act differently if non-supported commands are
 | 
				
			||||||
 | 
					executed. Some devices return an error, some devices return 0xff or 0xffff and
 | 
				
			||||||
 | 
					set a status error flag, and some devices may simply hang up.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Despite all those difficulties, a generic PMBus device driver is still useful
 | 
				
			||||||
 | 
					and supported since kernel version 2.6.39. However, it was necessary to support
 | 
				
			||||||
 | 
					device specific extensions in addition to the core PMBus driver, since it is
 | 
				
			||||||
 | 
					simply unknown what new device specific functionality PMBus device developers
 | 
				
			||||||
 | 
					come up with next.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To make device specific extensions as scalable as possible, and to avoid having
 | 
				
			||||||
 | 
					to modify the core PMBus driver repeatedly for new devices, the PMBus driver was
 | 
				
			||||||
 | 
					split into core, generic, and device specific code. The core code (in
 | 
				
			||||||
 | 
					pmbus_core.c) provides generic functionality. The generic code (in pmbus.c)
 | 
				
			||||||
 | 
					provides support for generic PMBus devices. Device specific code is responsible
 | 
				
			||||||
 | 
					for device specific initialization and, if needed, maps device specific
 | 
				
			||||||
 | 
					functionality into generic functionality. This is to some degree comparable
 | 
				
			||||||
 | 
					to PCI code, where generic code is augmented as needed with quirks for all kinds
 | 
				
			||||||
 | 
					of devices.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus device capabilities auto-detection
 | 
				
			||||||
 | 
					========================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For generic PMBus devices, code in pmbus.c attempts to auto-detect all supported
 | 
				
			||||||
 | 
					PMBus commands. Auto-detection is somewhat limited, since there are simply too
 | 
				
			||||||
 | 
					many variables to consider. For example, it is almost impossible to autodetect
 | 
				
			||||||
 | 
					which PMBus commands are paged and which commands are replicated across all
 | 
				
			||||||
 | 
					pages (see the PMBus specification for details on multi-page PMBus devices).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For this reason, it often makes sense to provide a device specific driver if not
 | 
				
			||||||
 | 
					all commands can be auto-detected. The data structures in this driver can be
 | 
				
			||||||
 | 
					used to inform the core driver about functionality supported by individual
 | 
				
			||||||
 | 
					chips.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some commands are always auto-detected. This applies to all limit commands
 | 
				
			||||||
 | 
					(lcrit, min, max, and crit attributes) as well as associated alarm attributes.
 | 
				
			||||||
 | 
					Limits and alarm attributes are auto-detected because there are simply too many
 | 
				
			||||||
 | 
					possible combinations to provide a manual configuration interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus internal API
 | 
				
			||||||
 | 
					==================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The API between core and device specific PMBus code is defined in
 | 
				
			||||||
 | 
					drivers/hwmon/pmbus/pmbus.h. In addition to the internal API, pmbus.h defines
 | 
				
			||||||
 | 
					standard PMBus commands and virtual PMBus commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Standard PMBus commands
 | 
				
			||||||
 | 
					-----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Standard PMBus commands (commands values 0x00 to 0xff) are defined in the PMBUs
 | 
				
			||||||
 | 
					specification.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Virtual PMBus commands
 | 
				
			||||||
 | 
					----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Virtual PMBus commands are provided to enable support for non-standard
 | 
				
			||||||
 | 
					functionality which has been implemented by several chip vendors and is thus
 | 
				
			||||||
 | 
					desirable to support.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Virtual PMBus commands start with command value 0x100 and can thus easily be
 | 
				
			||||||
 | 
					distinguished from standard PMBus commands (which can not have values larger
 | 
				
			||||||
 | 
					than 0xff). Support for virtual PMBus commands is device specific and thus has
 | 
				
			||||||
 | 
					to be implemented in device specific code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Virtual commands are named PMBUS_VIRT_xxx and start with PMBUS_VIRT_BASE. All
 | 
				
			||||||
 | 
					virtual commands are word sized.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are currently two types of virtual commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- READ commands are read-only; writes are either ignored or return an error.
 | 
				
			||||||
 | 
					- RESET commands are read/write. Reading reset registers returns zero
 | 
				
			||||||
 | 
					  (used for detection), writing any value causes the associated history to be
 | 
				
			||||||
 | 
					  reset.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Virtual commands have to be handled in device specific driver code. Chip driver
 | 
				
			||||||
 | 
					code returns non-negative values if a virtual command is supported, or a
 | 
				
			||||||
 | 
					negative error code if not. The chip driver may return -ENODATA or any other
 | 
				
			||||||
 | 
					Linux error code in this case, though an error code other than -ENODATA is
 | 
				
			||||||
 | 
					handled more efficiently and thus preferred. Either case, the calling PMBus
 | 
				
			||||||
 | 
					core code will abort if the chip driver returns an error code when reading
 | 
				
			||||||
 | 
					or writing virtual registers (in other words, the PMBus core code will never
 | 
				
			||||||
 | 
					send a virtual command to a chip).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus driver information
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus driver information, defined in struct pmbus_driver_info, is the main means
 | 
				
			||||||
 | 
					for device specific drivers to pass information to the core PMBus driver.
 | 
				
			||||||
 | 
					Specifically, it provides the following information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- For devices supporting its data in Direct Data Format, it provides coefficients
 | 
				
			||||||
 | 
					  for converting register values into normalized data. This data is usually
 | 
				
			||||||
 | 
					  provided by chip manufacturers in device datasheets.
 | 
				
			||||||
 | 
					- Supported chip functionality can be provided to the core driver. This may be
 | 
				
			||||||
 | 
					  necessary for chips which react badly if non-supported commands are executed,
 | 
				
			||||||
 | 
					  and/or to speed up device detection and initialization.
 | 
				
			||||||
 | 
					- Several function entry points are provided to support overriding and/or
 | 
				
			||||||
 | 
					  augmenting generic command execution. This functionality can be used to map
 | 
				
			||||||
 | 
					  non-standard PMBus commands to standard commands, or to augment standard
 | 
				
			||||||
 | 
					  command return values with device specific information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  API functions
 | 
				
			||||||
 | 
					  -------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Functions provided by chip driver
 | 
				
			||||||
 | 
					  ---------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  All functions return the command return value (read) or zero (write) if
 | 
				
			||||||
 | 
					  successful. A return value of -ENODATA indicates that there is no manufacturer
 | 
				
			||||||
 | 
					  specific command, but that a standard PMBus command may exist. Any other
 | 
				
			||||||
 | 
					  negative return value indicates that the commands does not exist for this
 | 
				
			||||||
 | 
					  chip, and that no attempt should be made to read or write the standard
 | 
				
			||||||
 | 
					  command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  As mentioned above, an exception to this rule applies to virtual commands,
 | 
				
			||||||
 | 
					  which  _must_ be handled in driver specific code. See "Virtual PMBus Commands"
 | 
				
			||||||
 | 
					  above for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Command execution in the core PMBus driver code is as follows.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (chip_access_function) {
 | 
				
			||||||
 | 
							status = chip_access_function();
 | 
				
			||||||
 | 
							if (status != -ENODATA)
 | 
				
			||||||
 | 
								return status;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (command >= PMBUS_VIRT_BASE)	/* For word commands/registers only */
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						return generic_access();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Chip drivers may provide pointers to the following functions in struct
 | 
				
			||||||
 | 
					  pmbus_driver_info. All functions are optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int (*read_byte_data)(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Read byte from page <page>, register <reg>.
 | 
				
			||||||
 | 
					  <page> may be -1, which means "current page".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int (*read_word_data)(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Read word from page <page>, register <reg>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int (*write_word_data)(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
 | 
							         u16 word);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Write word to page <page>, register <reg>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int (*write_byte)(struct i2c_client *client, int page, u8 value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Write byte to page <page>, register <reg>.
 | 
				
			||||||
 | 
					  <page> may be -1, which means "current page".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int (*identify)(struct i2c_client *client, struct pmbus_driver_info *info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Determine supported PMBus functionality. This function is only necessary
 | 
				
			||||||
 | 
					  if a chip driver supports multiple chips, and the chip functionality is not
 | 
				
			||||||
 | 
					  pre-determined. It is currently only used by the generic pmbus driver
 | 
				
			||||||
 | 
					  (pmbus.c).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Functions exported by core driver
 | 
				
			||||||
 | 
					  ---------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Chip drivers are expected to use the following functions to read or write
 | 
				
			||||||
 | 
					  PMBus registers. Chip drivers may also use direct I2C commands. If direct I2C
 | 
				
			||||||
 | 
					  commands are used, the chip driver code must not directly modify the current
 | 
				
			||||||
 | 
					  page, since the selected page is cached in the core driver and the core driver
 | 
				
			||||||
 | 
					  will assume that it is selected. Using pmbus_set_page() to select a new page
 | 
				
			||||||
 | 
					  is mandatory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_set_page(struct i2c_client *client, u8 page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Set PMBus page register to <page> for subsequent commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Read word data from <page>, <reg>. Similar to i2c_smbus_read_word_data(), but
 | 
				
			||||||
 | 
					  selects page first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg,
 | 
				
			||||||
 | 
								    u16 word);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Write word data to <page>, <reg>. Similar to i2c_smbus_write_word_data(), but
 | 
				
			||||||
 | 
					  selects page first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Read byte data from <page>, <reg>. Similar to i2c_smbus_read_byte_data(), but
 | 
				
			||||||
 | 
					  selects page first. <page> may be -1, which means "current page".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_write_byte(struct i2c_client *client, int page, u8 value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Write byte data to <page>, <reg>. Similar to i2c_smbus_write_byte(), but
 | 
				
			||||||
 | 
					  selects page first. <page> may be -1, which means "current page".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pmbus_clear_faults(struct i2c_client *client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Execute PMBus "Clear Fault" command on all chip pages.
 | 
				
			||||||
 | 
					  This function calls the device specific write_byte function if defined.
 | 
				
			||||||
 | 
					  Therefore, it must _not_ be called from that function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Check if byte register exists. Return true if the register exists, false
 | 
				
			||||||
 | 
					  otherwise.
 | 
				
			||||||
 | 
					  This function calls the device specific write_byte function if defined to
 | 
				
			||||||
 | 
					  obtain the chip status. Therefore, it must _not_ be called from that function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Check if word register exists. Return true if the register exists, false
 | 
				
			||||||
 | 
					  otherwise.
 | 
				
			||||||
 | 
					  This function calls the device specific write_byte function if defined to
 | 
				
			||||||
 | 
					  obtain the chip status. Therefore, it must _not_ be called from that function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
 | 
				
			||||||
 | 
					                     struct pmbus_driver_info *info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Execute probe function. Similar to standard probe function for other drivers,
 | 
				
			||||||
 | 
					  with the pointer to struct pmbus_driver_info as additional argument. Calls
 | 
				
			||||||
 | 
					  identify function if supported. Must only be called from device probe
 | 
				
			||||||
 | 
					  function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pmbus_do_remove(struct i2c_client *client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Execute driver remove function. Similar to standard driver remove function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const struct pmbus_driver_info
 | 
				
			||||||
 | 
						*pmbus_get_driver_info(struct i2c_client *client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Return pointer to struct pmbus_driver_info as passed to pmbus_do_probe().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus driver platform data
 | 
				
			||||||
 | 
					==========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBus platform data is defined in include/linux/i2c/pmbus.h. Platform data
 | 
				
			||||||
 | 
					currently only provides a flag field with a single bit used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PMBUS_SKIP_STATUS_CHECK (1 << 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct pmbus_platform_data {
 | 
				
			||||||
 | 
					        u32 flags;              /* Device specific flags */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Flags
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PMBUS_SKIP_STATUS_CHECK
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					During register detection, skip checking the status register for
 | 
				
			||||||
 | 
					communication or command errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some PMBus chips respond with valid data when trying to read an unsupported
 | 
				
			||||||
 | 
					register. For such chips, checking the status register is mandatory when
 | 
				
			||||||
 | 
					trying to determine if a chip register exists or not.
 | 
				
			||||||
 | 
					Other PMBus chips don't support the STATUS_CML register, or report
 | 
				
			||||||
 | 
					communication errors for no explicable reason. For such chips, checking the
 | 
				
			||||||
 | 
					status register must be disabled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some i2c controllers do not support single-byte commands (write commands with
 | 
				
			||||||
 | 
					no data, i2c_smbus_write_byte()). With such controllers, clearing the status
 | 
				
			||||||
 | 
					register is impossible, and the PMBUS_SKIP_STATUS_CHECK flag must be set.
 | 
				
			||||||
							
								
								
									
										125
									
								
								Documentation/hwmon/zl6100
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								Documentation/hwmon/zl6100
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					Kernel driver zl6100
 | 
				
			||||||
 | 
					====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Supported chips:
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL2004
 | 
				
			||||||
 | 
					    Prefix: 'zl2004'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6847.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL2006
 | 
				
			||||||
 | 
					    Prefix: 'zl2006'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6850.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL2008
 | 
				
			||||||
 | 
					    Prefix: 'zl2008'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6859.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL2105
 | 
				
			||||||
 | 
					    Prefix: 'zl2105'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6851.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL2106
 | 
				
			||||||
 | 
					    Prefix: 'zl2106'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6852.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL6100
 | 
				
			||||||
 | 
					    Prefix: 'zl6100'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6876.pdf
 | 
				
			||||||
 | 
					  * Intersil / Zilker Labs ZL6105
 | 
				
			||||||
 | 
					    Prefix: 'zl6105'
 | 
				
			||||||
 | 
					    Addresses scanned: -
 | 
				
			||||||
 | 
					    Datasheet: http://www.intersil.com/data/fn/fn6906.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Author: Guenter Roeck <guenter.roeck@ericsson.com>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Description
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This driver supports hardware montoring for Intersil / Zilker Labs ZL6100 and
 | 
				
			||||||
 | 
					compatible digital DC-DC controllers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The driver is a client driver to the core PMBus driver. Please see
 | 
				
			||||||
 | 
					Documentation/hwmon/pmbus and Documentation.hwmon/pmbus-core for details
 | 
				
			||||||
 | 
					on PMBus client drivers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage Notes
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This driver does not auto-detect devices. You will have to instantiate the
 | 
				
			||||||
 | 
					devices explicitly. Please see Documentation/i2c/instantiating-devices for
 | 
				
			||||||
 | 
					details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WARNING: Do not access chip registers using the i2cdump command, and do not use
 | 
				
			||||||
 | 
					any of the i2ctools commands on a command register used to save and restore
 | 
				
			||||||
 | 
					configuration data (0x11, 0x12, 0x15, 0x16, and 0xf4). The chips supported by
 | 
				
			||||||
 | 
					this driver interpret any access to those command registers (including read
 | 
				
			||||||
 | 
					commands) as request to execute the command in question. Unless write accesses
 | 
				
			||||||
 | 
					to those registers are protected, this may result in power loss, board resets,
 | 
				
			||||||
 | 
					and/or Flash corruption. Worst case, your board may turn into a brick.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Platform data support
 | 
				
			||||||
 | 
					---------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The driver supports standard PMBus driver platform data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Module parameters
 | 
				
			||||||
 | 
					-----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					delay
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some Intersil/Zilker Labs DC-DC controllers require a minimum interval between
 | 
				
			||||||
 | 
					I2C bus accesses. According to Intersil, the minimum interval is 2 ms, though
 | 
				
			||||||
 | 
					1 ms appears to be sufficient and has not caused any problems in testing.
 | 
				
			||||||
 | 
					The problem is known to affect ZL6100, ZL2105, and ZL2008. It is known not to
 | 
				
			||||||
 | 
					affect ZL2004 and ZL6105. The driver automatically sets the interval to 1 ms
 | 
				
			||||||
 | 
					except for ZL2004 and ZL6105. To enable manual override, the driver provides a
 | 
				
			||||||
 | 
					writeable module parameter, 'delay', which can be used to set the interval to
 | 
				
			||||||
 | 
					a value between 0 and 65,535 microseconds.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Sysfs entries
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The following attributes are supported. Limits are read-write; all other
 | 
				
			||||||
 | 
					attributes are read-only.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in1_label		"vin"
 | 
				
			||||||
 | 
					in1_input		Measured input voltage.
 | 
				
			||||||
 | 
					in1_min			Minimum input voltage.
 | 
				
			||||||
 | 
					in1_max			Maximum input voltage.
 | 
				
			||||||
 | 
					in1_lcrit		Critical minumum input voltage.
 | 
				
			||||||
 | 
					in1_crit		Critical maximum input voltage.
 | 
				
			||||||
 | 
					in1_min_alarm		Input voltage low alarm.
 | 
				
			||||||
 | 
					in1_max_alarm		Input voltage high alarm.
 | 
				
			||||||
 | 
					in1_lcrit_alarm		Input voltage critical low alarm.
 | 
				
			||||||
 | 
					in1_crit_alarm		Input voltage critical high alarm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in2_label		"vout1"
 | 
				
			||||||
 | 
					in2_input		Measured output voltage.
 | 
				
			||||||
 | 
					in2_lcrit		Critical minumum output Voltage.
 | 
				
			||||||
 | 
					in2_crit		Critical maximum output voltage.
 | 
				
			||||||
 | 
					in2_lcrit_alarm		Critical output voltage critical low alarm.
 | 
				
			||||||
 | 
					in2_crit_alarm		Critical output voltage critical high alarm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					curr1_label		"iout1"
 | 
				
			||||||
 | 
					curr1_input		Measured output current.
 | 
				
			||||||
 | 
					curr1_lcrit		Critical minimum output current.
 | 
				
			||||||
 | 
					curr1_crit		Critical maximum output current.
 | 
				
			||||||
 | 
					curr1_lcrit_alarm	Output current critical low alarm.
 | 
				
			||||||
 | 
					curr1_crit_alarm	Output current critical high alarm.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					temp[12]_input		Measured temperature.
 | 
				
			||||||
 | 
					temp[12]_min		Minimum temperature.
 | 
				
			||||||
 | 
					temp[12]_max		Maximum temperature.
 | 
				
			||||||
 | 
					temp[12]_lcrit		Critical low temperature.
 | 
				
			||||||
 | 
					temp[12]_crit		Critical high temperature.
 | 
				
			||||||
 | 
					temp[12]_min_alarm	Chip temperature low alarm.
 | 
				
			||||||
 | 
					temp[12]_max_alarm	Chip temperature high alarm.
 | 
				
			||||||
 | 
					temp[12]_lcrit_alarm	Chip temperature critical low alarm.
 | 
				
			||||||
 | 
					temp[12]_crit_alarm	Chip temperature critical high alarm.
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,16 @@ config SENSORS_ABITUGURU3
 | 
				
			||||||
	  This driver can also be built as a module.  If so, the module
 | 
						  This driver can also be built as a module.  If so, the module
 | 
				
			||||||
	  will be called abituguru3.
 | 
						  will be called abituguru3.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config SENSORS_AD7314
 | 
				
			||||||
 | 
						tristate "Analog Devices AD7314 and compatibles"
 | 
				
			||||||
 | 
						depends on SPI && EXPERIMENTAL
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  If you say yes here you get support for the Analog Devices
 | 
				
			||||||
 | 
						  AD7314, ADT7301 and ADT7302 temperature sensors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  This driver can also be built as a module. If so, the module
 | 
				
			||||||
 | 
						  will be called ad7314.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config SENSORS_AD7414
 | 
					config SENSORS_AD7414
 | 
				
			||||||
	tristate "Analog Devices AD7414"
 | 
						tristate "Analog Devices AD7414"
 | 
				
			||||||
	depends on I2C && EXPERIMENTAL
 | 
						depends on I2C && EXPERIMENTAL
 | 
				
			||||||
| 
						 | 
					@ -303,6 +313,16 @@ config SENSORS_DS1621
 | 
				
			||||||
	  This driver can also be built as a module.  If so, the module
 | 
						  This driver can also be built as a module.  If so, the module
 | 
				
			||||||
	  will be called ds1621.
 | 
						  will be called ds1621.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config SENSORS_EXYNOS4_TMU
 | 
				
			||||||
 | 
						tristate "Temperature sensor on Samsung EXYNOS4"
 | 
				
			||||||
 | 
						depends on EXYNOS4_DEV_TMU
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  If you say yes here you get support for TMU (Thermal Managment
 | 
				
			||||||
 | 
						  Unit) on SAMSUNG EXYNOS4 series of SoC.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  This driver can also be built as a module. If so, the module
 | 
				
			||||||
 | 
						  will be called exynos4-tmu.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config SENSORS_I5K_AMB
 | 
					config SENSORS_I5K_AMB
 | 
				
			||||||
	tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
 | 
						tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
 | 
				
			||||||
	depends on PCI && EXPERIMENTAL
 | 
						depends on PCI && EXPERIMENTAL
 | 
				
			||||||
| 
						 | 
					@ -531,6 +551,7 @@ config SENSORS_LM75
 | 
				
			||||||
	  If you say yes here you get support for one common type of
 | 
						  If you say yes here you get support for one common type of
 | 
				
			||||||
	  temperature sensor chip, with models including:
 | 
						  temperature sensor chip, with models including:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							- Analog Devices ADT75
 | 
				
			||||||
		- Dallas Semiconductor DS75 and DS1775
 | 
							- Dallas Semiconductor DS75 and DS1775
 | 
				
			||||||
		- Maxim MAX6625 and MAX6626
 | 
							- Maxim MAX6625 and MAX6626
 | 
				
			||||||
		- Microchip MCP980x
 | 
							- Microchip MCP980x
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,7 @@ obj-$(CONFIG_SENSORS_W83791D)	+= w83791d.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
 | 
					obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
 | 
					obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_SENSORS_AD7314)	+= ad7314.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_AD7414)	+= ad7414.o
 | 
					obj-$(CONFIG_SENSORS_AD7414)	+= ad7414.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_AD7418)	+= ad7418.o
 | 
					obj-$(CONFIG_SENSORS_AD7418)	+= ad7418.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_ADCXX)	+= adcxx.o
 | 
					obj-$(CONFIG_SENSORS_ADCXX)	+= adcxx.o
 | 
				
			||||||
| 
						 | 
					@ -47,6 +48,7 @@ obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 | 
					obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 | 
					obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
 | 
					obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_SENSORS_EXYNOS4_TMU)	+= exynos4_tmu.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 | 
					obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 | 
					obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
 | 
					obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										186
									
								
								drivers/hwmon/ad7314.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								drivers/hwmon/ad7314.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,186 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * AD7314 digital temperature sensor driver for AD7314, ADT7301 and ADT7302
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright 2010 Analog Devices Inc.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Licensed under the GPL-2 or later.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Conversion to hwmon from IIO done by Jonathan Cameron <jic23@cam.ac.uk>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include <linux/device.h>
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/sysfs.h>
 | 
				
			||||||
 | 
					#include <linux/spi/spi.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/hwmon.h>
 | 
				
			||||||
 | 
					#include <linux/hwmon-sysfs.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * AD7314 power mode
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define AD7314_PD		0x2000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * AD7314 temperature masks
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define AD7314_TEMP_SIGN		0x200
 | 
				
			||||||
 | 
					#define AD7314_TEMP_MASK		0x7FE0
 | 
				
			||||||
 | 
					#define AD7314_TEMP_OFFSET		5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * ADT7301 and ADT7302 temperature masks
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define ADT7301_TEMP_SIGN		0x2000
 | 
				
			||||||
 | 
					#define ADT7301_TEMP_MASK		0x3FFF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ad7314_variant {
 | 
				
			||||||
 | 
						adt7301,
 | 
				
			||||||
 | 
						adt7302,
 | 
				
			||||||
 | 
						ad7314,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ad7314_data {
 | 
				
			||||||
 | 
						struct spi_device	*spi_dev;
 | 
				
			||||||
 | 
						struct device		*hwmon_dev;
 | 
				
			||||||
 | 
						u16 rx ____cacheline_aligned;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ad7314_spi_read(struct ad7314_data *chip, s16 *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = spi_read(chip->spi_dev, (u8 *)&chip->rx, sizeof(chip->rx));
 | 
				
			||||||
 | 
						if (ret < 0) {
 | 
				
			||||||
 | 
							dev_err(&chip->spi_dev->dev, "SPI read error\n");
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*data = be16_to_cpu(chip->rx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t ad7314_show_temperature(struct device *dev,
 | 
				
			||||||
 | 
							struct device_attribute *attr,
 | 
				
			||||||
 | 
							char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct ad7314_data *chip = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						s16 data;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = ad7314_spi_read(chip, &data);
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						switch (spi_get_device_id(chip->spi_dev)->driver_data) {
 | 
				
			||||||
 | 
						case ad7314:
 | 
				
			||||||
 | 
							data = (data & AD7314_TEMP_MASK) >> AD7314_TEMP_OFFSET;
 | 
				
			||||||
 | 
							data = (data << 6) >> 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return sprintf(buf, "%d\n", 250 * data);
 | 
				
			||||||
 | 
						case adt7301:
 | 
				
			||||||
 | 
						case adt7302:
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * Documented as a 13 bit twos complement register
 | 
				
			||||||
 | 
							 * with a sign bit - which is a 14 bit 2's complement
 | 
				
			||||||
 | 
							 * register.  1lsb - 31.25 milli degrees centigrade
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							data &= ADT7301_TEMP_MASK;
 | 
				
			||||||
 | 
							data = (data << 2) >> 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return sprintf(buf, "%d\n",
 | 
				
			||||||
 | 
								       DIV_ROUND_CLOSEST(data * 3125, 100));
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
 | 
				
			||||||
 | 
								  ad7314_show_temperature, NULL, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct attribute *ad7314_attributes[] = {
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_input.dev_attr.attr,
 | 
				
			||||||
 | 
						NULL,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct attribute_group ad7314_group = {
 | 
				
			||||||
 | 
						.attrs = ad7314_attributes,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __devinit ad7314_probe(struct spi_device *spi_dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
						struct ad7314_data *chip;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						chip = kzalloc(sizeof(*chip), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (chip == NULL) {
 | 
				
			||||||
 | 
							ret = -ENOMEM;
 | 
				
			||||||
 | 
							goto error_ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dev_set_drvdata(&spi_dev->dev, chip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = sysfs_create_group(&spi_dev->dev.kobj, &ad7314_group);
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							goto error_free_chip;
 | 
				
			||||||
 | 
						chip->hwmon_dev = hwmon_device_register(&spi_dev->dev);
 | 
				
			||||||
 | 
						if (IS_ERR(chip->hwmon_dev)) {
 | 
				
			||||||
 | 
							ret = PTR_ERR(chip->hwmon_dev);
 | 
				
			||||||
 | 
							goto error_remove_group;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					error_remove_group:
 | 
				
			||||||
 | 
						sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
 | 
				
			||||||
 | 
					error_free_chip:
 | 
				
			||||||
 | 
						kfree(chip);
 | 
				
			||||||
 | 
					error_ret:
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __devexit ad7314_remove(struct spi_device *spi_dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct ad7314_data *chip = dev_get_drvdata(&spi_dev->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hwmon_device_unregister(chip->hwmon_dev);
 | 
				
			||||||
 | 
						sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
 | 
				
			||||||
 | 
						kfree(chip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct spi_device_id ad7314_id[] = {
 | 
				
			||||||
 | 
						{ "adt7301", adt7301 },
 | 
				
			||||||
 | 
						{ "adt7302", adt7302 },
 | 
				
			||||||
 | 
						{ "ad7314", ad7314 },
 | 
				
			||||||
 | 
						{ }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(spi, ad7314_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct spi_driver ad7314_driver = {
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							.name = "ad7314",
 | 
				
			||||||
 | 
							.bus = &spi_bus_type,
 | 
				
			||||||
 | 
							.owner = THIS_MODULE,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						.probe = ad7314_probe,
 | 
				
			||||||
 | 
						.remove = __devexit_p(ad7314_remove),
 | 
				
			||||||
 | 
						.id_table = ad7314_id,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static __init int ad7314_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return spi_register_driver(&ad7314_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					module_init(ad7314_init);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static __exit void ad7314_exit(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						spi_unregister_driver(&ad7314_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					module_exit(ad7314_exit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("Analog Devices AD7314, ADT7301 and ADT7302 digital"
 | 
				
			||||||
 | 
								" temperature sensor driver");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL v2");
 | 
				
			||||||
							
								
								
									
										524
									
								
								drivers/hwmon/exynos4_tmu.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										524
									
								
								drivers/hwmon/exynos4_tmu.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,524 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *  Copyright (C) 2011 Samsung Electronics
 | 
				
			||||||
 | 
					 *  Donggeun Kim <dg77.kim@samsung.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program; if not, write to the Free Software
 | 
				
			||||||
 | 
					 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/platform_device.h>
 | 
				
			||||||
 | 
					#include <linux/interrupt.h>
 | 
				
			||||||
 | 
					#include <linux/clk.h>
 | 
				
			||||||
 | 
					#include <linux/workqueue.h>
 | 
				
			||||||
 | 
					#include <linux/sysfs.h>
 | 
				
			||||||
 | 
					#include <linux/kobject.h>
 | 
				
			||||||
 | 
					#include <linux/io.h>
 | 
				
			||||||
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/hwmon.h>
 | 
				
			||||||
 | 
					#include <linux/hwmon-sysfs.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/platform_data/exynos4_tmu.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_TRIMINFO	0x0
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_CONTROL		0x20
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_STATUS		0x28
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_INTEN		0x70
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_INTSTAT		0x74
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REG_INTCLEAR	0x78
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_GAIN_SHIFT		8
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_CORE_ON	3
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_CORE_OFF	2
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
 | 
				
			||||||
 | 
					#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct exynos4_tmu_data {
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata;
 | 
				
			||||||
 | 
						struct device *hwmon_dev;
 | 
				
			||||||
 | 
						struct resource *mem;
 | 
				
			||||||
 | 
						void __iomem *base;
 | 
				
			||||||
 | 
						int irq;
 | 
				
			||||||
 | 
						struct work_struct irq_work;
 | 
				
			||||||
 | 
						struct mutex lock;
 | 
				
			||||||
 | 
						struct clk *clk;
 | 
				
			||||||
 | 
						u8 temp_error1, temp_error2;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * TMU treats temperature as a mapped temperature code.
 | 
				
			||||||
 | 
					 * The temperature is converted differently depending on the calibration type.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						int temp_code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* temp should range between 25 and 125 */
 | 
				
			||||||
 | 
						if (temp < 25 || temp > 125) {
 | 
				
			||||||
 | 
							temp_code = -EINVAL;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (pdata->cal_type) {
 | 
				
			||||||
 | 
						case TYPE_TWO_POINT_TRIMMING:
 | 
				
			||||||
 | 
							temp_code = (temp - 25) *
 | 
				
			||||||
 | 
							    (data->temp_error2 - data->temp_error1) /
 | 
				
			||||||
 | 
							    (85 - 25) + data->temp_error1;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case TYPE_ONE_POINT_TRIMMING:
 | 
				
			||||||
 | 
							temp_code = temp + data->temp_error1 - 25;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						return temp_code;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Calculate a temperature value from a temperature code.
 | 
				
			||||||
 | 
					 * The unit of the temperature is degree Celsius.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						int temp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* temp_code should range between 75 and 175 */
 | 
				
			||||||
 | 
						if (temp_code < 75 || temp_code > 175) {
 | 
				
			||||||
 | 
							temp = -ENODATA;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (pdata->cal_type) {
 | 
				
			||||||
 | 
						case TYPE_TWO_POINT_TRIMMING:
 | 
				
			||||||
 | 
							temp = (temp_code - data->temp_error1) * (85 - 25) /
 | 
				
			||||||
 | 
							    (data->temp_error2 - data->temp_error1) + 25;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case TYPE_ONE_POINT_TRIMMING:
 | 
				
			||||||
 | 
							temp = temp_code - data->temp_error1 + 25;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						return temp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int exynos4_tmu_initialize(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						unsigned int status, trim_info;
 | 
				
			||||||
 | 
						int ret = 0, threshold_code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&data->lock);
 | 
				
			||||||
 | 
						clk_enable(data->clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
 | 
				
			||||||
 | 
						if (!status) {
 | 
				
			||||||
 | 
							ret = -EBUSY;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Save trimming info in order to perform calibration */
 | 
				
			||||||
 | 
						trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
 | 
				
			||||||
 | 
						data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
 | 
				
			||||||
 | 
						data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Write temperature code for threshold */
 | 
				
			||||||
 | 
						threshold_code = temp_to_code(data, pdata->threshold);
 | 
				
			||||||
 | 
						if (threshold_code < 0) {
 | 
				
			||||||
 | 
							ret = threshold_code;
 | 
				
			||||||
 | 
							goto out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						writeb(threshold_code,
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writeb(pdata->trigger_levels[0],
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
 | 
				
			||||||
 | 
						writeb(pdata->trigger_levels[1],
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
 | 
				
			||||||
 | 
						writeb(pdata->trigger_levels[2],
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
 | 
				
			||||||
 | 
						writeb(pdata->trigger_levels[3],
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writel(EXYNOS4_TMU_INTCLEAR_VAL,
 | 
				
			||||||
 | 
							data->base + EXYNOS4_TMU_REG_INTCLEAR);
 | 
				
			||||||
 | 
					out:
 | 
				
			||||||
 | 
						clk_disable(data->clk);
 | 
				
			||||||
 | 
						mutex_unlock(&data->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void exynos4_tmu_control(struct platform_device *pdev, bool on)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						unsigned int con, interrupt_en;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&data->lock);
 | 
				
			||||||
 | 
						clk_enable(data->clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
 | 
				
			||||||
 | 
							pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
 | 
				
			||||||
 | 
						if (on) {
 | 
				
			||||||
 | 
							con |= EXYNOS4_TMU_CORE_ON;
 | 
				
			||||||
 | 
							interrupt_en = pdata->trigger_level3_en << 12 |
 | 
				
			||||||
 | 
								pdata->trigger_level2_en << 8 |
 | 
				
			||||||
 | 
								pdata->trigger_level1_en << 4 |
 | 
				
			||||||
 | 
								pdata->trigger_level0_en;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							con |= EXYNOS4_TMU_CORE_OFF;
 | 
				
			||||||
 | 
							interrupt_en = 0; /* Disable all interrupts */
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
 | 
				
			||||||
 | 
						writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_disable(data->clk);
 | 
				
			||||||
 | 
						mutex_unlock(&data->lock);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u8 temp_code;
 | 
				
			||||||
 | 
						int temp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&data->lock);
 | 
				
			||||||
 | 
						clk_enable(data->clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
 | 
				
			||||||
 | 
						temp = code_to_temp(data, temp_code);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_disable(data->clk);
 | 
				
			||||||
 | 
						mutex_unlock(&data->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return temp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void exynos4_tmu_work(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = container_of(work,
 | 
				
			||||||
 | 
								struct exynos4_tmu_data, irq_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&data->lock);
 | 
				
			||||||
 | 
						clk_enable(data->clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enable_irq(data->irq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_disable(data->clk);
 | 
				
			||||||
 | 
						mutex_unlock(&data->lock);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static irqreturn_t exynos4_tmu_irq(int irq, void *id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						disable_irq_nosync(irq);
 | 
				
			||||||
 | 
						schedule_work(&data->irq_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return IRQ_HANDLED;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t exynos4_tmu_show_name(struct device *dev,
 | 
				
			||||||
 | 
							struct device_attribute *attr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return sprintf(buf, "exynos4-tmu\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t exynos4_tmu_show_temp(struct device *dev,
 | 
				
			||||||
 | 
							struct device_attribute *attr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = exynos4_tmu_read(data);
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* convert from degree Celsius to millidegree Celsius */
 | 
				
			||||||
 | 
						return sprintf(buf, "%d\n", ret * 1000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t exynos4_tmu_show_alarm(struct device *dev,
 | 
				
			||||||
 | 
							struct device_attribute *devattr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						int temp;
 | 
				
			||||||
 | 
						unsigned int trigger_level;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						temp = exynos4_tmu_read(data);
 | 
				
			||||||
 | 
						if (temp < 0)
 | 
				
			||||||
 | 
							return temp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sprintf(buf, "%d\n", !!(temp > trigger_level));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t exynos4_tmu_show_level(struct device *dev,
 | 
				
			||||||
 | 
							struct device_attribute *devattr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = data->pdata;
 | 
				
			||||||
 | 
						unsigned int temp = pdata->threshold +
 | 
				
			||||||
 | 
								pdata->trigger_levels[attr->index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sprintf(buf, "%u\n", temp * 1000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
 | 
				
			||||||
 | 
							exynos4_tmu_show_alarm, NULL, 1);
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
 | 
				
			||||||
 | 
							exynos4_tmu_show_alarm, NULL, 2);
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
 | 
				
			||||||
 | 
							exynos4_tmu_show_alarm, NULL, 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
 | 
				
			||||||
 | 
					static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
 | 
				
			||||||
 | 
							exynos4_tmu_show_level, NULL, 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct attribute *exynos4_tmu_attributes[] = {
 | 
				
			||||||
 | 
						&dev_attr_name.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_input.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_max.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_crit.dev_attr.attr,
 | 
				
			||||||
 | 
						&sensor_dev_attr_temp1_emergency.dev_attr.attr,
 | 
				
			||||||
 | 
						NULL,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct attribute_group exynos4_tmu_attr_group = {
 | 
				
			||||||
 | 
						.attrs = exynos4_tmu_attributes,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data;
 | 
				
			||||||
 | 
						struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!pdata) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "No platform init data supplied.\n");
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!data) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->irq = platform_get_irq(pdev, 0);
 | 
				
			||||||
 | 
						if (data->irq < 0) {
 | 
				
			||||||
 | 
							ret = data->irq;
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to get platform irq\n");
 | 
				
			||||||
 | 
							goto err_free;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						INIT_WORK(&data->irq_work, exynos4_tmu_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
				
			||||||
 | 
						if (!data->mem) {
 | 
				
			||||||
 | 
							ret = -ENOENT;
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to get platform resource\n");
 | 
				
			||||||
 | 
							goto err_free;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->mem = request_mem_region(data->mem->start,
 | 
				
			||||||
 | 
								resource_size(data->mem), pdev->name);
 | 
				
			||||||
 | 
						if (!data->mem) {
 | 
				
			||||||
 | 
							ret = -ENODEV;
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to request memory region\n");
 | 
				
			||||||
 | 
							goto err_free;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->base = ioremap(data->mem->start, resource_size(data->mem));
 | 
				
			||||||
 | 
						if (!data->base) {
 | 
				
			||||||
 | 
							ret = -ENODEV;
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to ioremap memory\n");
 | 
				
			||||||
 | 
							goto err_mem_region;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = request_irq(data->irq, exynos4_tmu_irq,
 | 
				
			||||||
 | 
							IRQF_TRIGGER_RISING,
 | 
				
			||||||
 | 
							"exynos4-tmu", data);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
 | 
				
			||||||
 | 
							goto err_io_remap;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->clk = clk_get(NULL, "tmu_apbif");
 | 
				
			||||||
 | 
						if (IS_ERR(data->clk)) {
 | 
				
			||||||
 | 
							ret = PTR_ERR(data->clk);
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to get clock\n");
 | 
				
			||||||
 | 
							goto err_irq;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->pdata = pdata;
 | 
				
			||||||
 | 
						platform_set_drvdata(pdev, data);
 | 
				
			||||||
 | 
						mutex_init(&data->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = exynos4_tmu_initialize(pdev);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to initialize TMU\n");
 | 
				
			||||||
 | 
							goto err_clk;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to create sysfs group\n");
 | 
				
			||||||
 | 
							goto err_clk;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->hwmon_dev = hwmon_device_register(&pdev->dev);
 | 
				
			||||||
 | 
						if (IS_ERR(data->hwmon_dev)) {
 | 
				
			||||||
 | 
							ret = PTR_ERR(data->hwmon_dev);
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "Failed to register hwmon device\n");
 | 
				
			||||||
 | 
							goto err_create_group;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						exynos4_tmu_control(pdev, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_create_group:
 | 
				
			||||||
 | 
						sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
 | 
				
			||||||
 | 
					err_clk:
 | 
				
			||||||
 | 
						platform_set_drvdata(pdev, NULL);
 | 
				
			||||||
 | 
						clk_put(data->clk);
 | 
				
			||||||
 | 
					err_irq:
 | 
				
			||||||
 | 
						free_irq(data->irq, data);
 | 
				
			||||||
 | 
					err_io_remap:
 | 
				
			||||||
 | 
						iounmap(data->base);
 | 
				
			||||||
 | 
					err_mem_region:
 | 
				
			||||||
 | 
						release_mem_region(data->mem->start, resource_size(data->mem));
 | 
				
			||||||
 | 
					err_free:
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						exynos4_tmu_control(pdev, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hwmon_device_unregister(data->hwmon_dev);
 | 
				
			||||||
 | 
						sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_put(data->clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						free_irq(data->irq, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						iounmap(data->base);
 | 
				
			||||||
 | 
						release_mem_region(data->mem->start, resource_size(data->mem));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						platform_set_drvdata(pdev, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_PM
 | 
				
			||||||
 | 
					static int exynos4_tmu_suspend(struct platform_device *pdev, pm_message_t state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						exynos4_tmu_control(pdev, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int exynos4_tmu_resume(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						exynos4_tmu_initialize(pdev);
 | 
				
			||||||
 | 
						exynos4_tmu_control(pdev, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#define exynos4_tmu_suspend NULL
 | 
				
			||||||
 | 
					#define exynos4_tmu_resume NULL
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct platform_driver exynos4_tmu_driver = {
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							.name   = "exynos4-tmu",
 | 
				
			||||||
 | 
							.owner  = THIS_MODULE,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						.probe = exynos4_tmu_probe,
 | 
				
			||||||
 | 
						.remove	= __devexit_p(exynos4_tmu_remove),
 | 
				
			||||||
 | 
						.suspend = exynos4_tmu_suspend,
 | 
				
			||||||
 | 
						.resume = exynos4_tmu_resume,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __init exynos4_tmu_driver_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return platform_driver_register(&exynos4_tmu_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					module_init(exynos4_tmu_driver_init);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void __exit exynos4_tmu_driver_exit(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						platform_driver_unregister(&exynos4_tmu_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					module_exit(exynos4_tmu_driver_exit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
 | 
					MODULE_ALIAS("platform:exynos4-tmu");
 | 
				
			||||||
| 
						 | 
					@ -605,7 +605,7 @@ static struct sensor_device_attribute_2 fxxxx_fan_beep_attr[] = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* PWM attr for the f71862fg, fewer pwms and fewer zones per pwm than the
 | 
					/* PWM attr for the f71862fg, fewer pwms and fewer zones per pwm than the
 | 
				
			||||||
   standard models */
 | 
					   standard models */
 | 
				
			||||||
static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
 | 
					static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[3][7] = { {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 0),
 | 
							      store_pwm_auto_point_channel, 0, 0),
 | 
				
			||||||
| 
						 | 
					@ -627,7 +627,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 0),
 | 
							      0, 0),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 1),
 | 
							      store_pwm_auto_point_channel, 0, 1),
 | 
				
			||||||
| 
						 | 
					@ -649,7 +649,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 1),
 | 
							      0, 1),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 2),
 | 
							      store_pwm_auto_point_channel, 0, 2),
 | 
				
			||||||
| 
						 | 
					@ -671,12 +671,12 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 2),
 | 
							      0, 2),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
				
			||||||
};
 | 
					} };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* PWM attr for the f71808e/f71869, almost identical to the f71862fg, but the
 | 
					/* PWM attr for the f71808e/f71869, almost identical to the f71862fg, but the
 | 
				
			||||||
   pwm setting when the temperature is above the pwmX_auto_point1_temp can be
 | 
					   pwm setting when the temperature is above the pwmX_auto_point1_temp can be
 | 
				
			||||||
   programmed instead of being hardcoded to 0xff */
 | 
					   programmed instead of being hardcoded to 0xff */
 | 
				
			||||||
static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
 | 
					static struct sensor_device_attribute_2 f71869_auto_pwm_attr[3][8] = { {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 0),
 | 
							      store_pwm_auto_point_channel, 0, 0),
 | 
				
			||||||
| 
						 | 
					@ -701,7 +701,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 0),
 | 
							      0, 0),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 1),
 | 
							      store_pwm_auto_point_channel, 0, 1),
 | 
				
			||||||
| 
						 | 
					@ -726,7 +726,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 1),
 | 
							      0, 1),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 2),
 | 
							      store_pwm_auto_point_channel, 0, 2),
 | 
				
			||||||
| 
						 | 
					@ -751,7 +751,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
 | 
				
			||||||
		      0, 2),
 | 
							      0, 2),
 | 
				
			||||||
	SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
				
			||||||
};
 | 
					} };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* PWM attr for the standard models */
 | 
					/* PWM attr for the standard models */
 | 
				
			||||||
static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { {
 | 
					static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { {
 | 
				
			||||||
| 
						 | 
					@ -928,7 +928,7 @@ static struct sensor_device_attribute_2 f8000_fan_attr[] = {
 | 
				
			||||||
/* PWM attr for the f8000, zones mapped to temp instead of to pwm!
 | 
					/* PWM attr for the f8000, zones mapped to temp instead of to pwm!
 | 
				
			||||||
   Also the register block at offset A0 maps to TEMP1 (so our temp2, as the
 | 
					   Also the register block at offset A0 maps to TEMP1 (so our temp2, as the
 | 
				
			||||||
   F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 */
 | 
					   F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 */
 | 
				
			||||||
static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
 | 
					static struct sensor_device_attribute_2 f8000_auto_pwm_attr[3][14] = { {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 0),
 | 
							      store_pwm_auto_point_channel, 0, 0),
 | 
				
			||||||
| 
						 | 
					@ -969,7 +969,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 2, 2),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 2, 2),
 | 
				
			||||||
	SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 2),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 1),
 | 
							      store_pwm_auto_point_channel, 0, 1),
 | 
				
			||||||
| 
						 | 
					@ -1010,7 +1010,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 2, 0),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 2, 0),
 | 
				
			||||||
	SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 0),
 | 
				
			||||||
 | 
					}, {
 | 
				
			||||||
	SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
						SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
 | 
				
			||||||
		      show_pwm_auto_point_channel,
 | 
							      show_pwm_auto_point_channel,
 | 
				
			||||||
		      store_pwm_auto_point_channel, 0, 2),
 | 
							      store_pwm_auto_point_channel, 0, 2),
 | 
				
			||||||
| 
						 | 
					@ -1051,7 +1051,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 2, 1),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 2, 1),
 | 
				
			||||||
	SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO,
 | 
						SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO,
 | 
				
			||||||
		      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
							      show_pwm_auto_point_temp_hyst, NULL, 3, 1),
 | 
				
			||||||
};
 | 
					} };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Super I/O functions */
 | 
					/* Super I/O functions */
 | 
				
			||||||
static inline int superio_inb(int base, int reg)
 | 
					static inline int superio_inb(int base, int reg)
 | 
				
			||||||
| 
						 | 
					@ -2154,6 +2154,104 @@ static void f71882fg_remove_sysfs_files(struct platform_device *pdev,
 | 
				
			||||||
		device_remove_file(&pdev->dev, &attr[i].dev_attr);
 | 
							device_remove_file(&pdev->dev, &attr[i].dev_attr);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __devinit f71882fg_create_fan_sysfs_files(
 | 
				
			||||||
 | 
						struct platform_device *pdev, int idx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct f71882fg_data *data = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Sanity check the pwm setting */
 | 
				
			||||||
 | 
						err = 0;
 | 
				
			||||||
 | 
						switch (data->type) {
 | 
				
			||||||
 | 
						case f71858fg:
 | 
				
			||||||
 | 
							if (((data->pwm_enable >> (idx * 2)) & 3) == 3)
 | 
				
			||||||
 | 
								err = 1;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case f71862fg:
 | 
				
			||||||
 | 
							if (((data->pwm_enable >> (idx * 2)) & 1) != 1)
 | 
				
			||||||
 | 
								err = 1;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case f8000:
 | 
				
			||||||
 | 
							if (idx == 2)
 | 
				
			||||||
 | 
								err = data->pwm_enable & 0x20;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (err) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev,
 | 
				
			||||||
 | 
								"Invalid (reserved) pwm settings: 0x%02x, "
 | 
				
			||||||
 | 
								"skipping fan %d\n",
 | 
				
			||||||
 | 
								(data->pwm_enable >> (idx * 2)) & 3, idx + 1);
 | 
				
			||||||
 | 
							return 0; /* This is a non fatal condition */
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = f71882fg_create_sysfs_files(pdev, &fxxxx_fan_attr[idx][0],
 | 
				
			||||||
 | 
										  ARRAY_SIZE(fxxxx_fan_attr[0]));
 | 
				
			||||||
 | 
						if (err)
 | 
				
			||||||
 | 
							return err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (f71882fg_fan_has_beep[data->type]) {
 | 
				
			||||||
 | 
							err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
 | 
											  &fxxxx_fan_beep_attr[idx],
 | 
				
			||||||
 | 
											  1);
 | 
				
			||||||
 | 
							if (err)
 | 
				
			||||||
 | 
								return err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_info(&pdev->dev, "Fan: %d is in %s mode\n", idx + 1,
 | 
				
			||||||
 | 
							 (data->pwm_enable & (1 << (2 * idx))) ? "duty-cycle" : "RPM");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Check for unsupported auto pwm settings */
 | 
				
			||||||
 | 
						switch (data->type) {
 | 
				
			||||||
 | 
						case f71808e:
 | 
				
			||||||
 | 
						case f71808a:
 | 
				
			||||||
 | 
						case f71869:
 | 
				
			||||||
 | 
						case f71869a:
 | 
				
			||||||
 | 
						case f71889fg:
 | 
				
			||||||
 | 
						case f71889ed:
 | 
				
			||||||
 | 
						case f71889a:
 | 
				
			||||||
 | 
							data->pwm_auto_point_mapping[idx] =
 | 
				
			||||||
 | 
								f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(idx));
 | 
				
			||||||
 | 
							if ((data->pwm_auto_point_mapping[idx] & 0x80) ||
 | 
				
			||||||
 | 
							    (data->pwm_auto_point_mapping[idx] & 3) == 0) {
 | 
				
			||||||
 | 
								dev_warn(&pdev->dev,
 | 
				
			||||||
 | 
									 "Auto pwm controlled by raw digital "
 | 
				
			||||||
 | 
									 "data, disabling pwm auto_point "
 | 
				
			||||||
 | 
									 "sysfs attributes for fan %d\n", idx + 1);
 | 
				
			||||||
 | 
								return 0; /* This is a non fatal condition */
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (data->type) {
 | 
				
			||||||
 | 
						case f71862fg:
 | 
				
			||||||
 | 
							err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
 | 
										&f71862fg_auto_pwm_attr[idx][0],
 | 
				
			||||||
 | 
										ARRAY_SIZE(f71862fg_auto_pwm_attr[0]));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case f71808e:
 | 
				
			||||||
 | 
						case f71869:
 | 
				
			||||||
 | 
							err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
 | 
										&f71869_auto_pwm_attr[idx][0],
 | 
				
			||||||
 | 
										ARRAY_SIZE(f71869_auto_pwm_attr[0]));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case f8000:
 | 
				
			||||||
 | 
							err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
 | 
										&f8000_auto_pwm_attr[idx][0],
 | 
				
			||||||
 | 
										ARRAY_SIZE(f8000_auto_pwm_attr[0]));
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
 | 
										&fxxxx_auto_pwm_attr[idx][0],
 | 
				
			||||||
 | 
										ARRAY_SIZE(fxxxx_auto_pwm_attr[0]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __devinit f71882fg_probe(struct platform_device *pdev)
 | 
					static int __devinit f71882fg_probe(struct platform_device *pdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct f71882fg_data *data;
 | 
						struct f71882fg_data *data;
 | 
				
			||||||
| 
						 | 
					@ -2272,117 +2370,29 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
 | 
				
			||||||
		data->pwm_enable =
 | 
							data->pwm_enable =
 | 
				
			||||||
			f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
 | 
								f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Sanity check the pwm settings */
 | 
					 | 
				
			||||||
		switch (data->type) {
 | 
					 | 
				
			||||||
		case f71858fg:
 | 
					 | 
				
			||||||
			err = 0;
 | 
					 | 
				
			||||||
			for (i = 0; i < nr_fans; i++)
 | 
					 | 
				
			||||||
				if (((data->pwm_enable >> (i * 2)) & 3) == 3)
 | 
					 | 
				
			||||||
					err = 1;
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case f71862fg:
 | 
					 | 
				
			||||||
			err = (data->pwm_enable & 0x15) != 0x15;
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case f8000:
 | 
					 | 
				
			||||||
			err = data->pwm_enable & 0x20;
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			err = 0;
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (err) {
 | 
					 | 
				
			||||||
			dev_err(&pdev->dev,
 | 
					 | 
				
			||||||
				"Invalid (reserved) pwm settings: 0x%02x\n",
 | 
					 | 
				
			||||||
				(unsigned int)data->pwm_enable);
 | 
					 | 
				
			||||||
			err = -ENODEV;
 | 
					 | 
				
			||||||
			goto exit_unregister_sysfs;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		err = f71882fg_create_sysfs_files(pdev, &fxxxx_fan_attr[0][0],
 | 
					 | 
				
			||||||
				ARRAY_SIZE(fxxxx_fan_attr[0]) * nr_fans);
 | 
					 | 
				
			||||||
		if (err)
 | 
					 | 
				
			||||||
			goto exit_unregister_sysfs;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (f71882fg_fan_has_beep[data->type]) {
 | 
					 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
					 | 
				
			||||||
					fxxxx_fan_beep_attr, nr_fans);
 | 
					 | 
				
			||||||
			if (err)
 | 
					 | 
				
			||||||
				goto exit_unregister_sysfs;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		switch (data->type) {
 | 
					 | 
				
			||||||
		case f71808e:
 | 
					 | 
				
			||||||
		case f71808a:
 | 
					 | 
				
			||||||
		case f71869:
 | 
					 | 
				
			||||||
		case f71869a:
 | 
					 | 
				
			||||||
		case f71889fg:
 | 
					 | 
				
			||||||
		case f71889ed:
 | 
					 | 
				
			||||||
		case f71889a:
 | 
					 | 
				
			||||||
		for (i = 0; i < nr_fans; i++) {
 | 
							for (i = 0; i < nr_fans; i++) {
 | 
				
			||||||
				data->pwm_auto_point_mapping[i] =
 | 
								err = f71882fg_create_fan_sysfs_files(pdev, i);
 | 
				
			||||||
					f71882fg_read8(data,
 | 
					 | 
				
			||||||
						F71882FG_REG_POINT_MAPPING(i));
 | 
					 | 
				
			||||||
				if ((data->pwm_auto_point_mapping[i] & 0x80) ||
 | 
					 | 
				
			||||||
				    (data->pwm_auto_point_mapping[i] & 3) == 0)
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if (i != nr_fans) {
 | 
					 | 
				
			||||||
				dev_warn(&pdev->dev,
 | 
					 | 
				
			||||||
					 "Auto pwm controlled by raw digital "
 | 
					 | 
				
			||||||
					 "data, disabling pwm auto_point "
 | 
					 | 
				
			||||||
					 "sysfs attributes\n");
 | 
					 | 
				
			||||||
				goto no_pwm_auto_point;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		switch (data->type) {
 | 
					 | 
				
			||||||
		case f71808a:
 | 
					 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
					 | 
				
			||||||
				&fxxxx_auto_pwm_attr[0][0],
 | 
					 | 
				
			||||||
				ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
 | 
					 | 
				
			||||||
			if (err)
 | 
								if (err)
 | 
				
			||||||
				goto exit_unregister_sysfs;
 | 
									goto exit_unregister_sysfs;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* Some types have 1 extra fan with limited functionality */
 | 
				
			||||||
 | 
							switch (data->type) {
 | 
				
			||||||
 | 
							case f71808a:
 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
								err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
					f71808a_fan3_attr,
 | 
										f71808a_fan3_attr,
 | 
				
			||||||
					ARRAY_SIZE(f71808a_fan3_attr));
 | 
										ARRAY_SIZE(f71808a_fan3_attr));
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case f71862fg:
 | 
					 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
					 | 
				
			||||||
					f71862fg_auto_pwm_attr,
 | 
					 | 
				
			||||||
					ARRAY_SIZE(f71862fg_auto_pwm_attr));
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case f71808e:
 | 
					 | 
				
			||||||
		case f71869:
 | 
					 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
					 | 
				
			||||||
					f71869_auto_pwm_attr,
 | 
					 | 
				
			||||||
					ARRAY_SIZE(f71869_auto_pwm_attr));
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		case f8000:
 | 
							case f8000:
 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
								err = f71882fg_create_sysfs_files(pdev,
 | 
				
			||||||
					f8000_fan_attr,
 | 
										f8000_fan_attr,
 | 
				
			||||||
					ARRAY_SIZE(f8000_fan_attr));
 | 
										ARRAY_SIZE(f8000_fan_attr));
 | 
				
			||||||
			if (err)
 | 
					 | 
				
			||||||
				goto exit_unregister_sysfs;
 | 
					 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
					 | 
				
			||||||
					f8000_auto_pwm_attr,
 | 
					 | 
				
			||||||
					ARRAY_SIZE(f8000_auto_pwm_attr));
 | 
					 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			err = f71882fg_create_sysfs_files(pdev,
 | 
								break;
 | 
				
			||||||
				&fxxxx_auto_pwm_attr[0][0],
 | 
					 | 
				
			||||||
				ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (err)
 | 
							if (err)
 | 
				
			||||||
			goto exit_unregister_sysfs;
 | 
								goto exit_unregister_sysfs;
 | 
				
			||||||
 | 
					 | 
				
			||||||
no_pwm_auto_point:
 | 
					 | 
				
			||||||
		for (i = 0; i < nr_fans; i++)
 | 
					 | 
				
			||||||
			dev_info(&pdev->dev, "Fan: %d is in %s mode\n", i + 1,
 | 
					 | 
				
			||||||
				 (data->pwm_enable & (1 << 2 * i)) ?
 | 
					 | 
				
			||||||
				 "duty-cycle" : "RPM");
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data->hwmon_dev = hwmon_device_register(&pdev->dev);
 | 
						data->hwmon_dev = hwmon_device_register(&pdev->dev);
 | 
				
			||||||
| 
						 | 
					@ -2476,22 +2486,23 @@ static int f71882fg_remove(struct platform_device *pdev)
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case f71862fg:
 | 
							case f71862fg:
 | 
				
			||||||
			f71882fg_remove_sysfs_files(pdev,
 | 
								f71882fg_remove_sysfs_files(pdev,
 | 
				
			||||||
					f71862fg_auto_pwm_attr,
 | 
									&f71862fg_auto_pwm_attr[0][0],
 | 
				
			||||||
					ARRAY_SIZE(f71862fg_auto_pwm_attr));
 | 
									ARRAY_SIZE(f71862fg_auto_pwm_attr[0]) *
 | 
				
			||||||
 | 
										nr_fans);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case f71808e:
 | 
							case f71808e:
 | 
				
			||||||
		case f71869:
 | 
							case f71869:
 | 
				
			||||||
			f71882fg_remove_sysfs_files(pdev,
 | 
								f71882fg_remove_sysfs_files(pdev,
 | 
				
			||||||
					f71869_auto_pwm_attr,
 | 
									&f71869_auto_pwm_attr[0][0],
 | 
				
			||||||
					ARRAY_SIZE(f71869_auto_pwm_attr));
 | 
									ARRAY_SIZE(f71869_auto_pwm_attr[0]) * nr_fans);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case f8000:
 | 
							case f8000:
 | 
				
			||||||
			f71882fg_remove_sysfs_files(pdev,
 | 
								f71882fg_remove_sysfs_files(pdev,
 | 
				
			||||||
					f8000_fan_attr,
 | 
										f8000_fan_attr,
 | 
				
			||||||
					ARRAY_SIZE(f8000_fan_attr));
 | 
										ARRAY_SIZE(f8000_fan_attr));
 | 
				
			||||||
			f71882fg_remove_sysfs_files(pdev,
 | 
								f71882fg_remove_sysfs_files(pdev,
 | 
				
			||||||
					f8000_auto_pwm_attr,
 | 
									&f8000_auto_pwm_attr[0][0],
 | 
				
			||||||
					ARRAY_SIZE(f8000_auto_pwm_attr));
 | 
									ARRAY_SIZE(f8000_auto_pwm_attr[0]) * nr_fans);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			f71882fg_remove_sysfs_files(pdev,
 | 
								f71882fg_remove_sysfs_files(pdev,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum lm75_type {		/* keep sorted in alphabetical order */
 | 
					enum lm75_type {		/* keep sorted in alphabetical order */
 | 
				
			||||||
 | 
						adt75,
 | 
				
			||||||
	ds1775,
 | 
						ds1775,
 | 
				
			||||||
	ds75,
 | 
						ds75,
 | 
				
			||||||
	lm75,
 | 
						lm75,
 | 
				
			||||||
| 
						 | 
					@ -213,6 +214,7 @@ static int lm75_remove(struct i2c_client *client)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id lm75_ids[] = {
 | 
					static const struct i2c_device_id lm75_ids[] = {
 | 
				
			||||||
 | 
						{ "adt75", adt75, },
 | 
				
			||||||
	{ "ds1775", ds1775, },
 | 
						{ "ds1775", ds1775, },
 | 
				
			||||||
	{ "ds75", ds75, },
 | 
						{ "ds75", ds75, },
 | 
				
			||||||
	{ "lm75", lm75, },
 | 
						{ "lm75", lm75, },
 | 
				
			||||||
| 
						 | 
					@ -247,19 +249,30 @@ static int lm75_detect(struct i2c_client *new_client,
 | 
				
			||||||
				     I2C_FUNC_SMBUS_WORD_DATA))
 | 
									     I2C_FUNC_SMBUS_WORD_DATA))
 | 
				
			||||||
		return -ENODEV;
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Now, we do the remaining detection. There is no identification-
 | 
						/*
 | 
				
			||||||
	   dedicated register so we have to rely on several tricks:
 | 
						 * Now, we do the remaining detection. There is no identification-
 | 
				
			||||||
	   unused bits, registers cycling over 8-address boundaries,
 | 
						 * dedicated register so we have to rely on several tricks:
 | 
				
			||||||
	   addresses 0x04-0x07 returning the last read value.
 | 
						 * unused bits, registers cycling over 8-address boundaries,
 | 
				
			||||||
	   The cycling+unused addresses combination is not tested,
 | 
						 * addresses 0x04-0x07 returning the last read value.
 | 
				
			||||||
	   since it would significantly slow the detection down and would
 | 
						 * The cycling+unused addresses combination is not tested,
 | 
				
			||||||
	   hardly add any value.
 | 
						 * since it would significantly slow the detection down and would
 | 
				
			||||||
 | 
						 * hardly add any value.
 | 
				
			||||||
	   The National Semiconductor LM75A is different than earlier
 | 
						 *
 | 
				
			||||||
	   LM75s.  It has an ID byte of 0xaX (where X is the chip
 | 
						 * The National Semiconductor LM75A is different than earlier
 | 
				
			||||||
	   revision, with 1 being the only revision in existence) in
 | 
						 * LM75s.  It has an ID byte of 0xaX (where X is the chip
 | 
				
			||||||
	   register 7, and unused registers return 0xff rather than the
 | 
						 * revision, with 1 being the only revision in existence) in
 | 
				
			||||||
	   last read value. */
 | 
						 * register 7, and unused registers return 0xff rather than the
 | 
				
			||||||
 | 
						 * last read value.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Note that this function only detects the original National
 | 
				
			||||||
 | 
						 * Semiconductor LM75 and the LM75A. Clones from other vendors
 | 
				
			||||||
 | 
						 * aren't detected, on purpose, because they are typically never
 | 
				
			||||||
 | 
						 * found on PC hardware. They are found on embedded designs where
 | 
				
			||||||
 | 
						 * they can be instantiated explicitly so detection is not needed.
 | 
				
			||||||
 | 
						 * The absence of identification registers on all these clones
 | 
				
			||||||
 | 
						 * would make their exhaustive detection very difficult and weak,
 | 
				
			||||||
 | 
						 * and odds are that the driver would bind to unsupported devices.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Unused bits */
 | 
						/* Unused bits */
 | 
				
			||||||
	conf = i2c_smbus_read_byte_data(new_client, 1);
 | 
						conf = i2c_smbus_read_byte_data(new_client, 1);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,17 +20,18 @@ config SENSORS_PMBUS
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	  If you say yes here you get hardware monitoring support for generic
 | 
						  If you say yes here you get hardware monitoring support for generic
 | 
				
			||||||
	  PMBus devices, including but not limited to ADP4000, BMR450, BMR451,
 | 
						  PMBus devices, including but not limited to ADP4000, BMR450, BMR451,
 | 
				
			||||||
	  BMR453, BMR454, LTC2978, NCP4200, and NCP4208.
 | 
						  BMR453, BMR454, NCP4200, and NCP4208.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	  This driver can also be built as a module. If so, the module will
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
	  be called pmbus.
 | 
						  be called pmbus.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config SENSORS_ADM1275
 | 
					config SENSORS_ADM1275
 | 
				
			||||||
	tristate "Analog Devices ADM1275"
 | 
						tristate "Analog Devices ADM1275 and compatibles"
 | 
				
			||||||
	default n
 | 
						default n
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	  If you say yes here you get hardware monitoring support for Analog
 | 
						  If you say yes here you get hardware monitoring support for Analog
 | 
				
			||||||
	  Devices ADM1275 Hot-Swap Controller and Digital Power Monitor.
 | 
						  Devices ADM1275 and ADM1276 Hot-Swap Controller and Digital Power
 | 
				
			||||||
 | 
						  Monitor.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	  This driver can also be built as a module. If so, the module will
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
	  be called adm1275.
 | 
						  be called adm1275.
 | 
				
			||||||
| 
						 | 
					@ -45,6 +46,16 @@ config SENSORS_LM25066
 | 
				
			||||||
	  This driver can also be built as a module. If so, the module will
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
	  be called lm25066.
 | 
						  be called lm25066.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config SENSORS_LTC2978
 | 
				
			||||||
 | 
						tristate "Linear Technologies LTC2978 and LTC3880"
 | 
				
			||||||
 | 
						default n
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  If you say yes here you get hardware monitoring support for Linear
 | 
				
			||||||
 | 
						  Technology LTC2978 and LTC3880.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
 | 
						  be called ltc2978.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config SENSORS_MAX16064
 | 
					config SENSORS_MAX16064
 | 
				
			||||||
	tristate "Maxim MAX16064"
 | 
						tristate "Maxim MAX16064"
 | 
				
			||||||
	default n
 | 
						default n
 | 
				
			||||||
| 
						 | 
					@ -97,4 +108,15 @@ config SENSORS_UCD9200
 | 
				
			||||||
	  This driver can also be built as a module. If so, the module will
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
	  be called ucd9200.
 | 
						  be called ucd9200.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config SENSORS_ZL6100
 | 
				
			||||||
 | 
						tristate "Intersil ZL6100 and compatibles"
 | 
				
			||||||
 | 
						default n
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  If you say yes here you get hardware monitoring support for Intersil
 | 
				
			||||||
 | 
						  ZL2004, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, and ZL6105 Digital
 | 
				
			||||||
 | 
						  DC/DC Controllers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  This driver can also be built as a module. If so, the module will
 | 
				
			||||||
 | 
						  be called zl6100.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
endif # PMBUS
 | 
					endif # PMBUS
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,10 @@ obj-$(CONFIG_PMBUS)		+= pmbus_core.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_PMBUS)	+= pmbus.o
 | 
					obj-$(CONFIG_SENSORS_PMBUS)	+= pmbus.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_ADM1275)	+= adm1275.o
 | 
					obj-$(CONFIG_SENSORS_ADM1275)	+= adm1275.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_LM25066)	+= lm25066.o
 | 
					obj-$(CONFIG_SENSORS_LM25066)	+= lm25066.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_SENSORS_LTC2978)	+= ltc2978.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_MAX16064)	+= max16064.o
 | 
					obj-$(CONFIG_SENSORS_MAX16064)	+= max16064.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o
 | 
					obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.o
 | 
					obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_UCD9000)	+= ucd9000.o
 | 
					obj-$(CONFIG_SENSORS_UCD9000)	+= ucd9000.o
 | 
				
			||||||
obj-$(CONFIG_SENSORS_UCD9200)	+= ucd9200.o
 | 
					obj-$(CONFIG_SENSORS_UCD9200)	+= ucd9200.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_SENSORS_ZL6100)	+= zl6100.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,8 @@
 | 
				
			||||||
#include <linux/i2c.h>
 | 
					#include <linux/i2c.h>
 | 
				
			||||||
#include "pmbus.h"
 | 
					#include "pmbus.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum chips { adm1275, adm1276 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define ADM1275_PEAK_IOUT		0xd0
 | 
					#define ADM1275_PEAK_IOUT		0xd0
 | 
				
			||||||
#define ADM1275_PEAK_VIN		0xd1
 | 
					#define ADM1275_PEAK_VIN		0xd1
 | 
				
			||||||
#define ADM1275_PEAK_VOUT		0xd2
 | 
					#define ADM1275_PEAK_VOUT		0xd2
 | 
				
			||||||
| 
						 | 
					@ -31,14 +33,47 @@
 | 
				
			||||||
#define ADM1275_VIN_VOUT_SELECT		(1 << 6)
 | 
					#define ADM1275_VIN_VOUT_SELECT		(1 << 6)
 | 
				
			||||||
#define ADM1275_VRANGE			(1 << 5)
 | 
					#define ADM1275_VRANGE			(1 << 5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ADM1275_IOUT_WARN2_LIMIT	0xd7
 | 
				
			||||||
 | 
					#define ADM1275_DEVICE_CONFIG		0xd8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ADM1275_IOUT_WARN2_SELECT	(1 << 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ADM1276_PEAK_PIN		0xda
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ADM1275_MFR_STATUS_IOUT_WARN2	(1 << 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct adm1275_data {
 | 
				
			||||||
 | 
						int id;
 | 
				
			||||||
 | 
						bool have_oc_fault;
 | 
				
			||||||
 | 
						struct pmbus_driver_info info;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define to_adm1275_data(x)  container_of(x, struct adm1275_data, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
 | 
					static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						const struct adm1275_data *data = to_adm1275_data(info);
 | 
				
			||||||
 | 
						int ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page)
 | 
						if (page)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_IOUT_UC_FAULT_LIMIT:
 | 
				
			||||||
 | 
							if (data->have_oc_fault) {
 | 
				
			||||||
 | 
								ret = -ENXIO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_IOUT_OC_FAULT_LIMIT:
 | 
				
			||||||
 | 
							if (!data->have_oc_fault) {
 | 
				
			||||||
 | 
								ret = -ENXIO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	case PMBUS_VIRT_READ_IOUT_MAX:
 | 
						case PMBUS_VIRT_READ_IOUT_MAX:
 | 
				
			||||||
		ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT);
 | 
							ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					@ -48,10 +83,20 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
	case PMBUS_VIRT_READ_VIN_MAX:
 | 
						case PMBUS_VIRT_READ_VIN_MAX:
 | 
				
			||||||
		ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN);
 | 
							ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_PIN_MAX:
 | 
				
			||||||
 | 
							if (data->id != adm1276) {
 | 
				
			||||||
 | 
								ret = -ENXIO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
						case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
				
			||||||
	case PMBUS_VIRT_RESET_VOUT_HISTORY:
 | 
						case PMBUS_VIRT_RESET_VOUT_HISTORY:
 | 
				
			||||||
	case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
						case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
				
			||||||
		ret = 0;
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_PIN_HISTORY:
 | 
				
			||||||
 | 
							if (data->id != adm1276)
 | 
				
			||||||
 | 
								ret = -ENXIO;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		ret = -ENODATA;
 | 
							ret = -ENODATA;
 | 
				
			||||||
| 
						 | 
					@ -66,9 +111,14 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page)
 | 
						if (page)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_IOUT_UC_FAULT_LIMIT:
 | 
				
			||||||
 | 
						case PMBUS_IOUT_OC_FAULT_LIMIT:
 | 
				
			||||||
 | 
							ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT,
 | 
				
			||||||
 | 
										    word);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
						case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
				
			||||||
		ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0);
 | 
							ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					@ -78,6 +128,41 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
	case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
						case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
				
			||||||
		ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0);
 | 
							ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_PIN_HISTORY:
 | 
				
			||||||
 | 
							ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = -ENODATA;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						const struct adm1275_data *data = to_adm1275_data(info);
 | 
				
			||||||
 | 
						int mfr_status, ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page > 0)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_STATUS_IOUT:
 | 
				
			||||||
 | 
							ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT);
 | 
				
			||||||
 | 
							if (ret < 0)
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							mfr_status = pmbus_read_byte_data(client, page,
 | 
				
			||||||
 | 
											  PMBUS_STATUS_MFR_SPECIFIC);
 | 
				
			||||||
 | 
							if (mfr_status < 0) {
 | 
				
			||||||
 | 
								ret = mfr_status;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) {
 | 
				
			||||||
 | 
								ret |= data->have_oc_fault ?
 | 
				
			||||||
 | 
								  PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		ret = -ENODATA;
 | 
							ret = -ENODATA;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					@ -88,16 +173,17 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
static int adm1275_probe(struct i2c_client *client,
 | 
					static int adm1275_probe(struct i2c_client *client,
 | 
				
			||||||
			 const struct i2c_device_id *id)
 | 
								 const struct i2c_device_id *id)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int config;
 | 
						int config, device_config;
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
	struct pmbus_driver_info *info;
 | 
						struct pmbus_driver_info *info;
 | 
				
			||||||
 | 
						struct adm1275_data *data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!i2c_check_functionality(client->adapter,
 | 
						if (!i2c_check_functionality(client->adapter,
 | 
				
			||||||
				     I2C_FUNC_SMBUS_READ_BYTE_DATA))
 | 
									     I2C_FUNC_SMBUS_READ_BYTE_DATA))
 | 
				
			||||||
		return -ENODEV;
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL);
 | 
						data = kzalloc(sizeof(struct adm1275_data), GFP_KERNEL);
 | 
				
			||||||
	if (!info)
 | 
						if (!data)
 | 
				
			||||||
		return -ENOMEM;
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
 | 
						config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
 | 
				
			||||||
| 
						 | 
					@ -106,6 +192,15 @@ static int adm1275_probe(struct i2c_client *client,
 | 
				
			||||||
		goto err_mem;
 | 
							goto err_mem;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG);
 | 
				
			||||||
 | 
						if (device_config < 0) {
 | 
				
			||||||
 | 
							ret = device_config;
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->id = id->driver_data;
 | 
				
			||||||
 | 
						info = &data->info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info->pages = 1;
 | 
						info->pages = 1;
 | 
				
			||||||
	info->format[PSC_VOLTAGE_IN] = direct;
 | 
						info->format[PSC_VOLTAGE_IN] = direct;
 | 
				
			||||||
	info->format[PSC_VOLTAGE_OUT] = direct;
 | 
						info->format[PSC_VOLTAGE_OUT] = direct;
 | 
				
			||||||
| 
						 | 
					@ -116,6 +211,7 @@ static int adm1275_probe(struct i2c_client *client,
 | 
				
			||||||
	info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
 | 
						info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info->read_word_data = adm1275_read_word_data;
 | 
						info->read_word_data = adm1275_read_word_data;
 | 
				
			||||||
 | 
						info->read_byte_data = adm1275_read_byte_data;
 | 
				
			||||||
	info->write_word_data = adm1275_write_word_data;
 | 
						info->write_word_data = adm1275_write_word_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (config & ADM1275_VRANGE) {
 | 
						if (config & ADM1275_VRANGE) {
 | 
				
			||||||
| 
						 | 
					@ -134,10 +230,36 @@ static int adm1275_probe(struct i2c_client *client,
 | 
				
			||||||
		info->R[PSC_VOLTAGE_OUT] = -1;
 | 
							info->R[PSC_VOLTAGE_OUT] = -1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (device_config & ADM1275_IOUT_WARN2_SELECT)
 | 
				
			||||||
 | 
							data->have_oc_fault = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (id->driver_data) {
 | 
				
			||||||
 | 
						case adm1275:
 | 
				
			||||||
		if (config & ADM1275_VIN_VOUT_SELECT)
 | 
							if (config & ADM1275_VIN_VOUT_SELECT)
 | 
				
			||||||
		info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
 | 
								info->func[0] |=
 | 
				
			||||||
 | 
								  PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
		info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT;
 | 
								info->func[0] |=
 | 
				
			||||||
 | 
								  PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case adm1276:
 | 
				
			||||||
 | 
							info->format[PSC_POWER] = direct;
 | 
				
			||||||
 | 
							info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_STATUS_INPUT;
 | 
				
			||||||
 | 
							if (config & ADM1275_VIN_VOUT_SELECT)
 | 
				
			||||||
 | 
								info->func[0] |=
 | 
				
			||||||
 | 
								  PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
 | 
				
			||||||
 | 
							if (config & ADM1275_VRANGE) {
 | 
				
			||||||
 | 
								info->m[PSC_POWER] = 6043;
 | 
				
			||||||
 | 
								info->b[PSC_POWER] = 0;
 | 
				
			||||||
 | 
								info->R[PSC_POWER] = -2;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								info->m[PSC_POWER] = 2115;
 | 
				
			||||||
 | 
								info->b[PSC_POWER] = 0;
 | 
				
			||||||
 | 
								info->R[PSC_POWER] = -1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = pmbus_do_probe(client, id, info);
 | 
						ret = pmbus_do_probe(client, id, info);
 | 
				
			||||||
	if (ret)
 | 
						if (ret)
 | 
				
			||||||
| 
						 | 
					@ -145,22 +267,23 @@ static int adm1275_probe(struct i2c_client *client,
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
err_mem:
 | 
					err_mem:
 | 
				
			||||||
	kfree(info);
 | 
						kfree(data);
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int adm1275_remove(struct i2c_client *client)
 | 
					static int adm1275_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
	int ret;
 | 
						const struct adm1275_data *data = to_adm1275_data(info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
	kfree(info);
 | 
						kfree(data);
 | 
				
			||||||
	return ret;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id adm1275_id[] = {
 | 
					static const struct i2c_device_id adm1275_id[] = {
 | 
				
			||||||
	{"adm1275", 0},
 | 
						{ "adm1275", adm1275 },
 | 
				
			||||||
 | 
						{ "adm1276", adm1276 },
 | 
				
			||||||
	{ }
 | 
						{ }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
MODULE_DEVICE_TABLE(i2c, adm1275_id);
 | 
					MODULE_DEVICE_TABLE(i2c, adm1275_id);
 | 
				
			||||||
| 
						 | 
					@ -185,7 +308,7 @@ static void __exit adm1275_exit(void)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MODULE_AUTHOR("Guenter Roeck");
 | 
					MODULE_AUTHOR("Guenter Roeck");
 | 
				
			||||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275");
 | 
					MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles");
 | 
				
			||||||
MODULE_LICENSE("GPL");
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
module_init(adm1275_init);
 | 
					module_init(adm1275_init);
 | 
				
			||||||
module_exit(adm1275_exit);
 | 
					module_exit(adm1275_exit);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page > 1)
 | 
						if (page > 1)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Map READ_VAUX into READ_VOUT register on page 1 */
 | 
						/* Map READ_VAUX into READ_VOUT register on page 1 */
 | 
				
			||||||
	if (page == 1) {
 | 
						if (page == 1) {
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			/* No other valid registers on page 1 */
 | 
								/* No other valid registers on page 1 */
 | 
				
			||||||
			ret = -EINVAL;
 | 
								ret = -ENXIO;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		goto done;
 | 
							goto done;
 | 
				
			||||||
| 
						 | 
					@ -138,7 +138,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page > 1)
 | 
						if (page > 1)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
	case PMBUS_IIN_OC_WARN_LIMIT:
 | 
						case PMBUS_IIN_OC_WARN_LIMIT:
 | 
				
			||||||
| 
						 | 
					@ -164,10 +164,10 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
static int lm25066_write_byte(struct i2c_client *client, int page, u8 value)
 | 
					static int lm25066_write_byte(struct i2c_client *client, int page, u8 value)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (page > 1)
 | 
						if (page > 1)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page == 0)
 | 
						if (page <= 0)
 | 
				
			||||||
		return pmbus_write_byte(client, 0, value);
 | 
							return pmbus_write_byte(client, page, value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -309,11 +309,10 @@ static int lm25066_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
	const struct lm25066_data *data = to_lm25066_data(info);
 | 
						const struct lm25066_data *data = to_lm25066_data(info);
 | 
				
			||||||
	int ret;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
	kfree(data);
 | 
						kfree(data);
 | 
				
			||||||
	return ret;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id lm25066_id[] = {
 | 
					static const struct i2c_device_id lm25066_id[] = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										408
									
								
								drivers/hwmon/pmbus/ltc2978.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								drivers/hwmon/pmbus/ltc2978.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,408 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Hardware monitoring driver for LTC2978 and LTC3880
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (c) 2011 Ericsson AB.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program; if not, write to the Free Software
 | 
				
			||||||
 | 
					 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/init.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/i2c.h>
 | 
				
			||||||
 | 
					#include "pmbus.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum chips { ltc2978, ltc3880 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* LTC2978 and LTC3880 */
 | 
				
			||||||
 | 
					#define LTC2978_MFR_VOUT_PEAK		0xdd
 | 
				
			||||||
 | 
					#define LTC2978_MFR_VIN_PEAK		0xde
 | 
				
			||||||
 | 
					#define LTC2978_MFR_TEMPERATURE_PEAK	0xdf
 | 
				
			||||||
 | 
					#define LTC2978_MFR_SPECIAL_ID		0xe7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* LTC2978 only */
 | 
				
			||||||
 | 
					#define LTC2978_MFR_VOUT_MIN		0xfb
 | 
				
			||||||
 | 
					#define LTC2978_MFR_VIN_MIN		0xfc
 | 
				
			||||||
 | 
					#define LTC2978_MFR_TEMPERATURE_MIN	0xfd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* LTC3880 only */
 | 
				
			||||||
 | 
					#define LTC3880_MFR_IOUT_PEAK		0xd7
 | 
				
			||||||
 | 
					#define LTC3880_MFR_CLEAR_PEAKS		0xe3
 | 
				
			||||||
 | 
					#define LTC3880_MFR_TEMPERATURE2_PEAK	0xf4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define LTC2978_ID_REV1			0x0121
 | 
				
			||||||
 | 
					#define LTC2978_ID_REV2			0x0122
 | 
				
			||||||
 | 
					#define LTC3880_ID			0x4000
 | 
				
			||||||
 | 
					#define LTC3880_ID_MASK			0xff00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which
 | 
				
			||||||
 | 
					 * happens pretty much each time chip data is updated. Raw peak data therefore
 | 
				
			||||||
 | 
					 * does not provide much value. To be able to provide useful peak data, keep an
 | 
				
			||||||
 | 
					 * internal cache of measured peak data, which is only cleared if an explicit
 | 
				
			||||||
 | 
					 * "clear peak" command is executed for the sensor in question.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct ltc2978_data {
 | 
				
			||||||
 | 
						enum chips id;
 | 
				
			||||||
 | 
						int vin_min, vin_max;
 | 
				
			||||||
 | 
						int temp_min, temp_max;
 | 
				
			||||||
 | 
						int vout_min[8], vout_max[8];
 | 
				
			||||||
 | 
						int iout_max[2];
 | 
				
			||||||
 | 
						int temp2_max[2];
 | 
				
			||||||
 | 
						struct pmbus_driver_info info;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define to_ltc2978_data(x)  container_of(x, struct ltc2978_data, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline int lin11_to_val(int data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						s16 e = ((s16)data) >> 11;
 | 
				
			||||||
 | 
						s32 m = (((s16)(data << 5)) >> 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * mantissa is 10 bit + sign, exponent adds up to 15 bit.
 | 
				
			||||||
 | 
						 * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						e += 6;
 | 
				
			||||||
 | 
						return (e < 0 ? m >> -e : m << e);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_read_word_data_common(struct i2c_client *client, int page,
 | 
				
			||||||
 | 
										 int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct ltc2978_data *data = to_ltc2978_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VIN_MAX:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret) > lin11_to_val(data->vin_max))
 | 
				
			||||||
 | 
									data->vin_max = ret;
 | 
				
			||||||
 | 
								ret = data->vin_max;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VOUT_MAX:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
								 * VOUT is 16 bit unsigned with fixed exponent,
 | 
				
			||||||
 | 
								 * so we can compare it directly
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								if (ret > data->vout_max[page])
 | 
				
			||||||
 | 
									data->vout_max[page] = ret;
 | 
				
			||||||
 | 
								ret = data->vout_max[page];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_TEMP_MAX:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page,
 | 
				
			||||||
 | 
										   LTC2978_MFR_TEMPERATURE_PEAK);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret) > lin11_to_val(data->temp_max))
 | 
				
			||||||
 | 
									data->temp_max = ret;
 | 
				
			||||||
 | 
								ret = data->temp_max;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_VOUT_HISTORY:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_TEMP_HISTORY:
 | 
				
			||||||
 | 
							ret = 0;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = -ENODATA;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct ltc2978_data *data = to_ltc2978_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VIN_MIN:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret) < lin11_to_val(data->vin_min))
 | 
				
			||||||
 | 
									data->vin_min = ret;
 | 
				
			||||||
 | 
								ret = data->vin_min;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VOUT_MIN:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								/*
 | 
				
			||||||
 | 
								 * VOUT_MIN is known to not be supported on some lots
 | 
				
			||||||
 | 
								 * of LTC2978 revision 1, and will return the maximum
 | 
				
			||||||
 | 
								 * possible voltage if read. If VOUT_MAX is valid and
 | 
				
			||||||
 | 
								 * lower than the reading of VOUT_MIN, use it instead.
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								if (data->vout_max[page] && ret > data->vout_max[page])
 | 
				
			||||||
 | 
									ret = data->vout_max[page];
 | 
				
			||||||
 | 
								if (ret < data->vout_min[page])
 | 
				
			||||||
 | 
									data->vout_min[page] = ret;
 | 
				
			||||||
 | 
								ret = data->vout_min[page];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_TEMP_MIN:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page,
 | 
				
			||||||
 | 
										   LTC2978_MFR_TEMPERATURE_MIN);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret)
 | 
				
			||||||
 | 
								    < lin11_to_val(data->temp_min))
 | 
				
			||||||
 | 
									data->temp_min = ret;
 | 
				
			||||||
 | 
								ret = data->temp_min;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_IOUT_MAX:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_TEMP2_MAX:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_TEMP2_HISTORY:
 | 
				
			||||||
 | 
							ret = -ENXIO;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = ltc2978_read_word_data_common(client, page, reg);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct ltc2978_data *data = to_ltc2978_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_IOUT_MAX:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret)
 | 
				
			||||||
 | 
								    > lin11_to_val(data->iout_max[page]))
 | 
				
			||||||
 | 
									data->iout_max[page] = ret;
 | 
				
			||||||
 | 
								ret = data->iout_max[page];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_TEMP2_MAX:
 | 
				
			||||||
 | 
							ret = pmbus_read_word_data(client, page,
 | 
				
			||||||
 | 
										   LTC3880_MFR_TEMPERATURE2_PEAK);
 | 
				
			||||||
 | 
							if (ret >= 0) {
 | 
				
			||||||
 | 
								if (lin11_to_val(ret)
 | 
				
			||||||
 | 
								    > lin11_to_val(data->temp2_max[page]))
 | 
				
			||||||
 | 
									data->temp2_max[page] = ret;
 | 
				
			||||||
 | 
								ret = data->temp2_max[page];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VIN_MIN:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_VOUT_MIN:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_READ_TEMP_MIN:
 | 
				
			||||||
 | 
							ret = -ENXIO;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_TEMP2_HISTORY:
 | 
				
			||||||
 | 
							ret = 0;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = ltc2978_read_word_data_common(client, page, reg);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_clear_peaks(struct i2c_client *client, int page,
 | 
				
			||||||
 | 
								       enum chips id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (id == ltc2978)
 | 
				
			||||||
 | 
							ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_write_word_data(struct i2c_client *client, int page,
 | 
				
			||||||
 | 
									    int reg, u16 word)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct ltc2978_data *data = to_ltc2978_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (reg) {
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_IOUT_HISTORY:
 | 
				
			||||||
 | 
							data->iout_max[page] = 0x7fff;
 | 
				
			||||||
 | 
							ret = ltc2978_clear_peaks(client, page, data->id);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_TEMP2_HISTORY:
 | 
				
			||||||
 | 
							data->temp2_max[page] = 0x7fff;
 | 
				
			||||||
 | 
							ret = ltc2978_clear_peaks(client, page, data->id);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_VOUT_HISTORY:
 | 
				
			||||||
 | 
							data->vout_min[page] = 0xffff;
 | 
				
			||||||
 | 
							data->vout_max[page] = 0;
 | 
				
			||||||
 | 
							ret = ltc2978_clear_peaks(client, page, data->id);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_VIN_HISTORY:
 | 
				
			||||||
 | 
							data->vin_min = 0x7bff;
 | 
				
			||||||
 | 
							data->vin_max = 0;
 | 
				
			||||||
 | 
							ret = ltc2978_clear_peaks(client, page, data->id);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PMBUS_VIRT_RESET_TEMP_HISTORY:
 | 
				
			||||||
 | 
							data->temp_min = 0x7bff;
 | 
				
			||||||
 | 
							data->temp_max = 0x7fff;
 | 
				
			||||||
 | 
							ret = ltc2978_clear_peaks(client, page, data->id);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = -ENODATA;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct i2c_device_id ltc2978_id[] = {
 | 
				
			||||||
 | 
						{"ltc2978", ltc2978},
 | 
				
			||||||
 | 
						{"ltc3880", ltc3880},
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(i2c, ltc2978_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_probe(struct i2c_client *client,
 | 
				
			||||||
 | 
								 const struct i2c_device_id *id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int chip_id, ret, i;
 | 
				
			||||||
 | 
						struct ltc2978_data *data;
 | 
				
			||||||
 | 
						struct pmbus_driver_info *info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!i2c_check_functionality(client->adapter,
 | 
				
			||||||
 | 
									     I2C_FUNC_SMBUS_READ_WORD_DATA))
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data = kzalloc(sizeof(struct ltc2978_data), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!data)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID);
 | 
				
			||||||
 | 
						if (chip_id < 0) {
 | 
				
			||||||
 | 
							ret = chip_id;
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) {
 | 
				
			||||||
 | 
							data->id = ltc2978;
 | 
				
			||||||
 | 
						} else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) {
 | 
				
			||||||
 | 
							data->id = ltc3880;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id);
 | 
				
			||||||
 | 
							ret = -ENODEV;
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (data->id != id->driver_data)
 | 
				
			||||||
 | 
							dev_warn(&client->dev,
 | 
				
			||||||
 | 
								 "Device mismatch: Configured %s, detected %s\n",
 | 
				
			||||||
 | 
								 id->name,
 | 
				
			||||||
 | 
								 ltc2978_id[data->id].name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info = &data->info;
 | 
				
			||||||
 | 
						info->write_word_data = ltc2978_write_word_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->vout_min[0] = 0xffff;
 | 
				
			||||||
 | 
						data->vin_min = 0x7bff;
 | 
				
			||||||
 | 
						data->temp_min = 0x7bff;
 | 
				
			||||||
 | 
						data->temp_max = 0x7fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (id->driver_data) {
 | 
				
			||||||
 | 
						case ltc2978:
 | 
				
			||||||
 | 
							info->read_word_data = ltc2978_read_word_data;
 | 
				
			||||||
 | 
							info->pages = 8;
 | 
				
			||||||
 | 
							info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
 | 
				
			||||||
 | 
							for (i = 1; i < 8; i++) {
 | 
				
			||||||
 | 
								info->func[i] = PMBUS_HAVE_VOUT
 | 
				
			||||||
 | 
								  | PMBUS_HAVE_STATUS_VOUT;
 | 
				
			||||||
 | 
								data->vout_min[i] = 0xffff;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case ltc3880:
 | 
				
			||||||
 | 
							info->read_word_data = ltc3880_read_word_data;
 | 
				
			||||||
 | 
							info->pages = 2;
 | 
				
			||||||
 | 
							info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_STATUS_INPUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
 | 
				
			||||||
 | 
							info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_POUT
 | 
				
			||||||
 | 
							  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
 | 
				
			||||||
 | 
							data->vout_min[1] = 0xffff;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ret = -ENODEV;
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = pmbus_do_probe(client, id, info);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_mem:
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ltc2978_remove(struct i2c_client *client)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						const struct ltc2978_data *data = to_ltc2978_data(info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* This is the driver that will be inserted */
 | 
				
			||||||
 | 
					static struct i2c_driver ltc2978_driver = {
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							   .name = "ltc2978",
 | 
				
			||||||
 | 
							   },
 | 
				
			||||||
 | 
						.probe = ltc2978_probe,
 | 
				
			||||||
 | 
						.remove = ltc2978_remove,
 | 
				
			||||||
 | 
						.id_table = ltc2978_id,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __init ltc2978_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return i2c_add_driver(<c2978_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void __exit ltc2978_exit(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						i2c_del_driver(<c2978_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Guenter Roeck");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("PMBus driver for LTC2978 and LTC3880");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
 | 
					module_init(ltc2978_init);
 | 
				
			||||||
 | 
					module_exit(ltc2978_exit);
 | 
				
			||||||
| 
						 | 
					@ -105,7 +105,8 @@ static int max16064_probe(struct i2c_client *client,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int max16064_remove(struct i2c_client *client)
 | 
					static int max16064_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id max16064_id[] = {
 | 
					static const struct i2c_device_id max16064_id[] = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,12 +93,14 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int max34440_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
					static int max34440_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
						int ret = 0;
 | 
				
			||||||
	int mfg_status;
 | 
						int mfg_status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page >= 0) {
 | 
				
			||||||
		ret = pmbus_set_page(client, page);
 | 
							ret = pmbus_set_page(client, page);
 | 
				
			||||||
		if (ret < 0)
 | 
							if (ret < 0)
 | 
				
			||||||
			return ret;
 | 
								return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
	case PMBUS_STATUS_IOUT:
 | 
						case PMBUS_STATUS_IOUT:
 | 
				
			||||||
| 
						 | 
					@ -224,7 +226,8 @@ static int max34440_probe(struct i2c_client *client,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int max34440_remove(struct i2c_client *client)
 | 
					static int max34440_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id max34440_id[] = {
 | 
					static const struct i2c_device_id max34440_id[] = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@ static int max8688_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page)
 | 
						if (page)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
	case PMBUS_VIRT_READ_VOUT_MAX:
 | 
						case PMBUS_VIRT_READ_VOUT_MAX:
 | 
				
			||||||
| 
						 | 
					@ -101,8 +101,8 @@ static int max8688_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
	int ret = 0;
 | 
						int ret = 0;
 | 
				
			||||||
	int mfg_status;
 | 
						int mfg_status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (page)
 | 
						if (page > 0)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
	case PMBUS_STATUS_VOUT:
 | 
						case PMBUS_STATUS_VOUT:
 | 
				
			||||||
| 
						 | 
					@ -182,7 +182,8 @@ static int max8688_probe(struct i2c_client *client,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int max8688_remove(struct i2c_client *client)
 | 
					static int max8688_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct i2c_device_id max8688_id[] = {
 | 
					static const struct i2c_device_id max8688_id[] = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -187,13 +187,12 @@ out:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int pmbus_remove(struct i2c_client *client)
 | 
					static int pmbus_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
					 | 
				
			||||||
	const struct pmbus_driver_info *info;
 | 
						const struct pmbus_driver_info *info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info = pmbus_get_driver_info(client);
 | 
						info = pmbus_get_driver_info(client);
 | 
				
			||||||
	ret = pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
	kfree(info);
 | 
						kfree(info);
 | 
				
			||||||
	return ret;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -205,10 +204,13 @@ static const struct i2c_device_id pmbus_id[] = {
 | 
				
			||||||
	{"bmr451", 1},
 | 
						{"bmr451", 1},
 | 
				
			||||||
	{"bmr453", 1},
 | 
						{"bmr453", 1},
 | 
				
			||||||
	{"bmr454", 1},
 | 
						{"bmr454", 1},
 | 
				
			||||||
	{"ltc2978", 8},
 | 
					 | 
				
			||||||
	{"ncp4200", 1},
 | 
						{"ncp4200", 1},
 | 
				
			||||||
	{"ncp4208", 1},
 | 
						{"ncp4208", 1},
 | 
				
			||||||
 | 
						{"pdt003", 1},
 | 
				
			||||||
 | 
						{"pdt006", 1},
 | 
				
			||||||
 | 
						{"pdt012", 1},
 | 
				
			||||||
	{"pmbus", 0},
 | 
						{"pmbus", 0},
 | 
				
			||||||
 | 
						{"udt020", 1},
 | 
				
			||||||
	{}
 | 
						{}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,8 +134,16 @@
 | 
				
			||||||
 * Semantics:
 | 
					 * Semantics:
 | 
				
			||||||
 * Virtual registers are all word size.
 | 
					 * Virtual registers are all word size.
 | 
				
			||||||
 * READ registers are read-only; writes are either ignored or return an error.
 | 
					 * READ registers are read-only; writes are either ignored or return an error.
 | 
				
			||||||
 * RESET registers are read/write. Reading returns zero (used for detection),
 | 
					 * RESET registers are read/write. Reading reset registers returns zero
 | 
				
			||||||
 * writing any value causes the associated history to be reset.
 | 
					 * (used for detection), writing any value causes the associated history to be
 | 
				
			||||||
 | 
					 * reset.
 | 
				
			||||||
 | 
					 * Virtual registers have to be handled in device specific driver code. Chip
 | 
				
			||||||
 | 
					 * driver code returns non-negative register values if a virtual register is
 | 
				
			||||||
 | 
					 * supported, or a negative error code if not. The chip driver may return
 | 
				
			||||||
 | 
					 * -ENODATA or any other error code in this case, though an error code other
 | 
				
			||||||
 | 
					 * than -ENODATA is handled more efficiently and thus preferred. Either case,
 | 
				
			||||||
 | 
					 * the calling PMBus core code will abort if the chip driver returns an error
 | 
				
			||||||
 | 
					 * code when reading or writing virtual registers.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
#define PMBUS_VIRT_BASE			0x100
 | 
					#define PMBUS_VIRT_BASE			0x100
 | 
				
			||||||
#define PMBUS_VIRT_READ_TEMP_MIN	(PMBUS_VIRT_BASE + 0)
 | 
					#define PMBUS_VIRT_READ_TEMP_MIN	(PMBUS_VIRT_BASE + 0)
 | 
				
			||||||
| 
						 | 
					@ -160,6 +168,9 @@
 | 
				
			||||||
#define PMBUS_VIRT_READ_IOUT_MIN	(PMBUS_VIRT_BASE + 19)
 | 
					#define PMBUS_VIRT_READ_IOUT_MIN	(PMBUS_VIRT_BASE + 19)
 | 
				
			||||||
#define PMBUS_VIRT_READ_IOUT_MAX	(PMBUS_VIRT_BASE + 20)
 | 
					#define PMBUS_VIRT_READ_IOUT_MAX	(PMBUS_VIRT_BASE + 20)
 | 
				
			||||||
#define PMBUS_VIRT_RESET_IOUT_HISTORY	(PMBUS_VIRT_BASE + 21)
 | 
					#define PMBUS_VIRT_RESET_IOUT_HISTORY	(PMBUS_VIRT_BASE + 21)
 | 
				
			||||||
 | 
					#define PMBUS_VIRT_READ_TEMP2_MIN	(PMBUS_VIRT_BASE + 22)
 | 
				
			||||||
 | 
					#define PMBUS_VIRT_READ_TEMP2_MAX	(PMBUS_VIRT_BASE + 23)
 | 
				
			||||||
 | 
					#define PMBUS_VIRT_RESET_TEMP2_HISTORY	(PMBUS_VIRT_BASE + 24)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * CAPABILITY
 | 
					 * CAPABILITY
 | 
				
			||||||
| 
						 | 
					@ -320,6 +331,12 @@ struct pmbus_driver_info {
 | 
				
			||||||
	 * The following functions map manufacturing specific register values
 | 
						 * The following functions map manufacturing specific register values
 | 
				
			||||||
	 * to PMBus standard register values. Specify only if mapping is
 | 
						 * to PMBus standard register values. Specify only if mapping is
 | 
				
			||||||
	 * necessary.
 | 
						 * necessary.
 | 
				
			||||||
 | 
						 * Functions return the register value (read) or zero (write) if
 | 
				
			||||||
 | 
						 * successful. A return value of -ENODATA indicates that there is no
 | 
				
			||||||
 | 
						 * manufacturer specific register, but that a standard PMBus register
 | 
				
			||||||
 | 
						 * may exist. Any other negative return value indicates that the
 | 
				
			||||||
 | 
						 * register does not exist, and that no attempt should be made to read
 | 
				
			||||||
 | 
						 * the standard register.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	int (*read_byte_data)(struct i2c_client *client, int page, int reg);
 | 
						int (*read_byte_data)(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
	int (*read_word_data)(struct i2c_client *client, int page, int reg);
 | 
						int (*read_word_data)(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
| 
						 | 
					@ -347,7 +364,7 @@ bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
 | 
					bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
 | 
				
			||||||
int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
 | 
					int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
 | 
				
			||||||
		   struct pmbus_driver_info *info);
 | 
							   struct pmbus_driver_info *info);
 | 
				
			||||||
int pmbus_do_remove(struct i2c_client *client);
 | 
					void pmbus_do_remove(struct i2c_client *client);
 | 
				
			||||||
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client
 | 
					const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client
 | 
				
			||||||
						      *client);
 | 
											      *client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -160,7 +160,7 @@ int pmbus_set_page(struct i2c_client *client, u8 page)
 | 
				
			||||||
		rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
 | 
							rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
 | 
				
			||||||
		newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
 | 
							newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
 | 
				
			||||||
		if (newpage != page)
 | 
							if (newpage != page)
 | 
				
			||||||
			rv = -EINVAL;
 | 
								rv = -EIO;
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			data->currpage = page;
 | 
								data->currpage = page;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -229,7 +229,7 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
			return status;
 | 
								return status;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (reg >= PMBUS_VIRT_BASE)
 | 
						if (reg >= PMBUS_VIRT_BASE)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
	return pmbus_write_word_data(client, page, reg, word);
 | 
						return pmbus_write_word_data(client, page, reg, word);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -261,7 +261,7 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
			return status;
 | 
								return status;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (reg >= PMBUS_VIRT_BASE)
 | 
						if (reg >= PMBUS_VIRT_BASE)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -ENXIO;
 | 
				
			||||||
	return pmbus_read_word_data(client, page, reg);
 | 
						return pmbus_read_word_data(client, page, reg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -316,11 +316,11 @@ static int pmbus_check_status_cml(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int status, status2;
 | 
						int status, status2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	status = pmbus_read_byte_data(client, -1, PMBUS_STATUS_BYTE);
 | 
						status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_BYTE);
 | 
				
			||||||
	if (status < 0 || (status & PB_STATUS_CML)) {
 | 
						if (status < 0 || (status & PB_STATUS_CML)) {
 | 
				
			||||||
		status2 = pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
 | 
							status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
 | 
				
			||||||
		if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND))
 | 
							if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND))
 | 
				
			||||||
			return -EINVAL;
 | 
								return -EIO;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -371,7 +371,7 @@ static struct pmbus_data *pmbus_update_device(struct device *dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (i = 0; i < info->pages; i++)
 | 
							for (i = 0; i < info->pages; i++)
 | 
				
			||||||
			data->status[PB_STATUS_BASE + i]
 | 
								data->status[PB_STATUS_BASE + i]
 | 
				
			||||||
			    = pmbus_read_byte_data(client, i,
 | 
								    = _pmbus_read_byte_data(client, i,
 | 
				
			||||||
						    PMBUS_STATUS_BYTE);
 | 
											    PMBUS_STATUS_BYTE);
 | 
				
			||||||
		for (i = 0; i < info->pages; i++) {
 | 
							for (i = 0; i < info->pages; i++) {
 | 
				
			||||||
			if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT))
 | 
								if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT))
 | 
				
			||||||
| 
						 | 
					@ -445,13 +445,8 @@ static long pmbus_reg2data_linear(struct pmbus_data *data,
 | 
				
			||||||
		exponent = data->exponent;
 | 
							exponent = data->exponent;
 | 
				
			||||||
		mantissa = (u16) sensor->data;
 | 
							mantissa = (u16) sensor->data;
 | 
				
			||||||
	} else {				/* LINEAR11 */
 | 
						} else {				/* LINEAR11 */
 | 
				
			||||||
		exponent = (sensor->data >> 11) & 0x001f;
 | 
							exponent = ((s16)sensor->data) >> 11;
 | 
				
			||||||
		mantissa = sensor->data & 0x07ff;
 | 
							mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (exponent > 0x0f)
 | 
					 | 
				
			||||||
			exponent |= 0xffe0;	/* sign extend exponent */
 | 
					 | 
				
			||||||
		if (mantissa > 0x03ff)
 | 
					 | 
				
			||||||
			mantissa |= 0xfffff800;	/* sign extend mantissa */
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	val = mantissa;
 | 
						val = mantissa;
 | 
				
			||||||
| 
						 | 
					@ -1401,7 +1396,42 @@ static const struct pmbus_limit_attr temp_limit_attrs[] = {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct pmbus_limit_attr temp_limit_attrs23[] = {
 | 
					static const struct pmbus_limit_attr temp_limit_attrs2[] = {
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.reg = PMBUS_UT_WARN_LIMIT,
 | 
				
			||||||
 | 
							.low = true,
 | 
				
			||||||
 | 
							.attr = "min",
 | 
				
			||||||
 | 
							.alarm = "min_alarm",
 | 
				
			||||||
 | 
							.sbit = PB_TEMP_UT_WARNING,
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_UT_FAULT_LIMIT,
 | 
				
			||||||
 | 
							.low = true,
 | 
				
			||||||
 | 
							.attr = "lcrit",
 | 
				
			||||||
 | 
							.alarm = "lcrit_alarm",
 | 
				
			||||||
 | 
							.sbit = PB_TEMP_UT_FAULT,
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_OT_WARN_LIMIT,
 | 
				
			||||||
 | 
							.attr = "max",
 | 
				
			||||||
 | 
							.alarm = "max_alarm",
 | 
				
			||||||
 | 
							.sbit = PB_TEMP_OT_WARNING,
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_OT_FAULT_LIMIT,
 | 
				
			||||||
 | 
							.attr = "crit",
 | 
				
			||||||
 | 
							.alarm = "crit_alarm",
 | 
				
			||||||
 | 
							.sbit = PB_TEMP_OT_FAULT,
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_VIRT_READ_TEMP2_MIN,
 | 
				
			||||||
 | 
							.attr = "lowest",
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_VIRT_READ_TEMP2_MAX,
 | 
				
			||||||
 | 
							.attr = "highest",
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY,
 | 
				
			||||||
 | 
							.attr = "reset_history",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct pmbus_limit_attr temp_limit_attrs3[] = {
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		.reg = PMBUS_UT_WARN_LIMIT,
 | 
							.reg = PMBUS_UT_WARN_LIMIT,
 | 
				
			||||||
		.low = true,
 | 
							.low = true,
 | 
				
			||||||
| 
						 | 
					@ -1450,8 +1480,8 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
 | 
				
			||||||
		.sfunc = PMBUS_HAVE_STATUS_TEMP,
 | 
							.sfunc = PMBUS_HAVE_STATUS_TEMP,
 | 
				
			||||||
		.sbase = PB_STATUS_TEMP_BASE,
 | 
							.sbase = PB_STATUS_TEMP_BASE,
 | 
				
			||||||
		.gbit = PB_STATUS_TEMPERATURE,
 | 
							.gbit = PB_STATUS_TEMPERATURE,
 | 
				
			||||||
		.limit = temp_limit_attrs23,
 | 
							.limit = temp_limit_attrs2,
 | 
				
			||||||
		.nlimit = ARRAY_SIZE(temp_limit_attrs23),
 | 
							.nlimit = ARRAY_SIZE(temp_limit_attrs2),
 | 
				
			||||||
	}, {
 | 
						}, {
 | 
				
			||||||
		.reg = PMBUS_READ_TEMPERATURE_3,
 | 
							.reg = PMBUS_READ_TEMPERATURE_3,
 | 
				
			||||||
		.class = PSC_TEMPERATURE,
 | 
							.class = PSC_TEMPERATURE,
 | 
				
			||||||
| 
						 | 
					@ -1462,8 +1492,8 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
 | 
				
			||||||
		.sfunc = PMBUS_HAVE_STATUS_TEMP,
 | 
							.sfunc = PMBUS_HAVE_STATUS_TEMP,
 | 
				
			||||||
		.sbase = PB_STATUS_TEMP_BASE,
 | 
							.sbase = PB_STATUS_TEMP_BASE,
 | 
				
			||||||
		.gbit = PB_STATUS_TEMPERATURE,
 | 
							.gbit = PB_STATUS_TEMPERATURE,
 | 
				
			||||||
		.limit = temp_limit_attrs23,
 | 
							.limit = temp_limit_attrs3,
 | 
				
			||||||
		.nlimit = ARRAY_SIZE(temp_limit_attrs23),
 | 
							.nlimit = ARRAY_SIZE(temp_limit_attrs3),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1593,10 +1623,10 @@ static void pmbus_find_attributes(struct i2c_client *client,
 | 
				
			||||||
static int pmbus_identify_common(struct i2c_client *client,
 | 
					static int pmbus_identify_common(struct i2c_client *client,
 | 
				
			||||||
				 struct pmbus_data *data)
 | 
									 struct pmbus_data *data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int vout_mode = -1, exponent;
 | 
						int vout_mode = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE))
 | 
						if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE))
 | 
				
			||||||
		vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
 | 
							vout_mode = _pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
 | 
				
			||||||
	if (vout_mode >= 0 && vout_mode != 0xff) {
 | 
						if (vout_mode >= 0 && vout_mode != 0xff) {
 | 
				
			||||||
		/*
 | 
							/*
 | 
				
			||||||
		 * Not all chips support the VOUT_MODE command,
 | 
							 * Not all chips support the VOUT_MODE command,
 | 
				
			||||||
| 
						 | 
					@ -1607,11 +1637,7 @@ static int pmbus_identify_common(struct i2c_client *client,
 | 
				
			||||||
			if (data->info->format[PSC_VOLTAGE_OUT] != linear)
 | 
								if (data->info->format[PSC_VOLTAGE_OUT] != linear)
 | 
				
			||||||
				return -ENODEV;
 | 
									return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			exponent = vout_mode & 0x1f;
 | 
								data->exponent = ((s8)(vout_mode << 3)) >> 3;
 | 
				
			||||||
			/* and sign-extend it */
 | 
					 | 
				
			||||||
			if (exponent & 0x10)
 | 
					 | 
				
			||||||
				exponent |= ~0x1f;
 | 
					 | 
				
			||||||
			data->exponent = exponent;
 | 
					 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case 1: /* VID mode         */
 | 
							case 1: /* VID mode         */
 | 
				
			||||||
			if (data->info->format[PSC_VOLTAGE_OUT] != vid)
 | 
								if (data->info->format[PSC_VOLTAGE_OUT] != vid)
 | 
				
			||||||
| 
						 | 
					@ -1682,7 +1708,7 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
 | 
				
			||||||
	if (info->pages <= 0 || info->pages > PMBUS_PAGES) {
 | 
						if (info->pages <= 0 || info->pages > PMBUS_PAGES) {
 | 
				
			||||||
		dev_err(&client->dev, "Bad number of PMBus pages: %d\n",
 | 
							dev_err(&client->dev, "Bad number of PMBus pages: %d\n",
 | 
				
			||||||
			info->pages);
 | 
								info->pages);
 | 
				
			||||||
		ret = -EINVAL;
 | 
							ret = -ENODEV;
 | 
				
			||||||
		goto out_data;
 | 
							goto out_data;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1764,7 +1790,7 @@ out_data:
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(pmbus_do_probe);
 | 
					EXPORT_SYMBOL_GPL(pmbus_do_probe);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int pmbus_do_remove(struct i2c_client *client)
 | 
					void pmbus_do_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct pmbus_data *data = i2c_get_clientdata(client);
 | 
						struct pmbus_data *data = i2c_get_clientdata(client);
 | 
				
			||||||
	hwmon_device_unregister(data->hwmon_dev);
 | 
						hwmon_device_unregister(data->hwmon_dev);
 | 
				
			||||||
| 
						 | 
					@ -1774,7 +1800,6 @@ int pmbus_do_remove(struct i2c_client *client)
 | 
				
			||||||
	kfree(data->booleans);
 | 
						kfree(data->booleans);
 | 
				
			||||||
	kfree(data->sensors);
 | 
						kfree(data->sensors);
 | 
				
			||||||
	kfree(data);
 | 
						kfree(data);
 | 
				
			||||||
	return 0;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(pmbus_do_remove);
 | 
					EXPORT_SYMBOL_GPL(pmbus_do_remove);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,8 +74,8 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (reg) {
 | 
						switch (reg) {
 | 
				
			||||||
	case PMBUS_FAN_CONFIG_12:
 | 
						case PMBUS_FAN_CONFIG_12:
 | 
				
			||||||
		if (page)
 | 
							if (page > 0)
 | 
				
			||||||
			return -EINVAL;
 | 
								return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ret = ucd9000_get_fan_config(client, 0);
 | 
							ret = ucd9000_get_fan_config(client, 0);
 | 
				
			||||||
		if (ret < 0)
 | 
							if (ret < 0)
 | 
				
			||||||
| 
						 | 
					@ -88,8 +88,8 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
		ret = fan_config;
 | 
							ret = fan_config;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case PMBUS_FAN_CONFIG_34:
 | 
						case PMBUS_FAN_CONFIG_34:
 | 
				
			||||||
		if (page)
 | 
							if (page > 0)
 | 
				
			||||||
			return -EINVAL;
 | 
								return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ret = ucd9000_get_fan_config(client, 2);
 | 
							ret = ucd9000_get_fan_config(client, 2);
 | 
				
			||||||
		if (ret < 0)
 | 
							if (ret < 0)
 | 
				
			||||||
| 
						 | 
					@ -239,13 +239,12 @@ out:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int ucd9000_remove(struct i2c_client *client)
 | 
					static int ucd9000_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
					 | 
				
			||||||
	struct ucd9000_data *data;
 | 
						struct ucd9000_data *data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data = to_ucd9000_data(pmbus_get_driver_info(client));
 | 
						data = to_ucd9000_data(pmbus_get_driver_info(client));
 | 
				
			||||||
	ret = pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
	kfree(data);
 | 
						kfree(data);
 | 
				
			||||||
	return ret;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,13 +171,12 @@ out:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int ucd9200_remove(struct i2c_client *client)
 | 
					static int ucd9200_remove(struct i2c_client *client)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
					 | 
				
			||||||
	const struct pmbus_driver_info *info;
 | 
						const struct pmbus_driver_info *info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info = pmbus_get_driver_info(client);
 | 
						info = pmbus_get_driver_info(client);
 | 
				
			||||||
	ret = pmbus_do_remove(client);
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
	kfree(info);
 | 
						kfree(info);
 | 
				
			||||||
	return ret;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										256
									
								
								drivers/hwmon/pmbus/zl6100.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								drivers/hwmon/pmbus/zl6100.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,256 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Hardware monitoring driver for ZL6100 and compatibles
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (c) 2011 Ericsson AB.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program; if not, write to the Free Software
 | 
				
			||||||
 | 
					 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/init.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/i2c.h>
 | 
				
			||||||
 | 
					#include <linux/ktime.h>
 | 
				
			||||||
 | 
					#include <linux/delay.h>
 | 
				
			||||||
 | 
					#include "pmbus.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum chips { zl2004, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct zl6100_data {
 | 
				
			||||||
 | 
						int id;
 | 
				
			||||||
 | 
						ktime_t access;		/* chip access time */
 | 
				
			||||||
 | 
						struct pmbus_driver_info info;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define to_zl6100_data(x)  container_of(x, struct zl6100_data, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ZL6100_DEVICE_ID		0xe4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ZL6100_WAIT_TIME		1000	/* uS	*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ushort delay = ZL6100_WAIT_TIME;
 | 
				
			||||||
 | 
					module_param(delay, ushort, 0644);
 | 
				
			||||||
 | 
					MODULE_PARM_DESC(delay, "Delay between chip accesses in uS");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Some chips need a delay between accesses */
 | 
				
			||||||
 | 
					static inline void zl6100_wait(const struct zl6100_data *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (delay) {
 | 
				
			||||||
 | 
							s64 delta = ktime_us_delta(ktime_get(), data->access);
 | 
				
			||||||
 | 
							if (delta < delay)
 | 
				
			||||||
 | 
								udelay(delay - delta);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_read_word_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct zl6100_data *data = to_zl6100_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page || reg >= PMBUS_VIRT_BASE)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zl6100_wait(data);
 | 
				
			||||||
 | 
						ret = pmbus_read_word_data(client, page, reg);
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct zl6100_data *data = to_zl6100_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page > 0)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zl6100_wait(data);
 | 
				
			||||||
 | 
						ret = pmbus_read_byte_data(client, page, reg);
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
 | 
				
			||||||
 | 
									  u16 word)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct zl6100_data *data = to_zl6100_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page || reg >= PMBUS_VIRT_BASE)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zl6100_wait(data);
 | 
				
			||||||
 | 
						ret = pmbus_write_word_data(client, page, reg, word);
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_write_byte(struct i2c_client *client, int page, u8 value)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						struct zl6100_data *data = to_zl6100_data(info);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page > 0)
 | 
				
			||||||
 | 
							return -ENXIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zl6100_wait(data);
 | 
				
			||||||
 | 
						ret = pmbus_write_byte(client, page, value);
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct i2c_device_id zl6100_id[] = {
 | 
				
			||||||
 | 
						{"zl2004", zl2004},
 | 
				
			||||||
 | 
						{"zl2006", zl2006},
 | 
				
			||||||
 | 
						{"zl2008", zl2008},
 | 
				
			||||||
 | 
						{"zl2105", zl2105},
 | 
				
			||||||
 | 
						{"zl2106", zl2106},
 | 
				
			||||||
 | 
						{"zl6100", zl6100},
 | 
				
			||||||
 | 
						{"zl6105", zl6105},
 | 
				
			||||||
 | 
						{ }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(i2c, zl6100_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_probe(struct i2c_client *client,
 | 
				
			||||||
 | 
								const struct i2c_device_id *id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
						struct zl6100_data *data;
 | 
				
			||||||
 | 
						struct pmbus_driver_info *info;
 | 
				
			||||||
 | 
						u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
 | 
				
			||||||
 | 
						const struct i2c_device_id *mid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!i2c_check_functionality(client->adapter,
 | 
				
			||||||
 | 
									     I2C_FUNC_SMBUS_READ_BYTE_DATA
 | 
				
			||||||
 | 
									     | I2C_FUNC_SMBUS_READ_BLOCK_DATA))
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID,
 | 
				
			||||||
 | 
										device_id);
 | 
				
			||||||
 | 
						if (ret < 0) {
 | 
				
			||||||
 | 
							dev_err(&client->dev, "Failed to read device ID\n");
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						device_id[ret] = '\0';
 | 
				
			||||||
 | 
						dev_info(&client->dev, "Device ID %s\n", device_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mid = NULL;
 | 
				
			||||||
 | 
						for (mid = zl6100_id; mid->name[0]; mid++) {
 | 
				
			||||||
 | 
							if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (!mid->name[0]) {
 | 
				
			||||||
 | 
							dev_err(&client->dev, "Unsupported device\n");
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (id->driver_data != mid->driver_data)
 | 
				
			||||||
 | 
							dev_notice(&client->dev,
 | 
				
			||||||
 | 
								   "Device mismatch: Configured %s, detected %s\n",
 | 
				
			||||||
 | 
								   id->name, mid->name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data = kzalloc(sizeof(struct zl6100_data), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!data)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->id = mid->driver_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * ZL2008, ZL2105, and ZL6100 are known to require a wait time
 | 
				
			||||||
 | 
						 * between I2C accesses. ZL2004 and ZL6105 are known to be safe.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Only clear the wait time for chips known to be safe. The wait time
 | 
				
			||||||
 | 
						 * can be cleared later for additional chips if tests show that it
 | 
				
			||||||
 | 
						 * is not needed (in other words, better be safe than sorry).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (data->id == zl2004 || data->id == zl6105)
 | 
				
			||||||
 | 
							delay = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Since there was a direct I2C device access above, wait before
 | 
				
			||||||
 | 
						 * accessing the chip again.
 | 
				
			||||||
 | 
						 * Set the timestamp, wait, then set it again. This should provide
 | 
				
			||||||
 | 
						 * enough buffer time to be safe.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
						zl6100_wait(data);
 | 
				
			||||||
 | 
						data->access = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info = &data->info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info->pages = 1;
 | 
				
			||||||
 | 
						info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
 | 
				
			||||||
 | 
						  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
 | 
				
			||||||
 | 
						  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
 | 
				
			||||||
 | 
						  | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info->read_word_data = zl6100_read_word_data;
 | 
				
			||||||
 | 
						info->read_byte_data = zl6100_read_byte_data;
 | 
				
			||||||
 | 
						info->write_word_data = zl6100_write_word_data;
 | 
				
			||||||
 | 
						info->write_byte = zl6100_write_byte;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = pmbus_do_probe(client, mid, info);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							goto err_mem;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_mem:
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int zl6100_remove(struct i2c_client *client)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
 | 
				
			||||||
 | 
						const struct zl6100_data *data = to_zl6100_data(info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pmbus_do_remove(client);
 | 
				
			||||||
 | 
						kfree(data);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct i2c_driver zl6100_driver = {
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							   .name = "zl6100",
 | 
				
			||||||
 | 
							   },
 | 
				
			||||||
 | 
						.probe = zl6100_probe,
 | 
				
			||||||
 | 
						.remove = zl6100_remove,
 | 
				
			||||||
 | 
						.id_table = zl6100_id,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int __init zl6100_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return i2c_add_driver(&zl6100_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void __exit zl6100_exit(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						i2c_del_driver(&zl6100_driver);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Guenter Roeck");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
 | 
					module_init(zl6100_init);
 | 
				
			||||||
 | 
					module_exit(zl6100_exit);
 | 
				
			||||||
| 
						 | 
					@ -197,6 +197,9 @@ static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 };
 | 
				
			||||||
#define W83627EHF_REG_ALARM2		0x45A
 | 
					#define W83627EHF_REG_ALARM2		0x45A
 | 
				
			||||||
#define W83627EHF_REG_ALARM3		0x45B
 | 
					#define W83627EHF_REG_ALARM3		0x45B
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define W83627EHF_REG_CASEOPEN_DET	0x42 /* SMI STATUS #2 */
 | 
				
			||||||
 | 
					#define W83627EHF_REG_CASEOPEN_CLR	0x46 /* SMI MASK #3 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* SmartFan registers */
 | 
					/* SmartFan registers */
 | 
				
			||||||
#define W83627EHF_REG_FAN_STEPUP_TIME 0x0f
 | 
					#define W83627EHF_REG_FAN_STEPUP_TIME 0x0f
 | 
				
			||||||
#define W83627EHF_REG_FAN_STEPDOWN_TIME 0x0e
 | 
					#define W83627EHF_REG_FAN_STEPDOWN_TIME 0x0e
 | 
				
			||||||
| 
						 | 
					@ -316,7 +319,7 @@ static const char *const nct6776_temp_label[] = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define NUM_REG_TEMP	ARRAY_SIZE(NCT6775_REG_TEMP)
 | 
					#define NUM_REG_TEMP	ARRAY_SIZE(NCT6775_REG_TEMP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline int is_word_sized(u16 reg)
 | 
					static int is_word_sized(u16 reg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return ((((reg & 0xff00) == 0x100
 | 
						return ((((reg & 0xff00) == 0x100
 | 
				
			||||||
	      || (reg & 0xff00) == 0x200)
 | 
						      || (reg & 0xff00) == 0x200)
 | 
				
			||||||
| 
						 | 
					@ -385,23 +388,6 @@ div_from_reg(u8 reg)
 | 
				
			||||||
	return 1 << reg;
 | 
						return 1 << reg;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline int
 | 
					 | 
				
			||||||
temp_from_reg(u16 reg, s16 regval)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	if (is_word_sized(reg))
 | 
					 | 
				
			||||||
		return LM75_TEMP_FROM_REG(regval);
 | 
					 | 
				
			||||||
	return ((s8)regval) * 1000;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static inline u16
 | 
					 | 
				
			||||||
temp_to_reg(u16 reg, long temp)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	if (is_word_sized(reg))
 | 
					 | 
				
			||||||
		return LM75_TEMP_TO_REG(temp);
 | 
					 | 
				
			||||||
	return (s8)DIV_ROUND_CLOSEST(SENSORS_LIMIT(temp, -127000, 128000),
 | 
					 | 
				
			||||||
				     1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Some of analog inputs have internal scaling (2x), 8mV is ADC LSB */
 | 
					/* Some of analog inputs have internal scaling (2x), 8mV is ADC LSB */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static u8 scale_in[10] = { 8, 8, 16, 16, 8, 8, 8, 16, 16, 8 };
 | 
					static u8 scale_in[10] = { 8, 8, 16, 16, 8, 8, 8, 16, 16, 8 };
 | 
				
			||||||
| 
						 | 
					@ -469,6 +455,7 @@ struct w83627ehf_data {
 | 
				
			||||||
	s16 temp_max[9];
 | 
						s16 temp_max[9];
 | 
				
			||||||
	s16 temp_max_hyst[9];
 | 
						s16 temp_max_hyst[9];
 | 
				
			||||||
	u32 alarms;
 | 
						u32 alarms;
 | 
				
			||||||
 | 
						u8 caseopen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */
 | 
						u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */
 | 
				
			||||||
	u8 pwm_enable[4]; /* 1->manual
 | 
						u8 pwm_enable[4]; /* 1->manual
 | 
				
			||||||
| 
						 | 
					@ -557,6 +544,26 @@ static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg,
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* We left-align 8-bit temperature values to make the code simpler */
 | 
				
			||||||
 | 
					static u16 w83627ehf_read_temp(struct w83627ehf_data *data, u16 reg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u16 res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res = w83627ehf_read_value(data, reg);
 | 
				
			||||||
 | 
						if (!is_word_sized(reg))
 | 
				
			||||||
 | 
							res <<= 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int w83627ehf_write_temp(struct w83627ehf_data *data, u16 reg,
 | 
				
			||||||
 | 
									       u16 value)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!is_word_sized(reg))
 | 
				
			||||||
 | 
							value >>= 8;
 | 
				
			||||||
 | 
						return w83627ehf_write_value(data, reg, value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* This function assumes that the caller holds data->update_lock */
 | 
					/* This function assumes that the caller holds data->update_lock */
 | 
				
			||||||
static void nct6775_write_fan_div(struct w83627ehf_data *data, int nr)
 | 
					static void nct6775_write_fan_div(struct w83627ehf_data *data, int nr)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -771,6 +778,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Measured voltages and limits */
 | 
							/* Measured voltages and limits */
 | 
				
			||||||
		for (i = 0; i < data->in_num; i++) {
 | 
							for (i = 0; i < data->in_num; i++) {
 | 
				
			||||||
 | 
								if ((i == 6) && data->in6_skip)
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			data->in[i] = w83627ehf_read_value(data,
 | 
								data->in[i] = w83627ehf_read_value(data,
 | 
				
			||||||
				      W83627EHF_REG_IN(i));
 | 
									      W83627EHF_REG_IN(i));
 | 
				
			||||||
			data->in_min[i] = w83627ehf_read_value(data,
 | 
								data->in_min[i] = w83627ehf_read_value(data,
 | 
				
			||||||
| 
						 | 
					@ -855,15 +865,15 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
 | 
				
			||||||
		for (i = 0; i < NUM_REG_TEMP; i++) {
 | 
							for (i = 0; i < NUM_REG_TEMP; i++) {
 | 
				
			||||||
			if (!(data->have_temp & (1 << i)))
 | 
								if (!(data->have_temp & (1 << i)))
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			data->temp[i] = w83627ehf_read_value(data,
 | 
								data->temp[i] = w83627ehf_read_temp(data,
 | 
				
			||||||
						data->reg_temp[i]);
 | 
											data->reg_temp[i]);
 | 
				
			||||||
			if (data->reg_temp_over[i])
 | 
								if (data->reg_temp_over[i])
 | 
				
			||||||
				data->temp_max[i]
 | 
									data->temp_max[i]
 | 
				
			||||||
				  = w83627ehf_read_value(data,
 | 
									  = w83627ehf_read_temp(data,
 | 
				
			||||||
						data->reg_temp_over[i]);
 | 
											data->reg_temp_over[i]);
 | 
				
			||||||
			if (data->reg_temp_hyst[i])
 | 
								if (data->reg_temp_hyst[i])
 | 
				
			||||||
				data->temp_max_hyst[i]
 | 
									data->temp_max_hyst[i]
 | 
				
			||||||
				  = w83627ehf_read_value(data,
 | 
									  = w83627ehf_read_temp(data,
 | 
				
			||||||
						data->reg_temp_hyst[i]);
 | 
											data->reg_temp_hyst[i]);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -874,6 +884,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
 | 
				
			||||||
			       (w83627ehf_read_value(data,
 | 
								       (w83627ehf_read_value(data,
 | 
				
			||||||
					W83627EHF_REG_ALARM3) << 16);
 | 
										W83627EHF_REG_ALARM3) << 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							data->caseopen = w83627ehf_read_value(data,
 | 
				
			||||||
 | 
											W83627EHF_REG_CASEOPEN_DET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		data->last_updated = jiffies;
 | 
							data->last_updated = jiffies;
 | 
				
			||||||
		data->valid = 1;
 | 
							data->valid = 1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1156,8 +1169,7 @@ show_##reg(struct device *dev, struct device_attribute *attr, \
 | 
				
			||||||
	struct sensor_device_attribute *sensor_attr = \
 | 
						struct sensor_device_attribute *sensor_attr = \
 | 
				
			||||||
		to_sensor_dev_attr(attr); \
 | 
							to_sensor_dev_attr(attr); \
 | 
				
			||||||
	int nr = sensor_attr->index; \
 | 
						int nr = sensor_attr->index; \
 | 
				
			||||||
	return sprintf(buf, "%d\n", \
 | 
						return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->reg[nr])); \
 | 
				
			||||||
		       temp_from_reg(data->addr[nr], data->reg[nr])); \
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
show_temp_reg(reg_temp, temp);
 | 
					show_temp_reg(reg_temp, temp);
 | 
				
			||||||
show_temp_reg(reg_temp_over, temp_max);
 | 
					show_temp_reg(reg_temp_over, temp_max);
 | 
				
			||||||
| 
						 | 
					@ -1178,9 +1190,8 @@ store_##reg(struct device *dev, struct device_attribute *attr, \
 | 
				
			||||||
	if (err < 0) \
 | 
						if (err < 0) \
 | 
				
			||||||
		return err; \
 | 
							return err; \
 | 
				
			||||||
	mutex_lock(&data->update_lock); \
 | 
						mutex_lock(&data->update_lock); \
 | 
				
			||||||
	data->reg[nr] = temp_to_reg(data->addr[nr], val); \
 | 
						data->reg[nr] = LM75_TEMP_TO_REG(val); \
 | 
				
			||||||
	w83627ehf_write_value(data, data->addr[nr], \
 | 
						w83627ehf_write_temp(data, data->addr[nr], data->reg[nr]); \
 | 
				
			||||||
			      data->reg[nr]); \
 | 
					 | 
				
			||||||
	mutex_unlock(&data->update_lock); \
 | 
						mutex_unlock(&data->update_lock); \
 | 
				
			||||||
	return count; \
 | 
						return count; \
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1655,6 +1666,48 @@ show_vid(struct device *dev, struct device_attribute *attr, char *buf)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
 | 
					static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Case open detection */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t
 | 
				
			||||||
 | 
					show_caseopen(struct device *dev, struct device_attribute *attr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct w83627ehf_data *data = w83627ehf_update_device(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sprintf(buf, "%d\n",
 | 
				
			||||||
 | 
							!!(data->caseopen & to_sensor_dev_attr_2(attr)->index));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t
 | 
				
			||||||
 | 
					clear_caseopen(struct device *dev, struct device_attribute *attr,
 | 
				
			||||||
 | 
								const char *buf, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct w83627ehf_data *data = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						unsigned long val;
 | 
				
			||||||
 | 
						u16 reg, mask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (strict_strtoul(buf, 10, &val) || val != 0)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mask = to_sensor_dev_attr_2(attr)->nr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&data->update_lock);
 | 
				
			||||||
 | 
						reg = w83627ehf_read_value(data, W83627EHF_REG_CASEOPEN_CLR);
 | 
				
			||||||
 | 
						w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg | mask);
 | 
				
			||||||
 | 
						w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg & ~mask);
 | 
				
			||||||
 | 
						data->valid = 0;	/* Force cache refresh */
 | 
				
			||||||
 | 
						mutex_unlock(&data->update_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct sensor_device_attribute_2 sda_caseopen[] = {
 | 
				
			||||||
 | 
						SENSOR_ATTR_2(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen,
 | 
				
			||||||
 | 
								clear_caseopen, 0x80, 0x10),
 | 
				
			||||||
 | 
						SENSOR_ATTR_2(intrusion1_alarm, S_IWUSR | S_IRUGO, show_caseopen,
 | 
				
			||||||
 | 
								clear_caseopen, 0x40, 0x40),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Driver and device management
 | 
					 * Driver and device management
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -1711,6 +1764,9 @@ static void w83627ehf_device_remove_files(struct device *dev)
 | 
				
			||||||
		device_remove_file(dev, &sda_temp_type[i].dev_attr);
 | 
							device_remove_file(dev, &sda_temp_type[i].dev_attr);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device_remove_file(dev, &sda_caseopen[0].dev_attr);
 | 
				
			||||||
 | 
						device_remove_file(dev, &sda_caseopen[1].dev_attr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device_remove_file(dev, &dev_attr_name);
 | 
						device_remove_file(dev, &dev_attr_name);
 | 
				
			||||||
	device_remove_file(dev, &dev_attr_cpu0_vid);
 | 
						device_remove_file(dev, &dev_attr_cpu0_vid);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1789,13 +1845,78 @@ static void w82627ehf_swap_tempreg(struct w83627ehf_data *data,
 | 
				
			||||||
	data->reg_temp_config[r2] = tmp;
 | 
						data->reg_temp_config[r2] = tmp;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void __devinit
 | 
				
			||||||
 | 
					w83627ehf_check_fan_inputs(const struct w83627ehf_sio_data *sio_data,
 | 
				
			||||||
 | 
								   struct w83627ehf_data *data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int fan3pin, fan4pin, fan4min, fan5pin, regval;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						superio_enter(sio_data->sioreg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* fan4 and fan5 share some pins with the GPIO and serial flash */
 | 
				
			||||||
 | 
						if (sio_data->kind == nct6775) {
 | 
				
			||||||
 | 
							/* On NCT6775, fan4 shares pins with the fdc interface */
 | 
				
			||||||
 | 
							fan3pin = 1;
 | 
				
			||||||
 | 
							fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
 | 
				
			||||||
 | 
							fan4min = 0;
 | 
				
			||||||
 | 
							fan5pin = 0;
 | 
				
			||||||
 | 
						} else if (sio_data->kind == nct6776) {
 | 
				
			||||||
 | 
							fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
 | 
				
			||||||
 | 
							fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01);
 | 
				
			||||||
 | 
							fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02);
 | 
				
			||||||
 | 
							fan4min = fan4pin;
 | 
				
			||||||
 | 
						} else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
 | 
				
			||||||
 | 
							fan3pin = 1;
 | 
				
			||||||
 | 
							fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40;
 | 
				
			||||||
 | 
							fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20;
 | 
				
			||||||
 | 
							fan4min = fan4pin;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fan3pin = 1;
 | 
				
			||||||
 | 
							fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06);
 | 
				
			||||||
 | 
							fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02);
 | 
				
			||||||
 | 
							fan4min = fan4pin;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						superio_exit(sio_data->sioreg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
 | 
				
			||||||
 | 
						data->has_fan |= (fan3pin << 2);
 | 
				
			||||||
 | 
						data->has_fan_min |= (fan3pin << 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (sio_data->kind == nct6775 || sio_data->kind == nct6776) {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1
 | 
				
			||||||
 | 
							 * register
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
 | 
				
			||||||
 | 
							data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * It looks like fan4 and fan5 pins can be alternatively used
 | 
				
			||||||
 | 
							 * as fan on/off switches, but fan5 control is write only :/
 | 
				
			||||||
 | 
							 * We assume that if the serial interface is disabled, designers
 | 
				
			||||||
 | 
							 * connected fan5 as input unless they are emitting log 1, which
 | 
				
			||||||
 | 
							 * is not the default.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							regval = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1);
 | 
				
			||||||
 | 
							if ((regval & (1 << 2)) && fan4pin) {
 | 
				
			||||||
 | 
								data->has_fan |= (1 << 3);
 | 
				
			||||||
 | 
								data->has_fan_min |= (1 << 3);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!(regval & (1 << 1)) && fan5pin) {
 | 
				
			||||||
 | 
								data->has_fan |= (1 << 4);
 | 
				
			||||||
 | 
								data->has_fan_min |= (1 << 4);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __devinit w83627ehf_probe(struct platform_device *pdev)
 | 
					static int __devinit w83627ehf_probe(struct platform_device *pdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct device *dev = &pdev->dev;
 | 
						struct device *dev = &pdev->dev;
 | 
				
			||||||
	struct w83627ehf_sio_data *sio_data = dev->platform_data;
 | 
						struct w83627ehf_sio_data *sio_data = dev->platform_data;
 | 
				
			||||||
	struct w83627ehf_data *data;
 | 
						struct w83627ehf_data *data;
 | 
				
			||||||
	struct resource *res;
 | 
						struct resource *res;
 | 
				
			||||||
	u8 fan3pin, fan4pin, fan4min, fan5pin, en_vrm10;
 | 
						u8 en_vrm10;
 | 
				
			||||||
	int i, err = 0;
 | 
						int i, err = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 | 
						res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 | 
				
			||||||
| 
						 | 
					@ -2080,30 +2201,6 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* fan4 and fan5 share some pins with the GPIO and serial flash */
 | 
					 | 
				
			||||||
	if (sio_data->kind == nct6775) {
 | 
					 | 
				
			||||||
		/* On NCT6775, fan4 shares pins with the fdc interface */
 | 
					 | 
				
			||||||
		fan3pin = 1;
 | 
					 | 
				
			||||||
		fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
 | 
					 | 
				
			||||||
		fan4min = 0;
 | 
					 | 
				
			||||||
		fan5pin = 0;
 | 
					 | 
				
			||||||
	} else if (sio_data->kind == nct6776) {
 | 
					 | 
				
			||||||
		fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
 | 
					 | 
				
			||||||
		fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01);
 | 
					 | 
				
			||||||
		fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02);
 | 
					 | 
				
			||||||
		fan4min = fan4pin;
 | 
					 | 
				
			||||||
	} else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
 | 
					 | 
				
			||||||
		fan3pin = 1;
 | 
					 | 
				
			||||||
		fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40;
 | 
					 | 
				
			||||||
		fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20;
 | 
					 | 
				
			||||||
		fan4min = fan4pin;
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		fan3pin = 1;
 | 
					 | 
				
			||||||
		fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06);
 | 
					 | 
				
			||||||
		fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02);
 | 
					 | 
				
			||||||
		fan4min = fan4pin;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (fan_debounce &&
 | 
						if (fan_debounce &&
 | 
				
			||||||
	    (sio_data->kind == nct6775 || sio_data->kind == nct6776)) {
 | 
						    (sio_data->kind == nct6775 || sio_data->kind == nct6776)) {
 | 
				
			||||||
		u8 tmp;
 | 
							u8 tmp;
 | 
				
			||||||
| 
						 | 
					@ -2121,34 +2218,7 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	superio_exit(sio_data->sioreg);
 | 
						superio_exit(sio_data->sioreg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* It looks like fan4 and fan5 pins can be alternatively used
 | 
						w83627ehf_check_fan_inputs(sio_data, data);
 | 
				
			||||||
	   as fan on/off switches, but fan5 control is write only :/
 | 
					 | 
				
			||||||
	   We assume that if the serial interface is disabled, designers
 | 
					 | 
				
			||||||
	   connected fan5 as input unless they are emitting log 1, which
 | 
					 | 
				
			||||||
	   is not the default. */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data->has_fan |= (fan3pin << 2);
 | 
					 | 
				
			||||||
	data->has_fan_min |= (fan3pin << 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/*
 | 
					 | 
				
			||||||
	 * NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1 register
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	if (sio_data->kind == nct6775 || sio_data->kind == nct6776) {
 | 
					 | 
				
			||||||
		data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
 | 
					 | 
				
			||||||
		data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1);
 | 
					 | 
				
			||||||
		if ((i & (1 << 2)) && fan4pin) {
 | 
					 | 
				
			||||||
			data->has_fan |= (1 << 3);
 | 
					 | 
				
			||||||
			data->has_fan_min |= (1 << 3);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (!(i & (1 << 1)) && fan5pin) {
 | 
					 | 
				
			||||||
			data->has_fan |= (1 << 4);
 | 
					 | 
				
			||||||
			data->has_fan_min |= (1 << 4);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Read fan clock dividers immediately */
 | 
						/* Read fan clock dividers immediately */
 | 
				
			||||||
	w83627ehf_update_fan_div_common(dev, data);
 | 
						w83627ehf_update_fan_div_common(dev, data);
 | 
				
			||||||
| 
						 | 
					@ -2269,6 +2339,16 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
 | 
				
			||||||
			goto exit_remove;
 | 
								goto exit_remove;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = device_create_file(dev, &sda_caseopen[0].dev_attr);
 | 
				
			||||||
 | 
						if (err)
 | 
				
			||||||
 | 
							goto exit_remove;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (sio_data->kind == nct6776) {
 | 
				
			||||||
 | 
							err = device_create_file(dev, &sda_caseopen[1].dev_attr);
 | 
				
			||||||
 | 
							if (err)
 | 
				
			||||||
 | 
								goto exit_remove;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = device_create_file(dev, &dev_attr_name);
 | 
						err = device_create_file(dev, &dev_attr_name);
 | 
				
			||||||
	if (err)
 | 
						if (err)
 | 
				
			||||||
		goto exit_remove;
 | 
							goto exit_remove;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										83
									
								
								include/linux/platform_data/exynos4_tmu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								include/linux/platform_data/exynos4_tmu.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *  Copyright (C) 2011 Samsung Electronics
 | 
				
			||||||
 | 
					 *  Donggeun Kim <dg77.kim@samsung.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation; either version 2 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program; if not, write to the Free Software
 | 
				
			||||||
 | 
					 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef _LINUX_EXYNOS4_TMU_H
 | 
				
			||||||
 | 
					#define _LINUX_EXYNOS4_TMU_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum calibration_type {
 | 
				
			||||||
 | 
						TYPE_ONE_POINT_TRIMMING,
 | 
				
			||||||
 | 
						TYPE_TWO_POINT_TRIMMING,
 | 
				
			||||||
 | 
						TYPE_NONE,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct exynos4_tmu_platform_data
 | 
				
			||||||
 | 
					 * @threshold: basic temperature for generating interrupt
 | 
				
			||||||
 | 
					 *	       25 <= threshold <= 125 [unit: degree Celsius]
 | 
				
			||||||
 | 
					 * @trigger_levels: array for each interrupt levels
 | 
				
			||||||
 | 
					 *	[unit: degree Celsius]
 | 
				
			||||||
 | 
					 *	0: temperature for trigger_level0 interrupt
 | 
				
			||||||
 | 
					 *	   condition for trigger_level0 interrupt:
 | 
				
			||||||
 | 
					 *		current temperature > threshold + trigger_levels[0]
 | 
				
			||||||
 | 
					 *	1: temperature for trigger_level1 interrupt
 | 
				
			||||||
 | 
					 *	   condition for trigger_level1 interrupt:
 | 
				
			||||||
 | 
					 *		current temperature > threshold + trigger_levels[1]
 | 
				
			||||||
 | 
					 *	2: temperature for trigger_level2 interrupt
 | 
				
			||||||
 | 
					 *	   condition for trigger_level2 interrupt:
 | 
				
			||||||
 | 
					 *		current temperature > threshold + trigger_levels[2]
 | 
				
			||||||
 | 
					 *	3: temperature for trigger_level3 interrupt
 | 
				
			||||||
 | 
					 *	   condition for trigger_level3 interrupt:
 | 
				
			||||||
 | 
					 *		current temperature > threshold + trigger_levels[3]
 | 
				
			||||||
 | 
					 * @trigger_level0_en:
 | 
				
			||||||
 | 
					 *	1 = enable trigger_level0 interrupt,
 | 
				
			||||||
 | 
					 *	0 = disable trigger_level0 interrupt
 | 
				
			||||||
 | 
					 * @trigger_level1_en:
 | 
				
			||||||
 | 
					 *	1 = enable trigger_level1 interrupt,
 | 
				
			||||||
 | 
					 *	0 = disable trigger_level1 interrupt
 | 
				
			||||||
 | 
					 * @trigger_level2_en:
 | 
				
			||||||
 | 
					 *	1 = enable trigger_level2 interrupt,
 | 
				
			||||||
 | 
					 *	0 = disable trigger_level2 interrupt
 | 
				
			||||||
 | 
					 * @trigger_level3_en:
 | 
				
			||||||
 | 
					 *	1 = enable trigger_level3 interrupt,
 | 
				
			||||||
 | 
					 *	0 = disable trigger_level3 interrupt
 | 
				
			||||||
 | 
					 * @gain: gain of amplifier in the positive-TC generator block
 | 
				
			||||||
 | 
					 *	0 <= gain <= 15
 | 
				
			||||||
 | 
					 * @reference_voltage: reference voltage of amplifier
 | 
				
			||||||
 | 
					 *	in the positive-TC generator block
 | 
				
			||||||
 | 
					 *	0 <= reference_voltage <= 31
 | 
				
			||||||
 | 
					 * @cal_type: calibration type for temperature
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This structure is required for configuration of exynos4_tmu driver.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct exynos4_tmu_platform_data {
 | 
				
			||||||
 | 
						u8 threshold;
 | 
				
			||||||
 | 
						u8 trigger_levels[4];
 | 
				
			||||||
 | 
						bool trigger_level0_en;
 | 
				
			||||||
 | 
						bool trigger_level1_en;
 | 
				
			||||||
 | 
						bool trigger_level2_en;
 | 
				
			||||||
 | 
						bool trigger_level3_en;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						u8 gain;
 | 
				
			||||||
 | 
						u8 reference_voltage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enum calibration_type cal_type;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					#endif /* _LINUX_EXYNOS4_TMU_H */
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue