hwmon updates for v4.9
- New hwmon registration API, including ports of several drivers to the new API - New hwmon driver for APM X-Gene SoC - Added support for UCD90160, DPS-460, DPS-800, and SGD009 PMBUs chips - Various cleanups, minor improvements, and fixes in several drivers -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJX8cQ6AAoJEMsfJm/On5mBlWIP/RpA4b05gsYOG1SVW/SuZZPu IJfFkvGgOORgMS0gg8bNk3WyNNnqinbB4A6xucR/7Q7gRyk+3NYR6mNIsyP3UcAm KHJF9jgUr02vO6reMGGrqrR3cQ14omHgHqXBWQny7JkZ3/6Xyvg/LuarAZ+ZsUfp cNALIBsHoxyBSF4W7g5PJILtVbA+aW854CQ7UU/S0I2rjR9cqLu9HHvPUeYIazfO Tn4XM1vZCPLH5OhvIRURCy7FaZjwmxhPZhgF5fqAgDUF0VTQAhiOZB9jcz5NQq2q sSzlXSd2qqdJSTabtaBDl3O9GsfFrtT0LsFT0Rycx9JedgbV+Euy240uGVUnbHdT Gh8TVmOYx6qldDj/Hu5DF+kKnIH8vL6fp5blA6b/3EKwv14MUBNC/hxrmOcxyo5T qBnos0FlF9Ut4QLdRWM0rorZkCWxJWCXV8CvPX79b8iospKD/+Fgr69z7UgZuljA rcXZJR65oz4cJGMFr4dR+CcC0y2Jqqn5mcQAaGSR0RcII6EpSa+3Zb+2nBE4VGyQ vZpmxEEjMu9C8SI5DAczLwIMOsW4/pTTB6f8lLKiKRM2eJ7mAyk0KCF+9JIaKjaL qigWHlJI9FpXQiuek+pMXTVBTzs6fO/NSVfPUfEyAoyyuQePCjqVsQdn/h+lzQHC 1FiFIvnzlxb4ESZ5pfS1 =FLdn -----END PGP SIGNATURE----- Merge tag 'hwmon-for-linus-v4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - New hwmon registration API, including ports of several drivers to the new API - New hwmon driver for APM X-Gene SoC - Added support for UCD90160, DPS-460, DPS-800, and SGD009 PMBUs chips - Various cleanups, minor improvements, and fixes in several drivers * tag 'hwmon-for-linus-v4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (54 commits) hwmon: (nct6775) Add support for multiple virtual temperature sources hwmon: (adt7470) No need for additional synchronization on kthread_stop() hwmon: (lm95241) Update module description to include LM95231 hwmon: (lm95245) Select REGMAP_I2C hwmon: (ibmpowernv) Fix label for cores numbers not threads hwmon: (adt7470) Allow faster removal hwmon: (adt7470) Add write support to alarm_mask hwmon: (xgene) access mailbox as RAM hwmon: (lm95245) Use new hwmon registration API hwmon: (lm95241) Convert to use new hwmon registration API hwmon: (jc42) Convert to use new hwmon registration API hwmon: (max31790) Convert to use new hwmon registration API hwmon: (nct7904) Convert to use new hwmon registration API hwmon: (ltc4245) Convert to use new hwmon registration API hwmon: (tmp421) Convert to use new hwmon registration API hwmon: (tmp102) Convert to use new hwmon registration API hwmon: (lm90) Convert to use new hwmon registration API hwmon: (lm75) Convert to use new hwmon registration API hwmon: (xgene) Fix crash when alarm occurs before driver probe hwmon: (iio_hwmon) defer probe when no channel is found ...
This commit is contained in:
commit
77b0a4aa07
|
@ -0,0 +1,18 @@
|
||||||
|
LTC4151 High Voltage I2C Current and Voltage Monitor
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Must be "lltc,ltc4151"
|
||||||
|
- reg: I2C address
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- shunt-resistor-micro-ohms
|
||||||
|
Shunt resistor value in micro-Ohms
|
||||||
|
Defaults to <1000> if unset.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
ltc4151@6e {
|
||||||
|
compatible = "lltc,ltc4151";
|
||||||
|
reg = <0x6e>;
|
||||||
|
shunt-resistor-micro-ohms = <1500>;
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
Bindings for MAX6651 and MAX6650 I2C fan controllers
|
||||||
|
|
||||||
|
Reference:
|
||||||
|
[1] https://datasheets.maximintegrated.com/en/ds/MAX6650-MAX6651.pdf
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible : One of "maxim,max6650" or "maxim,max6651"
|
||||||
|
- reg : I2C address, one of 0x1b, 0x1f, 0x4b, 0x48.
|
||||||
|
|
||||||
|
Optional properties, default is to retain the chip's current setting:
|
||||||
|
- maxim,fan-microvolt : The supply voltage of the fan, either 5000000 uV or
|
||||||
|
12000000 uV.
|
||||||
|
- maxim,fan-prescale : Pre-scaling value, as per datasheet [1]. Lower values
|
||||||
|
allow more fine-grained control of slower fans.
|
||||||
|
Valid: 1, 2, 4, 8, 16.
|
||||||
|
- maxim,fan-target-rpm: Initial requested fan rotation speed. If specified, the
|
||||||
|
driver selects closed-loop mode and the requested speed.
|
||||||
|
This ensures the fan is already running before userspace
|
||||||
|
takes over.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
fan-max6650: max6650@1b {
|
||||||
|
reg = <0x1b>;
|
||||||
|
compatible = "maxim,max6650";
|
||||||
|
maxim,fan-microvolt = <12000000>;
|
||||||
|
maxim,fan-prescale = <4>;
|
||||||
|
maxim,fan-target-rpm = <1200>;
|
||||||
|
};
|
|
@ -65,6 +65,23 @@ from 0 (off) to 255 (full speed). Fan speed will be set to maximum when the
|
||||||
temperature sensor associated with the PWM control exceeds
|
temperature sensor associated with the PWM control exceeds
|
||||||
pwm#_auto_point2_temp.
|
pwm#_auto_point2_temp.
|
||||||
|
|
||||||
|
The driver also allows control of the PWM frequency:
|
||||||
|
|
||||||
|
* pwm1_freq
|
||||||
|
|
||||||
|
The PWM frequency is rounded to the nearest one of:
|
||||||
|
|
||||||
|
* 11.0 Hz
|
||||||
|
* 14.7 Hz
|
||||||
|
* 22.1 Hz
|
||||||
|
* 29.4 Hz
|
||||||
|
* 35.3 Hz
|
||||||
|
* 44.1 Hz
|
||||||
|
* 58.8 Hz
|
||||||
|
* 88.2 Hz
|
||||||
|
* 1.4 kHz
|
||||||
|
* 22.5 kHz
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,19 @@ devm_hwmon_device_register_with_groups(struct device *dev,
|
||||||
const char *name, void *drvdata,
|
const char *name, void *drvdata,
|
||||||
const struct attribute_group **groups);
|
const struct attribute_group **groups);
|
||||||
|
|
||||||
|
struct device *
|
||||||
|
hwmon_device_register_with_info(struct device *dev,
|
||||||
|
const char *name, void *drvdata,
|
||||||
|
const struct hwmon_chip_info *info,
|
||||||
|
const struct attribute_group **groups);
|
||||||
|
|
||||||
|
struct device *
|
||||||
|
devm_hwmon_device_register_with_info(struct device *dev,
|
||||||
|
const char *name,
|
||||||
|
void *drvdata,
|
||||||
|
const struct hwmon_chip_info *info,
|
||||||
|
const struct attribute_group **groups);
|
||||||
|
|
||||||
void hwmon_device_unregister(struct device *dev);
|
void hwmon_device_unregister(struct device *dev);
|
||||||
void devm_hwmon_device_unregister(struct device *dev);
|
void devm_hwmon_device_unregister(struct device *dev);
|
||||||
|
|
||||||
|
@ -60,15 +73,229 @@ devm_hwmon_device_register_with_groups is similar to
|
||||||
hwmon_device_register_with_groups. However, it is device managed, meaning the
|
hwmon_device_register_with_groups. However, it is device managed, meaning the
|
||||||
hwmon device does not have to be removed explicitly by the removal function.
|
hwmon device does not have to be removed explicitly by the removal function.
|
||||||
|
|
||||||
|
hwmon_device_register_with_info is the most comprehensive and preferred means
|
||||||
|
to register a hardware monitoring device. It creates the standard sysfs
|
||||||
|
attributes in the hardware monitoring core, letting the driver focus on reading
|
||||||
|
from and writing to the chip instead of having to bother with sysfs attributes.
|
||||||
|
Its parameters are described in more detail below.
|
||||||
|
|
||||||
|
devm_hwmon_device_register_with_info is similar to
|
||||||
|
hwmon_device_register_with_info. However, it is device managed, meaning the
|
||||||
|
hwmon device does not have to be removed explicitly by the removal function.
|
||||||
|
|
||||||
hwmon_device_unregister deregisters a registered hardware monitoring device.
|
hwmon_device_unregister deregisters a registered hardware monitoring device.
|
||||||
The parameter of this function is the pointer to the registered hardware
|
The parameter of this function is the pointer to the registered hardware
|
||||||
monitoring device structure. This function must be called from the driver
|
monitoring device structure. This function must be called from the driver
|
||||||
remove function if the hardware monitoring device was registered with
|
remove function if the hardware monitoring device was registered with
|
||||||
hwmon_device_register or with hwmon_device_register_with_groups.
|
hwmon_device_register, hwmon_device_register_with_groups, or
|
||||||
|
hwmon_device_register_with_info.
|
||||||
|
|
||||||
devm_hwmon_device_unregister does not normally have to be called. It is only
|
devm_hwmon_device_unregister does not normally have to be called. It is only
|
||||||
needed for error handling, and only needed if the driver probe fails after
|
needed for error handling, and only needed if the driver probe fails after
|
||||||
the call to devm_hwmon_device_register_with_groups.
|
the call to devm_hwmon_device_register_with_groups and if the automatic
|
||||||
|
(device managed) removal would be too late.
|
||||||
|
|
||||||
|
Using devm_hwmon_device_register_with_info()
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
hwmon_device_register_with_info() registers a hardware monitoring device.
|
||||||
|
The parameters to this function are
|
||||||
|
|
||||||
|
struct device *dev Pointer to parent device
|
||||||
|
const char *name Device name
|
||||||
|
void *drvdata Driver private data
|
||||||
|
const struct hwmon_chip_info *info
|
||||||
|
Pointer to chip description.
|
||||||
|
const struct attribute_group **groups
|
||||||
|
Null-terminated list of additional sysfs attribute
|
||||||
|
groups.
|
||||||
|
|
||||||
|
This function returns a pointer to the created hardware monitoring device
|
||||||
|
on success and a negative error code for failure.
|
||||||
|
|
||||||
|
The hwmon_chip_info structure looks as follows.
|
||||||
|
|
||||||
|
struct hwmon_chip_info {
|
||||||
|
const struct hwmon_ops *ops;
|
||||||
|
const struct hwmon_channel_info **info;
|
||||||
|
};
|
||||||
|
|
||||||
|
It contains the following fields:
|
||||||
|
|
||||||
|
* ops: Pointer to device operations.
|
||||||
|
* info: NULL-terminated list of device channel descriptors.
|
||||||
|
|
||||||
|
The list of hwmon operations is defined as:
|
||||||
|
|
||||||
|
struct hwmon_ops {
|
||||||
|
umode_t (*is_visible)(const void *, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int);
|
||||||
|
int (*read)(struct device *, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int, long *);
|
||||||
|
int (*write)(struct device *, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int, long);
|
||||||
|
};
|
||||||
|
|
||||||
|
It defines the following operations.
|
||||||
|
|
||||||
|
* is_visible: Pointer to a function to return the file mode for each supported
|
||||||
|
attribute. This function is mandatory.
|
||||||
|
|
||||||
|
* read: Pointer to a function for reading a value from the chip. This function
|
||||||
|
is optional, but must be provided if any readable attributes exist.
|
||||||
|
|
||||||
|
* write: Pointer to a function for writing a value to the chip. This function is
|
||||||
|
optional, but must be provided if any writeable attributes exist.
|
||||||
|
|
||||||
|
Each sensor channel is described with struct hwmon_channel_info, which is
|
||||||
|
defined as follows.
|
||||||
|
|
||||||
|
struct hwmon_channel_info {
|
||||||
|
enum hwmon_sensor_types type;
|
||||||
|
u32 *config;
|
||||||
|
};
|
||||||
|
|
||||||
|
It contains following fields:
|
||||||
|
|
||||||
|
* type: The hardware monitoring sensor type.
|
||||||
|
Supported sensor types are
|
||||||
|
* hwmon_chip A virtual sensor type, used to describe attributes
|
||||||
|
which apply to the entire chip.
|
||||||
|
* hwmon_temp Temperature sensor
|
||||||
|
* hwmon_in Voltage sensor
|
||||||
|
* hwmon_curr Current sensor
|
||||||
|
* hwmon_power Power sensor
|
||||||
|
* hwmon_energy Energy sensor
|
||||||
|
* hwmon_humidity Humidity sensor
|
||||||
|
* hwmon_fan Fan speed sensor
|
||||||
|
* hwmon_pwm PWM control
|
||||||
|
|
||||||
|
* config: Pointer to a 0-terminated list of configuration values for each
|
||||||
|
sensor of the given type. Each value is a combination of bit values
|
||||||
|
describing the attributes supposed by a single sensor.
|
||||||
|
|
||||||
|
As an example, here is the complete description file for a LM75 compatible
|
||||||
|
sensor chip. The chip has a single temperature sensor. The driver wants to
|
||||||
|
register with the thermal subsystem (HWMON_C_REGISTER_TZ), and it supports
|
||||||
|
the update_interval attribute (HWMON_C_UPDATE_INTERVAL). The chip supports
|
||||||
|
reading the temperature (HWMON_T_INPUT), it has a maximum temperature
|
||||||
|
register (HWMON_T_MAX) as well as a maximum temperature hysteresis register
|
||||||
|
(HWMON_T_MAX_HYST).
|
||||||
|
|
||||||
|
static const u32 lm75_chip_config[] = {
|
||||||
|
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm75_chip = {
|
||||||
|
.type = hwmon_chip,
|
||||||
|
.config = lm75_chip_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 lm75_temp_config[] = {
|
||||||
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm75_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = lm75_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *lm75_info[] = {
|
||||||
|
&lm75_chip,
|
||||||
|
&lm75_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops lm75_hwmon_ops = {
|
||||||
|
.is_visible = lm75_is_visible,
|
||||||
|
.read = lm75_read,
|
||||||
|
.write = lm75_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info lm75_chip_info = {
|
||||||
|
.ops = &lm75_hwmon_ops,
|
||||||
|
.info = lm75_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
A complete list of bit values indicating individual attribute support
|
||||||
|
is defined in include/linux/hwmon.h. Definition prefixes are as follows.
|
||||||
|
|
||||||
|
HWMON_C_xxxx Chip attributes, for use with hwmon_chip.
|
||||||
|
HWMON_T_xxxx Temperature attributes, for use with hwmon_temp.
|
||||||
|
HWMON_I_xxxx Voltage attributes, for use with hwmon_in.
|
||||||
|
HWMON_C_xxxx Current attributes, for use with hwmon_curr.
|
||||||
|
Notice the prefix overlap with chip attributes.
|
||||||
|
HWMON_P_xxxx Power attributes, for use with hwmon_power.
|
||||||
|
HWMON_E_xxxx Energy attributes, for use with hwmon_energy.
|
||||||
|
HWMON_H_xxxx Humidity attributes, for use with hwmon_humidity.
|
||||||
|
HWMON_F_xxxx Fan speed attributes, for use with hwmon_fan.
|
||||||
|
HWMON_PWM_xxxx PWM control attributes, for use with hwmon_pwm.
|
||||||
|
|
||||||
|
Driver callback functions
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Each driver provides is_visible, read, and write functions. Parameters
|
||||||
|
and return values for those functions are as follows.
|
||||||
|
|
||||||
|
umode_t is_visible_func(const void *data, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel)
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
data: Pointer to device private data structure.
|
||||||
|
type: The sensor type.
|
||||||
|
attr: Attribute identifier associated with a specific attribute.
|
||||||
|
For example, the attribute value for HWMON_T_INPUT would be
|
||||||
|
hwmon_temp_input. For complete mappings of bit fields to
|
||||||
|
attribute values please see include/linux/hwmon.h.
|
||||||
|
channel:The sensor channel number.
|
||||||
|
|
||||||
|
Return value:
|
||||||
|
The file mode for this attribute. Typically, this will be 0 (the
|
||||||
|
attribute will not be created), S_IRUGO, or 'S_IRUGO | S_IWUSR'.
|
||||||
|
|
||||||
|
int read_func(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
dev: Pointer to the hardware monitoring device.
|
||||||
|
type: The sensor type.
|
||||||
|
attr: Attribute identifier associated with a specific attribute.
|
||||||
|
For example, the attribute value for HWMON_T_INPUT would be
|
||||||
|
hwmon_temp_input. For complete mappings please see
|
||||||
|
include/linux/hwmon.h.
|
||||||
|
channel:The sensor channel number.
|
||||||
|
val: Pointer to attribute value.
|
||||||
|
|
||||||
|
Return value:
|
||||||
|
0 on success, a negative error number otherwise.
|
||||||
|
|
||||||
|
int write_func(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long val)
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
dev: Pointer to the hardware monitoring device.
|
||||||
|
type: The sensor type.
|
||||||
|
attr: Attribute identifier associated with a specific attribute.
|
||||||
|
For example, the attribute value for HWMON_T_INPUT would be
|
||||||
|
hwmon_temp_input. For complete mappings please see
|
||||||
|
include/linux/hwmon.h.
|
||||||
|
channel:The sensor channel number.
|
||||||
|
val: The value to write to the chip.
|
||||||
|
|
||||||
|
Return value:
|
||||||
|
0 on success, a negative error number otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
Driver-provided sysfs attributes
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
If the hardware monitoring device is registered with
|
||||||
|
hwmon_device_register_with_info or devm_hwmon_device_register_with_info,
|
||||||
|
it is most likely not necessary to provide sysfs attributes. Only non-standard
|
||||||
|
sysfs attributes need to be provided when one of those registration functions
|
||||||
|
is used.
|
||||||
|
|
||||||
The header file linux/hwmon-sysfs.h provides a number of useful macros to
|
The header file linux/hwmon-sysfs.h provides a number of useful macros to
|
||||||
declare and use hardware monitoring sysfs attributes.
|
declare and use hardware monitoring sysfs attributes.
|
||||||
|
|
|
@ -34,6 +34,7 @@ fan3_input ro "
|
||||||
fan4_input ro "
|
fan4_input ro "
|
||||||
fan1_target rw desired fan speed in RPM (closed loop mode only)
|
fan1_target rw desired fan speed in RPM (closed loop mode only)
|
||||||
pwm1_enable rw regulator mode, 0=full on, 1=open loop, 2=closed loop
|
pwm1_enable rw regulator mode, 0=full on, 1=open loop, 2=closed loop
|
||||||
|
3=off
|
||||||
pwm1 rw relative speed (0-255), 255=max. speed.
|
pwm1 rw relative speed (0-255), 255=max. speed.
|
||||||
Used in open loop mode only.
|
Used in open loop mode only.
|
||||||
fan1_div rw sets the speed range the inputs can handle. Legal
|
fan1_div rw sets the speed range the inputs can handle. Legal
|
||||||
|
|
|
@ -2,12 +2,13 @@ Kernel driver ucd9000
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Supported chips:
|
Supported chips:
|
||||||
* TI UCD90120, UCD90124, UCD9090, and UCD90910
|
* TI UCD90120, UCD90124, UCD90160, UCD9090, and UCD90910
|
||||||
Prefixes: 'ucd90120', 'ucd90124', 'ucd9090', 'ucd90910'
|
Prefixes: 'ucd90120', 'ucd90124', 'ucd90160', 'ucd9090', 'ucd90910'
|
||||||
Addresses scanned: -
|
Addresses scanned: -
|
||||||
Datasheets:
|
Datasheets:
|
||||||
http://focus.ti.com/lit/ds/symlink/ucd90120.pdf
|
http://focus.ti.com/lit/ds/symlink/ucd90120.pdf
|
||||||
http://focus.ti.com/lit/ds/symlink/ucd90124.pdf
|
http://focus.ti.com/lit/ds/symlink/ucd90124.pdf
|
||||||
|
http://focus.ti.com/lit/ds/symlink/ucd90160.pdf
|
||||||
http://focus.ti.com/lit/ds/symlink/ucd9090.pdf
|
http://focus.ti.com/lit/ds/symlink/ucd9090.pdf
|
||||||
http://focus.ti.com/lit/ds/symlink/ucd90910.pdf
|
http://focus.ti.com/lit/ds/symlink/ucd90910.pdf
|
||||||
|
|
||||||
|
@ -32,6 +33,13 @@ interrupts, cascading, or other system functions. Twelve of these pins offer PWM
|
||||||
functionality. Using these pins, the UCD90124 offers support for fan control,
|
functionality. Using these pins, the UCD90124 offers support for fan control,
|
||||||
margining, and general-purpose PWM functions.
|
margining, and general-purpose PWM functions.
|
||||||
|
|
||||||
|
The UCD90160 is a 16-rail PMBus/I2C addressable power-supply sequencer and
|
||||||
|
monitor. The device integrates a 12-bit ADC for monitoring up to 16 power-supply
|
||||||
|
voltage inputs. Twenty-six GPIO pins can be used for power supply enables,
|
||||||
|
power-on reset signals, external interrupts, cascading, or other system
|
||||||
|
functions. Twelve of these pins offer PWM functionality. Using these pins, the
|
||||||
|
UCD90160 offers support for margining, and general-purpose PWM functions.
|
||||||
|
|
||||||
The UCD9090 is a 10-rail PMBus/I2C addressable power-supply sequencer and
|
The UCD9090 is a 10-rail PMBus/I2C addressable power-supply sequencer and
|
||||||
monitor. The device integrates a 12-bit ADC for monitoring up to 10 power-supply
|
monitor. The device integrates a 12-bit ADC for monitoring up to 10 power-supply
|
||||||
voltage inputs. Twenty-three GPIO pins can be used for power supply enables,
|
voltage inputs. Twenty-three GPIO pins can be used for power supply enables,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
Kernel driver xgene-hwmon
|
||||||
|
========================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
* APM X-Gene SoC
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This driver adds hardware temperature and power reading support for
|
||||||
|
APM X-Gene SoC using the mailbox communication interface.
|
||||||
|
For device tree, it is the standard DT mailbox.
|
||||||
|
For ACPI, it is the PCC mailbox.
|
||||||
|
|
||||||
|
The following sensors are supported
|
||||||
|
|
||||||
|
* Temperature
|
||||||
|
- SoC on-die temperature in milli-degree C
|
||||||
|
- Alarm when high/over temperature occurs
|
||||||
|
* Power
|
||||||
|
- CPU power in uW
|
||||||
|
- IO power in uW
|
||||||
|
|
||||||
|
sysfs-Interface
|
||||||
|
---------------
|
||||||
|
|
||||||
|
temp0_input - SoC on-die temperature (milli-degree C)
|
||||||
|
temp0_critical_alarm - An 1 would indicates on-die temperature exceeded threshold
|
||||||
|
power0_input - CPU power in (uW)
|
||||||
|
power1_input - IO power in (uW)
|
|
@ -969,7 +969,6 @@ config SENSORS_LM73
|
||||||
config SENSORS_LM75
|
config SENSORS_LM75
|
||||||
tristate "National Semiconductor LM75 and compatibles"
|
tristate "National Semiconductor LM75 and compatibles"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
depends on THERMAL || !THERMAL_OF
|
|
||||||
select REGMAP_I2C
|
select REGMAP_I2C
|
||||||
help
|
help
|
||||||
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
|
||||||
|
@ -1119,6 +1118,7 @@ config SENSORS_LM95241
|
||||||
config SENSORS_LM95245
|
config SENSORS_LM95245
|
||||||
tristate "National Semiconductor LM95245 and compatibles"
|
tristate "National Semiconductor LM95245 and compatibles"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
|
select REGMAP_I2C
|
||||||
help
|
help
|
||||||
If you say yes here you get support for LM95235 and LM95245
|
If you say yes here you get support for LM95235 and LM95245
|
||||||
temperature sensor chips.
|
temperature sensor chips.
|
||||||
|
@ -1572,7 +1572,6 @@ config SENSORS_THMC50
|
||||||
config SENSORS_TMP102
|
config SENSORS_TMP102
|
||||||
tristate "Texas Instruments TMP102"
|
tristate "Texas Instruments TMP102"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
depends on THERMAL || !THERMAL_OF
|
|
||||||
select REGMAP_I2C
|
select REGMAP_I2C
|
||||||
help
|
help
|
||||||
If you say yes here you get support for Texas Instruments TMP102
|
If you say yes here you get support for Texas Instruments TMP102
|
||||||
|
@ -1823,6 +1822,13 @@ config SENSORS_ULTRA45
|
||||||
This driver provides support for the Ultra45 workstation environmental
|
This driver provides support for the Ultra45 workstation environmental
|
||||||
sensors.
|
sensors.
|
||||||
|
|
||||||
|
config SENSORS_XGENE
|
||||||
|
tristate "APM X-Gene SoC hardware monitoring driver"
|
||||||
|
depends on XGENE_SLIMPRO_MBOX || PCC
|
||||||
|
help
|
||||||
|
If you say yes here you get support for the temperature
|
||||||
|
and power sensors for APM X-Gene SoC.
|
||||||
|
|
||||||
if ACPI
|
if ACPI
|
||||||
|
|
||||||
comment "ACPI drivers"
|
comment "ACPI drivers"
|
||||||
|
|
|
@ -165,6 +165,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
|
||||||
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
|
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
|
||||||
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
|
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
|
||||||
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
|
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
|
||||||
|
obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o
|
||||||
|
|
||||||
obj-$(CONFIG_PMBUS) += pmbus/
|
obj-$(CONFIG_PMBUS) += pmbus/
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
* it under the terms of the GNU General Public License version 2 as
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
* published by the Free Software Foundation.
|
* published by the Free Software Foundation.
|
||||||
*
|
*
|
||||||
* TODO: SPI, support for external temperature sensor
|
* TODO: SPI, use power-down mode for suspend?, interrupt handling?
|
||||||
* use power-down mode for suspend?, interrupt handling?
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
|
@ -31,6 +30,7 @@
|
||||||
#define ADT7411_REG_CFG1 0x18
|
#define ADT7411_REG_CFG1 0x18
|
||||||
#define ADT7411_CFG1_START_MONITOR (1 << 0)
|
#define ADT7411_CFG1_START_MONITOR (1 << 0)
|
||||||
#define ADT7411_CFG1_RESERVED_BIT1 (1 << 1)
|
#define ADT7411_CFG1_RESERVED_BIT1 (1 << 1)
|
||||||
|
#define ADT7411_CFG1_EXT_TDM (1 << 2)
|
||||||
#define ADT7411_CFG1_RESERVED_BIT3 (1 << 3)
|
#define ADT7411_CFG1_RESERVED_BIT3 (1 << 3)
|
||||||
|
|
||||||
#define ADT7411_REG_CFG2 0x19
|
#define ADT7411_REG_CFG2 0x19
|
||||||
|
@ -57,6 +57,7 @@ struct adt7411_data {
|
||||||
unsigned long next_update;
|
unsigned long next_update;
|
||||||
int vref_cached;
|
int vref_cached;
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
|
bool use_ext_temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -127,11 +128,20 @@ static ssize_t adt7411_show_vdd(struct device *dev,
|
||||||
static ssize_t adt7411_show_temp(struct device *dev,
|
static ssize_t adt7411_show_temp(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
int nr = to_sensor_dev_attr(attr)->index;
|
||||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
int val = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB,
|
int val;
|
||||||
ADT7411_REG_INT_TEMP_MSB, 0);
|
struct {
|
||||||
|
u8 low;
|
||||||
|
u8 high;
|
||||||
|
} reg[2] = {
|
||||||
|
{ ADT7411_REG_INT_TEMP_VDD_LSB, ADT7411_REG_INT_TEMP_MSB },
|
||||||
|
{ ADT7411_REG_EXT_TEMP_AIN14_LSB,
|
||||||
|
ADT7411_REG_EXT_TEMP_AIN1_MSB },
|
||||||
|
};
|
||||||
|
|
||||||
|
val = adt7411_read_10_bit(client, reg[nr].low, reg[nr].high, 0);
|
||||||
if (val < 0)
|
if (val < 0)
|
||||||
return val;
|
return val;
|
||||||
|
|
||||||
|
@ -218,11 +228,13 @@ static ssize_t adt7411_set_bit(struct device *dev,
|
||||||
return ret < 0 ? ret : count;
|
return ret < 0 ? ret : count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define ADT7411_BIT_ATTR(__name, __reg, __bit) \
|
#define ADT7411_BIT_ATTR(__name, __reg, __bit) \
|
||||||
SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \
|
SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \
|
||||||
adt7411_set_bit, __bit, __reg)
|
adt7411_set_bit, __bit, __reg)
|
||||||
|
|
||||||
static DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL);
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, adt7411_show_temp, NULL, 1);
|
||||||
static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL);
|
static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL);
|
||||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0);
|
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1);
|
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1);
|
||||||
|
@ -237,7 +249,8 @@ static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_22
|
||||||
static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD);
|
static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD);
|
||||||
|
|
||||||
static struct attribute *adt7411_attrs[] = {
|
static struct attribute *adt7411_attrs[] = {
|
||||||
&dev_attr_temp1_input.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||||
&dev_attr_in0_input.attr,
|
&dev_attr_in0_input.attr,
|
||||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||||
|
@ -253,7 +266,27 @@ static struct attribute *adt7411_attrs[] = {
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
ATTRIBUTE_GROUPS(adt7411);
|
static umode_t adt7411_attrs_visible(struct kobject *kobj,
|
||||||
|
struct attribute *attr, int index)
|
||||||
|
{
|
||||||
|
struct device *dev = container_of(kobj, struct device, kobj);
|
||||||
|
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||||
|
bool visible = true;
|
||||||
|
|
||||||
|
if (attr == &sensor_dev_attr_temp2_input.dev_attr.attr)
|
||||||
|
visible = data->use_ext_temp;
|
||||||
|
else if (attr == &sensor_dev_attr_in1_input.dev_attr.attr ||
|
||||||
|
attr == &sensor_dev_attr_in2_input.dev_attr.attr)
|
||||||
|
visible = !data->use_ext_temp;
|
||||||
|
|
||||||
|
return visible ? attr->mode : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct attribute_group adt7411_group = {
|
||||||
|
.attrs = adt7411_attrs,
|
||||||
|
.is_visible = adt7411_attrs_visible,
|
||||||
|
};
|
||||||
|
__ATTRIBUTE_GROUPS(adt7411);
|
||||||
|
|
||||||
static int adt7411_detect(struct i2c_client *client,
|
static int adt7411_detect(struct i2c_client *client,
|
||||||
struct i2c_board_info *info)
|
struct i2c_board_info *info)
|
||||||
|
@ -309,6 +342,8 @@ static int adt7411_init_device(struct adt7411_data *data)
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
data->use_ext_temp = ret & ADT7411_CFG1_EXT_TDM;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We must only write zero to bit 1 and only one to bit 3 according to
|
* We must only write zero to bit 1 and only one to bit 3 according to
|
||||||
* the datasheet.
|
* the datasheet.
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <linux/log2.h>
|
#include <linux/log2.h>
|
||||||
#include <linux/kthread.h>
|
#include <linux/kthread.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <linux/util_macros.h>
|
||||||
|
|
||||||
/* Addresses to scan */
|
/* Addresses to scan */
|
||||||
static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
||||||
|
@ -83,6 +84,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
||||||
#define ADT7470_REG_PWM_MIN_MAX_ADDR 0x6D
|
#define ADT7470_REG_PWM_MIN_MAX_ADDR 0x6D
|
||||||
#define ADT7470_REG_PWM_TEMP_MIN_BASE_ADDR 0x6E
|
#define ADT7470_REG_PWM_TEMP_MIN_BASE_ADDR 0x6E
|
||||||
#define ADT7470_REG_PWM_TEMP_MIN_MAX_ADDR 0x71
|
#define ADT7470_REG_PWM_TEMP_MIN_MAX_ADDR 0x71
|
||||||
|
#define ADT7470_REG_CFG_2 0x74
|
||||||
#define ADT7470_REG_ACOUSTICS12 0x75
|
#define ADT7470_REG_ACOUSTICS12 0x75
|
||||||
#define ADT7470_REG_ACOUSTICS34 0x76
|
#define ADT7470_REG_ACOUSTICS34 0x76
|
||||||
#define ADT7470_REG_DEVICE 0x3D
|
#define ADT7470_REG_DEVICE 0x3D
|
||||||
|
@ -142,6 +144,11 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
||||||
#define FAN_PERIOD_INVALID 65535
|
#define FAN_PERIOD_INVALID 65535
|
||||||
#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID)
|
#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID)
|
||||||
|
|
||||||
|
/* Config registers 1 and 2 include fields for selecting the PWM frequency */
|
||||||
|
#define ADT7470_CFG_LF 0x40
|
||||||
|
#define ADT7470_FREQ_MASK 0x70
|
||||||
|
#define ADT7470_FREQ_SHIFT 4
|
||||||
|
|
||||||
struct adt7470_data {
|
struct adt7470_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
|
@ -170,7 +177,6 @@ struct adt7470_data {
|
||||||
u8 pwm_auto_temp[ADT7470_PWM_COUNT];
|
u8 pwm_auto_temp[ADT7470_PWM_COUNT];
|
||||||
|
|
||||||
struct task_struct *auto_update;
|
struct task_struct *auto_update;
|
||||||
struct completion auto_update_stop;
|
|
||||||
unsigned int auto_update_interval;
|
unsigned int auto_update_interval;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -266,12 +272,14 @@ static int adt7470_update_thread(void *p)
|
||||||
mutex_lock(&data->lock);
|
mutex_lock(&data->lock);
|
||||||
adt7470_read_temperatures(client, data);
|
adt7470_read_temperatures(client, data);
|
||||||
mutex_unlock(&data->lock);
|
mutex_unlock(&data->lock);
|
||||||
|
|
||||||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||||||
if (kthread_should_stop())
|
if (kthread_should_stop())
|
||||||
break;
|
break;
|
||||||
msleep_interruptible(data->auto_update_interval);
|
|
||||||
|
schedule_timeout(msecs_to_jiffies(data->auto_update_interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
complete_all(&data->auto_update_stop);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +546,28 @@ static ssize_t show_alarm_mask(struct device *dev,
|
||||||
return sprintf(buf, "%x\n", data->alarms_mask);
|
return sprintf(buf, "%x\n", data->alarms_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t set_alarm_mask(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
const char *buf,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
struct adt7470_data *data = dev_get_drvdata(dev);
|
||||||
|
long mask;
|
||||||
|
|
||||||
|
if (kstrtoul(buf, 0, &mask))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (mask & ~0xffff)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
data->alarms_mask = mask;
|
||||||
|
adt7470_write_word_data(data->client, ADT7470_REG_ALARM1_MASK, mask);
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t show_fan_max(struct device *dev,
|
static ssize_t show_fan_max(struct device *dev,
|
||||||
struct device_attribute *devattr,
|
struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
|
@ -688,6 +718,70 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* These are the valid PWM frequencies to the nearest Hz */
|
||||||
|
static const int adt7470_freq_map[] = {
|
||||||
|
11, 15, 22, 29, 35, 44, 59, 88, 1400, 22500
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t show_pwm_freq(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct adt7470_data *data = adt7470_update_device(dev);
|
||||||
|
unsigned char cfg_reg_1;
|
||||||
|
unsigned char cfg_reg_2;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
cfg_reg_1 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG);
|
||||||
|
cfg_reg_2 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG_2);
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
|
||||||
|
index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT;
|
||||||
|
if (!(cfg_reg_1 & ADT7470_CFG_LF))
|
||||||
|
index += 8;
|
||||||
|
if (index >= ARRAY_SIZE(adt7470_freq_map))
|
||||||
|
index = ARRAY_SIZE(adt7470_freq_map) - 1;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", adt7470_freq_map[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_pwm_freq(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct adt7470_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
long freq;
|
||||||
|
int index;
|
||||||
|
int low_freq = ADT7470_CFG_LF;
|
||||||
|
unsigned char val;
|
||||||
|
|
||||||
|
if (kstrtol(buf, 10, &freq))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Round the user value given to the closest available frequency */
|
||||||
|
index = find_closest(freq, adt7470_freq_map,
|
||||||
|
ARRAY_SIZE(adt7470_freq_map));
|
||||||
|
|
||||||
|
if (index >= 8) {
|
||||||
|
index -= 8;
|
||||||
|
low_freq = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
/* Configuration Register 1 */
|
||||||
|
val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
|
||||||
|
i2c_smbus_write_byte_data(client, ADT7470_REG_CFG,
|
||||||
|
(val & ~ADT7470_CFG_LF) | low_freq);
|
||||||
|
/* Configuration Register 2 */
|
||||||
|
val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG_2);
|
||||||
|
i2c_smbus_write_byte_data(client, ADT7470_REG_CFG_2,
|
||||||
|
(val & ~ADT7470_FREQ_MASK) | (index << ADT7470_FREQ_SHIFT));
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t show_pwm_max(struct device *dev,
|
static ssize_t show_pwm_max(struct device *dev,
|
||||||
struct device_attribute *devattr,
|
struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
|
@ -918,7 +1012,8 @@ static ssize_t show_alarm(struct device *dev,
|
||||||
return sprintf(buf, "0\n");
|
return sprintf(buf, "0\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEVICE_ATTR(alarm_mask, S_IRUGO, show_alarm_mask, NULL);
|
static DEVICE_ATTR(alarm_mask, S_IWUSR | S_IRUGO, show_alarm_mask,
|
||||||
|
set_alarm_mask);
|
||||||
static DEVICE_ATTR(num_temp_sensors, S_IWUSR | S_IRUGO, show_num_temp_sensors,
|
static DEVICE_ATTR(num_temp_sensors, S_IWUSR | S_IRUGO, show_num_temp_sensors,
|
||||||
set_num_temp_sensors);
|
set_num_temp_sensors);
|
||||||
static DEVICE_ATTR(auto_update_interval, S_IWUSR | S_IRUGO,
|
static DEVICE_ATTR(auto_update_interval, S_IWUSR | S_IRUGO,
|
||||||
|
@ -1038,6 +1133,8 @@ static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1);
|
||||||
static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
|
static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
|
||||||
static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
|
static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
|
||||||
|
|
||||||
|
static DEVICE_ATTR(pwm1_freq, S_IWUSR | S_IRUGO, show_pwm_freq, set_pwm_freq);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO,
|
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO,
|
||||||
show_pwm_min, set_pwm_min, 0);
|
show_pwm_min, set_pwm_min, 0);
|
||||||
static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO,
|
static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO,
|
||||||
|
@ -1154,6 +1251,7 @@ static struct attribute *adt7470_attrs[] = {
|
||||||
&sensor_dev_attr_fan4_alarm.dev_attr.attr,
|
&sensor_dev_attr_fan4_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_force_pwm_max.dev_attr.attr,
|
&sensor_dev_attr_force_pwm_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_pwm1.dev_attr.attr,
|
&sensor_dev_attr_pwm1.dev_attr.attr,
|
||||||
|
&dev_attr_pwm1_freq.attr,
|
||||||
&sensor_dev_attr_pwm2.dev_attr.attr,
|
&sensor_dev_attr_pwm2.dev_attr.attr,
|
||||||
&sensor_dev_attr_pwm3.dev_attr.attr,
|
&sensor_dev_attr_pwm3.dev_attr.attr,
|
||||||
&sensor_dev_attr_pwm4.dev_attr.attr,
|
&sensor_dev_attr_pwm4.dev_attr.attr,
|
||||||
|
@ -1256,7 +1354,6 @@ static int adt7470_probe(struct i2c_client *client,
|
||||||
if (IS_ERR(hwmon_dev))
|
if (IS_ERR(hwmon_dev))
|
||||||
return PTR_ERR(hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
|
|
||||||
init_completion(&data->auto_update_stop);
|
|
||||||
data->auto_update = kthread_run(adt7470_update_thread, client, "%s",
|
data->auto_update = kthread_run(adt7470_update_thread, client, "%s",
|
||||||
dev_name(hwmon_dev));
|
dev_name(hwmon_dev));
|
||||||
if (IS_ERR(data->auto_update)) {
|
if (IS_ERR(data->auto_update)) {
|
||||||
|
@ -1271,7 +1368,6 @@ static int adt7470_remove(struct i2c_client *client)
|
||||||
struct adt7470_data *data = i2c_get_clientdata(client);
|
struct adt7470_data *data = i2c_get_clientdata(client);
|
||||||
|
|
||||||
kthread_stop(data->auto_update);
|
kthread_stop(data->auto_update);
|
||||||
wait_for_completion(&data->auto_update_stop);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
#define FTS_EVENT_STATUS_REG 0x0006
|
#define FTS_EVENT_STATUS_REG 0x0006
|
||||||
#define FTS_GLOBAL_CONTROL_REG 0x0007
|
#define FTS_GLOBAL_CONTROL_REG 0x0007
|
||||||
|
|
||||||
|
#define FTS_DEVICE_DETECT_REG_1 0x0C
|
||||||
|
#define FTS_DEVICE_DETECT_REG_2 0x0D
|
||||||
|
#define FTS_DEVICE_DETECT_REG_3 0x0E
|
||||||
|
|
||||||
#define FTS_SENSOR_EVENT_REG 0x0010
|
#define FTS_SENSOR_EVENT_REG 0x0010
|
||||||
|
|
||||||
#define FTS_FAN_EVENT_REG 0x0014
|
#define FTS_FAN_EVENT_REG 0x0014
|
||||||
|
@ -54,6 +58,8 @@
|
||||||
#define FTS_NO_TEMP_SENSORS 0x10
|
#define FTS_NO_TEMP_SENSORS 0x10
|
||||||
#define FTS_NO_VOLT_SENSORS 0x04
|
#define FTS_NO_VOLT_SENSORS 0x04
|
||||||
|
|
||||||
|
static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
|
||||||
|
|
||||||
static struct i2c_device_id fts_id[] = {
|
static struct i2c_device_id fts_id[] = {
|
||||||
{ "ftsteutates", 0 },
|
{ "ftsteutates", 0 },
|
||||||
{ }
|
{ }
|
||||||
|
@ -734,6 +740,42 @@ static const struct attribute_group *fts_attr_groups[] = {
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/* Module initialization / remove functions */
|
/* Module initialization / remove functions */
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
static int fts_detect(struct i2c_client *client,
|
||||||
|
struct i2c_board_info *info)
|
||||||
|
{
|
||||||
|
int val;
|
||||||
|
|
||||||
|
/* detection works with revsion greater or equal to 0x2b */
|
||||||
|
val = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG);
|
||||||
|
if (val < 0x2b)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Device Detect Regs must have 0x17 0x34 and 0x54 */
|
||||||
|
val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_1);
|
||||||
|
if (val != 0x17)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_2);
|
||||||
|
if (val != 0x34)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_3);
|
||||||
|
if (val != 0x54)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 0x10 == Baseboard Management Controller, 0x01 == Teutates
|
||||||
|
* Device ID Reg needs to be 0x11
|
||||||
|
*/
|
||||||
|
val = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG);
|
||||||
|
if (val != 0x11)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
strlcpy(info->type, fts_id[0].name, I2C_NAME_SIZE);
|
||||||
|
info->flags = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int fts_remove(struct i2c_client *client)
|
static int fts_remove(struct i2c_client *client)
|
||||||
{
|
{
|
||||||
struct fts_data *data = dev_get_drvdata(&client->dev);
|
struct fts_data *data = dev_get_drvdata(&client->dev);
|
||||||
|
@ -804,12 +846,15 @@ static int fts_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
/* Module Details */
|
/* Module Details */
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
static struct i2c_driver fts_driver = {
|
static struct i2c_driver fts_driver = {
|
||||||
|
.class = I2C_CLASS_HWMON,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "ftsteutates",
|
.name = "ftsteutates",
|
||||||
},
|
},
|
||||||
.id_table = fts_id,
|
.id_table = fts_id,
|
||||||
.probe = fts_probe,
|
.probe = fts_probe,
|
||||||
.remove = fts_remove,
|
.remove = fts_remove,
|
||||||
|
.detect = fts_detect,
|
||||||
|
.address_list = normal_i2c,
|
||||||
};
|
};
|
||||||
|
|
||||||
module_i2c_driver(fts_driver);
|
module_i2c_driver(fts_driver);
|
||||||
|
|
|
@ -12,17 +12,17 @@
|
||||||
|
|
||||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/kdev_t.h>
|
|
||||||
#include <linux/idr.h>
|
|
||||||
#include <linux/hwmon.h>
|
|
||||||
#include <linux/gfp.h>
|
#include <linux/gfp.h>
|
||||||
#include <linux/spinlock.h>
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/idr.h>
|
||||||
|
#include <linux/module.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
|
#include <linux/thermal.h>
|
||||||
|
|
||||||
#define HWMON_ID_PREFIX "hwmon"
|
#define HWMON_ID_PREFIX "hwmon"
|
||||||
#define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d"
|
#define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d"
|
||||||
|
@ -30,9 +30,35 @@
|
||||||
struct hwmon_device {
|
struct hwmon_device {
|
||||||
const char *name;
|
const char *name;
|
||||||
struct device dev;
|
struct device dev;
|
||||||
|
const struct hwmon_chip_info *chip;
|
||||||
|
|
||||||
|
struct attribute_group group;
|
||||||
|
const struct attribute_group **groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define to_hwmon_device(d) container_of(d, struct hwmon_device, dev)
|
#define to_hwmon_device(d) container_of(d, struct hwmon_device, dev)
|
||||||
|
|
||||||
|
struct hwmon_device_attribute {
|
||||||
|
struct device_attribute dev_attr;
|
||||||
|
const struct hwmon_ops *ops;
|
||||||
|
enum hwmon_sensor_types type;
|
||||||
|
u32 attr;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_hwmon_attr(d) \
|
||||||
|
container_of(d, struct hwmon_device_attribute, dev_attr)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thermal zone information
|
||||||
|
* In addition to the reference to the hwmon device,
|
||||||
|
* also provides the sensor index.
|
||||||
|
*/
|
||||||
|
struct hwmon_thermal_data {
|
||||||
|
struct hwmon_device *hwdev; /* Reference to hwmon device */
|
||||||
|
int index; /* sensor index */
|
||||||
|
};
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
show_name(struct device *dev, struct device_attribute *attr, char *buf)
|
show_name(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
@ -80,6 +106,498 @@ static struct class hwmon_class = {
|
||||||
|
|
||||||
static DEFINE_IDA(hwmon_ida);
|
static DEFINE_IDA(hwmon_ida);
|
||||||
|
|
||||||
|
/* Thermal zone handling */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The complex conditional is necessary to avoid a cyclic dependency
|
||||||
|
* between hwmon and thermal_sys modules.
|
||||||
|
*/
|
||||||
|
#if IS_REACHABLE(CONFIG_THERMAL) && defined(CONFIG_THERMAL_OF) && \
|
||||||
|
(!defined(CONFIG_THERMAL_HWMON) || \
|
||||||
|
!(defined(MODULE) && IS_MODULE(CONFIG_THERMAL)))
|
||||||
|
static int hwmon_thermal_get_temp(void *data, int *temp)
|
||||||
|
{
|
||||||
|
struct hwmon_thermal_data *tdata = data;
|
||||||
|
struct hwmon_device *hwdev = tdata->hwdev;
|
||||||
|
int ret;
|
||||||
|
long t;
|
||||||
|
|
||||||
|
ret = hwdev->chip->ops->read(&hwdev->dev, hwmon_temp, hwmon_temp_input,
|
||||||
|
tdata->index, &t);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*temp = t;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct thermal_zone_of_device_ops hwmon_thermal_ops = {
|
||||||
|
.get_temp = hwmon_thermal_get_temp,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hwmon_thermal_add_sensor(struct device *dev,
|
||||||
|
struct hwmon_device *hwdev, int index)
|
||||||
|
{
|
||||||
|
struct hwmon_thermal_data *tdata;
|
||||||
|
|
||||||
|
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
|
||||||
|
if (!tdata)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
tdata->hwdev = hwdev;
|
||||||
|
tdata->index = index;
|
||||||
|
|
||||||
|
devm_thermal_zone_of_sensor_register(&hwdev->dev, index, tdata,
|
||||||
|
&hwmon_thermal_ops);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static int hwmon_thermal_add_sensor(struct device *dev,
|
||||||
|
struct hwmon_device *hwdev, int index)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
|
||||||
|
|
||||||
|
/* sysfs attribute management */
|
||||||
|
|
||||||
|
static ssize_t hwmon_attr_show(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||||
|
long val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index,
|
||||||
|
&val);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return sprintf(buf, "%ld\n", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t hwmon_attr_store(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||||
|
long val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = kstrtol(buf, 10, &val);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index,
|
||||||
|
val);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hwmon_attr_base(enum hwmon_sensor_types type)
|
||||||
|
{
|
||||||
|
if (type == hwmon_in)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct attribute *hwmon_genattr(struct device *dev,
|
||||||
|
const void *drvdata,
|
||||||
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr,
|
||||||
|
int index,
|
||||||
|
const char *template,
|
||||||
|
const struct hwmon_ops *ops)
|
||||||
|
{
|
||||||
|
struct hwmon_device_attribute *hattr;
|
||||||
|
struct device_attribute *dattr;
|
||||||
|
struct attribute *a;
|
||||||
|
umode_t mode;
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
/* The attribute is invisible if there is no template string */
|
||||||
|
if (!template)
|
||||||
|
return ERR_PTR(-ENOENT);
|
||||||
|
|
||||||
|
mode = ops->is_visible(drvdata, type, attr, index);
|
||||||
|
if (!mode)
|
||||||
|
return ERR_PTR(-ENOENT);
|
||||||
|
|
||||||
|
if ((mode & S_IRUGO) && !ops->read)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
if ((mode & S_IWUGO) && !ops->write)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
if (type == hwmon_chip) {
|
||||||
|
name = (char *)template;
|
||||||
|
} else {
|
||||||
|
name = devm_kzalloc(dev, strlen(template) + 16, GFP_KERNEL);
|
||||||
|
if (!name)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
scnprintf(name, strlen(template) + 16, template,
|
||||||
|
index + hwmon_attr_base(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
hattr = devm_kzalloc(dev, sizeof(*hattr), GFP_KERNEL);
|
||||||
|
if (!hattr)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
hattr->type = type;
|
||||||
|
hattr->attr = attr;
|
||||||
|
hattr->index = index;
|
||||||
|
hattr->ops = ops;
|
||||||
|
|
||||||
|
dattr = &hattr->dev_attr;
|
||||||
|
dattr->show = hwmon_attr_show;
|
||||||
|
dattr->store = hwmon_attr_store;
|
||||||
|
|
||||||
|
a = &dattr->attr;
|
||||||
|
sysfs_attr_init(a);
|
||||||
|
a->name = name;
|
||||||
|
a->mode = mode;
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char * const hwmon_chip_attr_templates[] = {
|
||||||
|
[hwmon_chip_temp_reset_history] = "temp_reset_history",
|
||||||
|
[hwmon_chip_in_reset_history] = "in_reset_history",
|
||||||
|
[hwmon_chip_curr_reset_history] = "curr_reset_history",
|
||||||
|
[hwmon_chip_power_reset_history] = "power_reset_history",
|
||||||
|
[hwmon_chip_update_interval] = "update_interval",
|
||||||
|
[hwmon_chip_alarms] = "alarms",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_temp_attr_templates[] = {
|
||||||
|
[hwmon_temp_input] = "temp%d_input",
|
||||||
|
[hwmon_temp_type] = "temp%d_type",
|
||||||
|
[hwmon_temp_lcrit] = "temp%d_lcrit",
|
||||||
|
[hwmon_temp_lcrit_hyst] = "temp%d_lcrit_hyst",
|
||||||
|
[hwmon_temp_min] = "temp%d_min",
|
||||||
|
[hwmon_temp_min_hyst] = "temp%d_min_hyst",
|
||||||
|
[hwmon_temp_max] = "temp%d_max",
|
||||||
|
[hwmon_temp_max_hyst] = "temp%d_max_hyst",
|
||||||
|
[hwmon_temp_crit] = "temp%d_crit",
|
||||||
|
[hwmon_temp_crit_hyst] = "temp%d_crit_hyst",
|
||||||
|
[hwmon_temp_emergency] = "temp%d_emergency",
|
||||||
|
[hwmon_temp_emergency_hyst] = "temp%d_emergency_hyst",
|
||||||
|
[hwmon_temp_alarm] = "temp%d_alarm",
|
||||||
|
[hwmon_temp_lcrit_alarm] = "temp%d_lcrit_alarm",
|
||||||
|
[hwmon_temp_min_alarm] = "temp%d_min_alarm",
|
||||||
|
[hwmon_temp_max_alarm] = "temp%d_max_alarm",
|
||||||
|
[hwmon_temp_crit_alarm] = "temp%d_crit_alarm",
|
||||||
|
[hwmon_temp_emergency_alarm] = "temp%d_emergency_alarm",
|
||||||
|
[hwmon_temp_fault] = "temp%d_fault",
|
||||||
|
[hwmon_temp_offset] = "temp%d_offset",
|
||||||
|
[hwmon_temp_label] = "temp%d_label",
|
||||||
|
[hwmon_temp_lowest] = "temp%d_lowest",
|
||||||
|
[hwmon_temp_highest] = "temp%d_highest",
|
||||||
|
[hwmon_temp_reset_history] = "temp%d_reset_history",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_in_attr_templates[] = {
|
||||||
|
[hwmon_in_input] = "in%d_input",
|
||||||
|
[hwmon_in_min] = "in%d_min",
|
||||||
|
[hwmon_in_max] = "in%d_max",
|
||||||
|
[hwmon_in_lcrit] = "in%d_lcrit",
|
||||||
|
[hwmon_in_crit] = "in%d_crit",
|
||||||
|
[hwmon_in_average] = "in%d_average",
|
||||||
|
[hwmon_in_lowest] = "in%d_lowest",
|
||||||
|
[hwmon_in_highest] = "in%d_highest",
|
||||||
|
[hwmon_in_reset_history] = "in%d_reset_history",
|
||||||
|
[hwmon_in_label] = "in%d_label",
|
||||||
|
[hwmon_in_alarm] = "in%d_alarm",
|
||||||
|
[hwmon_in_min_alarm] = "in%d_min_alarm",
|
||||||
|
[hwmon_in_max_alarm] = "in%d_max_alarm",
|
||||||
|
[hwmon_in_lcrit_alarm] = "in%d_lcrit_alarm",
|
||||||
|
[hwmon_in_crit_alarm] = "in%d_crit_alarm",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_curr_attr_templates[] = {
|
||||||
|
[hwmon_curr_input] = "curr%d_input",
|
||||||
|
[hwmon_curr_min] = "curr%d_min",
|
||||||
|
[hwmon_curr_max] = "curr%d_max",
|
||||||
|
[hwmon_curr_lcrit] = "curr%d_lcrit",
|
||||||
|
[hwmon_curr_crit] = "curr%d_crit",
|
||||||
|
[hwmon_curr_average] = "curr%d_average",
|
||||||
|
[hwmon_curr_lowest] = "curr%d_lowest",
|
||||||
|
[hwmon_curr_highest] = "curr%d_highest",
|
||||||
|
[hwmon_curr_reset_history] = "curr%d_reset_history",
|
||||||
|
[hwmon_curr_label] = "curr%d_label",
|
||||||
|
[hwmon_curr_alarm] = "curr%d_alarm",
|
||||||
|
[hwmon_curr_min_alarm] = "curr%d_min_alarm",
|
||||||
|
[hwmon_curr_max_alarm] = "curr%d_max_alarm",
|
||||||
|
[hwmon_curr_lcrit_alarm] = "curr%d_lcrit_alarm",
|
||||||
|
[hwmon_curr_crit_alarm] = "curr%d_crit_alarm",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_power_attr_templates[] = {
|
||||||
|
[hwmon_power_average] = "power%d_average",
|
||||||
|
[hwmon_power_average_interval] = "power%d_average_interval",
|
||||||
|
[hwmon_power_average_interval_max] = "power%d_interval_max",
|
||||||
|
[hwmon_power_average_interval_min] = "power%d_interval_min",
|
||||||
|
[hwmon_power_average_highest] = "power%d_average_highest",
|
||||||
|
[hwmon_power_average_lowest] = "power%d_average_lowest",
|
||||||
|
[hwmon_power_average_max] = "power%d_average_max",
|
||||||
|
[hwmon_power_average_min] = "power%d_average_min",
|
||||||
|
[hwmon_power_input] = "power%d_input",
|
||||||
|
[hwmon_power_input_highest] = "power%d_input_highest",
|
||||||
|
[hwmon_power_input_lowest] = "power%d_input_lowest",
|
||||||
|
[hwmon_power_reset_history] = "power%d_reset_history",
|
||||||
|
[hwmon_power_accuracy] = "power%d_accuracy",
|
||||||
|
[hwmon_power_cap] = "power%d_cap",
|
||||||
|
[hwmon_power_cap_hyst] = "power%d_cap_hyst",
|
||||||
|
[hwmon_power_cap_max] = "power%d_cap_max",
|
||||||
|
[hwmon_power_cap_min] = "power%d_cap_min",
|
||||||
|
[hwmon_power_max] = "power%d_max",
|
||||||
|
[hwmon_power_crit] = "power%d_crit",
|
||||||
|
[hwmon_power_label] = "power%d_label",
|
||||||
|
[hwmon_power_alarm] = "power%d_alarm",
|
||||||
|
[hwmon_power_cap_alarm] = "power%d_cap_alarm",
|
||||||
|
[hwmon_power_max_alarm] = "power%d_max_alarm",
|
||||||
|
[hwmon_power_crit_alarm] = "power%d_crit_alarm",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_energy_attr_templates[] = {
|
||||||
|
[hwmon_energy_input] = "energy%d_input",
|
||||||
|
[hwmon_energy_label] = "energy%d_label",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_humidity_attr_templates[] = {
|
||||||
|
[hwmon_humidity_input] = "humidity%d_input",
|
||||||
|
[hwmon_humidity_label] = "humidity%d_label",
|
||||||
|
[hwmon_humidity_min] = "humidity%d_min",
|
||||||
|
[hwmon_humidity_min_hyst] = "humidity%d_min_hyst",
|
||||||
|
[hwmon_humidity_max] = "humidity%d_max",
|
||||||
|
[hwmon_humidity_max_hyst] = "humidity%d_max_hyst",
|
||||||
|
[hwmon_humidity_alarm] = "humidity%d_alarm",
|
||||||
|
[hwmon_humidity_fault] = "humidity%d_fault",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_fan_attr_templates[] = {
|
||||||
|
[hwmon_fan_input] = "fan%d_input",
|
||||||
|
[hwmon_fan_label] = "fan%d_label",
|
||||||
|
[hwmon_fan_min] = "fan%d_min",
|
||||||
|
[hwmon_fan_max] = "fan%d_max",
|
||||||
|
[hwmon_fan_div] = "fan%d_div",
|
||||||
|
[hwmon_fan_pulses] = "fan%d_pulses",
|
||||||
|
[hwmon_fan_target] = "fan%d_target",
|
||||||
|
[hwmon_fan_alarm] = "fan%d_alarm",
|
||||||
|
[hwmon_fan_min_alarm] = "fan%d_min_alarm",
|
||||||
|
[hwmon_fan_max_alarm] = "fan%d_max_alarm",
|
||||||
|
[hwmon_fan_fault] = "fan%d_fault",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const hwmon_pwm_attr_templates[] = {
|
||||||
|
[hwmon_pwm_input] = "pwm%d",
|
||||||
|
[hwmon_pwm_enable] = "pwm%d_enable",
|
||||||
|
[hwmon_pwm_mode] = "pwm%d_mode",
|
||||||
|
[hwmon_pwm_freq] = "pwm%d_freq",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const *__templates[] = {
|
||||||
|
[hwmon_chip] = hwmon_chip_attr_templates,
|
||||||
|
[hwmon_temp] = hwmon_temp_attr_templates,
|
||||||
|
[hwmon_in] = hwmon_in_attr_templates,
|
||||||
|
[hwmon_curr] = hwmon_curr_attr_templates,
|
||||||
|
[hwmon_power] = hwmon_power_attr_templates,
|
||||||
|
[hwmon_energy] = hwmon_energy_attr_templates,
|
||||||
|
[hwmon_humidity] = hwmon_humidity_attr_templates,
|
||||||
|
[hwmon_fan] = hwmon_fan_attr_templates,
|
||||||
|
[hwmon_pwm] = hwmon_pwm_attr_templates,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int __templates_size[] = {
|
||||||
|
[hwmon_chip] = ARRAY_SIZE(hwmon_chip_attr_templates),
|
||||||
|
[hwmon_temp] = ARRAY_SIZE(hwmon_temp_attr_templates),
|
||||||
|
[hwmon_in] = ARRAY_SIZE(hwmon_in_attr_templates),
|
||||||
|
[hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates),
|
||||||
|
[hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates),
|
||||||
|
[hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates),
|
||||||
|
[hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates),
|
||||||
|
[hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates),
|
||||||
|
[hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
||||||
|
{
|
||||||
|
int i, n;
|
||||||
|
|
||||||
|
for (i = n = 0; info->config[i]; i++)
|
||||||
|
n += hweight32(info->config[i]);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hwmon_genattrs(struct device *dev,
|
||||||
|
const void *drvdata,
|
||||||
|
struct attribute **attrs,
|
||||||
|
const struct hwmon_ops *ops,
|
||||||
|
const struct hwmon_channel_info *info)
|
||||||
|
{
|
||||||
|
const char * const *templates;
|
||||||
|
int template_size;
|
||||||
|
int i, aindex = 0;
|
||||||
|
|
||||||
|
if (info->type >= ARRAY_SIZE(__templates))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
templates = __templates[info->type];
|
||||||
|
template_size = __templates_size[info->type];
|
||||||
|
|
||||||
|
for (i = 0; info->config[i]; i++) {
|
||||||
|
u32 attr_mask = info->config[i];
|
||||||
|
u32 attr;
|
||||||
|
|
||||||
|
while (attr_mask) {
|
||||||
|
struct attribute *a;
|
||||||
|
|
||||||
|
attr = __ffs(attr_mask);
|
||||||
|
attr_mask &= ~BIT(attr);
|
||||||
|
if (attr >= template_size)
|
||||||
|
return -EINVAL;
|
||||||
|
a = hwmon_genattr(dev, drvdata, info->type, attr, i,
|
||||||
|
templates[attr], ops);
|
||||||
|
if (IS_ERR(a)) {
|
||||||
|
if (PTR_ERR(a) != -ENOENT)
|
||||||
|
return PTR_ERR(a);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attrs[aindex++] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct attribute **
|
||||||
|
__hwmon_create_attrs(struct device *dev, const void *drvdata,
|
||||||
|
const struct hwmon_chip_info *chip)
|
||||||
|
{
|
||||||
|
int ret, i, aindex = 0, nattrs = 0;
|
||||||
|
struct attribute **attrs;
|
||||||
|
|
||||||
|
for (i = 0; chip->info[i]; i++)
|
||||||
|
nattrs += hwmon_num_channel_attrs(chip->info[i]);
|
||||||
|
|
||||||
|
if (nattrs == 0)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
attrs = devm_kcalloc(dev, nattrs + 1, sizeof(*attrs), GFP_KERNEL);
|
||||||
|
if (!attrs)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
for (i = 0; chip->info[i]; i++) {
|
||||||
|
ret = hwmon_genattrs(dev, drvdata, &attrs[aindex], chip->ops,
|
||||||
|
chip->info[i]);
|
||||||
|
if (ret < 0)
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
aindex += ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct device *
|
||||||
|
__hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||||
|
const struct hwmon_chip_info *chip,
|
||||||
|
const struct attribute_group **groups)
|
||||||
|
{
|
||||||
|
struct hwmon_device *hwdev;
|
||||||
|
struct device *hdev;
|
||||||
|
int i, j, err, id;
|
||||||
|
|
||||||
|
/* Do not accept invalid characters in hwmon name attribute */
|
||||||
|
if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL);
|
||||||
|
if (id < 0)
|
||||||
|
return ERR_PTR(id);
|
||||||
|
|
||||||
|
hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL);
|
||||||
|
if (hwdev == NULL) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto ida_remove;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdev = &hwdev->dev;
|
||||||
|
|
||||||
|
if (chip && chip->ops->is_visible) {
|
||||||
|
struct attribute **attrs;
|
||||||
|
int ngroups = 2;
|
||||||
|
|
||||||
|
if (groups)
|
||||||
|
for (i = 0; groups[i]; i++)
|
||||||
|
ngroups++;
|
||||||
|
|
||||||
|
hwdev->groups = devm_kcalloc(dev, ngroups, sizeof(*groups),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!hwdev->groups)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
attrs = __hwmon_create_attrs(dev, drvdata, chip);
|
||||||
|
if (IS_ERR(attrs)) {
|
||||||
|
err = PTR_ERR(attrs);
|
||||||
|
goto free_hwmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
hwdev->group.attrs = attrs;
|
||||||
|
ngroups = 0;
|
||||||
|
hwdev->groups[ngroups++] = &hwdev->group;
|
||||||
|
|
||||||
|
if (groups) {
|
||||||
|
for (i = 0; groups[i]; i++)
|
||||||
|
hwdev->groups[ngroups++] = groups[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
hdev->groups = hwdev->groups;
|
||||||
|
} else {
|
||||||
|
hdev->groups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
hwdev->name = name;
|
||||||
|
hdev->class = &hwmon_class;
|
||||||
|
hdev->parent = dev;
|
||||||
|
hdev->of_node = dev ? dev->of_node : NULL;
|
||||||
|
hwdev->chip = chip;
|
||||||
|
dev_set_drvdata(hdev, drvdata);
|
||||||
|
dev_set_name(hdev, HWMON_ID_FORMAT, id);
|
||||||
|
err = device_register(hdev);
|
||||||
|
if (err)
|
||||||
|
goto free_hwmon;
|
||||||
|
|
||||||
|
if (chip && chip->ops->is_visible && chip->ops->read &&
|
||||||
|
chip->info[0]->type == hwmon_chip &&
|
||||||
|
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
|
||||||
|
const struct hwmon_channel_info **info = chip->info;
|
||||||
|
|
||||||
|
for (i = 1; info[i]; i++) {
|
||||||
|
if (info[i]->type != hwmon_temp)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (j = 0; info[i]->config[j]; j++) {
|
||||||
|
if (!chip->ops->is_visible(drvdata, hwmon_temp,
|
||||||
|
hwmon_temp_input, j))
|
||||||
|
continue;
|
||||||
|
if (info[i]->config[j] & HWMON_T_INPUT)
|
||||||
|
hwmon_thermal_add_sensor(dev, hwdev, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hdev;
|
||||||
|
|
||||||
|
free_hwmon:
|
||||||
|
kfree(hwdev);
|
||||||
|
ida_remove:
|
||||||
|
ida_simple_remove(&hwmon_ida, id);
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hwmon_device_register_with_groups - register w/ hwmon
|
* hwmon_device_register_with_groups - register w/ hwmon
|
||||||
* @dev: the parent device
|
* @dev: the parent device
|
||||||
|
@ -97,44 +615,36 @@ hwmon_device_register_with_groups(struct device *dev, const char *name,
|
||||||
void *drvdata,
|
void *drvdata,
|
||||||
const struct attribute_group **groups)
|
const struct attribute_group **groups)
|
||||||
{
|
{
|
||||||
struct hwmon_device *hwdev;
|
return __hwmon_device_register(dev, name, drvdata, NULL, groups);
|
||||||
int err, id;
|
|
||||||
|
|
||||||
/* Do not accept invalid characters in hwmon name attribute */
|
|
||||||
if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
|
|
||||||
return ERR_PTR(-EINVAL);
|
|
||||||
|
|
||||||
id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL);
|
|
||||||
if (id < 0)
|
|
||||||
return ERR_PTR(id);
|
|
||||||
|
|
||||||
hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL);
|
|
||||||
if (hwdev == NULL) {
|
|
||||||
err = -ENOMEM;
|
|
||||||
goto ida_remove;
|
|
||||||
}
|
|
||||||
|
|
||||||
hwdev->name = name;
|
|
||||||
hwdev->dev.class = &hwmon_class;
|
|
||||||
hwdev->dev.parent = dev;
|
|
||||||
hwdev->dev.groups = groups;
|
|
||||||
hwdev->dev.of_node = dev ? dev->of_node : NULL;
|
|
||||||
dev_set_drvdata(&hwdev->dev, drvdata);
|
|
||||||
dev_set_name(&hwdev->dev, HWMON_ID_FORMAT, id);
|
|
||||||
err = device_register(&hwdev->dev);
|
|
||||||
if (err)
|
|
||||||
goto free;
|
|
||||||
|
|
||||||
return &hwdev->dev;
|
|
||||||
|
|
||||||
free:
|
|
||||||
kfree(hwdev);
|
|
||||||
ida_remove:
|
|
||||||
ida_simple_remove(&hwmon_ida, id);
|
|
||||||
return ERR_PTR(err);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups);
|
EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hwmon_device_register_with_info - register w/ hwmon
|
||||||
|
* @dev: the parent device
|
||||||
|
* @name: hwmon name attribute
|
||||||
|
* @drvdata: driver data to attach to created device
|
||||||
|
* @info: Pointer to hwmon chip information
|
||||||
|
* @groups - pointer to list of driver specific attribute groups
|
||||||
|
*
|
||||||
|
* hwmon_device_unregister() must be called when the device is no
|
||||||
|
* longer needed.
|
||||||
|
*
|
||||||
|
* Returns the pointer to the new device.
|
||||||
|
*/
|
||||||
|
struct device *
|
||||||
|
hwmon_device_register_with_info(struct device *dev, const char *name,
|
||||||
|
void *drvdata,
|
||||||
|
const struct hwmon_chip_info *chip,
|
||||||
|
const struct attribute_group **groups)
|
||||||
|
{
|
||||||
|
if (chip && (!chip->ops || !chip->info))
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
return __hwmon_device_register(dev, name, drvdata, chip, groups);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hwmon_device_register - register w/ hwmon
|
* hwmon_device_register - register w/ hwmon
|
||||||
* @dev: the device to register
|
* @dev: the device to register
|
||||||
|
@ -213,6 +723,48 @@ error:
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups);
|
EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* devm_hwmon_device_register_with_info - register w/ hwmon
|
||||||
|
* @dev: the parent device
|
||||||
|
* @name: hwmon name attribute
|
||||||
|
* @drvdata: driver data to attach to created device
|
||||||
|
* @info: Pointer to hwmon chip information
|
||||||
|
* @groups - pointer to list of driver specific attribute groups
|
||||||
|
*
|
||||||
|
* Returns the pointer to the new device. The new device is automatically
|
||||||
|
* unregistered with the parent device.
|
||||||
|
*/
|
||||||
|
struct device *
|
||||||
|
devm_hwmon_device_register_with_info(struct device *dev, const char *name,
|
||||||
|
void *drvdata,
|
||||||
|
const struct hwmon_chip_info *chip,
|
||||||
|
const struct attribute_group **groups)
|
||||||
|
{
|
||||||
|
struct device **ptr, *hwdev;
|
||||||
|
|
||||||
|
if (!dev)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL);
|
||||||
|
if (!ptr)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
hwdev = hwmon_device_register_with_info(dev, name, drvdata, chip,
|
||||||
|
groups);
|
||||||
|
if (IS_ERR(hwdev))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
*ptr = hwdev;
|
||||||
|
devres_add(dev, ptr);
|
||||||
|
|
||||||
|
return hwdev;
|
||||||
|
|
||||||
|
error:
|
||||||
|
devres_free(ptr);
|
||||||
|
return hwdev;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_info);
|
||||||
|
|
||||||
static int devm_hwmon_match(struct device *dev, void *res, void *data)
|
static int devm_hwmon_match(struct device *dev, void *res, void *data)
|
||||||
{
|
{
|
||||||
struct device **hwdev = res;
|
struct device **hwdev = res;
|
||||||
|
|
|
@ -143,13 +143,11 @@ static void __init make_sensor_label(struct device_node *np,
|
||||||
if (cpuid >= 0)
|
if (cpuid >= 0)
|
||||||
/*
|
/*
|
||||||
* The digital thermal sensors are associated
|
* The digital thermal sensors are associated
|
||||||
* with a core. Let's print out the range of
|
* with a core.
|
||||||
* cpu ids corresponding to the hardware
|
|
||||||
* threads of the core.
|
|
||||||
*/
|
*/
|
||||||
n += snprintf(sdata->label + n,
|
n += snprintf(sdata->label + n,
|
||||||
sizeof(sdata->label) - n, " %d-%d",
|
sizeof(sdata->label) - n, " %d",
|
||||||
cpuid, cpuid + threads_per_core - 1);
|
cpuid);
|
||||||
else
|
else
|
||||||
n += snprintf(sdata->label + n,
|
n += snprintf(sdata->label + n,
|
||||||
sizeof(sdata->label) - n, " phy%d", id);
|
sizeof(sdata->label) - n, " phy%d", id);
|
||||||
|
|
|
@ -73,8 +73,11 @@ static int iio_hwmon_probe(struct platform_device *pdev)
|
||||||
name = dev->of_node->name;
|
name = dev->of_node->name;
|
||||||
|
|
||||||
channels = iio_channel_get_all(dev);
|
channels = iio_channel_get_all(dev);
|
||||||
if (IS_ERR(channels))
|
if (IS_ERR(channels)) {
|
||||||
|
if (PTR_ERR(channels) == -ENODEV)
|
||||||
|
return -EPROBE_DEFER;
|
||||||
return PTR_ERR(channels);
|
return PTR_ERR(channels);
|
||||||
|
}
|
||||||
|
|
||||||
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
||||||
if (st == NULL) {
|
if (st == NULL) {
|
||||||
|
|
|
@ -2011,10 +2011,10 @@ static struct attribute *it87_attributes_in[] = {
|
||||||
&sensor_dev_attr_in7_beep.dev_attr.attr, /* 39 */
|
&sensor_dev_attr_in7_beep.dev_attr.attr, /* 39 */
|
||||||
|
|
||||||
&sensor_dev_attr_in8_input.dev_attr.attr, /* 40 */
|
&sensor_dev_attr_in8_input.dev_attr.attr, /* 40 */
|
||||||
&sensor_dev_attr_in9_input.dev_attr.attr, /* 41 */
|
&sensor_dev_attr_in9_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in10_input.dev_attr.attr, /* 41 */
|
&sensor_dev_attr_in10_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in11_input.dev_attr.attr, /* 41 */
|
&sensor_dev_attr_in11_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in12_input.dev_attr.attr, /* 41 */
|
&sensor_dev_attr_in12_input.dev_attr.attr,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
@ -254,170 +253,148 @@ abort:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sysfs functions */
|
static int jc42_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
|
|
||||||
char *buf)
|
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct jc42_data *data = jc42_update_device(dev);
|
|
||||||
if (IS_ERR(data))
|
|
||||||
return PTR_ERR(data);
|
|
||||||
return sprintf(buf, "%d\n",
|
|
||||||
jc42_temp_from_reg(data->temp[attr->index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t show_temp_hyst(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct jc42_data *data = jc42_update_device(dev);
|
struct jc42_data *data = jc42_update_device(dev);
|
||||||
int temp, hyst;
|
int temp, hyst;
|
||||||
|
|
||||||
if (IS_ERR(data))
|
if (IS_ERR(data))
|
||||||
return PTR_ERR(data);
|
return PTR_ERR(data);
|
||||||
|
|
||||||
temp = jc42_temp_from_reg(data->temp[attr->index]);
|
switch (attr) {
|
||||||
hyst = jc42_hysteresis[(data->config & JC42_CFG_HYST_MASK)
|
case hwmon_temp_input:
|
||||||
>> JC42_CFG_HYST_SHIFT];
|
*val = jc42_temp_from_reg(data->temp[t_input]);
|
||||||
return sprintf(buf, "%d\n", temp - hyst);
|
return 0;
|
||||||
|
case hwmon_temp_min:
|
||||||
|
*val = jc42_temp_from_reg(data->temp[t_min]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
*val = jc42_temp_from_reg(data->temp[t_max]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit:
|
||||||
|
*val = jc42_temp_from_reg(data->temp[t_crit]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
temp = jc42_temp_from_reg(data->temp[t_max]);
|
||||||
|
hyst = jc42_hysteresis[(data->config & JC42_CFG_HYST_MASK)
|
||||||
|
>> JC42_CFG_HYST_SHIFT];
|
||||||
|
*val = temp - hyst;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit_hyst:
|
||||||
|
temp = jc42_temp_from_reg(data->temp[t_crit]);
|
||||||
|
hyst = jc42_hysteresis[(data->config & JC42_CFG_HYST_MASK)
|
||||||
|
>> JC42_CFG_HYST_SHIFT];
|
||||||
|
*val = temp - hyst;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_min_alarm:
|
||||||
|
*val = (data->temp[t_input] >> JC42_ALARM_MIN_BIT) & 1;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max_alarm:
|
||||||
|
*val = (data->temp[t_input] >> JC42_ALARM_MAX_BIT) & 1;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit_alarm:
|
||||||
|
*val = (data->temp[t_input] >> JC42_ALARM_CRIT_BIT) & 1;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_temp(struct device *dev, struct device_attribute *devattr,
|
static int jc42_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
const char *buf, size_t count)
|
u32 attr, int channel, long val)
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct jc42_data *data = dev_get_drvdata(dev);
|
|
||||||
int err, ret = count;
|
|
||||||
int nr = attr->index;
|
|
||||||
long val;
|
|
||||||
|
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
data->temp[nr] = jc42_temp_to_reg(val, data->extended);
|
|
||||||
err = i2c_smbus_write_word_swapped(data->client, temp_regs[nr],
|
|
||||||
data->temp[nr]);
|
|
||||||
if (err < 0)
|
|
||||||
ret = err;
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* JC42.4 compliant chips only support four hysteresis values.
|
|
||||||
* Pick best choice and go from there.
|
|
||||||
*/
|
|
||||||
static ssize_t set_temp_crit_hyst(struct device *dev,
|
|
||||||
struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct jc42_data *data = dev_get_drvdata(dev);
|
struct jc42_data *data = dev_get_drvdata(dev);
|
||||||
long val;
|
struct i2c_client *client = data->client;
|
||||||
int diff, hyst;
|
int diff, hyst;
|
||||||
int err;
|
int ret;
|
||||||
int ret = count;
|
|
||||||
|
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
mutex_lock(&data->update_lock);
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
val = clamp_val(val, (data->extended ? JC42_TEMP_MIN_EXTENDED :
|
switch (attr) {
|
||||||
JC42_TEMP_MIN) - 6000, JC42_TEMP_MAX);
|
case hwmon_temp_min:
|
||||||
diff = jc42_temp_from_reg(data->temp[t_crit]) - val;
|
data->temp[t_min] = jc42_temp_to_reg(val, data->extended);
|
||||||
|
ret = i2c_smbus_write_word_swapped(client, temp_regs[t_min],
|
||||||
hyst = 0;
|
data->temp[t_min]);
|
||||||
if (diff > 0) {
|
break;
|
||||||
if (diff < 2250)
|
case hwmon_temp_max:
|
||||||
hyst = 1; /* 1.5 degrees C */
|
data->temp[t_max] = jc42_temp_to_reg(val, data->extended);
|
||||||
else if (diff < 4500)
|
ret = i2c_smbus_write_word_swapped(client, temp_regs[t_max],
|
||||||
hyst = 2; /* 3.0 degrees C */
|
data->temp[t_max]);
|
||||||
else
|
break;
|
||||||
hyst = 3; /* 6.0 degrees C */
|
case hwmon_temp_crit:
|
||||||
|
data->temp[t_crit] = jc42_temp_to_reg(val, data->extended);
|
||||||
|
ret = i2c_smbus_write_word_swapped(client, temp_regs[t_crit],
|
||||||
|
data->temp[t_crit]);
|
||||||
|
break;
|
||||||
|
case hwmon_temp_crit_hyst:
|
||||||
|
/*
|
||||||
|
* JC42.4 compliant chips only support four hysteresis values.
|
||||||
|
* Pick best choice and go from there.
|
||||||
|
*/
|
||||||
|
val = clamp_val(val, (data->extended ? JC42_TEMP_MIN_EXTENDED
|
||||||
|
: JC42_TEMP_MIN) - 6000,
|
||||||
|
JC42_TEMP_MAX);
|
||||||
|
diff = jc42_temp_from_reg(data->temp[t_crit]) - val;
|
||||||
|
hyst = 0;
|
||||||
|
if (diff > 0) {
|
||||||
|
if (diff < 2250)
|
||||||
|
hyst = 1; /* 1.5 degrees C */
|
||||||
|
else if (diff < 4500)
|
||||||
|
hyst = 2; /* 3.0 degrees C */
|
||||||
|
else
|
||||||
|
hyst = 3; /* 6.0 degrees C */
|
||||||
|
}
|
||||||
|
data->config = (data->config & ~JC42_CFG_HYST_MASK) |
|
||||||
|
(hyst << JC42_CFG_HYST_SHIFT);
|
||||||
|
ret = i2c_smbus_write_word_swapped(data->client,
|
||||||
|
JC42_REG_CONFIG,
|
||||||
|
data->config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
data->config = (data->config & ~JC42_CFG_HYST_MASK)
|
|
||||||
| (hyst << JC42_CFG_HYST_SHIFT);
|
|
||||||
err = i2c_smbus_write_word_swapped(data->client, JC42_REG_CONFIG,
|
|
||||||
data->config);
|
|
||||||
if (err < 0)
|
|
||||||
ret = err;
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_alarm(struct device *dev,
|
static umode_t jc42_is_visible(const void *_data, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *attr, char *buf)
|
u32 attr, int channel)
|
||||||
{
|
{
|
||||||
u16 bit = to_sensor_dev_attr(attr)->index;
|
const struct jc42_data *data = _data;
|
||||||
struct jc42_data *data = jc42_update_device(dev);
|
|
||||||
u16 val;
|
|
||||||
|
|
||||||
if (IS_ERR(data))
|
|
||||||
return PTR_ERR(data);
|
|
||||||
|
|
||||||
val = data->temp[t_input];
|
|
||||||
if (bit != JC42_ALARM_CRIT_BIT && (data->config & JC42_CFG_CRIT_ONLY))
|
|
||||||
val = 0;
|
|
||||||
return sprintf(buf, "%u\n", (val >> bit) & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, t_input);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, set_temp, t_crit);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp, set_temp, t_min);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, set_temp, t_max);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_hyst,
|
|
||||||
set_temp_crit_hyst, t_crit);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_temp_hyst, NULL, t_max);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL,
|
|
||||||
JC42_ALARM_CRIT_BIT);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL,
|
|
||||||
JC42_ALARM_MIN_BIT);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL,
|
|
||||||
JC42_ALARM_MAX_BIT);
|
|
||||||
|
|
||||||
static struct attribute *jc42_attributes[] = {
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_min.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static umode_t jc42_attribute_mode(struct kobject *kobj,
|
|
||||||
struct attribute *attr, int index)
|
|
||||||
{
|
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
|
||||||
struct jc42_data *data = dev_get_drvdata(dev);
|
|
||||||
unsigned int config = data->config;
|
unsigned int config = data->config;
|
||||||
bool readonly;
|
umode_t mode = S_IRUGO;
|
||||||
|
|
||||||
if (attr == &sensor_dev_attr_temp1_crit.dev_attr.attr)
|
switch (attr) {
|
||||||
readonly = config & JC42_CFG_TCRIT_LOCK;
|
case hwmon_temp_min:
|
||||||
else if (attr == &sensor_dev_attr_temp1_min.dev_attr.attr ||
|
case hwmon_temp_max:
|
||||||
attr == &sensor_dev_attr_temp1_max.dev_attr.attr)
|
if (!(config & JC42_CFG_EVENT_LOCK))
|
||||||
readonly = config & JC42_CFG_EVENT_LOCK;
|
mode |= S_IWUSR;
|
||||||
else if (attr == &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr)
|
break;
|
||||||
readonly = config & (JC42_CFG_EVENT_LOCK | JC42_CFG_TCRIT_LOCK);
|
case hwmon_temp_crit:
|
||||||
else
|
if (!(config & JC42_CFG_TCRIT_LOCK))
|
||||||
readonly = true;
|
mode |= S_IWUSR;
|
||||||
|
break;
|
||||||
return S_IRUGO | (readonly ? 0 : S_IWUSR);
|
case hwmon_temp_crit_hyst:
|
||||||
|
if (!(config & (JC42_CFG_EVENT_LOCK | JC42_CFG_TCRIT_LOCK)))
|
||||||
|
mode |= S_IWUSR;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_input:
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
case hwmon_temp_min_alarm:
|
||||||
|
case hwmon_temp_max_alarm:
|
||||||
|
case hwmon_temp_crit_alarm:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct attribute_group jc42_group = {
|
|
||||||
.attrs = jc42_attributes,
|
|
||||||
.is_visible = jc42_attribute_mode,
|
|
||||||
};
|
|
||||||
__ATTRIBUTE_GROUPS(jc42);
|
|
||||||
|
|
||||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||||
static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
|
static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||||
{
|
{
|
||||||
|
@ -450,6 +427,34 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const u32 jc42_temp_config[] = {
|
||||||
|
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT |
|
||||||
|
HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST |
|
||||||
|
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info jc42_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = jc42_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *jc42_info[] = {
|
||||||
|
&jc42_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops jc42_hwmon_ops = {
|
||||||
|
.is_visible = jc42_is_visible,
|
||||||
|
.read = jc42_read,
|
||||||
|
.write = jc42_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info jc42_chip_info = {
|
||||||
|
.ops = &jc42_hwmon_ops,
|
||||||
|
.info = jc42_info,
|
||||||
|
};
|
||||||
|
|
||||||
static int jc42_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
static int jc42_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
|
@ -482,9 +487,9 @@ static int jc42_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
}
|
}
|
||||||
data->config = config;
|
data->config = config;
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
data,
|
data, &jc42_chip_info,
|
||||||
jc42_groups);
|
NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/thermal.h>
|
|
||||||
#include "lm75.h"
|
#include "lm75.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,56 +87,75 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
||||||
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
|
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sysfs attributes for hwmon */
|
static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
static int lm75_read_temp(void *dev, int *temp)
|
|
||||||
{
|
{
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
unsigned int _temp;
|
unsigned int regval;
|
||||||
int err;
|
int err, reg;
|
||||||
|
|
||||||
err = regmap_read(data->regmap, LM75_REG_TEMP, &_temp);
|
switch (type) {
|
||||||
if (err < 0)
|
case hwmon_chip:
|
||||||
return err;
|
switch (attr) {
|
||||||
|
case hwmon_chip_update_interval:
|
||||||
*temp = lm75_reg_to_mc(_temp, data->resolution);
|
*val = data->sample_time;
|
||||||
|
break;;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hwmon_temp:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_temp_input:
|
||||||
|
reg = LM75_REG_TEMP;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
reg = LM75_REG_MAX;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
reg = LM75_REG_HYST;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
err = regmap_read(data->regmap, reg, ®val);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
*val = lm75_reg_to_mc(regval, data->resolution);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
static int lm75_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
char *buf)
|
u32 attr, int channel, long temp)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
unsigned int temp = 0;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = regmap_read(data->regmap, attr->index, &temp);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
return sprintf(buf, "%ld\n", lm75_reg_to_mc(temp, data->resolution));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
|
||||||
long temp;
|
|
||||||
int error;
|
|
||||||
u8 resolution;
|
u8 resolution;
|
||||||
|
int reg;
|
||||||
|
|
||||||
error = kstrtol(buf, 10, &temp);
|
if (type != hwmon_temp)
|
||||||
if (error)
|
return -EINVAL;
|
||||||
return error;
|
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_temp_max:
|
||||||
|
reg = LM75_REG_MAX;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
reg = LM75_REG_HYST;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolution of limit registers is assumed to be the same as the
|
* Resolution of limit registers is assumed to be the same as the
|
||||||
* temperature input register resolution unless given explicitly.
|
* temperature input register resolution unless given explicitly.
|
||||||
*/
|
*/
|
||||||
if (attr->index && data->resolution_limits)
|
if (data->resolution_limits)
|
||||||
resolution = data->resolution_limits;
|
resolution = data->resolution_limits;
|
||||||
else
|
else
|
||||||
resolution = data->resolution;
|
resolution = data->resolution;
|
||||||
|
@ -145,46 +163,78 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||||
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||||
temp = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
temp = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
||||||
1000) << (16 - resolution);
|
1000) << (16 - resolution);
|
||||||
error = regmap_write(data->regmap, attr->index, temp);
|
|
||||||
if (error < 0)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
return count;
|
return regmap_write(data->regmap, reg, temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_update_interval(struct device *dev,
|
static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *da, char *buf)
|
u32 attr, int channel)
|
||||||
{
|
{
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
switch (type) {
|
||||||
|
case hwmon_chip:
|
||||||
return sprintf(buf, "%u\n", data->sample_time);
|
switch (attr) {
|
||||||
|
case hwmon_chip_update_interval:
|
||||||
|
return S_IRUGO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hwmon_temp:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_temp_input:
|
||||||
|
return S_IRUGO;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
return S_IRUGO | S_IWUSR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
|
||||||
show_temp, set_temp, LM75_REG_MAX);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
|
||||||
show_temp, set_temp, LM75_REG_HYST);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, LM75_REG_TEMP);
|
|
||||||
static DEVICE_ATTR(update_interval, S_IRUGO, show_update_interval, NULL);
|
|
||||||
|
|
||||||
static struct attribute *lm75_attrs[] = {
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
|
||||||
&dev_attr_update_interval.attr,
|
|
||||||
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
ATTRIBUTE_GROUPS(lm75);
|
|
||||||
|
|
||||||
static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = {
|
|
||||||
.get_temp = lm75_read_temp,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
/* device probe and removal */
|
/* device probe and removal */
|
||||||
|
|
||||||
|
/* chip configuration */
|
||||||
|
|
||||||
|
static const u32 lm75_chip_config[] = {
|
||||||
|
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm75_chip = {
|
||||||
|
.type = hwmon_chip,
|
||||||
|
.config = lm75_chip_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 lm75_temp_config[] = {
|
||||||
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm75_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = lm75_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *lm75_info[] = {
|
||||||
|
&lm75_chip,
|
||||||
|
&lm75_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops lm75_hwmon_ops = {
|
||||||
|
.is_visible = lm75_is_visible,
|
||||||
|
.read = lm75_read,
|
||||||
|
.write = lm75_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info lm75_chip_info = {
|
||||||
|
.ops = &lm75_hwmon_ops,
|
||||||
|
.info = lm75_info,
|
||||||
|
};
|
||||||
|
|
||||||
static bool lm75_is_writeable_reg(struct device *dev, unsigned int reg)
|
static bool lm75_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||||
{
|
{
|
||||||
return reg != LM75_REG_TEMP;
|
return reg != LM75_REG_TEMP;
|
||||||
|
@ -337,15 +387,12 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
|
|
||||||
dev_dbg(dev, "Config %02x\n", new);
|
dev_dbg(dev, "Config %02x\n", new);
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
data, lm75_groups);
|
data, &lm75_chip_info,
|
||||||
|
NULL);
|
||||||
if (IS_ERR(hwmon_dev))
|
if (IS_ERR(hwmon_dev))
|
||||||
return PTR_ERR(hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
|
|
||||||
devm_thermal_zone_of_sensor_register(hwmon_dev, 0,
|
|
||||||
hwmon_dev,
|
|
||||||
&lm75_of_thermal_ops);
|
|
||||||
|
|
||||||
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
|
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,22 +15,17 @@
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* 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/module.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/jiffies.h>
|
|
||||||
#include <linux/i2c.h>
|
|
||||||
#include <linux/hwmon.h>
|
|
||||||
#include <linux/hwmon-sysfs.h>
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/jiffies.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/module.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/sysfs.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
#define DEVNAME "lm95241"
|
#define DEVNAME "lm95241"
|
||||||
|
|
||||||
|
@ -54,26 +49,25 @@ static const unsigned short normal_i2c[] = {
|
||||||
#define LM95241_REG_RW_REMOTE_MODEL 0x30
|
#define LM95241_REG_RW_REMOTE_MODEL 0x30
|
||||||
|
|
||||||
/* LM95241 specific bitfields */
|
/* LM95241 specific bitfields */
|
||||||
#define CFG_STOP 0x40
|
#define CFG_STOP BIT(6)
|
||||||
#define CFG_CR0076 0x00
|
#define CFG_CR0076 0x00
|
||||||
#define CFG_CR0182 0x10
|
#define CFG_CR0182 BIT(4)
|
||||||
#define CFG_CR1000 0x20
|
#define CFG_CR1000 BIT(5)
|
||||||
#define CFG_CR2700 0x30
|
#define CFG_CR2700 (BIT(4) | BIT(5))
|
||||||
#define R1MS_SHIFT 0
|
#define CFG_CRMASK (BIT(4) | BIT(5))
|
||||||
#define R2MS_SHIFT 2
|
#define R1MS_MASK BIT(0)
|
||||||
#define R1MS_MASK (0x01 << (R1MS_SHIFT))
|
#define R2MS_MASK BIT(2)
|
||||||
#define R2MS_MASK (0x01 << (R2MS_SHIFT))
|
#define R1DF_MASK BIT(1)
|
||||||
#define R1DF_SHIFT 1
|
#define R2DF_MASK BIT(2)
|
||||||
#define R2DF_SHIFT 2
|
#define R1FE_MASK BIT(0)
|
||||||
#define R1DF_MASK (0x01 << (R1DF_SHIFT))
|
#define R2FE_MASK BIT(2)
|
||||||
#define R2DF_MASK (0x01 << (R2DF_SHIFT))
|
#define R1DM BIT(0)
|
||||||
#define R1FE_MASK 0x01
|
#define R2DM BIT(1)
|
||||||
#define R2FE_MASK 0x05
|
#define TT1_SHIFT 0
|
||||||
#define TT1_SHIFT 0
|
#define TT2_SHIFT 4
|
||||||
#define TT2_SHIFT 4
|
#define TT_OFF 0
|
||||||
#define TT_OFF 0
|
#define TT_ON 1
|
||||||
#define TT_ON 1
|
#define TT_MASK 7
|
||||||
#define TT_MASK 7
|
|
||||||
#define NATSEMI_MAN_ID 0x01
|
#define NATSEMI_MAN_ID 0x01
|
||||||
#define LM95231_CHIP_ID 0xA1
|
#define LM95231_CHIP_ID 0xA1
|
||||||
#define LM95241_CHIP_ID 0xA4
|
#define LM95241_CHIP_ID 0xA4
|
||||||
|
@ -91,11 +85,12 @@ static const u8 lm95241_reg_address[] = {
|
||||||
struct lm95241_data {
|
struct lm95241_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
unsigned long last_updated, interval; /* in jiffies */
|
unsigned long last_updated; /* in jiffies */
|
||||||
|
unsigned long interval; /* in milli-seconds */
|
||||||
char valid; /* zero until following fields are valid */
|
char valid; /* zero until following fields are valid */
|
||||||
/* registers values */
|
/* registers values */
|
||||||
u8 temp[ARRAY_SIZE(lm95241_reg_address)];
|
u8 temp[ARRAY_SIZE(lm95241_reg_address)];
|
||||||
u8 config, model, trutherm;
|
u8 status, config, model, trutherm;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Conversions */
|
/* Conversions */
|
||||||
|
@ -118,7 +113,8 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
if (time_after(jiffies, data->last_updated + data->interval) ||
|
if (time_after(jiffies, data->last_updated
|
||||||
|
+ msecs_to_jiffies(data->interval)) ||
|
||||||
!data->valid) {
|
!data->valid) {
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -127,6 +123,9 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
|
||||||
data->temp[i]
|
data->temp[i]
|
||||||
= i2c_smbus_read_byte_data(client,
|
= i2c_smbus_read_byte_data(client,
|
||||||
lm95241_reg_address[i]);
|
lm95241_reg_address[i]);
|
||||||
|
|
||||||
|
data->status = i2c_smbus_read_byte_data(client,
|
||||||
|
LM95241_REG_R_STATUS);
|
||||||
data->last_updated = jiffies;
|
data->last_updated = jiffies;
|
||||||
data->valid = 1;
|
data->valid = 1;
|
||||||
}
|
}
|
||||||
|
@ -136,197 +135,241 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sysfs stuff */
|
static int lm95241_read_chip(struct device *dev, u32 attr, int channel,
|
||||||
static ssize_t show_input(struct device *dev, struct device_attribute *attr,
|
long *val)
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct lm95241_data *data = lm95241_update_device(dev);
|
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%d\n",
|
|
||||||
index == 0 || (data->config & (1 << (index / 2))) ?
|
|
||||||
temp_from_reg_signed(data->temp[index], data->temp[index + 1]) :
|
|
||||||
temp_from_reg_unsigned(data->temp[index],
|
|
||||||
data->temp[index + 1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t show_type(struct device *dev, struct device_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
{
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
struct lm95241_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1,
|
switch (attr) {
|
||||||
data->model & to_sensor_dev_attr(attr)->index ? "1\n" : "2\n");
|
case hwmon_chip_update_interval:
|
||||||
|
*val = data->interval;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_type(struct device *dev, struct device_attribute *attr,
|
static int lm95241_read_temp(struct device *dev, u32 attr, int channel,
|
||||||
const char *buf, size_t count)
|
long *val)
|
||||||
|
{
|
||||||
|
struct lm95241_data *data = lm95241_update_device(dev);
|
||||||
|
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_temp_input:
|
||||||
|
if (!channel || (data->config & BIT(channel - 1)))
|
||||||
|
*val = temp_from_reg_signed(data->temp[channel * 2],
|
||||||
|
data->temp[channel * 2 + 1]);
|
||||||
|
else
|
||||||
|
*val = temp_from_reg_unsigned(data->temp[channel * 2],
|
||||||
|
data->temp[channel * 2 + 1]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_min:
|
||||||
|
if (channel == 1)
|
||||||
|
*val = (data->config & R1DF_MASK) ? -128000 : 0;
|
||||||
|
else
|
||||||
|
*val = (data->config & R2DF_MASK) ? -128000 : 0;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
if (channel == 1)
|
||||||
|
*val = (data->config & R1DF_MASK) ? 127875 : 255875;
|
||||||
|
else
|
||||||
|
*val = (data->config & R2DF_MASK) ? 127875 : 255875;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_type:
|
||||||
|
if (channel == 1)
|
||||||
|
*val = (data->model & R1MS_MASK) ? 1 : 2;
|
||||||
|
else
|
||||||
|
*val = (data->model & R2MS_MASK) ? 1 : 2;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_fault:
|
||||||
|
if (channel == 1)
|
||||||
|
*val = !!(data->status & R1DM);
|
||||||
|
else
|
||||||
|
*val = !!(data->status & R2DM);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm95241_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_chip:
|
||||||
|
return lm95241_read_chip(dev, attr, channel, val);
|
||||||
|
case hwmon_temp:
|
||||||
|
return lm95241_read_temp(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm95241_write_chip(struct device *dev, u32 attr, int channel,
|
||||||
|
long val)
|
||||||
|
{
|
||||||
|
struct lm95241_data *data = dev_get_drvdata(dev);
|
||||||
|
int convrate;
|
||||||
|
u8 config;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_chip_update_interval:
|
||||||
|
config = data->config & ~CFG_CRMASK;
|
||||||
|
if (val < 130) {
|
||||||
|
convrate = 76;
|
||||||
|
config |= CFG_CR0076;
|
||||||
|
} else if (val < 590) {
|
||||||
|
convrate = 182;
|
||||||
|
config |= CFG_CR0182;
|
||||||
|
} else if (val < 1850) {
|
||||||
|
convrate = 1000;
|
||||||
|
config |= CFG_CR1000;
|
||||||
|
} else {
|
||||||
|
convrate = 2700;
|
||||||
|
config |= CFG_CR2700;
|
||||||
|
}
|
||||||
|
data->interval = convrate;
|
||||||
|
data->config = config;
|
||||||
|
ret = i2c_smbus_write_byte_data(data->client,
|
||||||
|
LM95241_REG_RW_CONFIG, config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm95241_write_temp(struct device *dev, u32 attr, int channel,
|
||||||
|
long val)
|
||||||
{
|
{
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
struct lm95241_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
unsigned long val;
|
int ret;
|
||||||
int shift;
|
|
||||||
u8 mask = to_sensor_dev_attr(attr)->index;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
if (val != 1 && val != 2)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
shift = mask == R1MS_MASK ? TT1_SHIFT : TT2_SHIFT;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
data->trutherm &= ~(TT_MASK << shift);
|
switch (attr) {
|
||||||
if (val == 1) {
|
case hwmon_temp_min:
|
||||||
data->model |= mask;
|
if (channel == 1) {
|
||||||
data->trutherm |= (TT_ON << shift);
|
if (val < 0)
|
||||||
} else {
|
data->config |= R1DF_MASK;
|
||||||
data->model &= ~mask;
|
else
|
||||||
data->trutherm |= (TT_OFF << shift);
|
data->config &= ~R1DF_MASK;
|
||||||
|
} else {
|
||||||
|
if (val < 0)
|
||||||
|
data->config |= R2DF_MASK;
|
||||||
|
else
|
||||||
|
data->config &= ~R2DF_MASK;
|
||||||
|
}
|
||||||
|
data->valid = 0;
|
||||||
|
ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG,
|
||||||
|
data->config);
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
if (channel == 1) {
|
||||||
|
if (val <= 127875)
|
||||||
|
data->config |= R1DF_MASK;
|
||||||
|
else
|
||||||
|
data->config &= ~R1DF_MASK;
|
||||||
|
} else {
|
||||||
|
if (val <= 127875)
|
||||||
|
data->config |= R2DF_MASK;
|
||||||
|
else
|
||||||
|
data->config &= ~R2DF_MASK;
|
||||||
|
}
|
||||||
|
data->valid = 0;
|
||||||
|
ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG,
|
||||||
|
data->config);
|
||||||
|
break;
|
||||||
|
case hwmon_temp_type:
|
||||||
|
if (val != 1 && val != 2) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (channel == 1) {
|
||||||
|
data->trutherm &= ~(TT_MASK << TT1_SHIFT);
|
||||||
|
if (val == 1) {
|
||||||
|
data->model |= R1MS_MASK;
|
||||||
|
data->trutherm |= (TT_ON << TT1_SHIFT);
|
||||||
|
} else {
|
||||||
|
data->model &= ~R1MS_MASK;
|
||||||
|
data->trutherm |= (TT_OFF << TT1_SHIFT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data->trutherm &= ~(TT_MASK << TT2_SHIFT);
|
||||||
|
if (val == 1) {
|
||||||
|
data->model |= R2MS_MASK;
|
||||||
|
data->trutherm |= (TT_ON << TT2_SHIFT);
|
||||||
|
} else {
|
||||||
|
data->model &= ~R2MS_MASK;
|
||||||
|
data->trutherm |= (TT_OFF << TT2_SHIFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = i2c_smbus_write_byte_data(client,
|
||||||
|
LM95241_REG_RW_REMOTE_MODEL,
|
||||||
|
data->model);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM,
|
||||||
|
data->trutherm);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
data->valid = 0;
|
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, LM95241_REG_RW_REMOTE_MODEL,
|
|
||||||
data->model);
|
|
||||||
i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM,
|
|
||||||
data->trutherm);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return count;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_min(struct device *dev, struct device_attribute *attr,
|
static int lm95241_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
char *buf)
|
u32 attr, int channel, long val)
|
||||||
{
|
{
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
switch (type) {
|
||||||
|
case hwmon_chip:
|
||||||
return snprintf(buf, PAGE_SIZE - 1,
|
return lm95241_write_chip(dev, attr, channel, val);
|
||||||
data->config & to_sensor_dev_attr(attr)->index ?
|
case hwmon_temp:
|
||||||
"-127000\n" : "0\n");
|
return lm95241_write_temp(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_min(struct device *dev, struct device_attribute *attr,
|
static umode_t lm95241_is_visible(const void *data,
|
||||||
const char *buf, size_t count)
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel)
|
||||||
{
|
{
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
switch (type) {
|
||||||
long val;
|
case hwmon_chip:
|
||||||
|
switch (attr) {
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
case hwmon_chip_update_interval:
|
||||||
return -EINVAL;
|
return S_IRUGO | S_IWUSR;
|
||||||
if (val < -128000)
|
}
|
||||||
return -EINVAL;
|
break;
|
||||||
|
case hwmon_temp:
|
||||||
mutex_lock(&data->update_lock);
|
switch (attr) {
|
||||||
|
case hwmon_temp_input:
|
||||||
if (val < 0)
|
return S_IRUGO;
|
||||||
data->config |= to_sensor_dev_attr(attr)->index;
|
case hwmon_temp_fault:
|
||||||
else
|
return S_IRUGO;
|
||||||
data->config &= ~to_sensor_dev_attr(attr)->index;
|
case hwmon_temp_min:
|
||||||
data->valid = 0;
|
case hwmon_temp_max:
|
||||||
|
case hwmon_temp_type:
|
||||||
i2c_smbus_write_byte_data(data->client, LM95241_REG_RW_CONFIG,
|
return S_IRUGO | S_IWUSR;
|
||||||
data->config);
|
}
|
||||||
|
break;
|
||||||
mutex_unlock(&data->update_lock);
|
default:
|
||||||
|
break;
|
||||||
return count;
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_max(struct device *dev, struct device_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1,
|
|
||||||
data->config & to_sensor_dev_attr(attr)->index ?
|
|
||||||
"127000\n" : "255000\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_max(struct device *dev, struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
|
||||||
long val;
|
|
||||||
|
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
if (val >= 256000)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
if (val <= 127000)
|
|
||||||
data->config |= to_sensor_dev_attr(attr)->index;
|
|
||||||
else
|
|
||||||
data->config &= ~to_sensor_dev_attr(attr)->index;
|
|
||||||
data->valid = 0;
|
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(data->client, LM95241_REG_RW_CONFIG,
|
|
||||||
data->config);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t show_interval(struct device *dev, struct device_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct lm95241_data *data = lm95241_update_device(dev);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%lu\n", 1000 * data->interval
|
|
||||||
/ HZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_interval(struct device *dev, struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct lm95241_data *data = dev_get_drvdata(dev);
|
|
||||||
unsigned long val;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
data->interval = val * HZ / 1000;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type, set_type,
|
|
||||||
R1MS_MASK);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_type, S_IWUSR | S_IRUGO, show_type, set_type,
|
|
||||||
R2MS_MASK);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min,
|
|
||||||
R1DF_MASK);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min,
|
|
||||||
R2DF_MASK);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max,
|
|
||||||
R1DF_MASK);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max,
|
|
||||||
R2DF_MASK);
|
|
||||||
static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_interval,
|
|
||||||
set_interval);
|
|
||||||
|
|
||||||
static struct attribute *lm95241_attrs[] = {
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_type.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_type.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_min.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_min.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_max.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_max.dev_attr.attr,
|
|
||||||
&dev_attr_update_interval.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
ATTRIBUTE_GROUPS(lm95241);
|
|
||||||
|
|
||||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||||
static int lm95241_detect(struct i2c_client *new_client,
|
static int lm95241_detect(struct i2c_client *new_client,
|
||||||
struct i2c_board_info *info)
|
struct i2c_board_info *info)
|
||||||
|
@ -362,8 +405,8 @@ static int lm95241_detect(struct i2c_client *new_client,
|
||||||
static void lm95241_init_client(struct i2c_client *client,
|
static void lm95241_init_client(struct i2c_client *client,
|
||||||
struct lm95241_data *data)
|
struct lm95241_data *data)
|
||||||
{
|
{
|
||||||
data->interval = HZ; /* 1 sec default */
|
data->interval = 1000;
|
||||||
data->config = CFG_CR0076;
|
data->config = CFG_CR1000;
|
||||||
data->trutherm = (TT_OFF << TT1_SHIFT) | (TT_OFF << TT2_SHIFT);
|
data->trutherm = (TT_OFF << TT1_SHIFT) | (TT_OFF << TT2_SHIFT);
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, data->config);
|
i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, data->config);
|
||||||
|
@ -375,6 +418,47 @@ static void lm95241_init_client(struct i2c_client *client,
|
||||||
data->model);
|
data->model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const u32 lm95241_chip_config[] = {
|
||||||
|
HWMON_C_UPDATE_INTERVAL,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm95241_chip = {
|
||||||
|
.type = hwmon_chip,
|
||||||
|
.config = lm95241_chip_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 lm95241_temp_config[] = {
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_TYPE |
|
||||||
|
HWMON_T_FAULT,
|
||||||
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_TYPE |
|
||||||
|
HWMON_T_FAULT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm95241_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = lm95241_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *lm95241_info[] = {
|
||||||
|
&lm95241_chip,
|
||||||
|
&lm95241_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops lm95241_hwmon_ops = {
|
||||||
|
.is_visible = lm95241_is_visible,
|
||||||
|
.read = lm95241_read,
|
||||||
|
.write = lm95241_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info lm95241_chip_info = {
|
||||||
|
.ops = &lm95241_hwmon_ops,
|
||||||
|
.info = lm95241_info,
|
||||||
|
};
|
||||||
|
|
||||||
static int lm95241_probe(struct i2c_client *client,
|
static int lm95241_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
|
@ -392,9 +476,10 @@ static int lm95241_probe(struct i2c_client *client,
|
||||||
/* Initialize the LM95241 chip */
|
/* Initialize the LM95241 chip */
|
||||||
lm95241_init_client(client, data);
|
lm95241_init_client(client, data);
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
data,
|
data,
|
||||||
lm95241_groups);
|
&lm95241_chip_info,
|
||||||
|
NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,5 +505,5 @@ static struct i2c_driver lm95241_driver = {
|
||||||
module_i2c_driver(lm95241_driver);
|
module_i2c_driver(lm95241_driver);
|
||||||
|
|
||||||
MODULE_AUTHOR("Davide Rizzo <elpa.rizzo@gmail.com>");
|
MODULE_AUTHOR("Davide Rizzo <elpa.rizzo@gmail.com>");
|
||||||
MODULE_DESCRIPTION("LM95241 sensor driver");
|
MODULE_DESCRIPTION("LM95231/LM95241 sensor driver");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
@ -15,22 +15,16 @@
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* 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/module.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/jiffies.h>
|
|
||||||
#include <linux/i2c.h>
|
|
||||||
#include <linux/hwmon.h>
|
|
||||||
#include <linux/hwmon-sysfs.h>
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/module.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/sysfs.h>
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
static const unsigned short normal_i2c[] = {
|
static const unsigned short normal_i2c[] = {
|
||||||
0x18, 0x19, 0x29, 0x4c, 0x4d, I2C_CLIENT_END };
|
0x18, 0x19, 0x29, 0x4c, 0x4d, I2C_CLIENT_END };
|
||||||
|
@ -89,6 +83,7 @@ static const unsigned short normal_i2c[] = {
|
||||||
#define RATE_CR1000 0x02
|
#define RATE_CR1000 0x02
|
||||||
#define RATE_CR2500 0x03
|
#define RATE_CR2500 0x03
|
||||||
|
|
||||||
|
#define STATUS1_ROS 0x10
|
||||||
#define STATUS1_DIODE_FAULT 0x04
|
#define STATUS1_DIODE_FAULT 0x04
|
||||||
#define STATUS1_RTCRIT 0x02
|
#define STATUS1_RTCRIT 0x02
|
||||||
#define STATUS1_LOC 0x01
|
#define STATUS1_LOC 0x01
|
||||||
|
@ -112,14 +107,9 @@ static const u8 lm95245_reg_address[] = {
|
||||||
|
|
||||||
/* Client data (each client gets its own) */
|
/* Client data (each client gets its own) */
|
||||||
struct lm95245_data {
|
struct lm95245_data {
|
||||||
struct i2c_client *client;
|
struct regmap *regmap;
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
unsigned long last_updated; /* in jiffies */
|
int interval; /* in msecs */
|
||||||
unsigned long interval; /* in msecs */
|
|
||||||
bool valid; /* zero until following fields are valid */
|
|
||||||
/* registers values */
|
|
||||||
u8 regs[ARRAY_SIZE(lm95245_reg_address)];
|
|
||||||
u8 config1, config2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Conversions */
|
/* Conversions */
|
||||||
|
@ -135,60 +125,36 @@ static int temp_from_reg_signed(u8 val_h, u8 val_l)
|
||||||
return temp_from_reg_unsigned(val_h, val_l);
|
return temp_from_reg_unsigned(val_h, val_l);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct lm95245_data *lm95245_update_device(struct device *dev)
|
static int lm95245_read_conversion_rate(struct lm95245_data *data)
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
unsigned int rate;
|
||||||
struct i2c_client *client = data->client;
|
int ret;
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
ret = regmap_read(data->regmap, LM95245_REG_RW_CONVERS_RATE, &rate);
|
||||||
|
if (ret < 0)
|
||||||
if (time_after(jiffies, data->last_updated
|
return ret;
|
||||||
+ msecs_to_jiffies(data->interval)) || !data->valid) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(lm95245_reg_address); i++)
|
|
||||||
data->regs[i]
|
|
||||||
= i2c_smbus_read_byte_data(client,
|
|
||||||
lm95245_reg_address[i]);
|
|
||||||
data->last_updated = jiffies;
|
|
||||||
data->valid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long lm95245_read_conversion_rate(struct i2c_client *client)
|
|
||||||
{
|
|
||||||
int rate;
|
|
||||||
unsigned long interval;
|
|
||||||
|
|
||||||
rate = i2c_smbus_read_byte_data(client, LM95245_REG_RW_CONVERS_RATE);
|
|
||||||
|
|
||||||
switch (rate) {
|
switch (rate) {
|
||||||
case RATE_CR0063:
|
case RATE_CR0063:
|
||||||
interval = 63;
|
data->interval = 63;
|
||||||
break;
|
break;
|
||||||
case RATE_CR0364:
|
case RATE_CR0364:
|
||||||
interval = 364;
|
data->interval = 364;
|
||||||
break;
|
break;
|
||||||
case RATE_CR1000:
|
case RATE_CR1000:
|
||||||
interval = 1000;
|
data->interval = 1000;
|
||||||
break;
|
break;
|
||||||
case RATE_CR2500:
|
case RATE_CR2500:
|
||||||
default:
|
default:
|
||||||
interval = 2500;
|
data->interval = 2500;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
return interval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned long lm95245_set_conversion_rate(struct i2c_client *client,
|
static int lm95245_set_conversion_rate(struct lm95245_data *data, long interval)
|
||||||
unsigned long interval)
|
|
||||||
{
|
{
|
||||||
int rate;
|
int ret, rate;
|
||||||
|
|
||||||
if (interval <= 63) {
|
if (interval <= 63) {
|
||||||
interval = 63;
|
interval = 63;
|
||||||
|
@ -204,220 +170,288 @@ static unsigned long lm95245_set_conversion_rate(struct i2c_client *client,
|
||||||
rate = RATE_CR2500;
|
rate = RATE_CR2500;
|
||||||
}
|
}
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, LM95245_REG_RW_CONVERS_RATE, rate);
|
ret = regmap_write(data->regmap, LM95245_REG_RW_CONVERS_RATE, rate);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
return interval;
|
data->interval = interval;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sysfs stuff */
|
static int lm95245_read_temp(struct device *dev, u32 attr, int channel,
|
||||||
static ssize_t show_input(struct device *dev, struct device_attribute *attr,
|
long *val)
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct lm95245_data *data = lm95245_update_device(dev);
|
|
||||||
int temp;
|
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Index 0 (Local temp) is always signed
|
|
||||||
* Index 2 (Remote temp) has both signed and unsigned data
|
|
||||||
* use signed calculation for remote if signed bit is set
|
|
||||||
*/
|
|
||||||
if (index == 0 || data->regs[index] & 0x80)
|
|
||||||
temp = temp_from_reg_signed(data->regs[index],
|
|
||||||
data->regs[index + 1]);
|
|
||||||
else
|
|
||||||
temp = temp_from_reg_unsigned(data->regs[index + 2],
|
|
||||||
data->regs[index + 3]);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%d\n", temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t show_limit(struct device *dev, struct device_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct lm95245_data *data = lm95245_update_device(dev);
|
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%d\n",
|
|
||||||
data->regs[index] * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_limit(struct device *dev, struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
struct lm95245_data *data = dev_get_drvdata(dev);
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
struct regmap *regmap = data->regmap;
|
||||||
struct i2c_client *client = data->client;
|
int ret, regl, regh, regvall, regvalh;
|
||||||
unsigned long val;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
switch (attr) {
|
||||||
return -EINVAL;
|
case hwmon_temp_input:
|
||||||
|
regl = channel ? LM95245_REG_R_REMOTE_TEMPL_S :
|
||||||
val /= 1000;
|
LM95245_REG_R_LOCAL_TEMPL_S;
|
||||||
|
regh = channel ? LM95245_REG_R_REMOTE_TEMPH_S :
|
||||||
val = clamp_val(val, 0, (index == 6 ? 127 : 255));
|
LM95245_REG_R_LOCAL_TEMPH_S;
|
||||||
|
ret = regmap_read(regmap, regl, ®vall);
|
||||||
mutex_lock(&data->update_lock);
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
data->valid = 0;
|
ret = regmap_read(regmap, regh, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
i2c_smbus_write_byte_data(client, lm95245_reg_address[index], val);
|
return ret;
|
||||||
|
/*
|
||||||
mutex_unlock(&data->update_lock);
|
* Local temp is always signed.
|
||||||
|
* Remote temp has both signed and unsigned data.
|
||||||
return count;
|
* Use signed calculation for remote if signed bit is set
|
||||||
|
* or if reported temperature is below signed limit.
|
||||||
|
*/
|
||||||
|
if (!channel || (regvalh & 0x80) || regvalh < 0x7f) {
|
||||||
|
*val = temp_from_reg_signed(regvalh, regvall);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPL_U,
|
||||||
|
®vall);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U,
|
||||||
|
®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = temp_from_reg_unsigned(regvalh, regvall);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT,
|
||||||
|
®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = regvalh * 1000;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit:
|
||||||
|
regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
|
||||||
|
LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
|
||||||
|
ret = regmap_read(regmap, regh, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = regvalh * 1000;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT,
|
||||||
|
®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
|
||||||
|
®vall);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = (regvalh - regvall) * 1000;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit_hyst:
|
||||||
|
regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
|
||||||
|
LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
|
||||||
|
ret = regmap_read(regmap, regh, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
|
||||||
|
®vall);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = (regvalh - regvall) * 1000;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_type:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = (regvalh & CFG2_REMOTE_TT) ? 1 : 2;
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_offset:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFL,
|
||||||
|
®vall);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFH,
|
||||||
|
®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = temp_from_reg_signed(regvalh, regvall);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_max_alarm:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = !!(regvalh & STATUS1_ROS);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_crit_alarm:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = !!(regvalh & (channel ? STATUS1_RTCRIT : STATUS1_LOC));
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_fault:
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = !!(regvalh & STATUS1_DIODE_FAULT);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_crit_hyst(struct device *dev, struct device_attribute *attr,
|
static int lm95245_write_temp(struct device *dev, u32 attr, int channel,
|
||||||
char *buf)
|
long val)
|
||||||
{
|
|
||||||
struct lm95245_data *data = lm95245_update_device(dev);
|
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
|
||||||
int hyst = data->regs[index] - data->regs[8];
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%d\n", hyst * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_crit_hyst(struct device *dev, struct device_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
struct lm95245_data *data = dev_get_drvdata(dev);
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
struct regmap *regmap = data->regmap;
|
||||||
struct i2c_client *client = data->client;
|
unsigned int regval;
|
||||||
unsigned long val;
|
int ret, reg;
|
||||||
int hyst, limit;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
switch (attr) {
|
||||||
return -EINVAL;
|
case hwmon_temp_max:
|
||||||
|
val = clamp_val(val / 1000, 0, 255);
|
||||||
mutex_lock(&data->update_lock);
|
ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, val);
|
||||||
|
return ret;
|
||||||
limit = i2c_smbus_read_byte_data(client, lm95245_reg_address[index]);
|
case hwmon_temp_crit:
|
||||||
hyst = limit - val / 1000;
|
reg = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
|
||||||
hyst = clamp_val(hyst, 0, 31);
|
LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
|
||||||
data->regs[8] = hyst;
|
val = clamp_val(val / 1000, 0, channel ? 255 : 127);
|
||||||
|
ret = regmap_write(regmap, reg, val);
|
||||||
/* shared crit hysteresis */
|
return ret;
|
||||||
i2c_smbus_write_byte_data(client, LM95245_REG_RW_COMMON_HYSTERESIS,
|
case hwmon_temp_crit_hyst:
|
||||||
hyst);
|
mutex_lock(&data->update_lock);
|
||||||
|
ret = regmap_read(regmap, LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT,
|
||||||
mutex_unlock(&data->update_lock);
|
®val);
|
||||||
|
if (ret < 0) {
|
||||||
return count;
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/* Clamp to reasonable range to prevent overflow */
|
||||||
|
val = clamp_val(val, -1000000, 1000000);
|
||||||
|
val = regval - val / 1000;
|
||||||
|
val = clamp_val(val, 0, 31);
|
||||||
|
ret = regmap_write(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
|
||||||
|
val);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
case hwmon_temp_offset:
|
||||||
|
val = clamp_val(val, -128000, 127875);
|
||||||
|
val = val * 256 / 1000;
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFL,
|
||||||
|
val & 0xe0);
|
||||||
|
if (ret < 0) {
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFH,
|
||||||
|
(val >> 8) & 0xff);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
case hwmon_temp_type:
|
||||||
|
if (val != 1 && val != 2)
|
||||||
|
return -EINVAL;
|
||||||
|
ret = regmap_update_bits(regmap, LM95245_REG_RW_CONFIG2,
|
||||||
|
CFG2_REMOTE_TT,
|
||||||
|
val == 1 ? CFG2_REMOTE_TT : 0);
|
||||||
|
return ret;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_type(struct device *dev, struct device_attribute *attr,
|
static int lm95245_read_chip(struct device *dev, u32 attr, int channel,
|
||||||
char *buf)
|
long *val)
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
struct lm95245_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE - 1,
|
switch (attr) {
|
||||||
data->config2 & CFG2_REMOTE_TT ? "1\n" : "2\n");
|
case hwmon_chip_update_interval:
|
||||||
|
*val = data->interval;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_type(struct device *dev, struct device_attribute *attr,
|
static int lm95245_write_chip(struct device *dev, u32 attr, int channel,
|
||||||
const char *buf, size_t count)
|
long val)
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
struct lm95245_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
int ret;
|
||||||
unsigned long val;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
switch (attr) {
|
||||||
return -EINVAL;
|
case hwmon_chip_update_interval:
|
||||||
if (val != 1 && val != 2)
|
mutex_lock(&data->update_lock);
|
||||||
return -EINVAL;
|
ret = lm95245_set_conversion_rate(data, val);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
mutex_lock(&data->update_lock);
|
return ret;
|
||||||
|
default:
|
||||||
if (val == 1)
|
return -EOPNOTSUPP;
|
||||||
data->config2 |= CFG2_REMOTE_TT;
|
}
|
||||||
else
|
|
||||||
data->config2 &= ~CFG2_REMOTE_TT;
|
|
||||||
|
|
||||||
data->valid = 0;
|
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, LM95245_REG_RW_CONFIG2,
|
|
||||||
data->config2);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
|
static int lm95245_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
char *buf)
|
u32 attr, int channel, long *val)
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = lm95245_update_device(dev);
|
switch (type) {
|
||||||
int index = to_sensor_dev_attr(attr)->index;
|
case hwmon_chip:
|
||||||
|
return lm95245_read_chip(dev, attr, channel, val);
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%d\n",
|
case hwmon_temp:
|
||||||
!!(data->regs[9] & index));
|
return lm95245_read_temp(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_interval(struct device *dev, struct device_attribute *attr,
|
static int lm95245_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
char *buf)
|
u32 attr, int channel, long val)
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = lm95245_update_device(dev);
|
switch (type) {
|
||||||
|
case hwmon_chip:
|
||||||
return snprintf(buf, PAGE_SIZE - 1, "%lu\n", data->interval);
|
return lm95245_write_chip(dev, attr, channel, val);
|
||||||
|
case hwmon_temp:
|
||||||
|
return lm95245_write_temp(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_interval(struct device *dev, struct device_attribute *attr,
|
static umode_t lm95245_temp_is_visible(const void *data, u32 attr, int channel)
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct lm95245_data *data = dev_get_drvdata(dev);
|
switch (attr) {
|
||||||
struct i2c_client *client = data->client;
|
case hwmon_temp_input:
|
||||||
unsigned long val;
|
case hwmon_temp_max_alarm:
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
case hwmon_temp_crit_alarm:
|
||||||
return -EINVAL;
|
case hwmon_temp_fault:
|
||||||
|
return S_IRUGO;
|
||||||
mutex_lock(&data->update_lock);
|
case hwmon_temp_type:
|
||||||
|
case hwmon_temp_max:
|
||||||
data->interval = lm95245_set_conversion_rate(client, val);
|
case hwmon_temp_crit:
|
||||||
|
case hwmon_temp_offset:
|
||||||
mutex_unlock(&data->update_lock);
|
return S_IRUGO | S_IWUSR;
|
||||||
|
case hwmon_temp_crit_hyst:
|
||||||
return count;
|
return (channel == 0) ? S_IRUGO | S_IWUSR : S_IRUGO;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0);
|
static umode_t lm95245_is_visible(const void *data,
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_limit,
|
enum hwmon_sensor_types type,
|
||||||
set_limit, 6);
|
u32 attr, int channel)
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_crit_hyst,
|
{
|
||||||
set_crit_hyst, 6);
|
switch (type) {
|
||||||
static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL,
|
case hwmon_chip:
|
||||||
STATUS1_LOC);
|
switch (attr) {
|
||||||
|
case hwmon_chip_update_interval:
|
||||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2);
|
return S_IRUGO | S_IWUSR;
|
||||||
static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_limit,
|
default:
|
||||||
set_limit, 7);
|
return 0;
|
||||||
static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_crit_hyst, NULL, 7);
|
}
|
||||||
static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL,
|
case hwmon_temp:
|
||||||
STATUS1_RTCRIT);
|
return lm95245_temp_is_visible(data, attr, channel);
|
||||||
static SENSOR_DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type,
|
default:
|
||||||
set_type, 0);
|
return 0;
|
||||||
static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL,
|
}
|
||||||
STATUS1_DIODE_FAULT);
|
}
|
||||||
|
|
||||||
static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_interval,
|
|
||||||
set_interval);
|
|
||||||
|
|
||||||
static struct attribute *lm95245_attrs[] = {
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_crit.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_type.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_fault.dev_attr.attr,
|
|
||||||
&dev_attr_update_interval.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
ATTRIBUTE_GROUPS(lm95245);
|
|
||||||
|
|
||||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||||
static int lm95245_detect(struct i2c_client *new_client,
|
static int lm95245_detect(struct i2c_client *new_client,
|
||||||
|
@ -453,44 +487,130 @@ static int lm95245_detect(struct i2c_client *new_client,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lm95245_init_client(struct i2c_client *client,
|
static int lm95245_init_client(struct lm95245_data *data)
|
||||||
struct lm95245_data *data)
|
|
||||||
{
|
{
|
||||||
data->interval = lm95245_read_conversion_rate(client);
|
int ret;
|
||||||
|
|
||||||
data->config1 = i2c_smbus_read_byte_data(client,
|
ret = lm95245_read_conversion_rate(data);
|
||||||
LM95245_REG_RW_CONFIG1);
|
if (ret < 0)
|
||||||
data->config2 = i2c_smbus_read_byte_data(client,
|
return ret;
|
||||||
LM95245_REG_RW_CONFIG2);
|
|
||||||
|
|
||||||
if (data->config1 & CFG_STOP) {
|
return regmap_update_bits(data->regmap, LM95245_REG_RW_CONFIG1,
|
||||||
/* Clear the standby bit */
|
CFG_STOP, 0);
|
||||||
data->config1 &= ~CFG_STOP;
|
}
|
||||||
i2c_smbus_write_byte_data(client, LM95245_REG_RW_CONFIG1,
|
|
||||||
data->config1);
|
static bool lm95245_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case LM95245_REG_RW_CONFIG1:
|
||||||
|
case LM95245_REG_RW_CONVERS_RATE:
|
||||||
|
case LM95245_REG_W_ONE_SHOT:
|
||||||
|
case LM95245_REG_RW_CONFIG2:
|
||||||
|
case LM95245_REG_RW_REMOTE_OFFH:
|
||||||
|
case LM95245_REG_RW_REMOTE_OFFL:
|
||||||
|
case LM95245_REG_RW_REMOTE_OS_LIMIT:
|
||||||
|
case LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT:
|
||||||
|
case LM95245_REG_RW_REMOTE_TCRIT_LIMIT:
|
||||||
|
case LM95245_REG_RW_COMMON_HYSTERESIS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool lm95245_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case LM95245_REG_R_STATUS1:
|
||||||
|
case LM95245_REG_R_STATUS2:
|
||||||
|
case LM95245_REG_R_LOCAL_TEMPH_S:
|
||||||
|
case LM95245_REG_R_LOCAL_TEMPL_S:
|
||||||
|
case LM95245_REG_R_REMOTE_TEMPH_S:
|
||||||
|
case LM95245_REG_R_REMOTE_TEMPL_S:
|
||||||
|
case LM95245_REG_R_REMOTE_TEMPH_U:
|
||||||
|
case LM95245_REG_R_REMOTE_TEMPL_U:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct regmap_config lm95245_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.writeable_reg = lm95245_is_writeable_reg,
|
||||||
|
.volatile_reg = lm95245_is_volatile_reg,
|
||||||
|
.cache_type = REGCACHE_RBTREE,
|
||||||
|
.use_single_rw = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 lm95245_chip_config[] = {
|
||||||
|
HWMON_C_UPDATE_INTERVAL,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm95245_chip = {
|
||||||
|
.type = hwmon_chip,
|
||||||
|
.config = lm95245_chip_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 lm95245_temp_config[] = {
|
||||||
|
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_CRIT_ALARM,
|
||||||
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT |
|
||||||
|
HWMON_T_CRIT_HYST | HWMON_T_FAULT | HWMON_T_MAX_ALARM |
|
||||||
|
HWMON_T_CRIT_ALARM | HWMON_T_TYPE | HWMON_T_OFFSET,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info lm95245_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = lm95245_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *lm95245_info[] = {
|
||||||
|
&lm95245_chip,
|
||||||
|
&lm95245_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops lm95245_hwmon_ops = {
|
||||||
|
.is_visible = lm95245_is_visible,
|
||||||
|
.read = lm95245_read,
|
||||||
|
.write = lm95245_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info lm95245_chip_info = {
|
||||||
|
.ops = &lm95245_hwmon_ops,
|
||||||
|
.info = lm95245_info,
|
||||||
|
};
|
||||||
|
|
||||||
static int lm95245_probe(struct i2c_client *client,
|
static int lm95245_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct lm95245_data *data;
|
struct lm95245_data *data;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
data = devm_kzalloc(dev, sizeof(struct lm95245_data), GFP_KERNEL);
|
data = devm_kzalloc(dev, sizeof(struct lm95245_data), GFP_KERNEL);
|
||||||
if (!data)
|
if (!data)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
data->client = client;
|
data->regmap = devm_regmap_init_i2c(client, &lm95245_regmap_config);
|
||||||
|
if (IS_ERR(data->regmap))
|
||||||
|
return PTR_ERR(data->regmap);
|
||||||
|
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
/* Initialize the LM95245 chip */
|
/* Initialize the LM95245 chip */
|
||||||
lm95245_init_client(client, data);
|
ret = lm95245_init_client(data);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
data,
|
data,
|
||||||
lm95245_groups);
|
&lm95245_chip_info,
|
||||||
|
NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
@ -52,6 +53,7 @@ struct ltc4151_data {
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
bool valid;
|
bool valid;
|
||||||
unsigned long last_updated; /* in jiffies */
|
unsigned long last_updated; /* in jiffies */
|
||||||
|
unsigned int shunt; /* in micro ohms */
|
||||||
|
|
||||||
/* Registers */
|
/* Registers */
|
||||||
u8 regs[6];
|
u8 regs[6];
|
||||||
|
@ -111,9 +113,9 @@ static int ltc4151_get_value(struct ltc4151_data *data, u8 reg)
|
||||||
case LTC4151_SENSE_H:
|
case LTC4151_SENSE_H:
|
||||||
/*
|
/*
|
||||||
* 20uV resolution. Convert to current as measured with
|
* 20uV resolution. Convert to current as measured with
|
||||||
* an 1 mOhm sense resistor, in mA.
|
* a given sense resistor, in mA.
|
||||||
*/
|
*/
|
||||||
val = val * 20;
|
val = val * 20 * 1000 / data->shunt;
|
||||||
break;
|
break;
|
||||||
case LTC4151_VIN_H:
|
case LTC4151_VIN_H:
|
||||||
/* 25 mV per increment */
|
/* 25 mV per increment */
|
||||||
|
@ -176,6 +178,7 @@ static int ltc4151_probe(struct i2c_client *client,
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct ltc4151_data *data;
|
struct ltc4151_data *data;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
|
u32 shunt;
|
||||||
|
|
||||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
@ -184,6 +187,15 @@ static int ltc4151_probe(struct i2c_client *client,
|
||||||
if (!data)
|
if (!data)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (of_property_read_u32(client->dev.of_node,
|
||||||
|
"shunt-resistor-micro-ohms", &shunt))
|
||||||
|
shunt = 1000; /* 1 mOhm if not set via DT */
|
||||||
|
|
||||||
|
if (shunt == 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
data->shunt = shunt;
|
||||||
|
|
||||||
data->client = client;
|
data->client = client;
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
|
@ -199,10 +211,16 @@ static const struct i2c_device_id ltc4151_id[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, ltc4151_id);
|
MODULE_DEVICE_TABLE(i2c, ltc4151_id);
|
||||||
|
|
||||||
|
static const struct of_device_id ltc4151_match[] = {
|
||||||
|
{ .compatible = "lltc,ltc4151" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
/* This is the driver that will be inserted */
|
/* This is the driver that will be inserted */
|
||||||
static struct i2c_driver ltc4151_driver = {
|
static struct i2c_driver ltc4151_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "ltc4151",
|
.name = "ltc4151",
|
||||||
|
.of_match_table = of_match_ptr(ltc4151_match),
|
||||||
},
|
},
|
||||||
.probe = ltc4151_probe,
|
.probe = ltc4151_probe,
|
||||||
.id_table = ltc4151_id,
|
.id_table = ltc4151_id,
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
|
@ -53,8 +54,6 @@ enum ltc4245_cmd {
|
||||||
struct ltc4245_data {
|
struct ltc4245_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
|
|
||||||
const struct attribute_group *groups[3];
|
|
||||||
|
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
bool valid;
|
bool valid;
|
||||||
unsigned long last_updated; /* in jiffies */
|
unsigned long last_updated; /* in jiffies */
|
||||||
|
@ -162,7 +161,7 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev)
|
||||||
ltc4245_update_gpios(dev);
|
ltc4245_update_gpios(dev);
|
||||||
|
|
||||||
data->last_updated = jiffies;
|
data->last_updated = jiffies;
|
||||||
data->valid = 1;
|
data->valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
@ -256,214 +255,205 @@ static unsigned int ltc4245_get_current(struct device *dev, u8 reg)
|
||||||
return curr;
|
return curr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t ltc4245_show_voltage(struct device *dev,
|
/* Map from voltage channel index to voltage register */
|
||||||
struct device_attribute *da,
|
|
||||||
char *buf)
|
static const s8 ltc4245_in_regs[] = {
|
||||||
|
LTC4245_12VIN, LTC4245_5VIN, LTC4245_3VIN, LTC4245_VEEIN,
|
||||||
|
LTC4245_12VOUT, LTC4245_5VOUT, LTC4245_3VOUT, LTC4245_VEEOUT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Map from current channel index to current register */
|
||||||
|
|
||||||
|
static const s8 ltc4245_curr_regs[] = {
|
||||||
|
LTC4245_12VSENSE, LTC4245_5VSENSE, LTC4245_3VSENSE, LTC4245_VEESENSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ltc4245_read_curr(struct device *dev, u32 attr, int channel,
|
||||||
|
long *val)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
const int voltage = ltc4245_get_voltage(dev, attr->index);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE, "%d\n", voltage);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ltc4245_show_current(struct device *dev,
|
|
||||||
struct device_attribute *da,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
const unsigned int curr = ltc4245_get_current(dev, attr->index);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE, "%u\n", curr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ltc4245_show_power(struct device *dev,
|
|
||||||
struct device_attribute *da,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
const unsigned int curr = ltc4245_get_current(dev, attr->index);
|
|
||||||
const int output_voltage = ltc4245_get_voltage(dev, attr->index+1);
|
|
||||||
|
|
||||||
/* current in mA * voltage in mV == power in uW */
|
|
||||||
const unsigned int power = abs(output_voltage * curr);
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE, "%u\n", power);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ltc4245_show_alarm(struct device *dev,
|
|
||||||
struct device_attribute *da,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da);
|
|
||||||
struct ltc4245_data *data = ltc4245_update_device(dev);
|
struct ltc4245_data *data = ltc4245_update_device(dev);
|
||||||
const u8 reg = data->cregs[attr->index];
|
|
||||||
const u32 mask = attr->nr;
|
|
||||||
|
|
||||||
return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
|
switch (attr) {
|
||||||
|
case hwmon_curr_input:
|
||||||
|
*val = ltc4245_get_current(dev, ltc4245_curr_regs[channel]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_curr_max_alarm:
|
||||||
|
*val = !!(data->cregs[LTC4245_FAULT1] & BIT(channel + 4));
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t ltc4245_show_gpio(struct device *dev,
|
static int ltc4245_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||||
struct device_attribute *da,
|
|
||||||
char *buf)
|
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
||||||
struct ltc4245_data *data = ltc4245_update_device(dev);
|
struct ltc4245_data *data = ltc4245_update_device(dev);
|
||||||
int val = data->gpios[attr->index];
|
|
||||||
|
|
||||||
/* handle stale GPIO's */
|
switch (attr) {
|
||||||
if (val < 0)
|
case hwmon_in_input:
|
||||||
return val;
|
if (channel < 8) {
|
||||||
|
*val = ltc4245_get_voltage(dev,
|
||||||
|
ltc4245_in_regs[channel]);
|
||||||
|
} else {
|
||||||
|
int regval = data->gpios[channel - 8];
|
||||||
|
|
||||||
/* Convert to millivolts and print */
|
if (regval < 0)
|
||||||
return snprintf(buf, PAGE_SIZE, "%u\n", val * 10);
|
return regval;
|
||||||
|
*val = regval * 10;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case hwmon_in_min_alarm:
|
||||||
|
if (channel < 4)
|
||||||
|
*val = !!(data->cregs[LTC4245_FAULT1] & BIT(channel));
|
||||||
|
else
|
||||||
|
*val = !!(data->cregs[LTC4245_FAULT2] &
|
||||||
|
BIT(channel - 4));
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Construct a sensor_device_attribute structure for each register */
|
static int ltc4245_read_power(struct device *dev, u32 attr, int channel,
|
||||||
|
long *val)
|
||||||
/* Input voltages */
|
|
||||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_12VIN);
|
|
||||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_5VIN);
|
|
||||||
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_3VIN);
|
|
||||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_VEEIN);
|
|
||||||
|
|
||||||
/* Input undervoltage alarms */
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 0, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in2_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 1, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in3_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 2, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in4_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 3, LTC4245_FAULT1);
|
|
||||||
|
|
||||||
/* Currents (via sense resistor) */
|
|
||||||
static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4245_show_current, NULL,
|
|
||||||
LTC4245_12VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc4245_show_current, NULL,
|
|
||||||
LTC4245_5VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO, ltc4245_show_current, NULL,
|
|
||||||
LTC4245_3VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(curr4_input, S_IRUGO, ltc4245_show_current, NULL,
|
|
||||||
LTC4245_VEESENSE);
|
|
||||||
|
|
||||||
/* Overcurrent alarms */
|
|
||||||
static SENSOR_DEVICE_ATTR_2(curr1_max_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 4, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(curr2_max_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 5, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(curr3_max_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 6, LTC4245_FAULT1);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(curr4_max_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 7, LTC4245_FAULT1);
|
|
||||||
|
|
||||||
/* Output voltages */
|
|
||||||
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_12VOUT);
|
|
||||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_5VOUT);
|
|
||||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_3VOUT);
|
|
||||||
static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, ltc4245_show_voltage, NULL,
|
|
||||||
LTC4245_VEEOUT);
|
|
||||||
|
|
||||||
/* Power Bad alarms */
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in5_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 0, LTC4245_FAULT2);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in6_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 1, LTC4245_FAULT2);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in7_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 2, LTC4245_FAULT2);
|
|
||||||
static SENSOR_DEVICE_ATTR_2(in8_min_alarm, S_IRUGO, ltc4245_show_alarm, NULL,
|
|
||||||
1 << 3, LTC4245_FAULT2);
|
|
||||||
|
|
||||||
/* GPIO voltages */
|
|
||||||
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, ltc4245_show_gpio, NULL, 0);
|
|
||||||
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, ltc4245_show_gpio, NULL, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, ltc4245_show_gpio, NULL, 2);
|
|
||||||
|
|
||||||
/* Power Consumption (virtual) */
|
|
||||||
static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ltc4245_show_power, NULL,
|
|
||||||
LTC4245_12VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(power2_input, S_IRUGO, ltc4245_show_power, NULL,
|
|
||||||
LTC4245_5VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(power3_input, S_IRUGO, ltc4245_show_power, NULL,
|
|
||||||
LTC4245_3VSENSE);
|
|
||||||
static SENSOR_DEVICE_ATTR(power4_input, S_IRUGO, ltc4245_show_power, NULL,
|
|
||||||
LTC4245_VEESENSE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Finally, construct an array of pointers to members of the above objects,
|
|
||||||
* as required for sysfs_create_group()
|
|
||||||
*/
|
|
||||||
static struct attribute *ltc4245_std_attributes[] = {
|
|
||||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_in1_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in2_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in3_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in4_min_alarm.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_curr1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr4_input.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_curr1_max_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr2_max_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr3_max_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_curr4_max_alarm.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in8_input.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_in5_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in6_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in7_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in8_min_alarm.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_in9_input.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_power1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_power2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_power3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_power4_input.dev_attr.attr,
|
|
||||||
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct attribute *ltc4245_gpio_attributes[] = {
|
|
||||||
&sensor_dev_attr_in10_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in11_input.dev_attr.attr,
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group ltc4245_std_group = {
|
|
||||||
.attrs = ltc4245_std_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group ltc4245_gpio_group = {
|
|
||||||
.attrs = ltc4245_gpio_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void ltc4245_sysfs_add_groups(struct ltc4245_data *data)
|
|
||||||
{
|
{
|
||||||
/* standard sysfs attributes */
|
unsigned long curr;
|
||||||
data->groups[0] = <c4245_std_group;
|
long voltage;
|
||||||
|
|
||||||
/* if we're using the extra gpio support, register it's attributes */
|
switch (attr) {
|
||||||
if (data->use_extra_gpios)
|
case hwmon_power_input:
|
||||||
data->groups[1] = <c4245_gpio_group;
|
(void)ltc4245_update_device(dev);
|
||||||
|
curr = ltc4245_get_current(dev, ltc4245_curr_regs[channel]);
|
||||||
|
voltage = ltc4245_get_voltage(dev, ltc4245_in_regs[channel]);
|
||||||
|
*val = abs(curr * voltage);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ltc4245_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_curr:
|
||||||
|
return ltc4245_read_curr(dev, attr, channel, val);
|
||||||
|
case hwmon_power:
|
||||||
|
return ltc4245_read_power(dev, attr, channel, val);
|
||||||
|
case hwmon_in:
|
||||||
|
return ltc4245_read_in(dev, attr, channel - 1, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static umode_t ltc4245_is_visible(const void *_data,
|
||||||
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel)
|
||||||
|
{
|
||||||
|
const struct ltc4245_data *data = _data;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_in:
|
||||||
|
if (channel == 0)
|
||||||
|
return 0;
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_in_input:
|
||||||
|
if (channel > 9 && !data->use_extra_gpios)
|
||||||
|
return 0;
|
||||||
|
return S_IRUGO;
|
||||||
|
case hwmon_in_min_alarm:
|
||||||
|
if (channel > 8)
|
||||||
|
return 0;
|
||||||
|
return S_IRUGO;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case hwmon_curr:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_curr_input:
|
||||||
|
case hwmon_curr_max_alarm:
|
||||||
|
return S_IRUGO;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case hwmon_power:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_power_input:
|
||||||
|
return S_IRUGO;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u32 ltc4245_in_config[] = {
|
||||||
|
HWMON_I_INPUT, /* dummy, skipped in is_visible */
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT | HWMON_I_MIN_ALARM,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info ltc4245_in = {
|
||||||
|
.type = hwmon_in,
|
||||||
|
.config = ltc4245_in_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 ltc4245_curr_config[] = {
|
||||||
|
HWMON_C_INPUT | HWMON_C_MAX_ALARM,
|
||||||
|
HWMON_C_INPUT | HWMON_C_MAX_ALARM,
|
||||||
|
HWMON_C_INPUT | HWMON_C_MAX_ALARM,
|
||||||
|
HWMON_C_INPUT | HWMON_C_MAX_ALARM,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info ltc4245_curr = {
|
||||||
|
.type = hwmon_curr,
|
||||||
|
.config = ltc4245_curr_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 ltc4245_power_config[] = {
|
||||||
|
HWMON_P_INPUT,
|
||||||
|
HWMON_P_INPUT,
|
||||||
|
HWMON_P_INPUT,
|
||||||
|
HWMON_P_INPUT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info ltc4245_power = {
|
||||||
|
.type = hwmon_power,
|
||||||
|
.config = ltc4245_power_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *ltc4245_info[] = {
|
||||||
|
<c4245_in,
|
||||||
|
<c4245_curr,
|
||||||
|
<c4245_power,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops ltc4245_hwmon_ops = {
|
||||||
|
.is_visible = ltc4245_is_visible,
|
||||||
|
.read = ltc4245_read,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info ltc4245_chip_info = {
|
||||||
|
.ops = <c4245_hwmon_ops,
|
||||||
|
.info = ltc4245_info,
|
||||||
|
};
|
||||||
|
|
||||||
static bool ltc4245_use_extra_gpios(struct i2c_client *client)
|
static bool ltc4245_use_extra_gpios(struct i2c_client *client)
|
||||||
{
|
{
|
||||||
struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev);
|
struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||||
|
@ -502,12 +492,10 @@ static int ltc4245_probe(struct i2c_client *client,
|
||||||
i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00);
|
i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00);
|
||||||
i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00);
|
i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00);
|
||||||
|
|
||||||
/* Add sysfs hooks */
|
hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
|
||||||
ltc4245_sysfs_add_groups(data);
|
client->name, data,
|
||||||
|
<c4245_chip_info,
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
|
NULL);
|
||||||
client->name, data,
|
|
||||||
data->groups);
|
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
|
@ -169,362 +168,288 @@ static u8 bits_for_tach_period(int rpm)
|
||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t get_fan(struct device *dev,
|
static int max31790_read_fan(struct device *dev, u32 attr, int channel,
|
||||||
struct device_attribute *devattr, char *buf)
|
long *val)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = max31790_update_device(dev);
|
struct max31790_data *data = max31790_update_device(dev);
|
||||||
int sr, rpm;
|
int sr, rpm;
|
||||||
|
|
||||||
if (IS_ERR(data))
|
if (IS_ERR(data))
|
||||||
return PTR_ERR(data);
|
return PTR_ERR(data);
|
||||||
|
|
||||||
sr = get_tach_period(data->fan_dynamics[attr->index]);
|
switch (attr) {
|
||||||
rpm = RPM_FROM_REG(data->tach[attr->index], sr);
|
case hwmon_fan_input:
|
||||||
|
sr = get_tach_period(data->fan_dynamics[channel]);
|
||||||
return sprintf(buf, "%d\n", rpm);
|
rpm = RPM_FROM_REG(data->tach[channel], sr);
|
||||||
|
*val = rpm;
|
||||||
|
return 0;
|
||||||
|
case hwmon_fan_target:
|
||||||
|
sr = get_tach_period(data->fan_dynamics[channel]);
|
||||||
|
rpm = RPM_FROM_REG(data->target_count[channel], sr);
|
||||||
|
*val = rpm;
|
||||||
|
return 0;
|
||||||
|
case hwmon_fan_fault:
|
||||||
|
*val = !!(data->fault_status & (1 << channel));
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t get_fan_target(struct device *dev,
|
static int max31790_write_fan(struct device *dev, u32 attr, int channel,
|
||||||
struct device_attribute *devattr, char *buf)
|
long val)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = max31790_update_device(dev);
|
|
||||||
int sr, rpm;
|
|
||||||
|
|
||||||
if (IS_ERR(data))
|
|
||||||
return PTR_ERR(data);
|
|
||||||
|
|
||||||
sr = get_tach_period(data->fan_dynamics[attr->index]);
|
|
||||||
rpm = RPM_FROM_REG(data->target_count[attr->index], sr);
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", rpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_fan_target(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = dev_get_drvdata(dev);
|
struct max31790_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
|
int target_count;
|
||||||
|
int err = 0;
|
||||||
u8 bits;
|
u8 bits;
|
||||||
int sr;
|
int sr;
|
||||||
int target_count;
|
|
||||||
unsigned long rpm;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &rpm);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
|
switch (attr) {
|
||||||
bits = bits_for_tach_period(rpm);
|
case hwmon_fan_target:
|
||||||
data->fan_dynamics[attr->index] =
|
val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX);
|
||||||
((data->fan_dynamics[attr->index]
|
bits = bits_for_tach_period(val);
|
||||||
& ~MAX31790_FAN_DYN_SR_MASK)
|
data->fan_dynamics[channel] =
|
||||||
| (bits << MAX31790_FAN_DYN_SR_SHIFT));
|
((data->fan_dynamics[channel] &
|
||||||
err = i2c_smbus_write_byte_data(client,
|
~MAX31790_FAN_DYN_SR_MASK) |
|
||||||
MAX31790_REG_FAN_DYNAMICS(attr->index),
|
(bits << MAX31790_FAN_DYN_SR_SHIFT));
|
||||||
data->fan_dynamics[attr->index]);
|
err = i2c_smbus_write_byte_data(client,
|
||||||
|
MAX31790_REG_FAN_DYNAMICS(channel),
|
||||||
|
data->fan_dynamics[channel]);
|
||||||
|
if (err < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
if (err < 0) {
|
sr = get_tach_period(data->fan_dynamics[channel]);
|
||||||
mutex_unlock(&data->update_lock);
|
target_count = RPM_TO_REG(val, sr);
|
||||||
return err;
|
target_count = clamp_val(target_count, 0x1, 0x7FF);
|
||||||
}
|
|
||||||
|
|
||||||
sr = get_tach_period(data->fan_dynamics[attr->index]);
|
data->target_count[channel] = target_count << 5;
|
||||||
target_count = RPM_TO_REG(rpm, sr);
|
|
||||||
target_count = clamp_val(target_count, 0x1, 0x7FF);
|
|
||||||
|
|
||||||
data->target_count[attr->index] = target_count << 5;
|
err = i2c_smbus_write_word_swapped(client,
|
||||||
|
MAX31790_REG_TARGET_COUNT(channel),
|
||||||
err = i2c_smbus_write_word_swapped(client,
|
data->target_count[channel]);
|
||||||
MAX31790_REG_TARGET_COUNT(attr->index),
|
|
||||||
data->target_count[attr->index]);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t get_pwm(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = max31790_update_device(dev);
|
|
||||||
int pwm;
|
|
||||||
|
|
||||||
if (IS_ERR(data))
|
|
||||||
return PTR_ERR(data);
|
|
||||||
|
|
||||||
pwm = data->pwm[attr->index] >> 8;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", pwm);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_pwm(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
unsigned long pwm;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &pwm);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
if (pwm > 255)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
data->pwm[attr->index] = pwm << 8;
|
|
||||||
err = i2c_smbus_write_word_swapped(client,
|
|
||||||
MAX31790_REG_PWMOUT(attr->index),
|
|
||||||
data->pwm[attr->index]);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t get_pwm_enable(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = max31790_update_device(dev);
|
|
||||||
int mode;
|
|
||||||
|
|
||||||
if (IS_ERR(data))
|
|
||||||
return PTR_ERR(data);
|
|
||||||
|
|
||||||
if (data->fan_config[attr->index] & MAX31790_FAN_CFG_RPM_MODE)
|
|
||||||
mode = 2;
|
|
||||||
else if (data->fan_config[attr->index] & MAX31790_FAN_CFG_TACH_INPUT_EN)
|
|
||||||
mode = 1;
|
|
||||||
else
|
|
||||||
mode = 0;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t set_pwm_enable(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
unsigned long mode;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &mode);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case 0:
|
|
||||||
data->fan_config[attr->index] =
|
|
||||||
data->fan_config[attr->index]
|
|
||||||
& ~(MAX31790_FAN_CFG_TACH_INPUT_EN
|
|
||||||
| MAX31790_FAN_CFG_RPM_MODE);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
data->fan_config[attr->index] =
|
|
||||||
(data->fan_config[attr->index]
|
|
||||||
| MAX31790_FAN_CFG_TACH_INPUT_EN)
|
|
||||||
& ~MAX31790_FAN_CFG_RPM_MODE;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
data->fan_config[attr->index] =
|
|
||||||
data->fan_config[attr->index]
|
|
||||||
| MAX31790_FAN_CFG_TACH_INPUT_EN
|
|
||||||
| MAX31790_FAN_CFG_RPM_MODE;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
err = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
err = i2c_smbus_write_byte_data(client,
|
|
||||||
MAX31790_REG_FAN_CONFIG(attr->index),
|
|
||||||
data->fan_config[attr->index]);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
if (err < 0)
|
return err;
|
||||||
return err;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t get_fan_fault(struct device *dev,
|
static umode_t max31790_fan_is_visible(const void *_data, u32 attr, int channel)
|
||||||
struct device_attribute *devattr, char *buf)
|
{
|
||||||
|
const struct max31790_data *data = _data;
|
||||||
|
u8 fan_config = data->fan_config[channel % NR_CHANNEL];
|
||||||
|
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_fan_input:
|
||||||
|
case hwmon_fan_fault:
|
||||||
|
if (channel < NR_CHANNEL ||
|
||||||
|
(fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
||||||
|
return S_IRUGO;
|
||||||
|
return 0;
|
||||||
|
case hwmon_fan_target:
|
||||||
|
if (channel < NR_CHANNEL &&
|
||||||
|
!(fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
||||||
|
return S_IRUGO | S_IWUSR;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max31790_read_pwm(struct device *dev, u32 attr, int channel,
|
||||||
|
long *val)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max31790_data *data = max31790_update_device(dev);
|
struct max31790_data *data = max31790_update_device(dev);
|
||||||
int fault;
|
u8 fan_config = data->fan_config[channel];
|
||||||
|
|
||||||
if (IS_ERR(data))
|
if (IS_ERR(data))
|
||||||
return PTR_ERR(data);
|
return PTR_ERR(data);
|
||||||
|
|
||||||
fault = !!(data->fault_status & (1 << attr->index));
|
switch (attr) {
|
||||||
|
case hwmon_pwm_input:
|
||||||
return sprintf(buf, "%d\n", fault);
|
*val = data->pwm[channel] >> 8;
|
||||||
|
return 0;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
if (fan_config & MAX31790_FAN_CFG_RPM_MODE)
|
||||||
|
*val = 2;
|
||||||
|
else if (fan_config & MAX31790_FAN_CFG_TACH_INPUT_EN)
|
||||||
|
*val = 1;
|
||||||
|
else
|
||||||
|
*val = 0;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0);
|
static int max31790_write_pwm(struct device *dev, u32 attr, int channel,
|
||||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1);
|
long val)
|
||||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2);
|
{
|
||||||
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3);
|
struct max31790_data *data = dev_get_drvdata(dev);
|
||||||
static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, get_fan, NULL, 4);
|
struct i2c_client *client = data->client;
|
||||||
static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, get_fan, NULL, 5);
|
u8 fan_config;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_fault, NULL, 0);
|
mutex_lock(&data->update_lock);
|
||||||
static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, get_fan_fault, NULL, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, get_fan_fault, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, get_fan_fault, NULL, 3);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, get_fan_fault, NULL, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, get_fan_fault, NULL, 5);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, get_fan, NULL, 6);
|
switch (attr) {
|
||||||
static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, get_fan, NULL, 7);
|
case hwmon_pwm_input:
|
||||||
static SENSOR_DEVICE_ATTR(fan9_input, S_IRUGO, get_fan, NULL, 8);
|
if (val < 0 || val > 255) {
|
||||||
static SENSOR_DEVICE_ATTR(fan10_input, S_IRUGO, get_fan, NULL, 9);
|
err = -EINVAL;
|
||||||
static SENSOR_DEVICE_ATTR(fan11_input, S_IRUGO, get_fan, NULL, 10);
|
break;
|
||||||
static SENSOR_DEVICE_ATTR(fan12_input, S_IRUGO, get_fan, NULL, 11);
|
}
|
||||||
|
data->pwm[channel] = val << 8;
|
||||||
|
err = i2c_smbus_write_word_swapped(client,
|
||||||
|
MAX31790_REG_PWMOUT(channel),
|
||||||
|
val);
|
||||||
|
break;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
fan_config = data->fan_config[channel];
|
||||||
|
if (val == 0) {
|
||||||
|
fan_config &= ~(MAX31790_FAN_CFG_TACH_INPUT_EN |
|
||||||
|
MAX31790_FAN_CFG_RPM_MODE);
|
||||||
|
} else if (val == 1) {
|
||||||
|
fan_config = (fan_config |
|
||||||
|
MAX31790_FAN_CFG_TACH_INPUT_EN) &
|
||||||
|
~MAX31790_FAN_CFG_RPM_MODE;
|
||||||
|
} else if (val == 2) {
|
||||||
|
fan_config |= MAX31790_FAN_CFG_TACH_INPUT_EN |
|
||||||
|
MAX31790_FAN_CFG_RPM_MODE;
|
||||||
|
} else {
|
||||||
|
err = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->fan_config[channel] = fan_config;
|
||||||
|
err = i2c_smbus_write_byte_data(client,
|
||||||
|
MAX31790_REG_FAN_CONFIG(channel),
|
||||||
|
fan_config);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan7_fault, S_IRUGO, get_fan_fault, NULL, 6);
|
mutex_unlock(&data->update_lock);
|
||||||
static SENSOR_DEVICE_ATTR(fan8_fault, S_IRUGO, get_fan_fault, NULL, 7);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan9_fault, S_IRUGO, get_fan_fault, NULL, 8);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan10_fault, S_IRUGO, get_fan_fault, NULL, 9);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan11_fault, S_IRUGO, get_fan_fault, NULL, 10);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan12_fault, S_IRUGO, get_fan_fault, NULL, 11);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO,
|
return err;
|
||||||
get_fan_target, set_fan_target, 0);
|
}
|
||||||
static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO,
|
|
||||||
get_fan_target, set_fan_target, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO,
|
|
||||||
get_fan_target, set_fan_target, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO,
|
|
||||||
get_fan_target, set_fan_target, 3);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan5_target, S_IWUSR | S_IRUGO,
|
|
||||||
get_fan_target, set_fan_target, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan6_target, S_IWUSR | S_IRUGO,
|
|
||||||
get_fan_target, set_fan_target, 5);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0);
|
static umode_t max31790_pwm_is_visible(const void *_data, u32 attr, int channel)
|
||||||
static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1);
|
{
|
||||||
static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2);
|
const struct max31790_data *data = _data;
|
||||||
static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3);
|
u8 fan_config = data->fan_config[channel];
|
||||||
static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 5);
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
|
switch (attr) {
|
||||||
get_pwm_enable, set_pwm_enable, 0);
|
case hwmon_pwm_input:
|
||||||
static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO,
|
case hwmon_pwm_enable:
|
||||||
get_pwm_enable, set_pwm_enable, 1);
|
if (!(fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
||||||
static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO,
|
return S_IRUGO | S_IWUSR;
|
||||||
get_pwm_enable, set_pwm_enable, 2);
|
return 0;
|
||||||
static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO,
|
default:
|
||||||
get_pwm_enable, set_pwm_enable, 3);
|
return 0;
|
||||||
static SENSOR_DEVICE_ATTR(pwm5_enable, S_IWUSR | S_IRUGO,
|
}
|
||||||
get_pwm_enable, set_pwm_enable, 4);
|
}
|
||||||
static SENSOR_DEVICE_ATTR(pwm6_enable, S_IWUSR | S_IRUGO,
|
|
||||||
get_pwm_enable, set_pwm_enable, 5);
|
|
||||||
|
|
||||||
static struct attribute *max31790_attrs[] = {
|
static int max31790_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
u32 attr, int channel, long *val)
|
||||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
{
|
||||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
switch (type) {
|
||||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
case hwmon_fan:
|
||||||
&sensor_dev_attr_fan5_input.dev_attr.attr,
|
return max31790_read_fan(dev, attr, channel, val);
|
||||||
&sensor_dev_attr_fan6_input.dev_attr.attr,
|
case hwmon_pwm:
|
||||||
|
return max31790_read_pwm(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&sensor_dev_attr_fan1_fault.dev_attr.attr,
|
static int max31790_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
&sensor_dev_attr_fan2_fault.dev_attr.attr,
|
u32 attr, int channel, long val)
|
||||||
&sensor_dev_attr_fan3_fault.dev_attr.attr,
|
{
|
||||||
&sensor_dev_attr_fan4_fault.dev_attr.attr,
|
switch (type) {
|
||||||
&sensor_dev_attr_fan5_fault.dev_attr.attr,
|
case hwmon_fan:
|
||||||
&sensor_dev_attr_fan6_fault.dev_attr.attr,
|
return max31790_write_fan(dev, attr, channel, val);
|
||||||
|
case hwmon_pwm:
|
||||||
|
return max31790_write_pwm(dev, attr, channel, val);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&sensor_dev_attr_fan7_input.dev_attr.attr,
|
static umode_t max31790_is_visible(const void *data,
|
||||||
&sensor_dev_attr_fan8_input.dev_attr.attr,
|
enum hwmon_sensor_types type,
|
||||||
&sensor_dev_attr_fan9_input.dev_attr.attr,
|
u32 attr, int channel)
|
||||||
&sensor_dev_attr_fan10_input.dev_attr.attr,
|
{
|
||||||
&sensor_dev_attr_fan11_input.dev_attr.attr,
|
switch (type) {
|
||||||
&sensor_dev_attr_fan12_input.dev_attr.attr,
|
case hwmon_fan:
|
||||||
|
return max31790_fan_is_visible(data, attr, channel);
|
||||||
|
case hwmon_pwm:
|
||||||
|
return max31790_pwm_is_visible(data, attr, channel);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&sensor_dev_attr_fan7_fault.dev_attr.attr,
|
static const u32 max31790_fan_config[] = {
|
||||||
&sensor_dev_attr_fan8_fault.dev_attr.attr,
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
&sensor_dev_attr_fan9_fault.dev_attr.attr,
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
&sensor_dev_attr_fan10_fault.dev_attr.attr,
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
&sensor_dev_attr_fan11_fault.dev_attr.attr,
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
&sensor_dev_attr_fan12_fault.dev_attr.attr,
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
&sensor_dev_attr_fan1_target.dev_attr.attr,
|
static const struct hwmon_channel_info max31790_fan = {
|
||||||
&sensor_dev_attr_fan2_target.dev_attr.attr,
|
.type = hwmon_fan,
|
||||||
&sensor_dev_attr_fan3_target.dev_attr.attr,
|
.config = max31790_fan_config,
|
||||||
&sensor_dev_attr_fan4_target.dev_attr.attr,
|
};
|
||||||
&sensor_dev_attr_fan5_target.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan6_target.dev_attr.attr,
|
|
||||||
|
|
||||||
&sensor_dev_attr_pwm1.dev_attr.attr,
|
static const u32 max31790_pwm_config[] = {
|
||||||
&sensor_dev_attr_pwm2.dev_attr.attr,
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
&sensor_dev_attr_pwm3.dev_attr.attr,
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
&sensor_dev_attr_pwm4.dev_attr.attr,
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
&sensor_dev_attr_pwm5.dev_attr.attr,
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
&sensor_dev_attr_pwm6.dev_attr.attr,
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
static const struct hwmon_channel_info max31790_pwm = {
|
||||||
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
|
.type = hwmon_pwm,
|
||||||
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
.config = max31790_pwm_config,
|
||||||
&sensor_dev_attr_pwm4_enable.dev_attr.attr,
|
};
|
||||||
&sensor_dev_attr_pwm5_enable.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_pwm6_enable.dev_attr.attr,
|
static const struct hwmon_channel_info *max31790_info[] = {
|
||||||
|
&max31790_fan,
|
||||||
|
&max31790_pwm,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static umode_t max31790_attrs_visible(struct kobject *kobj,
|
static const struct hwmon_ops max31790_hwmon_ops = {
|
||||||
struct attribute *a, int n)
|
.is_visible = max31790_is_visible,
|
||||||
{
|
.read = max31790_read,
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
.write = max31790_write,
|
||||||
struct max31790_data *data = dev_get_drvdata(dev);
|
};
|
||||||
struct device_attribute *devattr =
|
|
||||||
container_of(a, struct device_attribute, attr);
|
static const struct hwmon_chip_info max31790_chip_info = {
|
||||||
int index = to_sensor_dev_attr(devattr)->index % NR_CHANNEL;
|
.ops = &max31790_hwmon_ops,
|
||||||
u8 fan_config;
|
.info = max31790_info,
|
||||||
|
|
||||||
fan_config = data->fan_config[index];
|
|
||||||
|
|
||||||
if (n >= NR_CHANNEL * 2 && n < NR_CHANNEL * 4 &&
|
|
||||||
!(fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
|
||||||
return 0;
|
|
||||||
if (n >= NR_CHANNEL * 4 && (fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return a->mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct attribute_group max31790_group = {
|
|
||||||
.attrs = max31790_attrs,
|
|
||||||
.is_visible = max31790_attrs_visible,
|
|
||||||
};
|
};
|
||||||
__ATTRIBUTE_GROUPS(max31790);
|
|
||||||
|
|
||||||
static int max31790_init_client(struct i2c_client *client,
|
static int max31790_init_client(struct i2c_client *client,
|
||||||
struct max31790_data *data)
|
struct max31790_data *data)
|
||||||
|
@ -575,8 +500,10 @@ static int max31790_probe(struct i2c_client *client,
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
client->name, data, max31790_groups);
|
data,
|
||||||
|
&max31790_chip_info,
|
||||||
|
NULL);
|
||||||
|
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
#include <linux/hwmon-sysfs.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insmod parameters
|
* Insmod parameters
|
||||||
|
@ -48,7 +49,7 @@
|
||||||
static int fan_voltage;
|
static int fan_voltage;
|
||||||
/* prescaler: Possible values are 1, 2, 4, 8, 16 or 0 for don't change */
|
/* prescaler: Possible values are 1, 2, 4, 8, 16 or 0 for don't change */
|
||||||
static int prescaler;
|
static int prescaler;
|
||||||
/* clock: The clock frequency of the chip the driver should assume */
|
/* clock: The clock frequency of the chip (max6651 can be clocked externally) */
|
||||||
static int clock = 254000;
|
static int clock = 254000;
|
||||||
|
|
||||||
module_param(fan_voltage, int, S_IRUGO);
|
module_param(fan_voltage, int, S_IRUGO);
|
||||||
|
@ -133,6 +134,19 @@ static const u8 tach_reg[] = {
|
||||||
MAX6650_REG_TACH3,
|
MAX6650_REG_TACH3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct of_device_id max6650_dt_match[] = {
|
||||||
|
{
|
||||||
|
.compatible = "maxim,max6650",
|
||||||
|
.data = (void *)1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.compatible = "maxim,max6651",
|
||||||
|
.data = (void *)4
|
||||||
|
},
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, max6650_dt_match);
|
||||||
|
|
||||||
static struct max6650_data *max6650_update_device(struct device *dev)
|
static struct max6650_data *max6650_update_device(struct device *dev)
|
||||||
{
|
{
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
|
@ -171,6 +185,30 @@ static struct max6650_data *max6650_update_device(struct device *dev)
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the operating mode of the chip (if needed).
|
||||||
|
* mode is one of the MAX6650_CFG_MODE_* values.
|
||||||
|
*/
|
||||||
|
static int max6650_set_operating_mode(struct max6650_data *data, u8 mode)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
u8 config = data->config;
|
||||||
|
|
||||||
|
if (mode == (config & MAX6650_CFG_MODE_MASK))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
config = (config & ~MAX6650_CFG_MODE_MASK) | mode;
|
||||||
|
|
||||||
|
result = i2c_smbus_write_byte_data(data->client, MAX6650_REG_CONFIG,
|
||||||
|
config);
|
||||||
|
if (result < 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
data->config = config;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t get_fan(struct device *dev, struct device_attribute *devattr,
|
static ssize_t get_fan(struct device *dev, struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
|
@ -252,18 +290,12 @@ static ssize_t get_target(struct device *dev, struct device_attribute *devattr,
|
||||||
return sprintf(buf, "%d\n", rpm);
|
return sprintf(buf, "%d\n", rpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_target(struct device *dev, struct device_attribute *devattr,
|
static int max6650_set_target(struct max6650_data *data, unsigned long rpm)
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
int kscale, ktach;
|
int kscale, ktach;
|
||||||
unsigned long rpm;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &rpm);
|
if (rpm == 0)
|
||||||
if (err)
|
return max6650_set_operating_mode(data, MAX6650_CFG_MODE_OFF);
|
||||||
return err;
|
|
||||||
|
|
||||||
rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
|
rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
|
||||||
|
|
||||||
|
@ -274,8 +306,6 @@ static ssize_t set_target(struct device *dev, struct device_attribute *devattr,
|
||||||
* KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1
|
* KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
kscale = DIV_FROM_REG(data->config);
|
kscale = DIV_FROM_REG(data->config);
|
||||||
ktach = ((clock * kscale) / (256 * rpm / 60)) - 1;
|
ktach = ((clock * kscale) / (256 * rpm / 60)) - 1;
|
||||||
if (ktach < 0)
|
if (ktach < 0)
|
||||||
|
@ -284,10 +314,30 @@ static ssize_t set_target(struct device *dev, struct device_attribute *devattr,
|
||||||
ktach = 255;
|
ktach = 255;
|
||||||
data->speed = ktach;
|
data->speed = ktach;
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, MAX6650_REG_SPEED, data->speed);
|
return i2c_smbus_write_byte_data(data->client, MAX6650_REG_SPEED,
|
||||||
|
data->speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_target(struct device *dev, struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
|
unsigned long rpm;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = kstrtoul(buf, 10, &rpm);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
|
err = max6650_set_target(data, rpm);
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,12 +391,11 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
|
||||||
data->dac = 180 - (180 * pwm)/255;
|
data->dac = 180 - (180 * pwm)/255;
|
||||||
else
|
else
|
||||||
data->dac = 76 - (76 * pwm)/255;
|
data->dac = 76 - (76 * pwm)/255;
|
||||||
|
err = i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
||||||
i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return count;
|
return err < 0 ? err : count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -355,14 +404,14 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
|
||||||
* 0 = Fan always on
|
* 0 = Fan always on
|
||||||
* 1 = Open loop, Voltage is set according to speed, not regulated.
|
* 1 = Open loop, Voltage is set according to speed, not regulated.
|
||||||
* 2 = Closed loop, RPM for all fans regulated by fan1 tachometer
|
* 2 = Closed loop, RPM for all fans regulated by fan1 tachometer
|
||||||
|
* 3 = Fan off
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static ssize_t get_enable(struct device *dev, struct device_attribute *devattr,
|
static ssize_t get_enable(struct device *dev, struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
struct max6650_data *data = max6650_update_device(dev);
|
||||||
int mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4;
|
int mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4;
|
||||||
int sysfs_modes[4] = {0, 1, 2, 1};
|
int sysfs_modes[4] = {0, 3, 2, 1};
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", sysfs_modes[mode]);
|
return sprintf(buf, "%d\n", sysfs_modes[mode]);
|
||||||
}
|
}
|
||||||
|
@ -371,25 +420,25 @@ static ssize_t set_enable(struct device *dev, struct device_attribute *devattr,
|
||||||
const char *buf, size_t count)
|
const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
int max6650_modes[3] = {0, 3, 2};
|
|
||||||
unsigned long mode;
|
unsigned long mode;
|
||||||
int err;
|
int err;
|
||||||
|
const u8 max6650_modes[] = {
|
||||||
|
MAX6650_CFG_MODE_ON,
|
||||||
|
MAX6650_CFG_MODE_OPEN_LOOP,
|
||||||
|
MAX6650_CFG_MODE_CLOSED_LOOP,
|
||||||
|
MAX6650_CFG_MODE_OFF,
|
||||||
|
};
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &mode);
|
err = kstrtoul(buf, 10, &mode);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (mode > 2)
|
if (mode >= ARRAY_SIZE(max6650_modes))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
data->config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
max6650_set_operating_mode(data, max6650_modes[mode]);
|
||||||
data->config = (data->config & ~MAX6650_CFG_MODE_MASK)
|
|
||||||
| (max6650_modes[mode] << 4);
|
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, data->config);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
@ -566,6 +615,18 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
int config;
|
int config;
|
||||||
int err = -EIO;
|
int err = -EIO;
|
||||||
|
u32 voltage;
|
||||||
|
u32 prescale;
|
||||||
|
u32 target_rpm;
|
||||||
|
|
||||||
|
if (of_property_read_u32(dev->of_node, "maxim,fan-microvolt",
|
||||||
|
&voltage))
|
||||||
|
voltage = fan_voltage;
|
||||||
|
else
|
||||||
|
voltage /= 1000000; /* Microvolts to volts */
|
||||||
|
if (of_property_read_u32(dev->of_node, "maxim,fan-prescale",
|
||||||
|
&prescale))
|
||||||
|
prescale = prescaler;
|
||||||
|
|
||||||
config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
||||||
|
|
||||||
|
@ -574,7 +635,7 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (fan_voltage) {
|
switch (voltage) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
|
@ -584,14 +645,10 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
config |= MAX6650_CFG_V12;
|
config |= MAX6650_CFG_V12;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(dev, "illegal value for fan_voltage (%d)\n",
|
dev_err(dev, "illegal value for fan_voltage (%d)\n", voltage);
|
||||||
fan_voltage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(dev, "Fan voltage is set to %dV.\n",
|
switch (prescale) {
|
||||||
(config & MAX6650_CFG_V12) ? 12 : 5);
|
|
||||||
|
|
||||||
switch (prescaler) {
|
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -614,28 +671,13 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
| MAX6650_CFG_PRESCALER_16;
|
| MAX6650_CFG_PRESCALER_16;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(dev, "illegal value for prescaler (%d)\n", prescaler);
|
dev_err(dev, "illegal value for prescaler (%d)\n", prescale);
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(dev, "Prescaler is set to %d.\n",
|
dev_info(dev, "Fan voltage: %dV, prescaler: %d.\n",
|
||||||
|
(config & MAX6650_CFG_V12) ? 12 : 5,
|
||||||
1 << (config & MAX6650_CFG_PRESCALER_MASK));
|
1 << (config & MAX6650_CFG_PRESCALER_MASK));
|
||||||
|
|
||||||
/*
|
|
||||||
* If mode is set to "full off", we change it to "open loop" and
|
|
||||||
* set DAC to 255, which has the same effect. We do this because
|
|
||||||
* there's no "full off" mode defined in hwmon specifications.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if ((config & MAX6650_CFG_MODE_MASK) == MAX6650_CFG_MODE_OFF) {
|
|
||||||
dev_dbg(dev, "Change mode to open loop, full off.\n");
|
|
||||||
config = (config & ~MAX6650_CFG_MODE_MASK)
|
|
||||||
| MAX6650_CFG_MODE_OPEN_LOOP;
|
|
||||||
if (i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, 255)) {
|
|
||||||
dev_err(dev, "DAC write error, aborting.\n");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, config)) {
|
if (i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, config)) {
|
||||||
dev_err(dev, "Config write error, aborting.\n");
|
dev_err(dev, "Config write error, aborting.\n");
|
||||||
return err;
|
return err;
|
||||||
|
@ -644,6 +686,12 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
data->config = config;
|
data->config = config;
|
||||||
data->count = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT);
|
data->count = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT);
|
||||||
|
|
||||||
|
if (!of_property_read_u32(client->dev.of_node, "maxim,fan-target-rpm",
|
||||||
|
&target_rpm)) {
|
||||||
|
max6650_set_target(data, target_rpm);
|
||||||
|
max6650_set_operating_mode(data, MAX6650_CFG_MODE_CLOSED_LOOP);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,6 +699,8 @@ static int max6650_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
|
const struct of_device_id *of_id =
|
||||||
|
of_match_device(of_match_ptr(max6650_dt_match), dev);
|
||||||
struct max6650_data *data;
|
struct max6650_data *data;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
int err;
|
int err;
|
||||||
|
@ -661,7 +711,7 @@ static int max6650_probe(struct i2c_client *client,
|
||||||
|
|
||||||
data->client = client;
|
data->client = client;
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
data->nr_fans = id->driver_data;
|
data->nr_fans = of_id ? (int)(uintptr_t)of_id->data : id->driver_data;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize the max6650 chip
|
* Initialize the max6650 chip
|
||||||
|
@ -691,6 +741,7 @@ MODULE_DEVICE_TABLE(i2c, max6650_id);
|
||||||
static struct i2c_driver max6650_driver = {
|
static struct i2c_driver max6650_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "max6650",
|
.name = "max6650",
|
||||||
|
.of_match_table = of_match_ptr(max6650_dt_match),
|
||||||
},
|
},
|
||||||
.probe = max6650_probe,
|
.probe = max6650_probe,
|
||||||
.id_table = max6650_id,
|
.id_table = max6650_id,
|
||||||
|
|
|
@ -195,6 +195,8 @@ superio_exit(int ioreg)
|
||||||
|
|
||||||
#define NUM_FAN 6
|
#define NUM_FAN 6
|
||||||
|
|
||||||
|
#define TEMP_SOURCE_VIRTUAL 0x1f
|
||||||
|
|
||||||
/* Common and NCT6775 specific data */
|
/* Common and NCT6775 specific data */
|
||||||
|
|
||||||
/* Voltage min/max registers for nr=7..14 are in bank 5 */
|
/* Voltage min/max registers for nr=7..14 are in bank 5 */
|
||||||
|
@ -3940,7 +3942,7 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
src = nct6775_read_value(data, data->REG_TEMP_SEL[i]) & 0x1f;
|
src = nct6775_read_value(data, data->REG_TEMP_SEL[i]) & 0x1f;
|
||||||
if (!src || (mask & (1 << src)))
|
if (!src)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (src >= data->temp_label_num ||
|
if (src >= data->temp_label_num ||
|
||||||
|
@ -3952,7 +3954,16 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mask |= 1 << src;
|
/*
|
||||||
|
* For virtual temperature sources, the 'virtual' temperature
|
||||||
|
* for each fan reflects a different temperature, and there
|
||||||
|
* are no duplicates.
|
||||||
|
*/
|
||||||
|
if (src != TEMP_SOURCE_VIRTUAL) {
|
||||||
|
if (mask & (1 << src))
|
||||||
|
continue;
|
||||||
|
mask |= 1 << src;
|
||||||
|
}
|
||||||
|
|
||||||
/* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */
|
/* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */
|
||||||
if (src <= data->temp_fixed_num) {
|
if (src <= data->temp_fixed_num) {
|
||||||
|
@ -4232,11 +4243,11 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (force_id)
|
val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) |
|
||||||
|
superio_inb(sioaddr, SIO_REG_DEVID + 1);
|
||||||
|
if (force_id && val != 0xffff)
|
||||||
val = force_id;
|
val = force_id;
|
||||||
else
|
|
||||||
val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8)
|
|
||||||
| superio_inb(sioaddr, SIO_REG_DEVID + 1);
|
|
||||||
switch (val & SIO_ID_MASK) {
|
switch (val & SIO_ID_MASK) {
|
||||||
case SIO_NCT6106_ID:
|
case SIO_NCT6106_ID:
|
||||||
sio_data->kind = nct6106;
|
sio_data->kind = nct6106;
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
|
||||||
|
|
||||||
#define VENDOR_ID_REG 0x7A /* Any bank */
|
#define VENDOR_ID_REG 0x7A /* Any bank */
|
||||||
#define NUVOTON_ID 0x50
|
#define NUVOTON_ID 0x50
|
||||||
|
@ -153,341 +152,230 @@ static int nct7904_write_reg(struct nct7904_data *data,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FANIN ATTR */
|
static int nct7904_read_fan(struct device *dev, u32 attr, int channel,
|
||||||
static ssize_t show_fan(struct device *dev,
|
long *val)
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||||
|
unsigned int cnt, rpm;
|
||||||
int ret;
|
int ret;
|
||||||
unsigned cnt, rpm;
|
|
||||||
|
|
||||||
ret = nct7904_read_reg16(data, BANK_0, FANIN1_HV_REG + index * 2);
|
switch(attr) {
|
||||||
if (ret < 0)
|
case hwmon_fan_input:
|
||||||
return ret;
|
ret = nct7904_read_reg16(data, BANK_0,
|
||||||
cnt = ((ret & 0xff00) >> 3) | (ret & 0x1f);
|
FANIN1_HV_REG + channel * 2);
|
||||||
if (cnt == 0x1fff)
|
if (ret < 0)
|
||||||
rpm = 0;
|
return ret;
|
||||||
else
|
cnt = ((ret & 0xff00) >> 3) | (ret & 0x1f);
|
||||||
rpm = 1350000 / cnt;
|
if (cnt == 0x1fff)
|
||||||
return sprintf(buf, "%u\n", rpm);
|
rpm = 0;
|
||||||
|
else
|
||||||
|
rpm = 1350000 / cnt;
|
||||||
|
*val = rpm;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static umode_t nct7904_fanin_is_visible(struct kobject *kobj,
|
static umode_t nct7904_fan_is_visible(const void *_data, u32 attr, int channel)
|
||||||
struct attribute *a, int n)
|
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
const struct nct7904_data *data = _data;
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
|
||||||
|
|
||||||
if (data->fanin_mask & (1 << n))
|
if (attr == hwmon_fan_input && data->fanin_mask & (1 << channel))
|
||||||
return a->mode;
|
return S_IRUGO;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
static u8 nct7904_chan_to_index[] = {
|
||||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
0, /* Not used */
|
||||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
|
18, 19, 20, 16
|
||||||
static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan, NULL, 5);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan, NULL, 6);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan, NULL, 7);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan9_input, S_IRUGO, show_fan, NULL, 8);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan10_input, S_IRUGO, show_fan, NULL, 9);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan11_input, S_IRUGO, show_fan, NULL, 10);
|
|
||||||
static SENSOR_DEVICE_ATTR(fan12_input, S_IRUGO, show_fan, NULL, 11);
|
|
||||||
|
|
||||||
static struct attribute *nct7904_fanin_attrs[] = {
|
|
||||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan5_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan6_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan7_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan8_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan9_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan10_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan11_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan12_input.dev_attr.attr,
|
|
||||||
NULL
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group nct7904_fanin_group = {
|
static int nct7904_read_in(struct device *dev, u32 attr, int channel,
|
||||||
.attrs = nct7904_fanin_attrs,
|
long *val)
|
||||||
.is_visible = nct7904_fanin_is_visible,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* VSEN ATTR */
|
|
||||||
static ssize_t show_voltage(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||||
int ret;
|
int ret, volt, index;
|
||||||
int volt;
|
|
||||||
|
|
||||||
ret = nct7904_read_reg16(data, BANK_0, VSEN1_HV_REG + index * 2);
|
index = nct7904_chan_to_index[channel];
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
volt = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
|
||||||
if (index < 14)
|
|
||||||
volt *= 2; /* 0.002V scale */
|
|
||||||
else
|
|
||||||
volt *= 6; /* 0.006V scale */
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", volt);
|
switch(attr) {
|
||||||
|
case hwmon_in_input:
|
||||||
|
ret = nct7904_read_reg16(data, BANK_0,
|
||||||
|
VSEN1_HV_REG + index * 2);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
volt = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||||
|
if (index < 14)
|
||||||
|
volt *= 2; /* 0.002V scale */
|
||||||
|
else
|
||||||
|
volt *= 6; /* 0.006V scale */
|
||||||
|
*val = volt;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_ltemp(struct device *dev,
|
static umode_t nct7904_in_is_visible(const void *_data, u32 attr, int channel)
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
{
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
const struct nct7904_data *data = _data;
|
||||||
int ret;
|
int index = nct7904_chan_to_index[channel];
|
||||||
int temp;
|
|
||||||
|
|
||||||
ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG);
|
if (channel > 0 && attr == hwmon_in_input &&
|
||||||
if (ret < 0)
|
(data->vsen_mask & BIT(index)))
|
||||||
return ret;
|
return S_IRUGO;
|
||||||
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
|
||||||
temp = sign_extend32(temp, 10) * 125;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static umode_t nct7904_vsen_is_visible(struct kobject *kobj,
|
|
||||||
struct attribute *a, int n)
|
|
||||||
{
|
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
|
||||||
|
|
||||||
if (data->vsen_mask & (1 << n))
|
|
||||||
return a->mode;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0);
|
static int nct7904_read_temp(struct device *dev, u32 attr, int channel,
|
||||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 1);
|
long *val)
|
||||||
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_voltage, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_voltage, NULL, 3);
|
|
||||||
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 5);
|
|
||||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 6);
|
|
||||||
static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_voltage, NULL, 7);
|
|
||||||
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_voltage, NULL, 8);
|
|
||||||
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_voltage, NULL, 9);
|
|
||||||
static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_voltage, NULL, 10);
|
|
||||||
static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_voltage, NULL, 11);
|
|
||||||
static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_voltage, NULL, 12);
|
|
||||||
static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, show_voltage, NULL, 13);
|
|
||||||
/*
|
|
||||||
* Next 3 voltage sensors have specific names in the Nuvoton doc
|
|
||||||
* (3VDD, VBAT, 3VSB) but we use vacant numbers for them.
|
|
||||||
*/
|
|
||||||
static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, show_voltage, NULL, 14);
|
|
||||||
static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_voltage, NULL, 15);
|
|
||||||
static SENSOR_DEVICE_ATTR(in20_input, S_IRUGO, show_voltage, NULL, 16);
|
|
||||||
/* This is not a voltage, but a local temperature sensor. */
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_ltemp, NULL, 0);
|
|
||||||
static SENSOR_DEVICE_ATTR(in17_input, S_IRUGO, show_voltage, NULL, 18);
|
|
||||||
static SENSOR_DEVICE_ATTR(in18_input, S_IRUGO, show_voltage, NULL, 19);
|
|
||||||
static SENSOR_DEVICE_ATTR(in19_input, S_IRUGO, show_voltage, NULL, 20);
|
|
||||||
|
|
||||||
static struct attribute *nct7904_vsen_attrs[] = {
|
|
||||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in8_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in9_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in10_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in11_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in12_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in13_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in14_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in15_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in16_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in20_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in17_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in18_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in19_input.dev_attr.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group nct7904_vsen_group = {
|
|
||||||
.attrs = nct7904_vsen_attrs,
|
|
||||||
.is_visible = nct7904_vsen_is_visible,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* CPU_TEMP ATTR */
|
|
||||||
static ssize_t show_tcpu(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||||
int ret;
|
int ret, temp;
|
||||||
int temp;
|
|
||||||
|
|
||||||
ret = nct7904_read_reg16(data, BANK_0, T_CPU1_HV_REG + index * 2);
|
switch(attr) {
|
||||||
if (ret < 0)
|
case hwmon_temp_input:
|
||||||
return ret;
|
if (channel == 0)
|
||||||
|
ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG);
|
||||||
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
else
|
||||||
temp = sign_extend32(temp, 10) * 125;
|
ret = nct7904_read_reg16(data, BANK_0,
|
||||||
return sprintf(buf, "%d\n", temp);
|
T_CPU1_HV_REG + (channel - 1) * 2);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||||
|
*val = sign_extend32(temp, 10) * 125;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static umode_t nct7904_tcpu_is_visible(struct kobject *kobj,
|
static umode_t nct7904_temp_is_visible(const void *_data, u32 attr, int channel)
|
||||||
struct attribute *a, int n)
|
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
const struct nct7904_data *data = _data;
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
|
||||||
|
if (attr == hwmon_temp_input) {
|
||||||
|
if (channel == 0) {
|
||||||
|
if (data->vsen_mask & BIT(17))
|
||||||
|
return S_IRUGO;
|
||||||
|
} else {
|
||||||
|
if (data->tcpu_mask & BIT(channel - 1))
|
||||||
|
return S_IRUGO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data->tcpu_mask & (1 << n))
|
|
||||||
return a->mode;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "temp1_input" reserved for local temp */
|
static int nct7904_read_pwm(struct device *dev, u32 attr, int channel,
|
||||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_tcpu, NULL, 0);
|
long *val)
|
||||||
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcpu, NULL, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tcpu, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_tcpu, NULL, 3);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_tcpu, NULL, 4);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_tcpu, NULL, 5);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_tcpu, NULL, 6);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_tcpu, NULL, 7);
|
|
||||||
|
|
||||||
static struct attribute *nct7904_tcpu_attrs[] = {
|
|
||||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp5_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp6_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp7_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp8_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp9_input.dev_attr.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group nct7904_tcpu_group = {
|
|
||||||
.attrs = nct7904_tcpu_attrs,
|
|
||||||
.is_visible = nct7904_tcpu_is_visible,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* PWM ATTR */
|
|
||||||
static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||||
unsigned long val;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
switch(attr) {
|
||||||
return -EINVAL;
|
case hwmon_pwm_input:
|
||||||
if (val > 255)
|
ret = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + channel);
|
||||||
return -EINVAL;
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
*val = ret;
|
||||||
|
return 0;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + channel);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
ret = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + index, val);
|
*val = ret ? 2 : 1;
|
||||||
|
return 0;
|
||||||
return ret ? ret : count;
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_pwm(struct device *dev,
|
static int nct7904_write_pwm(struct device *dev, u32 attr, int channel,
|
||||||
struct device_attribute *devattr, char *buf)
|
long val)
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
struct nct7904_data *data = dev_get_drvdata(dev);
|
||||||
int val;
|
|
||||||
|
|
||||||
val = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + index);
|
|
||||||
if (val < 0)
|
|
||||||
return val;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t store_enable(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
|
||||||
unsigned long val;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
switch(attr) {
|
||||||
return -EINVAL;
|
case hwmon_pwm_input:
|
||||||
if (val < 1 || val > 2 || (val == 2 && !data->fan_mode[index]))
|
if (val < 0 || val > 255)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
ret = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + channel,
|
||||||
ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + index,
|
val);
|
||||||
val == 2 ? data->fan_mode[index] : 0);
|
return ret;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
return ret ? ret : count;
|
if (val < 1 || val > 2 ||
|
||||||
|
(val == 2 && !data->fan_mode[channel]))
|
||||||
|
return -EINVAL;
|
||||||
|
ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + channel,
|
||||||
|
val == 2 ? data->fan_mode[channel] : 0);
|
||||||
|
return ret;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return 1 for manual mode or 2 for SmartFan mode */
|
static umode_t nct7904_pwm_is_visible(const void *_data, u32 attr, int channel)
|
||||||
static ssize_t show_enable(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
switch(attr) {
|
||||||
struct nct7904_data *data = dev_get_drvdata(dev);
|
case hwmon_pwm_input:
|
||||||
int val;
|
case hwmon_pwm_enable:
|
||||||
|
return S_IRUGO | S_IWUSR;
|
||||||
val = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + index);
|
default:
|
||||||
if (val < 0)
|
return 0;
|
||||||
return val;
|
}
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", val ? 2 : 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2 attributes per channel: pwm and mode */
|
static int nct7904_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR,
|
u32 attr, int channel, long *val)
|
||||||
show_pwm, store_pwm, 0);
|
{
|
||||||
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
|
switch (type) {
|
||||||
show_enable, store_enable, 0);
|
case hwmon_in:
|
||||||
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR,
|
return nct7904_read_in(dev, attr, channel, val);
|
||||||
show_pwm, store_pwm, 1);
|
case hwmon_fan:
|
||||||
static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR,
|
return nct7904_read_fan(dev, attr, channel, val);
|
||||||
show_enable, store_enable, 1);
|
case hwmon_pwm:
|
||||||
static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR,
|
return nct7904_read_pwm(dev, attr, channel, val);
|
||||||
show_pwm, store_pwm, 2);
|
case hwmon_temp:
|
||||||
static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR,
|
return nct7904_read_temp(dev, attr, channel, val);
|
||||||
show_enable, store_enable, 2);
|
default:
|
||||||
static SENSOR_DEVICE_ATTR(pwm4, S_IRUGO | S_IWUSR,
|
return -EOPNOTSUPP;
|
||||||
show_pwm, store_pwm, 3);
|
}
|
||||||
static SENSOR_DEVICE_ATTR(pwm4_enable, S_IRUGO | S_IWUSR,
|
}
|
||||||
show_enable, store_enable, 3);
|
|
||||||
|
|
||||||
static struct attribute *nct7904_fanctl_attrs[] = {
|
static int nct7904_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
&sensor_dev_attr_pwm1.dev_attr.attr,
|
u32 attr, int channel, long val)
|
||||||
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
{
|
||||||
&sensor_dev_attr_pwm2.dev_attr.attr,
|
switch (type) {
|
||||||
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
|
case hwmon_pwm:
|
||||||
&sensor_dev_attr_pwm3.dev_attr.attr,
|
return nct7904_write_pwm(dev, attr, channel, val);
|
||||||
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
default:
|
||||||
&sensor_dev_attr_pwm4.dev_attr.attr,
|
return -EOPNOTSUPP;
|
||||||
&sensor_dev_attr_pwm4_enable.dev_attr.attr,
|
}
|
||||||
NULL
|
}
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group nct7904_fanctl_group = {
|
static umode_t nct7904_is_visible(const void *data,
|
||||||
.attrs = nct7904_fanctl_attrs,
|
enum hwmon_sensor_types type,
|
||||||
};
|
u32 attr, int channel)
|
||||||
|
{
|
||||||
static const struct attribute_group *nct7904_groups[] = {
|
switch (type) {
|
||||||
&nct7904_fanin_group,
|
case hwmon_in:
|
||||||
&nct7904_vsen_group,
|
return nct7904_in_is_visible(data, attr, channel);
|
||||||
&nct7904_tcpu_group,
|
case hwmon_fan:
|
||||||
&nct7904_fanctl_group,
|
return nct7904_fan_is_visible(data, attr, channel);
|
||||||
NULL
|
case hwmon_pwm:
|
||||||
};
|
return nct7904_pwm_is_visible(data, attr, channel);
|
||||||
|
case hwmon_temp:
|
||||||
|
return nct7904_temp_is_visible(data, attr, channel);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||||
static int nct7904_detect(struct i2c_client *client,
|
static int nct7904_detect(struct i2c_client *client,
|
||||||
|
@ -512,6 +400,103 @@ static int nct7904_detect(struct i2c_client *client,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const u32 nct7904_in_config[] = {
|
||||||
|
HWMON_I_INPUT, /* dummy, skipped in is_visible */
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
HWMON_I_INPUT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info nct7904_in = {
|
||||||
|
.type = hwmon_in,
|
||||||
|
.config = nct7904_in_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 nct7904_fan_config[] = {
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
HWMON_F_INPUT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info nct7904_fan = {
|
||||||
|
.type = hwmon_fan,
|
||||||
|
.config = nct7904_fan_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 nct7904_pwm_config[] = {
|
||||||
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info nct7904_pwm = {
|
||||||
|
.type = hwmon_pwm,
|
||||||
|
.config = nct7904_pwm_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u32 nct7904_temp_config[] = {
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
HWMON_T_INPUT,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info nct7904_temp = {
|
||||||
|
.type = hwmon_temp,
|
||||||
|
.config = nct7904_temp_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *nct7904_info[] = {
|
||||||
|
&nct7904_in,
|
||||||
|
&nct7904_fan,
|
||||||
|
&nct7904_pwm,
|
||||||
|
&nct7904_temp,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops nct7904_hwmon_ops = {
|
||||||
|
.is_visible = nct7904_is_visible,
|
||||||
|
.read = nct7904_read,
|
||||||
|
.write = nct7904_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info nct7904_chip_info = {
|
||||||
|
.ops = &nct7904_hwmon_ops,
|
||||||
|
.info = nct7904_info,
|
||||||
|
};
|
||||||
|
|
||||||
static int nct7904_probe(struct i2c_client *client,
|
static int nct7904_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
|
@ -566,8 +551,8 @@ static int nct7904_probe(struct i2c_client *client,
|
||||||
}
|
}
|
||||||
|
|
||||||
hwmon_dev =
|
hwmon_dev =
|
||||||
devm_hwmon_device_register_with_groups(dev, client->name, data,
|
devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||||
nct7904_groups);
|
&nct7904_chip_info, NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,12 +253,9 @@ static const struct ntc_compensation b57330v2103[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ntc_data {
|
struct ntc_data {
|
||||||
struct device *hwmon_dev;
|
|
||||||
struct ntc_thermistor_platform_data *pdata;
|
struct ntc_thermistor_platform_data *pdata;
|
||||||
const struct ntc_compensation *comp;
|
const struct ntc_compensation *comp;
|
||||||
struct device *dev;
|
|
||||||
int n_comp;
|
int n_comp;
|
||||||
char name[PLATFORM_NAME_SIZE];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO)
|
#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO)
|
||||||
|
@ -316,22 +313,22 @@ static const struct of_device_id ntc_match[] = {
|
||||||
MODULE_DEVICE_TABLE(of, ntc_match);
|
MODULE_DEVICE_TABLE(of, ntc_match);
|
||||||
|
|
||||||
static struct ntc_thermistor_platform_data *
|
static struct ntc_thermistor_platform_data *
|
||||||
ntc_thermistor_parse_dt(struct platform_device *pdev)
|
ntc_thermistor_parse_dt(struct device *dev)
|
||||||
{
|
{
|
||||||
struct iio_channel *chan;
|
struct iio_channel *chan;
|
||||||
enum iio_chan_type type;
|
enum iio_chan_type type;
|
||||||
struct device_node *np = pdev->dev.of_node;
|
struct device_node *np = dev->of_node;
|
||||||
struct ntc_thermistor_platform_data *pdata;
|
struct ntc_thermistor_platform_data *pdata;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!np)
|
if (!np)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||||
if (!pdata)
|
if (!pdata)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
chan = iio_channel_get(&pdev->dev, NULL);
|
chan = devm_iio_channel_get(dev, NULL);
|
||||||
if (IS_ERR(chan))
|
if (IS_ERR(chan))
|
||||||
return ERR_CAST(chan);
|
return ERR_CAST(chan);
|
||||||
|
|
||||||
|
@ -359,22 +356,15 @@ ntc_thermistor_parse_dt(struct platform_device *pdev)
|
||||||
|
|
||||||
return pdata;
|
return pdata;
|
||||||
}
|
}
|
||||||
static void ntc_iio_channel_release(struct ntc_thermistor_platform_data *pdata)
|
|
||||||
{
|
|
||||||
if (pdata->chan)
|
|
||||||
iio_channel_release(pdata->chan);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
static struct ntc_thermistor_platform_data *
|
static struct ntc_thermistor_platform_data *
|
||||||
ntc_thermistor_parse_dt(struct platform_device *pdev)
|
ntc_thermistor_parse_dt(struct device *dev)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ntc_match NULL
|
#define ntc_match NULL
|
||||||
|
|
||||||
static void ntc_iio_channel_release(struct ntc_thermistor_platform_data *pdata)
|
|
||||||
{ }
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
|
static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
|
||||||
|
@ -516,9 +506,8 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ntc_read_temp(void *dev, int *temp)
|
static int ntc_read_temp(void *data, int *temp)
|
||||||
{
|
{
|
||||||
struct ntc_data *data = dev_get_drvdata(dev);
|
|
||||||
int ohm;
|
int ohm;
|
||||||
|
|
||||||
ohm = ntc_thermistor_get_ohm(data);
|
ohm = ntc_thermistor_get_ohm(data);
|
||||||
|
@ -530,14 +519,6 @@ static int ntc_read_temp(void *dev, int *temp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t ntc_show_name(struct device *dev,
|
|
||||||
struct device_attribute *attr, char *buf)
|
|
||||||
{
|
|
||||||
struct ntc_data *data = dev_get_drvdata(dev);
|
|
||||||
|
|
||||||
return sprintf(buf, "%s\n", data->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ntc_show_type(struct device *dev,
|
static ssize_t ntc_show_type(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
@ -559,18 +540,13 @@ static ssize_t ntc_show_temp(struct device *dev,
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0);
|
static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0);
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0);
|
||||||
static DEVICE_ATTR(name, S_IRUGO, ntc_show_name, NULL);
|
|
||||||
|
|
||||||
static struct attribute *ntc_attributes[] = {
|
static struct attribute *ntc_attrs[] = {
|
||||||
&dev_attr_name.attr,
|
|
||||||
&sensor_dev_attr_temp1_type.dev_attr.attr,
|
&sensor_dev_attr_temp1_type.dev_attr.attr,
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
ATTRIBUTE_GROUPS(ntc);
|
||||||
static const struct attribute_group ntc_attr_group = {
|
|
||||||
.attrs = ntc_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct thermal_zone_of_device_ops ntc_of_thermal_ops = {
|
static const struct thermal_zone_of_device_ops ntc_of_thermal_ops = {
|
||||||
.get_temp = ntc_read_temp,
|
.get_temp = ntc_read_temp,
|
||||||
|
@ -579,33 +555,34 @@ static const struct thermal_zone_of_device_ops ntc_of_thermal_ops = {
|
||||||
static int ntc_thermistor_probe(struct platform_device *pdev)
|
static int ntc_thermistor_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct thermal_zone_device *tz;
|
struct thermal_zone_device *tz;
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
const struct of_device_id *of_id =
|
const struct of_device_id *of_id =
|
||||||
of_match_device(of_match_ptr(ntc_match), &pdev->dev);
|
of_match_device(of_match_ptr(ntc_match), dev);
|
||||||
const struct platform_device_id *pdev_id;
|
const struct platform_device_id *pdev_id;
|
||||||
struct ntc_thermistor_platform_data *pdata;
|
struct ntc_thermistor_platform_data *pdata;
|
||||||
|
struct device *hwmon_dev;
|
||||||
struct ntc_data *data;
|
struct ntc_data *data;
|
||||||
int ret;
|
|
||||||
|
|
||||||
pdata = ntc_thermistor_parse_dt(pdev);
|
pdata = ntc_thermistor_parse_dt(dev);
|
||||||
if (IS_ERR(pdata))
|
if (IS_ERR(pdata))
|
||||||
return PTR_ERR(pdata);
|
return PTR_ERR(pdata);
|
||||||
else if (pdata == NULL)
|
else if (pdata == NULL)
|
||||||
pdata = dev_get_platdata(&pdev->dev);
|
pdata = dev_get_platdata(dev);
|
||||||
|
|
||||||
if (!pdata) {
|
if (!pdata) {
|
||||||
dev_err(&pdev->dev, "No platform init data supplied.\n");
|
dev_err(dev, "No platform init data supplied.\n");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Either one of the two is required. */
|
/* Either one of the two is required. */
|
||||||
if (!pdata->read_uv && !pdata->read_ohm) {
|
if (!pdata->read_uv && !pdata->read_ohm) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(dev,
|
||||||
"Both read_uv and read_ohm missing. Need either one of the two.\n");
|
"Both read_uv and read_ohm missing. Need either one of the two.\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdata->read_uv && pdata->read_ohm) {
|
if (pdata->read_uv && pdata->read_ohm) {
|
||||||
dev_warn(&pdev->dev,
|
dev_warn(dev,
|
||||||
"Only one of read_uv and read_ohm is needed; ignoring read_uv.\n");
|
"Only one of read_uv and read_ohm is needed; ignoring read_uv.\n");
|
||||||
pdata->read_uv = NULL;
|
pdata->read_uv = NULL;
|
||||||
}
|
}
|
||||||
|
@ -617,20 +594,17 @@ static int ntc_thermistor_probe(struct platform_device *pdev)
|
||||||
NTC_CONNECTED_POSITIVE) ||
|
NTC_CONNECTED_POSITIVE) ||
|
||||||
(pdata->connect != NTC_CONNECTED_POSITIVE &&
|
(pdata->connect != NTC_CONNECTED_POSITIVE &&
|
||||||
pdata->connect != NTC_CONNECTED_GROUND))) {
|
pdata->connect != NTC_CONNECTED_GROUND))) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(dev, "Required data to use read_uv not supplied.\n");
|
||||||
"Required data to use read_uv not supplied.\n");
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = devm_kzalloc(&pdev->dev, sizeof(struct ntc_data), GFP_KERNEL);
|
data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL);
|
||||||
if (!data)
|
if (!data)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
pdev_id = of_id ? of_id->data : platform_get_device_id(pdev);
|
pdev_id = of_id ? of_id->data : platform_get_device_id(pdev);
|
||||||
|
|
||||||
data->dev = &pdev->dev;
|
|
||||||
data->pdata = pdata;
|
data->pdata = pdata;
|
||||||
strlcpy(data->name, pdev_id->name, sizeof(data->name));
|
|
||||||
|
|
||||||
switch (pdev_id->driver_data) {
|
switch (pdev_id->driver_data) {
|
||||||
case TYPE_NCPXXWB473:
|
case TYPE_NCPXXWB473:
|
||||||
|
@ -654,49 +628,25 @@ static int ntc_thermistor_probe(struct platform_device *pdev)
|
||||||
data->n_comp = ARRAY_SIZE(ncpXXxh103);
|
data->n_comp = ARRAY_SIZE(ncpXXxh103);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n",
|
dev_err(dev, "Unknown device type: %lu(%s)\n",
|
||||||
pdev_id->driver_data, pdev_id->name);
|
pdev_id->driver_data, pdev_id->name);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, data);
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, pdev_id->name,
|
||||||
|
data, ntc_groups);
|
||||||
ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group);
|
if (IS_ERR(hwmon_dev)) {
|
||||||
if (ret) {
|
dev_err(dev, "unable to register as hwmon device.\n");
|
||||||
dev_err(data->dev, "unable to create sysfs files\n");
|
return PTR_ERR(hwmon_dev);
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data->hwmon_dev = hwmon_device_register(data->dev);
|
dev_info(dev, "Thermistor type: %s successfully probed.\n",
|
||||||
if (IS_ERR(data->hwmon_dev)) {
|
pdev_id->name);
|
||||||
dev_err(data->dev, "unable to register as hwmon device.\n");
|
|
||||||
ret = PTR_ERR(data->hwmon_dev);
|
|
||||||
goto err_after_sysfs;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev_info(&pdev->dev, "Thermistor type: %s successfully probed.\n",
|
tz = devm_thermal_zone_of_sensor_register(dev, 0, data,
|
||||||
pdev_id->name);
|
|
||||||
|
|
||||||
tz = devm_thermal_zone_of_sensor_register(data->dev, 0, data->dev,
|
|
||||||
&ntc_of_thermal_ops);
|
&ntc_of_thermal_ops);
|
||||||
if (IS_ERR(tz))
|
if (IS_ERR(tz))
|
||||||
dev_dbg(&pdev->dev, "Failed to register to thermal fw.\n");
|
dev_dbg(dev, "Failed to register to thermal fw.\n");
|
||||||
|
|
||||||
return 0;
|
|
||||||
err_after_sysfs:
|
|
||||||
sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
|
|
||||||
ntc_iio_channel_release(pdata);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ntc_thermistor_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct ntc_data *data = platform_get_drvdata(pdev);
|
|
||||||
struct ntc_thermistor_platform_data *pdata = data->pdata;
|
|
||||||
|
|
||||||
hwmon_device_unregister(data->hwmon_dev);
|
|
||||||
sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
|
|
||||||
ntc_iio_channel_release(pdata);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -707,7 +657,6 @@ static struct platform_driver ntc_thermistor_driver = {
|
||||||
.of_match_table = of_match_ptr(ntc_match),
|
.of_match_table = of_match_ptr(ntc_match),
|
||||||
},
|
},
|
||||||
.probe = ntc_thermistor_probe,
|
.probe = ntc_thermistor_probe,
|
||||||
.remove = ntc_thermistor_remove,
|
|
||||||
.id_table = ntc_thermistor_id,
|
.id_table = ntc_thermistor_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -126,12 +126,12 @@ config SENSORS_TPS40422
|
||||||
be called tps40422.
|
be called tps40422.
|
||||||
|
|
||||||
config SENSORS_UCD9000
|
config SENSORS_UCD9000
|
||||||
tristate "TI UCD90120, UCD90124, UCD9090, UCD90910"
|
tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910"
|
||||||
default n
|
default n
|
||||||
help
|
help
|
||||||
If you say yes here you get hardware monitoring support for TI
|
If you say yes here you get hardware monitoring support for TI
|
||||||
UCD90120, UCD90124, UCD9090, UCD90910 Sequencer and System Health
|
UCD90120, UCD90124, UCD90160, UCD9090, UCD90910, Sequencer and System
|
||||||
Controllers.
|
Health Controllers.
|
||||||
|
|
||||||
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 ucd9000.
|
be called ucd9000.
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/i2c/pmbus.h>
|
||||||
#include "pmbus.h"
|
#include "pmbus.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -167,14 +168,26 @@ static int pmbus_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct pmbus_driver_info *info;
|
struct pmbus_driver_info *info;
|
||||||
|
struct pmbus_platform_data *pdata = NULL;
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
|
||||||
info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info),
|
info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
||||||
GFP_KERNEL);
|
|
||||||
if (!info)
|
if (!info)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (!strcmp(id->name, "dps460") || !strcmp(id->name, "dps800") ||
|
||||||
|
!strcmp(id->name, "sgd009")) {
|
||||||
|
pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!pdata)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
pdata->flags = PMBUS_SKIP_STATUS_CHECK;
|
||||||
|
}
|
||||||
|
|
||||||
info->pages = id->driver_data;
|
info->pages = id->driver_data;
|
||||||
info->identify = pmbus_identify;
|
info->identify = pmbus_identify;
|
||||||
|
dev->platform_data = pdata;
|
||||||
|
|
||||||
return pmbus_do_probe(client, id, info);
|
return pmbus_do_probe(client, id, info);
|
||||||
}
|
}
|
||||||
|
@ -186,6 +199,8 @@ static const struct i2c_device_id pmbus_id[] = {
|
||||||
{"adp4000", 1},
|
{"adp4000", 1},
|
||||||
{"bmr453", 1},
|
{"bmr453", 1},
|
||||||
{"bmr454", 1},
|
{"bmr454", 1},
|
||||||
|
{"dps460", 1},
|
||||||
|
{"dps800", 1},
|
||||||
{"mdt040", 1},
|
{"mdt040", 1},
|
||||||
{"ncp4200", 1},
|
{"ncp4200", 1},
|
||||||
{"ncp4208", 1},
|
{"ncp4208", 1},
|
||||||
|
@ -193,6 +208,7 @@ static const struct i2c_device_id pmbus_id[] = {
|
||||||
{"pdt006", 1},
|
{"pdt006", 1},
|
||||||
{"pdt012", 1},
|
{"pdt012", 1},
|
||||||
{"pmbus", 0},
|
{"pmbus", 0},
|
||||||
|
{"sgd009", 1},
|
||||||
{"tps40400", 1},
|
{"tps40400", 1},
|
||||||
{"tps544b20", 1},
|
{"tps544b20", 1},
|
||||||
{"tps544b25", 1},
|
{"tps544b25", 1},
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
#include <linux/i2c/pmbus.h>
|
#include <linux/i2c/pmbus.h>
|
||||||
#include "pmbus.h"
|
#include "pmbus.h"
|
||||||
|
|
||||||
enum chips { ucd9000, ucd90120, ucd90124, ucd9090, ucd90910 };
|
enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 };
|
||||||
|
|
||||||
#define UCD9000_MONITOR_CONFIG 0xd5
|
#define UCD9000_MONITOR_CONFIG 0xd5
|
||||||
#define UCD9000_NUM_PAGES 0xd6
|
#define UCD9000_NUM_PAGES 0xd6
|
||||||
|
@ -112,6 +112,7 @@ static const struct i2c_device_id ucd9000_id[] = {
|
||||||
{"ucd9000", ucd9000},
|
{"ucd9000", ucd9000},
|
||||||
{"ucd90120", ucd90120},
|
{"ucd90120", ucd90120},
|
||||||
{"ucd90124", ucd90124},
|
{"ucd90124", ucd90124},
|
||||||
|
{"ucd90160", ucd90160},
|
||||||
{"ucd9090", ucd9090},
|
{"ucd9090", ucd9090},
|
||||||
{"ucd90910", ucd90910},
|
{"ucd90910", ucd90910},
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -255,7 +255,6 @@ static const struct of_device_id scpi_of_match[] = {
|
||||||
static struct platform_driver scpi_hwmon_platdrv = {
|
static struct platform_driver scpi_hwmon_platdrv = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "scpi-hwmon",
|
.name = "scpi-hwmon",
|
||||||
.owner = THIS_MODULE,
|
|
||||||
.of_match_table = scpi_of_match,
|
.of_match_table = scpi_of_match,
|
||||||
},
|
},
|
||||||
.probe = scpi_hwmon_probe,
|
.probe = scpi_hwmon_probe,
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/thermal.h>
|
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
|
||||||
#define DRIVER_NAME "tmp102"
|
#define DRIVER_NAME "tmp102"
|
||||||
|
@ -79,84 +78,113 @@ static inline u16 tmp102_mC_to_reg(int val)
|
||||||
return (val * 128) / 1000;
|
return (val * 128) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tmp102_read_temp(void *dev, int *temp)
|
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *temp)
|
||||||
{
|
{
|
||||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||||
unsigned int reg;
|
unsigned int regval;
|
||||||
int ret;
|
int err, reg;
|
||||||
|
|
||||||
if (time_before(jiffies, tmp102->ready_time)) {
|
switch (attr) {
|
||||||
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
|
case hwmon_temp_input:
|
||||||
return -EAGAIN;
|
/* Is it too early to return a conversion ? */
|
||||||
|
if (time_before(jiffies, tmp102->ready_time)) {
|
||||||
|
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
reg = TMP102_TEMP_REG;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
|
reg = TMP102_TLOW_REG;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
reg = TMP102_THIGH_REG;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = regmap_read(tmp102->regmap, TMP102_TEMP_REG, ®);
|
err = regmap_read(tmp102->regmap, reg, ®val);
|
||||||
if (ret < 0)
|
if (err < 0)
|
||||||
return ret;
|
return err;
|
||||||
|
*temp = tmp102_reg_to_mC(regval);
|
||||||
*temp = tmp102_reg_to_mC(reg);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t tmp102_show_temp(struct device *dev,
|
static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *attr,
|
u32 attr, int channel, long temp)
|
||||||
char *buf)
|
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
|
||||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||||
int regaddr = sda->index;
|
int reg;
|
||||||
unsigned int reg;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
if (regaddr == TMP102_TEMP_REG &&
|
switch (attr) {
|
||||||
time_before(jiffies, tmp102->ready_time))
|
case hwmon_temp_max_hyst:
|
||||||
return -EAGAIN;
|
reg = TMP102_TLOW_REG;
|
||||||
|
break;
|
||||||
|
case hwmon_temp_max:
|
||||||
|
reg = TMP102_THIGH_REG;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
err = regmap_read(tmp102->regmap, regaddr, ®);
|
temp = clamp_val(temp, -256000, 255000);
|
||||||
if (err < 0)
|
return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(temp));
|
||||||
return err;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", tmp102_reg_to_mC(reg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t tmp102_set_temp(struct device *dev,
|
static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *attr,
|
u32 attr, int channel)
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
if (type != hwmon_temp)
|
||||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
return 0;
|
||||||
int reg = sda->index;
|
|
||||||
long val;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
switch (attr) {
|
||||||
return -EINVAL;
|
case hwmon_temp_input:
|
||||||
val = clamp_val(val, -256000, 255000);
|
return S_IRUGO;
|
||||||
|
case hwmon_temp_max_hyst:
|
||||||
err = regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val));
|
case hwmon_temp_max:
|
||||||
return err ? : count;
|
return S_IRUGO | S_IWUSR;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp102_show_temp, NULL,
|
static u32 tmp102_chip_config[] = {
|
||||||
TMP102_TEMP_REG);
|
HWMON_C_REGISTER_TZ,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
static const struct hwmon_channel_info tmp102_chip = {
|
||||||
tmp102_set_temp, TMP102_TLOW_REG);
|
.type = hwmon_chip,
|
||||||
|
.config = tmp102_chip_config,
|
||||||
|
};
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
static u32 tmp102_temp_config[] = {
|
||||||
tmp102_set_temp, TMP102_THIGH_REG);
|
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
static struct attribute *tmp102_attrs[] = {
|
static const struct hwmon_channel_info tmp102_temp = {
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
.type = hwmon_temp,
|
||||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
.config = tmp102_temp_config,
|
||||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *tmp102_info[] = {
|
||||||
|
&tmp102_chip,
|
||||||
|
&tmp102_temp,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
ATTRIBUTE_GROUPS(tmp102);
|
|
||||||
|
|
||||||
static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = {
|
static const struct hwmon_ops tmp102_hwmon_ops = {
|
||||||
.get_temp = tmp102_read_temp,
|
.is_visible = tmp102_is_visible,
|
||||||
|
.read = tmp102_read,
|
||||||
|
.write = tmp102_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info tmp102_chip_info = {
|
||||||
|
.ops = &tmp102_hwmon_ops,
|
||||||
|
.info = tmp102_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void tmp102_restore_config(void *data)
|
static void tmp102_restore_config(void *data)
|
||||||
|
@ -188,7 +216,7 @@ static const struct regmap_config tmp102_regmap_config = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static int tmp102_probe(struct i2c_client *client,
|
static int tmp102_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
|
@ -249,16 +277,14 @@ static int tmp102_probe(struct i2c_client *client,
|
||||||
tmp102->ready_time += msecs_to_jiffies(CONVERSION_TIME_MS);
|
tmp102->ready_time += msecs_to_jiffies(CONVERSION_TIME_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
tmp102,
|
tmp102,
|
||||||
tmp102_groups);
|
&tmp102_chip_info,
|
||||||
|
NULL);
|
||||||
if (IS_ERR(hwmon_dev)) {
|
if (IS_ERR(hwmon_dev)) {
|
||||||
dev_dbg(dev, "unable to register hwmon device\n");
|
dev_dbg(dev, "unable to register hwmon device\n");
|
||||||
return PTR_ERR(hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
}
|
}
|
||||||
devm_thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev,
|
|
||||||
&tmp102_of_thermal_ops);
|
|
||||||
|
|
||||||
dev_info(dev, "initialized\n");
|
dev_info(dev, "initialized\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -72,6 +72,10 @@ MODULE_DEVICE_TABLE(i2c, tmp421_id);
|
||||||
struct tmp421_data {
|
struct tmp421_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
|
u32 temp_config[5];
|
||||||
|
struct hwmon_channel_info temp_info;
|
||||||
|
const struct hwmon_channel_info *info[2];
|
||||||
|
struct hwmon_chip_info chip;
|
||||||
char valid;
|
char valid;
|
||||||
unsigned long last_updated;
|
unsigned long last_updated;
|
||||||
int channels;
|
int channels;
|
||||||
|
@ -125,85 +129,46 @@ static struct tmp421_data *tmp421_update_device(struct device *dev)
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_temp_value(struct device *dev,
|
static int tmp421_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *devattr, char *buf)
|
u32 attr, int channel, long *val)
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
struct tmp421_data *tmp421 = tmp421_update_device(dev);
|
||||||
struct tmp421_data *data = tmp421_update_device(dev);
|
|
||||||
int temp;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
switch (attr) {
|
||||||
if (data->config & TMP421_CONFIG_RANGE)
|
case hwmon_temp_input:
|
||||||
temp = temp_from_u16(data->temp[index]);
|
if (tmp421->config & TMP421_CONFIG_RANGE)
|
||||||
else
|
*val = temp_from_u16(tmp421->temp[channel]);
|
||||||
temp = temp_from_s16(data->temp[index]);
|
else
|
||||||
mutex_unlock(&data->update_lock);
|
*val = temp_from_s16(tmp421->temp[channel]);
|
||||||
|
return 0;
|
||||||
|
case hwmon_temp_fault:
|
||||||
|
/*
|
||||||
|
* The OPEN bit signals a fault. This is bit 0 of the temperature
|
||||||
|
* register (low byte).
|
||||||
|
*/
|
||||||
|
*val = tmp421->temp[channel] & 0x01;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", temp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_fault(struct device *dev,
|
static umode_t tmp421_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||||
struct device_attribute *devattr, char *buf)
|
u32 attr, int channel)
|
||||||
{
|
{
|
||||||
int index = to_sensor_dev_attr(devattr)->index;
|
switch (attr) {
|
||||||
struct tmp421_data *data = tmp421_update_device(dev);
|
case hwmon_temp_fault:
|
||||||
|
if (channel == 0)
|
||||||
/*
|
return 0;
|
||||||
* The OPEN bit signals a fault. This is bit 0 of the temperature
|
return S_IRUGO;
|
||||||
* register (low byte).
|
case hwmon_temp_input:
|
||||||
*/
|
return S_IRUGO;
|
||||||
if (data->temp[index] & 0x01)
|
default:
|
||||||
return sprintf(buf, "1\n");
|
return 0;
|
||||||
else
|
}
|
||||||
return sprintf(buf, "0\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static umode_t tmp421_is_visible(struct kobject *kobj, struct attribute *a,
|
|
||||||
int n)
|
|
||||||
{
|
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
|
||||||
struct tmp421_data *data = dev_get_drvdata(dev);
|
|
||||||
struct device_attribute *devattr;
|
|
||||||
unsigned int index;
|
|
||||||
|
|
||||||
devattr = container_of(a, struct device_attribute, attr);
|
|
||||||
index = to_sensor_dev_attr(devattr)->index;
|
|
||||||
|
|
||||||
if (index < data->channels)
|
|
||||||
return a->mode;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_value, NULL, 0);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_value, NULL, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_fault, NULL, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_value, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_fault, NULL, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_value, NULL, 3);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_fault, NULL, 3);
|
|
||||||
|
|
||||||
static struct attribute *tmp421_attr[] = {
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp2_fault.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp3_fault.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_temp4_fault.dev_attr.attr,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group tmp421_group = {
|
|
||||||
.attrs = tmp421_attr,
|
|
||||||
.is_visible = tmp421_is_visible,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct attribute_group *tmp421_groups[] = {
|
|
||||||
&tmp421_group,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static int tmp421_init_client(struct i2c_client *client)
|
static int tmp421_init_client(struct i2c_client *client)
|
||||||
{
|
{
|
||||||
int config, config_orig;
|
int config, config_orig;
|
||||||
|
@ -289,13 +254,18 @@ static int tmp421_detect(struct i2c_client *client,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct hwmon_ops tmp421_ops = {
|
||||||
|
.is_visible = tmp421_is_visible,
|
||||||
|
.read = tmp421_read,
|
||||||
|
};
|
||||||
|
|
||||||
static int tmp421_probe(struct i2c_client *client,
|
static int tmp421_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
struct tmp421_data *data;
|
struct tmp421_data *data;
|
||||||
int err;
|
int i, err;
|
||||||
|
|
||||||
data = devm_kzalloc(dev, sizeof(struct tmp421_data), GFP_KERNEL);
|
data = devm_kzalloc(dev, sizeof(struct tmp421_data), GFP_KERNEL);
|
||||||
if (!data)
|
if (!data)
|
||||||
|
@ -309,8 +279,21 @@ static int tmp421_probe(struct i2c_client *client,
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
for (i = 0; i < data->channels; i++)
|
||||||
data, tmp421_groups);
|
data->temp_config[i] = HWMON_T_INPUT | HWMON_T_FAULT;
|
||||||
|
|
||||||
|
data->chip.ops = &tmp421_ops;
|
||||||
|
data->chip.info = data->info;
|
||||||
|
|
||||||
|
data->info[0] = &data->temp_info;
|
||||||
|
|
||||||
|
data->temp_info.type = hwmon_temp;
|
||||||
|
data->temp_info.config = data->temp_config;
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||||
|
data,
|
||||||
|
&data->chip,
|
||||||
|
NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,787 @@
|
||||||
|
/*
|
||||||
|
* APM X-Gene SoC Hardware Monitoring Driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016, Applied Micro Circuits Corporation
|
||||||
|
* Author: Loc Ho <lho@apm.com>
|
||||||
|
* Hoan Tran <hotran@apm.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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This driver provides the following features:
|
||||||
|
* - Retrieve CPU total power (uW)
|
||||||
|
* - Retrieve IO total power (uW)
|
||||||
|
* - Retrieve SoC temperature (milli-degree C) and alarm
|
||||||
|
*/
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/dma-mapping.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/kfifo.h>
|
||||||
|
#include <linux/mailbox_controller.h>
|
||||||
|
#include <linux/mailbox_client.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#include <acpi/pcc.h>
|
||||||
|
|
||||||
|
/* SLIMpro message defines */
|
||||||
|
#define MSG_TYPE_DBG 0
|
||||||
|
#define MSG_TYPE_ERR 7
|
||||||
|
#define MSG_TYPE_PWRMGMT 9
|
||||||
|
|
||||||
|
#define MSG_TYPE(v) (((v) & 0xF0000000) >> 28)
|
||||||
|
#define MSG_TYPE_SET(v) (((v) << 28) & 0xF0000000)
|
||||||
|
#define MSG_SUBTYPE(v) (((v) & 0x0F000000) >> 24)
|
||||||
|
#define MSG_SUBTYPE_SET(v) (((v) << 24) & 0x0F000000)
|
||||||
|
|
||||||
|
#define DBG_SUBTYPE_SENSOR_READ 4
|
||||||
|
#define SENSOR_RD_MSG 0x04FFE902
|
||||||
|
#define SENSOR_RD_EN_ADDR(a) ((a) & 0x000FFFFF)
|
||||||
|
#define PMD_PWR_REG 0x20
|
||||||
|
#define PMD_PWR_MW_REG 0x26
|
||||||
|
#define SOC_PWR_REG 0x21
|
||||||
|
#define SOC_PWR_MW_REG 0x27
|
||||||
|
#define SOC_TEMP_REG 0x10
|
||||||
|
|
||||||
|
#define TEMP_NEGATIVE_BIT 8
|
||||||
|
#define SENSOR_INVALID_DATA BIT(15)
|
||||||
|
|
||||||
|
#define PWRMGMT_SUBTYPE_TPC 1
|
||||||
|
#define TPC_ALARM 2
|
||||||
|
#define TPC_GET_ALARM 3
|
||||||
|
#define TPC_CMD(v) (((v) & 0x00FF0000) >> 16)
|
||||||
|
#define TPC_CMD_SET(v) (((v) << 16) & 0x00FF0000)
|
||||||
|
#define TPC_EN_MSG(hndl, cmd, type) \
|
||||||
|
(MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \
|
||||||
|
MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type)
|
||||||
|
|
||||||
|
/* PCC defines */
|
||||||
|
#define PCC_SIGNATURE_MASK 0x50424300
|
||||||
|
#define PCCC_GENERATE_DB_INT BIT(15)
|
||||||
|
#define PCCS_CMD_COMPLETE BIT(0)
|
||||||
|
#define PCCS_SCI_DOORBEL BIT(1)
|
||||||
|
#define PCCS_PLATFORM_NOTIFICATION BIT(3)
|
||||||
|
/*
|
||||||
|
* Arbitrary retries in case the remote processor is slow to respond
|
||||||
|
* to PCC commands
|
||||||
|
*/
|
||||||
|
#define PCC_NUM_RETRIES 500
|
||||||
|
|
||||||
|
#define ASYNC_MSG_FIFO_SIZE 16
|
||||||
|
#define MBOX_OP_TIMEOUTMS 1000
|
||||||
|
|
||||||
|
#define WATT_TO_mWATT(x) ((x) * 1000)
|
||||||
|
#define mWATT_TO_uWATT(x) ((x) * 1000)
|
||||||
|
#define CELSIUS_TO_mCELSIUS(x) ((x) * 1000)
|
||||||
|
|
||||||
|
#define to_xgene_hwmon_dev(cl) \
|
||||||
|
container_of(cl, struct xgene_hwmon_dev, mbox_client)
|
||||||
|
|
||||||
|
struct slimpro_resp_msg {
|
||||||
|
u32 msg;
|
||||||
|
u32 param1;
|
||||||
|
u32 param2;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct xgene_hwmon_dev {
|
||||||
|
struct device *dev;
|
||||||
|
struct mbox_chan *mbox_chan;
|
||||||
|
struct mbox_client mbox_client;
|
||||||
|
int mbox_idx;
|
||||||
|
|
||||||
|
spinlock_t kfifo_lock;
|
||||||
|
struct mutex rd_mutex;
|
||||||
|
struct completion rd_complete;
|
||||||
|
int resp_pending;
|
||||||
|
struct slimpro_resp_msg sync_msg;
|
||||||
|
|
||||||
|
struct work_struct workq;
|
||||||
|
struct kfifo_rec_ptr_1 async_msg_fifo;
|
||||||
|
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
bool temp_critical_alarm;
|
||||||
|
|
||||||
|
phys_addr_t comm_base_addr;
|
||||||
|
void *pcc_comm_addr;
|
||||||
|
u64 usecs_lat;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function tests and clears a bitmask then returns its old value
|
||||||
|
*/
|
||||||
|
static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask)
|
||||||
|
{
|
||||||
|
u16 ret, val;
|
||||||
|
|
||||||
|
val = le16_to_cpu(READ_ONCE(*addr));
|
||||||
|
ret = val & mask;
|
||||||
|
val &= ~mask;
|
||||||
|
WRITE_ONCE(*addr, cpu_to_le16(val));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
|
||||||
|
{
|
||||||
|
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
|
||||||
|
u32 *ptr = (void *)(generic_comm_base + 1);
|
||||||
|
int rc, i;
|
||||||
|
u16 val;
|
||||||
|
|
||||||
|
mutex_lock(&ctx->rd_mutex);
|
||||||
|
init_completion(&ctx->rd_complete);
|
||||||
|
ctx->resp_pending = true;
|
||||||
|
|
||||||
|
/* Write signature for subspace */
|
||||||
|
WRITE_ONCE(generic_comm_base->signature,
|
||||||
|
cpu_to_le32(PCC_SIGNATURE_MASK | ctx->mbox_idx));
|
||||||
|
|
||||||
|
/* Write to the shared command region */
|
||||||
|
WRITE_ONCE(generic_comm_base->command,
|
||||||
|
cpu_to_le16(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT));
|
||||||
|
|
||||||
|
/* Flip CMD COMPLETE bit */
|
||||||
|
val = le16_to_cpu(READ_ONCE(generic_comm_base->status));
|
||||||
|
val &= ~PCCS_CMD_COMPLETE;
|
||||||
|
WRITE_ONCE(generic_comm_base->status, cpu_to_le16(val));
|
||||||
|
|
||||||
|
/* Copy the message to the PCC comm space */
|
||||||
|
for (i = 0; i < sizeof(struct slimpro_resp_msg) / 4; i++)
|
||||||
|
WRITE_ONCE(ptr[i], cpu_to_le32(msg[i]));
|
||||||
|
|
||||||
|
/* Ring the doorbell */
|
||||||
|
rc = mbox_send_message(ctx->mbox_chan, msg);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(ctx->dev, "Mailbox send error %d\n", rc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (!wait_for_completion_timeout(&ctx->rd_complete,
|
||||||
|
usecs_to_jiffies(ctx->usecs_lat))) {
|
||||||
|
dev_err(ctx->dev, "Mailbox operation timed out\n");
|
||||||
|
rc = -ETIMEDOUT;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for error message */
|
||||||
|
if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) {
|
||||||
|
rc = -EINVAL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg[0] = ctx->sync_msg.msg;
|
||||||
|
msg[1] = ctx->sync_msg.param1;
|
||||||
|
msg[2] = ctx->sync_msg.param2;
|
||||||
|
|
||||||
|
err:
|
||||||
|
mbox_chan_txdone(ctx->mbox_chan, 0);
|
||||||
|
ctx->resp_pending = false;
|
||||||
|
mutex_unlock(&ctx->rd_mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
mutex_lock(&ctx->rd_mutex);
|
||||||
|
init_completion(&ctx->rd_complete);
|
||||||
|
ctx->resp_pending = true;
|
||||||
|
|
||||||
|
rc = mbox_send_message(ctx->mbox_chan, msg);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(ctx->dev, "Mailbox send error %d\n", rc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wait_for_completion_timeout(&ctx->rd_complete,
|
||||||
|
msecs_to_jiffies(MBOX_OP_TIMEOUTMS))) {
|
||||||
|
dev_err(ctx->dev, "Mailbox operation timed out\n");
|
||||||
|
rc = -ETIMEDOUT;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for error message */
|
||||||
|
if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) {
|
||||||
|
rc = -EINVAL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg[0] = ctx->sync_msg.msg;
|
||||||
|
msg[1] = ctx->sync_msg.param1;
|
||||||
|
msg[2] = ctx->sync_msg.param2;
|
||||||
|
|
||||||
|
err:
|
||||||
|
ctx->resp_pending = false;
|
||||||
|
mutex_unlock(&ctx->rd_mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_reg_map_rd(struct xgene_hwmon_dev *ctx, u32 addr,
|
||||||
|
u32 *data)
|
||||||
|
{
|
||||||
|
u32 msg[3];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
msg[0] = SENSOR_RD_MSG;
|
||||||
|
msg[1] = SENSOR_RD_EN_ADDR(addr);
|
||||||
|
msg[2] = 0;
|
||||||
|
|
||||||
|
if (acpi_disabled)
|
||||||
|
rc = xgene_hwmon_rd(ctx, msg);
|
||||||
|
else
|
||||||
|
rc = xgene_hwmon_pcc_rd(ctx, msg);
|
||||||
|
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if sensor data is valid.
|
||||||
|
*/
|
||||||
|
if (msg[1] & SENSOR_INVALID_DATA)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
*data = msg[1];
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_get_notification_msg(struct xgene_hwmon_dev *ctx,
|
||||||
|
u32 *amsg)
|
||||||
|
{
|
||||||
|
u32 msg[3];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
msg[0] = TPC_EN_MSG(PWRMGMT_SUBTYPE_TPC, TPC_GET_ALARM, 0);
|
||||||
|
msg[1] = 0;
|
||||||
|
msg[2] = 0;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_pcc_rd(ctx, msg);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
amsg[0] = msg[0];
|
||||||
|
amsg[1] = msg[1];
|
||||||
|
amsg[2] = msg[2];
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_get_cpu_pwr(struct xgene_hwmon_dev *ctx, u32 *val)
|
||||||
|
{
|
||||||
|
u32 watt, mwatt;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_REG, &watt);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_MW_REG, &mwatt);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
*val = WATT_TO_mWATT(watt) + mwatt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_get_io_pwr(struct xgene_hwmon_dev *ctx, u32 *val)
|
||||||
|
{
|
||||||
|
u32 watt, mwatt;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_REG, &watt);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_MW_REG, &mwatt);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
*val = WATT_TO_mWATT(watt) + mwatt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_get_temp(struct xgene_hwmon_dev *ctx, u32 *val)
|
||||||
|
{
|
||||||
|
return xgene_hwmon_reg_map_rd(ctx, SOC_TEMP_REG, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sensor temperature/power functions
|
||||||
|
*/
|
||||||
|
static ssize_t temp1_input_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev);
|
||||||
|
int rc, temp;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_get_temp(ctx, &val);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
temp = sign_extend32(val, TEMP_NEGATIVE_BIT);
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", CELSIUS_TO_mCELSIUS(temp));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t temp1_label_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
return snprintf(buf, PAGE_SIZE, "SoC Temperature\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t temp1_critical_alarm_show(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", ctx->temp_critical_alarm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t power1_label_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
return snprintf(buf, PAGE_SIZE, "CPU power\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t power2_label_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
return snprintf(buf, PAGE_SIZE, "IO power\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t power1_input_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev);
|
||||||
|
u32 val;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_get_cpu_pwr(ctx, &val);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t power2_input_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev);
|
||||||
|
u32 val;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = xgene_hwmon_get_io_pwr(ctx, &val);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR_RO(temp1_label);
|
||||||
|
static DEVICE_ATTR_RO(temp1_input);
|
||||||
|
static DEVICE_ATTR_RO(temp1_critical_alarm);
|
||||||
|
static DEVICE_ATTR_RO(power1_label);
|
||||||
|
static DEVICE_ATTR_RO(power1_input);
|
||||||
|
static DEVICE_ATTR_RO(power2_label);
|
||||||
|
static DEVICE_ATTR_RO(power2_input);
|
||||||
|
|
||||||
|
static struct attribute *xgene_hwmon_attrs[] = {
|
||||||
|
&dev_attr_temp1_label.attr,
|
||||||
|
&dev_attr_temp1_input.attr,
|
||||||
|
&dev_attr_temp1_critical_alarm.attr,
|
||||||
|
&dev_attr_power1_label.attr,
|
||||||
|
&dev_attr_power1_input.attr,
|
||||||
|
&dev_attr_power2_label.attr,
|
||||||
|
&dev_attr_power2_input.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
ATTRIBUTE_GROUPS(xgene_hwmon);
|
||||||
|
|
||||||
|
static int xgene_hwmon_tpc_alarm(struct xgene_hwmon_dev *ctx,
|
||||||
|
struct slimpro_resp_msg *amsg)
|
||||||
|
{
|
||||||
|
ctx->temp_critical_alarm = !!amsg->param2;
|
||||||
|
sysfs_notify(&ctx->dev->kobj, NULL, "temp1_critical_alarm");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xgene_hwmon_process_pwrmsg(struct xgene_hwmon_dev *ctx,
|
||||||
|
struct slimpro_resp_msg *amsg)
|
||||||
|
{
|
||||||
|
if ((MSG_SUBTYPE(amsg->msg) == PWRMGMT_SUBTYPE_TPC) &&
|
||||||
|
(TPC_CMD(amsg->msg) == TPC_ALARM))
|
||||||
|
xgene_hwmon_tpc_alarm(ctx, amsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called to process async work queue
|
||||||
|
*/
|
||||||
|
static void xgene_hwmon_evt_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct slimpro_resp_msg amsg;
|
||||||
|
struct xgene_hwmon_dev *ctx;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ctx = container_of(work, struct xgene_hwmon_dev, workq);
|
||||||
|
while (kfifo_out_spinlocked(&ctx->async_msg_fifo, &amsg,
|
||||||
|
sizeof(struct slimpro_resp_msg),
|
||||||
|
&ctx->kfifo_lock)) {
|
||||||
|
/*
|
||||||
|
* If PCC, send a consumer command to Platform to get info
|
||||||
|
* If Slimpro Mailbox, get message from specific FIFO
|
||||||
|
*/
|
||||||
|
if (!acpi_disabled) {
|
||||||
|
ret = xgene_hwmon_get_notification_msg(ctx,
|
||||||
|
(u32 *)&amsg);
|
||||||
|
if (ret < 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MSG_TYPE(amsg.msg) == MSG_TYPE_PWRMGMT)
|
||||||
|
xgene_hwmon_process_pwrmsg(ctx, &amsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_rx_ready(struct xgene_hwmon_dev *ctx, void *msg)
|
||||||
|
{
|
||||||
|
if (IS_ERR_OR_NULL(ctx->hwmon_dev) && !ctx->resp_pending) {
|
||||||
|
/* Enqueue to the FIFO */
|
||||||
|
kfifo_in_spinlocked(&ctx->async_msg_fifo, msg,
|
||||||
|
sizeof(struct slimpro_resp_msg),
|
||||||
|
&ctx->kfifo_lock);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called when the SLIMpro Mailbox received a message
|
||||||
|
*/
|
||||||
|
static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While the driver registers with the mailbox framework, an interrupt
|
||||||
|
* can be pending before the probe function completes its
|
||||||
|
* initialization. If such condition occurs, just queue up the message
|
||||||
|
* as the driver is not ready for servicing the callback.
|
||||||
|
*/
|
||||||
|
if (xgene_hwmon_rx_ready(ctx, msg) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Response message format:
|
||||||
|
* msg[0] is the return code of the operation
|
||||||
|
* msg[1] is the first parameter word
|
||||||
|
* msg[2] is the second parameter word
|
||||||
|
*
|
||||||
|
* As message only supports dword size, just assign it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Check for sync query */
|
||||||
|
if (ctx->resp_pending &&
|
||||||
|
((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) ||
|
||||||
|
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG &&
|
||||||
|
MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) ||
|
||||||
|
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT &&
|
||||||
|
MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC &&
|
||||||
|
TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) {
|
||||||
|
ctx->sync_msg.msg = ((u32 *)msg)[0];
|
||||||
|
ctx->sync_msg.param1 = ((u32 *)msg)[1];
|
||||||
|
ctx->sync_msg.param2 = ((u32 *)msg)[2];
|
||||||
|
|
||||||
|
/* Operation waiting for response */
|
||||||
|
complete(&ctx->rd_complete);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enqueue to the FIFO */
|
||||||
|
kfifo_in_spinlocked(&ctx->async_msg_fifo, msg,
|
||||||
|
sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock);
|
||||||
|
/* Schedule the bottom handler */
|
||||||
|
schedule_work(&ctx->workq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called when the PCC Mailbox received a message
|
||||||
|
*/
|
||||||
|
static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl);
|
||||||
|
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
|
||||||
|
struct slimpro_resp_msg amsg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While the driver registers with the mailbox framework, an interrupt
|
||||||
|
* can be pending before the probe function completes its
|
||||||
|
* initialization. If such condition occurs, just queue up the message
|
||||||
|
* as the driver is not ready for servicing the callback.
|
||||||
|
*/
|
||||||
|
if (xgene_hwmon_rx_ready(ctx, &amsg) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
msg = generic_comm_base + 1;
|
||||||
|
/* Check if platform sends interrupt */
|
||||||
|
if (!xgene_word_tst_and_clr(&generic_comm_base->status,
|
||||||
|
PCCS_SCI_DOORBEL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Response message format:
|
||||||
|
* msg[0] is the return code of the operation
|
||||||
|
* msg[1] is the first parameter word
|
||||||
|
* msg[2] is the second parameter word
|
||||||
|
*
|
||||||
|
* As message only supports dword size, just assign it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Check for sync query */
|
||||||
|
if (ctx->resp_pending &&
|
||||||
|
((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) ||
|
||||||
|
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG &&
|
||||||
|
MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) ||
|
||||||
|
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT &&
|
||||||
|
MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC &&
|
||||||
|
TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) {
|
||||||
|
/* Check if platform completes command */
|
||||||
|
if (xgene_word_tst_and_clr(&generic_comm_base->status,
|
||||||
|
PCCS_CMD_COMPLETE)) {
|
||||||
|
ctx->sync_msg.msg = ((u32 *)msg)[0];
|
||||||
|
ctx->sync_msg.param1 = ((u32 *)msg)[1];
|
||||||
|
ctx->sync_msg.param2 = ((u32 *)msg)[2];
|
||||||
|
|
||||||
|
/* Operation waiting for response */
|
||||||
|
complete(&ctx->rd_complete);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Platform notifies interrupt to OSPM.
|
||||||
|
* OPSM schedules a consumer command to get this information
|
||||||
|
* in a workqueue. Platform must wait until OSPM has issued
|
||||||
|
* a consumer command that serves this notification.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Enqueue to the FIFO */
|
||||||
|
kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg,
|
||||||
|
sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock);
|
||||||
|
/* Schedule the bottom handler */
|
||||||
|
schedule_work(&ctx->workq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xgene_hwmon_tx_done(struct mbox_client *cl, void *msg, int ret)
|
||||||
|
{
|
||||||
|
if (ret) {
|
||||||
|
dev_dbg(cl->dev, "TX did not complete: CMD sent:%x, ret:%d\n",
|
||||||
|
*(u16 *)msg, ret);
|
||||||
|
} else {
|
||||||
|
dev_dbg(cl->dev, "TX completed. CMD sent:%x, ret:%d\n",
|
||||||
|
*(u16 *)msg, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx;
|
||||||
|
struct mbox_client *cl;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
|
||||||
|
if (!ctx)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ctx->dev = &pdev->dev;
|
||||||
|
platform_set_drvdata(pdev, ctx);
|
||||||
|
cl = &ctx->mbox_client;
|
||||||
|
|
||||||
|
spin_lock_init(&ctx->kfifo_lock);
|
||||||
|
mutex_init(&ctx->rd_mutex);
|
||||||
|
|
||||||
|
rc = kfifo_alloc(&ctx->async_msg_fifo,
|
||||||
|
sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (rc)
|
||||||
|
goto out_mbox_free;
|
||||||
|
|
||||||
|
INIT_WORK(&ctx->workq, xgene_hwmon_evt_work);
|
||||||
|
|
||||||
|
/* Request mailbox channel */
|
||||||
|
cl->dev = &pdev->dev;
|
||||||
|
cl->tx_done = xgene_hwmon_tx_done;
|
||||||
|
cl->tx_block = false;
|
||||||
|
cl->tx_tout = MBOX_OP_TIMEOUTMS;
|
||||||
|
cl->knows_txdone = false;
|
||||||
|
if (acpi_disabled) {
|
||||||
|
cl->rx_callback = xgene_hwmon_rx_cb;
|
||||||
|
ctx->mbox_chan = mbox_request_channel(cl, 0);
|
||||||
|
if (IS_ERR(ctx->mbox_chan)) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"SLIMpro mailbox channel request failed\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct acpi_pcct_hw_reduced *cppc_ss;
|
||||||
|
|
||||||
|
if (device_property_read_u32(&pdev->dev, "pcc-channel",
|
||||||
|
&ctx->mbox_idx)) {
|
||||||
|
dev_err(&pdev->dev, "no pcc-channel property\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
cl->rx_callback = xgene_hwmon_pcc_rx_cb;
|
||||||
|
ctx->mbox_chan = pcc_mbox_request_channel(cl, ctx->mbox_idx);
|
||||||
|
if (IS_ERR(ctx->mbox_chan)) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"PPC channel request failed\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The PCC mailbox controller driver should
|
||||||
|
* have parsed the PCCT (global table of all
|
||||||
|
* PCC channels) and stored pointers to the
|
||||||
|
* subspace communication region in con_priv.
|
||||||
|
*/
|
||||||
|
cppc_ss = ctx->mbox_chan->con_priv;
|
||||||
|
if (!cppc_ss) {
|
||||||
|
dev_err(&pdev->dev, "PPC subspace not found\n");
|
||||||
|
rc = -ENODEV;
|
||||||
|
goto out_mbox_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->mbox_chan->mbox->txdone_irq) {
|
||||||
|
dev_err(&pdev->dev, "PCC IRQ not supported\n");
|
||||||
|
rc = -ENODEV;
|
||||||
|
goto out_mbox_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the shared communication region
|
||||||
|
* for the OS and Platform to communicate over.
|
||||||
|
*/
|
||||||
|
ctx->comm_base_addr = cppc_ss->base_address;
|
||||||
|
if (ctx->comm_base_addr) {
|
||||||
|
ctx->pcc_comm_addr = memremap(ctx->comm_base_addr,
|
||||||
|
cppc_ss->length,
|
||||||
|
MEMREMAP_WB);
|
||||||
|
} else {
|
||||||
|
dev_err(&pdev->dev, "Failed to get PCC comm region\n");
|
||||||
|
rc = -ENODEV;
|
||||||
|
goto out_mbox_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->pcc_comm_addr) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"Failed to ioremap PCC comm region\n");
|
||||||
|
rc = -ENOMEM;
|
||||||
|
goto out_mbox_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cppc_ss->latency is just a Nominal value. In reality
|
||||||
|
* the remote processor could be much slower to reply.
|
||||||
|
* So add an arbitrary amount of wait on top of Nominal.
|
||||||
|
*/
|
||||||
|
ctx->usecs_lat = PCC_NUM_RETRIES * cppc_ss->latency;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev,
|
||||||
|
"apm_xgene",
|
||||||
|
ctx,
|
||||||
|
xgene_hwmon_groups);
|
||||||
|
if (IS_ERR(ctx->hwmon_dev)) {
|
||||||
|
dev_err(&pdev->dev, "Failed to register HW monitor device\n");
|
||||||
|
rc = PTR_ERR(ctx->hwmon_dev);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Schedule the bottom handler if there is a pending message.
|
||||||
|
*/
|
||||||
|
schedule_work(&ctx->workq);
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (acpi_disabled)
|
||||||
|
mbox_free_channel(ctx->mbox_chan);
|
||||||
|
else
|
||||||
|
pcc_mbox_free_channel(ctx->mbox_chan);
|
||||||
|
out_mbox_free:
|
||||||
|
kfifo_free(&ctx->async_msg_fifo);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xgene_hwmon_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct xgene_hwmon_dev *ctx = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
hwmon_device_unregister(ctx->hwmon_dev);
|
||||||
|
kfifo_free(&ctx->async_msg_fifo);
|
||||||
|
if (acpi_disabled)
|
||||||
|
mbox_free_channel(ctx->mbox_chan);
|
||||||
|
else
|
||||||
|
pcc_mbox_free_channel(ctx->mbox_chan);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
static const struct acpi_device_id xgene_hwmon_acpi_match[] = {
|
||||||
|
{"APMC0D29", 0},
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct of_device_id xgene_hwmon_of_match[] = {
|
||||||
|
{.compatible = "apm,xgene-slimpro-hwmon"},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver xgene_hwmon_driver __refdata = {
|
||||||
|
.probe = xgene_hwmon_probe,
|
||||||
|
.remove = xgene_hwmon_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "xgene-slimpro-hwmon",
|
||||||
|
.of_match_table = xgene_hwmon_of_match,
|
||||||
|
.acpi_match_table = ACPI_PTR(xgene_hwmon_acpi_match),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_platform_driver(xgene_hwmon_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("APM X-Gene SoC hardware monitor");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -14,9 +14,341 @@
|
||||||
#ifndef _HWMON_H_
|
#ifndef _HWMON_H_
|
||||||
#define _HWMON_H_
|
#define _HWMON_H_
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
|
||||||
struct device;
|
struct device;
|
||||||
struct attribute_group;
|
struct attribute_group;
|
||||||
|
|
||||||
|
enum hwmon_sensor_types {
|
||||||
|
hwmon_chip,
|
||||||
|
hwmon_temp,
|
||||||
|
hwmon_in,
|
||||||
|
hwmon_curr,
|
||||||
|
hwmon_power,
|
||||||
|
hwmon_energy,
|
||||||
|
hwmon_humidity,
|
||||||
|
hwmon_fan,
|
||||||
|
hwmon_pwm,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum hwmon_chip_attributes {
|
||||||
|
hwmon_chip_temp_reset_history,
|
||||||
|
hwmon_chip_in_reset_history,
|
||||||
|
hwmon_chip_curr_reset_history,
|
||||||
|
hwmon_chip_power_reset_history,
|
||||||
|
hwmon_chip_register_tz,
|
||||||
|
hwmon_chip_update_interval,
|
||||||
|
hwmon_chip_alarms,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_C_TEMP_RESET_HISTORY BIT(hwmon_chip_temp_reset_history)
|
||||||
|
#define HWMON_C_IN_RESET_HISTORY BIT(hwmon_chip_in_reset_history)
|
||||||
|
#define HWMON_C_CURR_RESET_HISTORY BIT(hwmon_chip_curr_reset_history)
|
||||||
|
#define HWMON_C_POWER_RESET_HISTORY BIT(hwmon_chip_power_reset_history)
|
||||||
|
#define HWMON_C_REGISTER_TZ BIT(hwmon_chip_register_tz)
|
||||||
|
#define HWMON_C_UPDATE_INTERVAL BIT(hwmon_chip_update_interval)
|
||||||
|
#define HWMON_C_ALARMS BIT(hwmon_chip_alarms)
|
||||||
|
|
||||||
|
enum hwmon_temp_attributes {
|
||||||
|
hwmon_temp_input = 0,
|
||||||
|
hwmon_temp_type,
|
||||||
|
hwmon_temp_lcrit,
|
||||||
|
hwmon_temp_lcrit_hyst,
|
||||||
|
hwmon_temp_min,
|
||||||
|
hwmon_temp_min_hyst,
|
||||||
|
hwmon_temp_max,
|
||||||
|
hwmon_temp_max_hyst,
|
||||||
|
hwmon_temp_crit,
|
||||||
|
hwmon_temp_crit_hyst,
|
||||||
|
hwmon_temp_emergency,
|
||||||
|
hwmon_temp_emergency_hyst,
|
||||||
|
hwmon_temp_alarm,
|
||||||
|
hwmon_temp_lcrit_alarm,
|
||||||
|
hwmon_temp_min_alarm,
|
||||||
|
hwmon_temp_max_alarm,
|
||||||
|
hwmon_temp_crit_alarm,
|
||||||
|
hwmon_temp_emergency_alarm,
|
||||||
|
hwmon_temp_fault,
|
||||||
|
hwmon_temp_offset,
|
||||||
|
hwmon_temp_label,
|
||||||
|
hwmon_temp_lowest,
|
||||||
|
hwmon_temp_highest,
|
||||||
|
hwmon_temp_reset_history,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_T_INPUT BIT(hwmon_temp_input)
|
||||||
|
#define HWMON_T_TYPE BIT(hwmon_temp_type)
|
||||||
|
#define HWMON_T_LCRIT BIT(hwmon_temp_lcrit)
|
||||||
|
#define HWMON_T_LCRIT_HYST BIT(hwmon_temp_lcrit_hyst)
|
||||||
|
#define HWMON_T_MIN BIT(hwmon_temp_min)
|
||||||
|
#define HWMON_T_MIN_HYST BIT(hwmon_temp_min_hyst)
|
||||||
|
#define HWMON_T_MAX BIT(hwmon_temp_max)
|
||||||
|
#define HWMON_T_MAX_HYST BIT(hwmon_temp_max_hyst)
|
||||||
|
#define HWMON_T_CRIT BIT(hwmon_temp_crit)
|
||||||
|
#define HWMON_T_CRIT_HYST BIT(hwmon_temp_crit_hyst)
|
||||||
|
#define HWMON_T_EMERGENCY BIT(hwmon_temp_emergency)
|
||||||
|
#define HWMON_T_EMERGENCY_HYST BIT(hwmon_temp_emergency_hyst)
|
||||||
|
#define HWMON_T_MIN_ALARM BIT(hwmon_temp_min_alarm)
|
||||||
|
#define HWMON_T_MAX_ALARM BIT(hwmon_temp_max_alarm)
|
||||||
|
#define HWMON_T_CRIT_ALARM BIT(hwmon_temp_crit_alarm)
|
||||||
|
#define HWMON_T_EMERGENCY_ALARM BIT(hwmon_temp_emergency_alarm)
|
||||||
|
#define HWMON_T_FAULT BIT(hwmon_temp_fault)
|
||||||
|
#define HWMON_T_OFFSET BIT(hwmon_temp_offset)
|
||||||
|
#define HWMON_T_LABEL BIT(hwmon_temp_label)
|
||||||
|
#define HWMON_T_LOWEST BIT(hwmon_temp_lowest)
|
||||||
|
#define HWMON_T_HIGHEST BIT(hwmon_temp_highest)
|
||||||
|
#define HWMON_T_RESET_HISTORY BIT(hwmon_temp_reset_history)
|
||||||
|
|
||||||
|
enum hwmon_in_attributes {
|
||||||
|
hwmon_in_input,
|
||||||
|
hwmon_in_min,
|
||||||
|
hwmon_in_max,
|
||||||
|
hwmon_in_lcrit,
|
||||||
|
hwmon_in_crit,
|
||||||
|
hwmon_in_average,
|
||||||
|
hwmon_in_lowest,
|
||||||
|
hwmon_in_highest,
|
||||||
|
hwmon_in_reset_history,
|
||||||
|
hwmon_in_label,
|
||||||
|
hwmon_in_alarm,
|
||||||
|
hwmon_in_min_alarm,
|
||||||
|
hwmon_in_max_alarm,
|
||||||
|
hwmon_in_lcrit_alarm,
|
||||||
|
hwmon_in_crit_alarm,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_I_INPUT BIT(hwmon_in_input)
|
||||||
|
#define HWMON_I_MIN BIT(hwmon_in_min)
|
||||||
|
#define HWMON_I_MAX BIT(hwmon_in_max)
|
||||||
|
#define HWMON_I_LCRIT BIT(hwmon_in_lcrit)
|
||||||
|
#define HWMON_I_CRIT BIT(hwmon_in_crit)
|
||||||
|
#define HWMON_I_AVERAGE BIT(hwmon_in_average)
|
||||||
|
#define HWMON_I_LOWEST BIT(hwmon_in_lowest)
|
||||||
|
#define HWMON_I_HIGHEST BIT(hwmon_in_highest)
|
||||||
|
#define HWMON_I_RESET_HISTORY BIT(hwmon_in_reset_history)
|
||||||
|
#define HWMON_I_LABEL BIT(hwmon_in_label)
|
||||||
|
#define HWMON_I_ALARM BIT(hwmon_in_alarm)
|
||||||
|
#define HWMON_I_MIN_ALARM BIT(hwmon_in_min_alarm)
|
||||||
|
#define HWMON_I_MAX_ALARM BIT(hwmon_in_max_alarm)
|
||||||
|
#define HWMON_I_LCRIT_ALARM BIT(hwmon_in_lcrit_alarm)
|
||||||
|
#define HWMON_I_CRIT_ALARM BIT(hwmon_in_crit_alarm)
|
||||||
|
|
||||||
|
enum hwmon_curr_attributes {
|
||||||
|
hwmon_curr_input,
|
||||||
|
hwmon_curr_min,
|
||||||
|
hwmon_curr_max,
|
||||||
|
hwmon_curr_lcrit,
|
||||||
|
hwmon_curr_crit,
|
||||||
|
hwmon_curr_average,
|
||||||
|
hwmon_curr_lowest,
|
||||||
|
hwmon_curr_highest,
|
||||||
|
hwmon_curr_reset_history,
|
||||||
|
hwmon_curr_label,
|
||||||
|
hwmon_curr_alarm,
|
||||||
|
hwmon_curr_min_alarm,
|
||||||
|
hwmon_curr_max_alarm,
|
||||||
|
hwmon_curr_lcrit_alarm,
|
||||||
|
hwmon_curr_crit_alarm,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_C_INPUT BIT(hwmon_curr_input)
|
||||||
|
#define HWMON_C_MIN BIT(hwmon_curr_min)
|
||||||
|
#define HWMON_C_MAX BIT(hwmon_curr_max)
|
||||||
|
#define HWMON_C_LCRIT BIT(hwmon_curr_lcrit)
|
||||||
|
#define HWMON_C_CRIT BIT(hwmon_curr_crit)
|
||||||
|
#define HWMON_C_AVERAGE BIT(hwmon_curr_average)
|
||||||
|
#define HWMON_C_LOWEST BIT(hwmon_curr_lowest)
|
||||||
|
#define HWMON_C_HIGHEST BIT(hwmon_curr_highest)
|
||||||
|
#define HWMON_C_RESET_HISTORY BIT(hwmon_curr_reset_history)
|
||||||
|
#define HWMON_C_LABEL BIT(hwmon_curr_label)
|
||||||
|
#define HWMON_C_ALARM BIT(hwmon_curr_alarm)
|
||||||
|
#define HWMON_C_MIN_ALARM BIT(hwmon_curr_min_alarm)
|
||||||
|
#define HWMON_C_MAX_ALARM BIT(hwmon_curr_max_alarm)
|
||||||
|
#define HWMON_C_LCRIT_ALARM BIT(hwmon_curr_lcrit_alarm)
|
||||||
|
#define HWMON_C_CRIT_ALARM BIT(hwmon_curr_crit_alarm)
|
||||||
|
|
||||||
|
enum hwmon_power_attributes {
|
||||||
|
hwmon_power_average,
|
||||||
|
hwmon_power_average_interval,
|
||||||
|
hwmon_power_average_interval_max,
|
||||||
|
hwmon_power_average_interval_min,
|
||||||
|
hwmon_power_average_highest,
|
||||||
|
hwmon_power_average_lowest,
|
||||||
|
hwmon_power_average_max,
|
||||||
|
hwmon_power_average_min,
|
||||||
|
hwmon_power_input,
|
||||||
|
hwmon_power_input_highest,
|
||||||
|
hwmon_power_input_lowest,
|
||||||
|
hwmon_power_reset_history,
|
||||||
|
hwmon_power_accuracy,
|
||||||
|
hwmon_power_cap,
|
||||||
|
hwmon_power_cap_hyst,
|
||||||
|
hwmon_power_cap_max,
|
||||||
|
hwmon_power_cap_min,
|
||||||
|
hwmon_power_max,
|
||||||
|
hwmon_power_crit,
|
||||||
|
hwmon_power_label,
|
||||||
|
hwmon_power_alarm,
|
||||||
|
hwmon_power_cap_alarm,
|
||||||
|
hwmon_power_max_alarm,
|
||||||
|
hwmon_power_crit_alarm,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_P_AVERAGE BIT(hwmon_power_average)
|
||||||
|
#define HWMON_P_AVERAGE_INTERVAL BIT(hwmon_power_average_interval)
|
||||||
|
#define HWMON_P_AVERAGE_INTERVAL_MAX BIT(hwmon_power_average_interval_max)
|
||||||
|
#define HWMON_P_AVERAGE_INTERVAL_MIN BIT(hwmon_power_average_interval_min)
|
||||||
|
#define HWMON_P_AVERAGE_HIGHEST BIT(hwmon_power_average_highest)
|
||||||
|
#define HWMON_P_AVERAGE_LOWEST BIT(hwmon_power_average_lowest)
|
||||||
|
#define HWMON_P_AVERAGE_MAX BIT(hwmon_power_average_max)
|
||||||
|
#define HWMON_P_AVERAGE_MIN BIT(hwmon_power_average_min)
|
||||||
|
#define HWMON_P_INPUT BIT(hwmon_power_input)
|
||||||
|
#define HWMON_P_INPUT_HIGHEST BIT(hwmon_power_input_highest)
|
||||||
|
#define HWMON_P_INPUT_LOWEST BIT(hwmon_power_input_lowest)
|
||||||
|
#define HWMON_P_RESET_HISTORY BIT(hwmon_power_reset_history)
|
||||||
|
#define HWMON_P_ACCURACY BIT(hwmon_power_accuracy)
|
||||||
|
#define HWMON_P_CAP BIT(hwmon_power_cap)
|
||||||
|
#define HWMON_P_CAP_HYST BIT(hwmon_power_cap_hyst)
|
||||||
|
#define HWMON_P_CAP_MAX BIT(hwmon_power_cap_max)
|
||||||
|
#define HWMON_P_CAP_MIN BIT(hwmon_power_cap_min)
|
||||||
|
#define HWMON_P_MAX BIT(hwmon_power_max)
|
||||||
|
#define HWMON_P_CRIT BIT(hwmon_power_crit)
|
||||||
|
#define HWMON_P_LABEL BIT(hwmon_power_label)
|
||||||
|
#define HWMON_P_ALARM BIT(hwmon_power_alarm)
|
||||||
|
#define HWMON_P_CAP_ALARM BIT(hwmon_power_cap_alarm)
|
||||||
|
#define HWMON_P_MAX_ALARM BIT(hwmon_power_max_alarm)
|
||||||
|
#define HWMON_P_CRIT_ALARM BIT(hwmon_power_crit_alarm)
|
||||||
|
|
||||||
|
enum hwmon_energy_attributes {
|
||||||
|
hwmon_energy_input,
|
||||||
|
hwmon_energy_label,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_E_INPUT BIT(hwmon_energy_input)
|
||||||
|
#define HWMON_E_LABEL BIT(hwmon_energy_label)
|
||||||
|
|
||||||
|
enum hwmon_humidity_attributes {
|
||||||
|
hwmon_humidity_input,
|
||||||
|
hwmon_humidity_label,
|
||||||
|
hwmon_humidity_min,
|
||||||
|
hwmon_humidity_min_hyst,
|
||||||
|
hwmon_humidity_max,
|
||||||
|
hwmon_humidity_max_hyst,
|
||||||
|
hwmon_humidity_alarm,
|
||||||
|
hwmon_humidity_fault,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_H_INPUT BIT(hwmon_humidity_input)
|
||||||
|
#define HWMON_H_LABEL BIT(hwmon_humidity_label)
|
||||||
|
#define HWMON_H_MIN BIT(hwmon_humidity_min)
|
||||||
|
#define HWMON_H_MIN_HYST BIT(hwmon_humidity_min_hyst)
|
||||||
|
#define HWMON_H_MAX BIT(hwmon_humidity_max)
|
||||||
|
#define HWMON_H_MAX_HYST BIT(hwmon_humidity_max_hyst)
|
||||||
|
#define HWMON_H_ALARM BIT(hwmon_humidity_alarm)
|
||||||
|
#define HWMON_H_FAULT BIT(hwmon_humidity_fault)
|
||||||
|
|
||||||
|
enum hwmon_fan_attributes {
|
||||||
|
hwmon_fan_input,
|
||||||
|
hwmon_fan_label,
|
||||||
|
hwmon_fan_min,
|
||||||
|
hwmon_fan_max,
|
||||||
|
hwmon_fan_div,
|
||||||
|
hwmon_fan_pulses,
|
||||||
|
hwmon_fan_target,
|
||||||
|
hwmon_fan_alarm,
|
||||||
|
hwmon_fan_min_alarm,
|
||||||
|
hwmon_fan_max_alarm,
|
||||||
|
hwmon_fan_fault,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_F_INPUT BIT(hwmon_fan_input)
|
||||||
|
#define HWMON_F_LABEL BIT(hwmon_fan_label)
|
||||||
|
#define HWMON_F_MIN BIT(hwmon_fan_min)
|
||||||
|
#define HWMON_F_MAX BIT(hwmon_fan_max)
|
||||||
|
#define HWMON_F_DIV BIT(hwmon_fan_div)
|
||||||
|
#define HWMON_F_PULSES BIT(hwmon_fan_pulses)
|
||||||
|
#define HWMON_F_TARGET BIT(hwmon_fan_target)
|
||||||
|
#define HWMON_F_ALARM BIT(hwmon_fan_alarm)
|
||||||
|
#define HWMON_F_MIN_ALARM BIT(hwmon_fan_min_alarm)
|
||||||
|
#define HWMON_F_MAX_ALARM BIT(hwmon_fan_max_alarm)
|
||||||
|
#define HWMON_F_FAULT BIT(hwmon_fan_fault)
|
||||||
|
|
||||||
|
enum hwmon_pwm_attributes {
|
||||||
|
hwmon_pwm_input,
|
||||||
|
hwmon_pwm_enable,
|
||||||
|
hwmon_pwm_mode,
|
||||||
|
hwmon_pwm_freq,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HWMON_PWM_INPUT BIT(hwmon_pwm_input)
|
||||||
|
#define HWMON_PWM_ENABLE BIT(hwmon_pwm_enable)
|
||||||
|
#define HWMON_PWM_MODE BIT(hwmon_pwm_mode)
|
||||||
|
#define HWMON_PWM_FREQ BIT(hwmon_pwm_freq)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct hwmon_ops - hwmon device operations
|
||||||
|
* @is_visible: Callback to return attribute visibility. Mandatory.
|
||||||
|
* Parameters are:
|
||||||
|
* @const void *drvdata:
|
||||||
|
* Pointer to driver-private data structure passed
|
||||||
|
* as argument to hwmon_device_register_with_info().
|
||||||
|
* @type: Sensor type
|
||||||
|
* @attr: Sensor attribute
|
||||||
|
* @channel:
|
||||||
|
* Channel number
|
||||||
|
* The function returns the file permissions.
|
||||||
|
* If the return value is 0, no attribute will be created.
|
||||||
|
* @read: Read callback. Optional. If not provided, attributes
|
||||||
|
* will not be readable.
|
||||||
|
* Parameters are:
|
||||||
|
* @dev: Pointer to hardware monitoring device
|
||||||
|
* @type: Sensor type
|
||||||
|
* @attr: Sensor attribute
|
||||||
|
* @channel:
|
||||||
|
* Channel number
|
||||||
|
* @val: Pointer to returned value
|
||||||
|
* The function returns 0 on success or a negative error number.
|
||||||
|
* @write: Write callback. Optional. If not provided, attributes
|
||||||
|
* will not be writable.
|
||||||
|
* Parameters are:
|
||||||
|
* @dev: Pointer to hardware monitoring device
|
||||||
|
* @type: Sensor type
|
||||||
|
* @attr: Sensor attribute
|
||||||
|
* @channel:
|
||||||
|
* Channel number
|
||||||
|
* @val: Value to write
|
||||||
|
* The function returns 0 on success or a negative error number.
|
||||||
|
*/
|
||||||
|
struct hwmon_ops {
|
||||||
|
umode_t (*is_visible)(const void *drvdata, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel);
|
||||||
|
int (*read)(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val);
|
||||||
|
int (*write)(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long val);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel information
|
||||||
|
* @type: Channel type.
|
||||||
|
* @config: Pointer to NULL-terminated list of channel parameters.
|
||||||
|
* Use for per-channel attributes.
|
||||||
|
*/
|
||||||
|
struct hwmon_channel_info {
|
||||||
|
enum hwmon_sensor_types type;
|
||||||
|
const u32 *config;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chip configuration
|
||||||
|
* @ops: Pointer to hwmon operations.
|
||||||
|
* @info: Null-terminated list of channel information.
|
||||||
|
*/
|
||||||
|
struct hwmon_chip_info {
|
||||||
|
const struct hwmon_ops *ops;
|
||||||
|
const struct hwmon_channel_info **info;
|
||||||
|
};
|
||||||
|
|
||||||
struct device *hwmon_device_register(struct device *dev);
|
struct device *hwmon_device_register(struct device *dev);
|
||||||
struct device *
|
struct device *
|
||||||
hwmon_device_register_with_groups(struct device *dev, const char *name,
|
hwmon_device_register_with_groups(struct device *dev, const char *name,
|
||||||
|
@ -26,6 +358,16 @@ struct device *
|
||||||
devm_hwmon_device_register_with_groups(struct device *dev, const char *name,
|
devm_hwmon_device_register_with_groups(struct device *dev, const char *name,
|
||||||
void *drvdata,
|
void *drvdata,
|
||||||
const struct attribute_group **groups);
|
const struct attribute_group **groups);
|
||||||
|
struct device *
|
||||||
|
hwmon_device_register_with_info(struct device *dev,
|
||||||
|
const char *name, void *drvdata,
|
||||||
|
const struct hwmon_chip_info *info,
|
||||||
|
const struct attribute_group **groups);
|
||||||
|
struct device *
|
||||||
|
devm_hwmon_device_register_with_info(struct device *dev,
|
||||||
|
const char *name, void *drvdata,
|
||||||
|
const struct hwmon_chip_info *info,
|
||||||
|
const struct attribute_group **groups);
|
||||||
|
|
||||||
void hwmon_device_unregister(struct device *dev);
|
void hwmon_device_unregister(struct device *dev);
|
||||||
void devm_hwmon_device_unregister(struct device *dev);
|
void devm_hwmon_device_unregister(struct device *dev);
|
||||||
|
|
Loading…
Reference in New Issue