hwmon updates for v4.10
- new drivers for TMP108 and TC654 - hwmon core code cleanup - coretemp driver cleanup - fix overflow issues in several drivers - minor fixes, cleanups and enhancements in various drivers -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJYTvvqAAoJEMsfJm/On5mBtlwQAJa0FaWLVk1t6iUi0/bD+hFL PmqhH6jpS8AN67LhWarte1ON+31cjlYK2s0eY51UK3HbvXOv393aNjw7KVAIGd+6 fi/JJMUieEUfKOQ2q92cUoLzEAnSpNy0+R0d/mROq8wHLo1dlfGkLzkr9Wwu8In+ qR/oH6wsSyhzPlaau3+1ZqXxigv28uaolNgVyBCyRxsGWIurqGkNldDl2tEVA9Hx p9QnAmUmiqVuHmB+aupkzwxPT5kMKBh7OI5qe4D1AkN6HjuEfFHGFQeAixR0RngZ JC7Pg1ccW8YRHFm4i+ByFJYjihy2yTvNJeFC1FO7lPYjYzZm4NcbJfIVWPpGEBkV kTqH/0IeLZm7ATY89XfYz/qHnoHOXluOZP+TMStWk1ji9/TDzPUrRm6g7W0qEtJf 89THGAo2JGMW9m6ywXHPSRTKRGFpFZVBCf5UjQeeJOCkLVCHGtpD6snqogR8W2Mk V2Iy2vmbiKBAYwMuHgVhkz0LALjBYUlvAY4EAS5PUs6Pd+2tpKojDxs9EHOjv37L J6goBWLxO3tryhlXXKn65svsCkcJToenHudoPuzKTluJJkmLZV5ZIjIJX18TqTaF 12Ca9r40+RnD5BbpGfn+sk2OJMRjl0TUpTsGRXeHcvPhrLDR674Pzj3UBxFh4gN9 KoCMNqUEYB3A6zbr+A8I =fCxP -----END PGP SIGNATURE----- Merge tag 'hwmon-for-linus-v4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - new drivers for TMP108 and TC654 - hwmon core code cleanup - coretemp driver cleanup - fix overflow issues in several drivers - minor fixes, cleanups and enhancements in various drivers * tag 'hwmon-for-linus-v4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (41 commits) hwmon: (g762) Fix overflows and crash seen when writing limit attributes hwmon: (emcw201) Fix overflows seen when writing into limit attributes hwmon: (emc2103) Fix overflows seen when temperature limit attributes hwmon: (lm85) Fix overflows seen when writing voltage limit attributes hwmon: (lm87) Fix overflow seen when writing voltage limit attributes hwmon: (nct7802) Fix overflows seen when writing into limit attributes hwmon: (adt7470) Fix overflows seen when writing into limit attributes hwmon: (adt7462) Fix overflows seen when writing into limit attributes hwmon: (adm1026) Fix overflows seen when writing into limit attributes hwmon: (adm1025) Fix overflows seen when writing voltage limits hwmon: (via-cputemp) Convert to hotplug state machine devicetree: hwmon: Add documentation for TMP108 driver. hwmon: Add Texas Instruments TMP108 temperature sensor driver. hwmon: (core) Simplify sysfs attribute name allocation hwmon: (core) Rename groups parameter in API to extra_groups hwmon: (core) Explain why at least two attribute groups are allocated hwmon: (core) Make is_visible callback truly mandatory hwmon: (core) Deprecate hwmon_device_register() hwmon: (core) Clarify use of chip attributes hwmon: (core) Add support for string attributes to new API ...
This commit is contained in:
commit
1c59e1edb1
|
@ -0,0 +1,21 @@
|
|||
mcp3021 properties
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be one of the following:
|
||||
- "microchip,mcp3021" for mcp3021
|
||||
- "microchip,mcp3221" for mcp3221
|
||||
- reg: I2C address
|
||||
|
||||
Optional properties:
|
||||
|
||||
- reference-voltage-microvolt
|
||||
Reference voltage in microvolt (uV)
|
||||
|
||||
Example:
|
||||
|
||||
mcp3021@4d {
|
||||
compatible = "microchip,mcp3021";
|
||||
reg = <0x4d>;
|
||||
|
||||
reference-voltage-microvolt = <4500000>; /* 4.5 V */
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
TMP108 temperature sensor
|
||||
-------------------------
|
||||
|
||||
This device supports I2C only.
|
||||
|
||||
Requires node properties:
|
||||
- compatible : "ti,tmp108"
|
||||
- reg : the I2C address of the device. This is 0x48, 0x49, 0x4a, or 0x4b.
|
||||
|
||||
Example:
|
||||
tmp108@48 {
|
||||
compatible = "ti,tmp108";
|
||||
reg = <0x48>;
|
||||
};
|
|
@ -124,6 +124,8 @@ microchip,mcp4662-502 Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem
|
|||
microchip,mcp4662-103 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k)
|
||||
microchip,mcp4662-503 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k)
|
||||
microchip,mcp4662-104 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k)
|
||||
microchip,tc654 PWM Fan Speed Controller With Fan Fault Detection
|
||||
microchip,tc655 PWM Fan Speed Controller With Fan Fault Detection
|
||||
miramems,da226 MiraMEMS DA226 2-axis 14-bit digital accelerometer
|
||||
miramems,da280 MiraMEMS DA280 3-axis 14-bit digital accelerometer
|
||||
miramems,da311 MiraMEMS DA311 3-axis 12-bit digital accelerometer
|
||||
|
|
|
@ -23,7 +23,6 @@ Each hardware monitoring driver must #include <linux/hwmon.h> and, in most
|
|||
cases, <linux/hwmon-sysfs.h>. linux/hwmon.h declares the following
|
||||
register/unregister functions:
|
||||
|
||||
struct device *hwmon_device_register(struct device *dev);
|
||||
struct device *
|
||||
hwmon_device_register_with_groups(struct device *dev, const char *name,
|
||||
void *drvdata,
|
||||
|
@ -38,36 +37,31 @@ 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);
|
||||
const struct attribute_group **extra_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);
|
||||
const char *name,
|
||||
void *drvdata,
|
||||
const struct hwmon_chip_info *info,
|
||||
const struct attribute_group **extra_groups);
|
||||
|
||||
void hwmon_device_unregister(struct device *dev);
|
||||
void devm_hwmon_device_unregister(struct device *dev);
|
||||
|
||||
hwmon_device_register registers a hardware monitoring device. The parameter
|
||||
of this function is a pointer to the parent device.
|
||||
This function returns a pointer to the newly created hardware monitoring device
|
||||
or PTR_ERR for failure. If this registration function is used, hardware
|
||||
monitoring sysfs attributes are expected to have been created and attached to
|
||||
the parent device prior to calling hwmon_device_register. A name attribute must
|
||||
have been created by the caller.
|
||||
|
||||
hwmon_device_register_with_groups is similar to hwmon_device_register. However,
|
||||
it has additional parameters. The name parameter is a pointer to the hwmon
|
||||
device name. The registration function wil create a name sysfs attribute
|
||||
pointing to this name. The drvdata parameter is the pointer to the local
|
||||
driver data. hwmon_device_register_with_groups will attach this pointer
|
||||
to the newly allocated hwmon device. The pointer can be retrieved by the driver
|
||||
using dev_get_drvdata() on the hwmon device pointer. The groups parameter is
|
||||
hwmon_device_register_with_groups registers a hardware monitoring device.
|
||||
The first parameter of this function is a pointer to the parent device.
|
||||
The name parameter is a pointer to the hwmon device name. The registration
|
||||
function wil create a name sysfs attribute pointing to this name.
|
||||
The drvdata parameter is the pointer to the local driver data.
|
||||
hwmon_device_register_with_groups will attach this pointer to the newly
|
||||
allocated hwmon device. The pointer can be retrieved by the driver using
|
||||
dev_get_drvdata() on the hwmon device pointer. The groups parameter is
|
||||
a pointer to a list of sysfs attribute groups. The list must be NULL terminated.
|
||||
hwmon_device_register_with_groups creates the hwmon device with name attribute
|
||||
as well as all sysfs attributes attached to the hwmon device.
|
||||
This function returns a pointer to the newly created hardware monitoring device
|
||||
or PTR_ERR for failure.
|
||||
|
||||
devm_hwmon_device_register_with_groups is similar to
|
||||
hwmon_device_register_with_groups. However, it is device managed, meaning the
|
||||
|
@ -87,13 +81,13 @@ hwmon_device_unregister deregisters a registered hardware monitoring device.
|
|||
The parameter of this function is the pointer to the registered hardware
|
||||
monitoring device structure. This function must be called from the driver
|
||||
remove function if the hardware monitoring device was registered with
|
||||
hwmon_device_register, hwmon_device_register_with_groups, or
|
||||
hwmon_device_register_with_info.
|
||||
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
|
||||
needed for error handling, and only needed if the driver probe fails after
|
||||
the call to devm_hwmon_device_register_with_groups and if the automatic
|
||||
(device managed) removal would be too late.
|
||||
the call to devm_hwmon_device_register_with_groups or
|
||||
hwmon_device_register_with_info and if the automatic (device managed)
|
||||
removal would be too late.
|
||||
|
||||
Using devm_hwmon_device_register_with_info()
|
||||
--------------------------------------------
|
||||
|
@ -106,9 +100,9 @@ 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.
|
||||
const struct attribute_group **extra_groups
|
||||
Null-terminated list of additional non-standard
|
||||
sysfs attribute groups.
|
||||
|
||||
This function returns a pointer to the created hardware monitoring device
|
||||
on success and a negative error code for failure.
|
||||
|
@ -160,7 +154,7 @@ 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.
|
||||
* which are not bound to a specific input or output
|
||||
* hwmon_temp Temperature sensor
|
||||
* hwmon_in Voltage sensor
|
||||
* hwmon_curr Current sensor
|
||||
|
@ -293,9 +287,9 @@ 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.
|
||||
it is most likely not necessary to provide sysfs attributes. Only additional
|
||||
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
|
||||
declare and use hardware monitoring sysfs attributes.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Kernel driver tc654
|
||||
===================
|
||||
|
||||
Supported chips:
|
||||
* Microship TC654 and TC655
|
||||
Prefix: 'tc654'
|
||||
Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf
|
||||
|
||||
Authors:
|
||||
Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Masahiko Iwamoto <iwamoto@allied-telesis.co.jp>
|
||||
|
||||
Description
|
||||
-----------
|
||||
This driver implements support for the Microchip TC654 and TC655.
|
||||
|
||||
The TC654 uses the 2-wire interface compatible with the SMBUS 2.0
|
||||
specification. The TC654 has two (2) inputs for measuring fan RPM and
|
||||
one (1) PWM output which can be used for fan control.
|
||||
|
||||
Configuration Notes
|
||||
-------------------
|
||||
Ordinarily the pwm1_mode ABI is used for controlling the pwm output
|
||||
mode. However, for this chip the output is always pwm, and the
|
||||
pwm1_mode determines if the pwm output is controlled via the pwm1 value
|
||||
or via the Vin analog input.
|
||||
|
||||
|
||||
Setting pwm1_mode to 1 will cause the pwm output to be driven based on
|
||||
the pwm1 value. Setting pwm1_mode to 0 will cause the pwm output to be
|
||||
driven based on the Vin input.
|
|
@ -0,0 +1,36 @@
|
|||
Kernel driver tmp108
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
* Texas Instruments TMP108
|
||||
Prefix: 'tmp108'
|
||||
Addresses scanned: none
|
||||
Datasheet: http://www.ti.com/product/tmp108
|
||||
|
||||
Author:
|
||||
John Muir <john@jmuir.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The Texas Instruments TMP108 implements one temperature sensor. An alert pin
|
||||
can be set when temperatures exceed minimum or maximum values plus or minus a
|
||||
hysteresis value. (This driver does not support interrupts for the alert pin,
|
||||
and the device runs in comparator mode.)
|
||||
|
||||
The sensor is accurate to 0.75C over the range of -25 to +85 C, and to 1.0
|
||||
degree from -40 to +125 C. Resolution of the sensor is 0.0625 degree. The
|
||||
operating temperature has a minimum of -55 C and a maximum of +150 C.
|
||||
Hysteresis values can be set to 0, 1, 2, or 4C.
|
||||
|
||||
The TMP108 has a programmable update rate that can select between 8, 4, 1, and
|
||||
0.5 Hz.
|
||||
|
||||
By default the TMP108 reads the temperature continuously. To conserve power,
|
||||
the TMP108 has a one-shot mode where the device is normally shut-down. When a
|
||||
one shot is requested the temperature is read, the result can be retrieved,
|
||||
and then the device is shut down automatically. (This driver only supports
|
||||
continuous mode.)
|
||||
|
||||
The driver provides the common sysfs-interface for temperatures (see
|
||||
Documentation/hwmon/sysfs-interface under Temperatures).
|
|
@ -907,6 +907,17 @@ config SENSORS_MCP3021
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called mcp3021.
|
||||
|
||||
config SENSORS_TC654
|
||||
tristate "Microchip TC654/TC655 and compatibles"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for TC654 and TC655.
|
||||
The TC654 and TC655 are PWM mode fan speed controllers with
|
||||
FanSense technology for use with brushless DC fans.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tc654.
|
||||
|
||||
config SENSORS_MENF21BMC_HWMON
|
||||
tristate "MEN 14F021P00 BMC Hardware Monitoring"
|
||||
depends on MFD_MENF21BMC
|
||||
|
@ -1068,8 +1079,8 @@ config SENSORS_LM90
|
|||
LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A,
|
||||
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659,
|
||||
MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008,
|
||||
Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, and GMT G781
|
||||
sensor chips.
|
||||
Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and
|
||||
Texas Instruments TMP451 sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called lm90.
|
||||
|
@ -1591,6 +1602,17 @@ config SENSORS_TMP103
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called tmp103.
|
||||
|
||||
config SENSORS_TMP108
|
||||
tristate "Texas Instruments TMP108"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments TMP108
|
||||
sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tmp108.
|
||||
|
||||
config SENSORS_TMP401
|
||||
tristate "Texas Instruments TMP401 and compatibles"
|
||||
depends on I2C
|
||||
|
|
|
@ -122,6 +122,7 @@ obj-$(CONFIG_SENSORS_MAX6697) += max6697.o
|
|||
obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
|
||||
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
||||
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
|
||||
obj-$(CONFIG_SENSORS_TC654) += tc654.o
|
||||
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
|
||||
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
|
||||
|
@ -152,6 +153,7 @@ obj-$(CONFIG_SENSORS_TC74) += tc74.o
|
|||
obj-$(CONFIG_SENSORS_THMC50) += thmc50.o
|
||||
obj-$(CONFIG_SENSORS_TMP102) += tmp102.o
|
||||
obj-$(CONFIG_SENSORS_TMP103) += tmp103.o
|
||||
obj-$(CONFIG_SENSORS_TMP108) += tmp108.o
|
||||
obj-$(CONFIG_SENSORS_TMP401) += tmp401.o
|
||||
obj-$(CONFIG_SENSORS_TMP421) += tmp421.o
|
||||
obj-$(CONFIG_SENSORS_TWL4030_MADC)+= twl4030-madc-hwmon.o
|
||||
|
|
|
@ -93,7 +93,7 @@ static const int in_scale[6] = { 2500, 2250, 3300, 5000, 12000, 3300 };
|
|||
|
||||
#define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192)
|
||||
#define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \
|
||||
(val) * 192 >= (scale) * 255 ? 255 : \
|
||||
(val) >= (scale) * 255 / 192 ? 255 : \
|
||||
((val) * 192 + (scale) / 2) / (scale))
|
||||
|
||||
#define TEMP_FROM_REG(reg) ((reg) * 1000)
|
||||
|
|
|
@ -197,8 +197,9 @@ static int adm1026_scaling[] = { /* .001 Volts */
|
|||
};
|
||||
#define NEG12_OFFSET 16000
|
||||
#define SCALE(val, from, to) (((val)*(to) + ((from)/2))/(from))
|
||||
#define INS_TO_REG(n, val) (clamp_val(SCALE(val, adm1026_scaling[n], 192),\
|
||||
0, 255))
|
||||
#define INS_TO_REG(n, val) \
|
||||
SCALE(clamp_val(val, 0, 255 * adm1026_scaling[n] / 192), \
|
||||
adm1026_scaling[n], 192)
|
||||
#define INS_FROM_REG(n, val) (SCALE(val, 192, adm1026_scaling[n]))
|
||||
|
||||
/*
|
||||
|
@ -215,11 +216,11 @@ static int adm1026_scaling[] = { /* .001 Volts */
|
|||
#define DIV_TO_REG(val) ((val) >= 8 ? 3 : (val) >= 4 ? 2 : (val) >= 2 ? 1 : 0)
|
||||
|
||||
/* Temperature is reported in 1 degC increments */
|
||||
#define TEMP_TO_REG(val) (clamp_val(((val) + ((val) < 0 ? -500 : 500)) \
|
||||
/ 1000, -127, 127))
|
||||
#define TEMP_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \
|
||||
1000)
|
||||
#define TEMP_FROM_REG(val) ((val) * 1000)
|
||||
#define OFFSET_TO_REG(val) (clamp_val(((val) + ((val) < 0 ? -500 : 500)) \
|
||||
/ 1000, -127, 127))
|
||||
#define OFFSET_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \
|
||||
1000)
|
||||
#define OFFSET_FROM_REG(val) ((val) * 1000)
|
||||
|
||||
#define PWM_TO_REG(val) (clamp_val(val, 0, 255))
|
||||
|
@ -233,7 +234,8 @@ static int adm1026_scaling[] = { /* .001 Volts */
|
|||
* indicates that the DAC could be used to drive the fans, but in our
|
||||
* example board (Arima HDAMA) it isn't connected to the fans at all.
|
||||
*/
|
||||
#define DAC_TO_REG(val) (clamp_val(((((val) * 255) + 500) / 2500), 0, 255))
|
||||
#define DAC_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, 0, 2500) * 255, \
|
||||
2500)
|
||||
#define DAC_FROM_REG(val) (((val) * 2500) / 255)
|
||||
|
||||
/*
|
||||
|
@ -593,7 +595,10 @@ static ssize_t set_in16_min(struct device *dev, struct device_attribute *attr,
|
|||
return err;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->in_min[16] = INS_TO_REG(16, val + NEG12_OFFSET);
|
||||
data->in_min[16] = INS_TO_REG(16,
|
||||
clamp_val(val, INT_MIN,
|
||||
INT_MAX - NEG12_OFFSET) +
|
||||
NEG12_OFFSET);
|
||||
adm1026_write_value(client, ADM1026_REG_IN_MIN[16], data->in_min[16]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
|
@ -618,7 +623,10 @@ static ssize_t set_in16_max(struct device *dev, struct device_attribute *attr,
|
|||
return err;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->in_max[16] = INS_TO_REG(16, val+NEG12_OFFSET);
|
||||
data->in_max[16] = INS_TO_REG(16,
|
||||
clamp_val(val, INT_MIN,
|
||||
INT_MAX - NEG12_OFFSET) +
|
||||
NEG12_OFFSET);
|
||||
adm1026_write_value(client, ADM1026_REG_IN_MAX[16], data->in_max[16]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
|
|
|
@ -98,13 +98,15 @@ static inline unsigned int IN_FROM_REG(u8 reg, int n)
|
|||
|
||||
static inline u8 IN_TO_REG(unsigned long val, int n)
|
||||
{
|
||||
return clamp_val(SCALE(val, 192, nom_mv[n]), 0, 255);
|
||||
val = clamp_val(val, 0, nom_mv[n] * 255 / 192);
|
||||
return SCALE(val, 192, nom_mv[n]);
|
||||
}
|
||||
|
||||
/* temperature range: -40..125, 127 disables temperature alarm */
|
||||
static inline s8 TEMP_TO_REG(long val)
|
||||
{
|
||||
return clamp_val(SCALE(val, 1, 1000), -40, 127);
|
||||
val = clamp_val(val, -40000, 127000);
|
||||
return SCALE(val, 1, 1000);
|
||||
}
|
||||
|
||||
/* two fans, each with low fan speed limit */
|
||||
|
@ -122,7 +124,8 @@ static inline unsigned int FAN_FROM_REG(u8 reg, u8 div)
|
|||
/* analog out 0..1250mV */
|
||||
static inline u8 AOUT_TO_REG(unsigned long val)
|
||||
{
|
||||
return clamp_val(SCALE(val, 255, 1250), 0, 255);
|
||||
val = clamp_val(val, 0, 1250);
|
||||
return SCALE(val, 255, 1250);
|
||||
}
|
||||
|
||||
static inline unsigned int AOUT_FROM_REG(u8 reg)
|
||||
|
|
|
@ -55,7 +55,7 @@ struct adt7411_data {
|
|||
struct mutex device_lock; /* for "atomic" device accesses */
|
||||
struct mutex update_lock;
|
||||
unsigned long next_update;
|
||||
int vref_cached;
|
||||
long vref_cached;
|
||||
struct i2c_client *client;
|
||||
bool use_ext_temp;
|
||||
};
|
||||
|
@ -114,85 +114,6 @@ static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t adt7411_show_vdd(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB,
|
||||
ADT7411_REG_VDD_MSB, 2);
|
||||
|
||||
return ret < 0 ? ret : sprintf(buf, "%u\n", ret * 7000 / 1024);
|
||||
}
|
||||
|
||||
static ssize_t adt7411_show_temp(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int val;
|
||||
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)
|
||||
return val;
|
||||
|
||||
val = val & 0x200 ? val - 0x400 : val; /* 10 bit signed */
|
||||
|
||||
return sprintf(buf, "%d\n", val * 250);
|
||||
}
|
||||
|
||||
static ssize_t adt7411_show_input(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int val;
|
||||
u8 lsb_reg, lsb_shift;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
if (time_after_eq(jiffies, data->next_update)) {
|
||||
val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3);
|
||||
if (val < 0)
|
||||
goto exit_unlock;
|
||||
|
||||
if (val & ADT7411_CFG3_REF_VDD) {
|
||||
val = adt7411_read_10_bit(client,
|
||||
ADT7411_REG_INT_TEMP_VDD_LSB,
|
||||
ADT7411_REG_VDD_MSB, 2);
|
||||
if (val < 0)
|
||||
goto exit_unlock;
|
||||
|
||||
data->vref_cached = val * 7000 / 1024;
|
||||
} else {
|
||||
data->vref_cached = 2250;
|
||||
}
|
||||
|
||||
data->next_update = jiffies + HZ;
|
||||
}
|
||||
|
||||
lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2);
|
||||
lsb_shift = 2 * (nr & 0x03);
|
||||
val = adt7411_read_10_bit(client, lsb_reg,
|
||||
ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, lsb_shift);
|
||||
if (val < 0)
|
||||
goto exit_unlock;
|
||||
|
||||
val = sprintf(buf, "%u\n", val * data->vref_cached / 1024);
|
||||
exit_unlock:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return val;
|
||||
}
|
||||
|
||||
static ssize_t adt7411_show_bit(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
|
@ -228,65 +149,157 @@ static ssize_t adt7411_set_bit(struct device *dev,
|
|||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
|
||||
#define ADT7411_BIT_ATTR(__name, __reg, __bit) \
|
||||
SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \
|
||||
adt7411_set_bit, __bit, __reg)
|
||||
|
||||
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 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(in3_input, S_IRUGO, adt7411_show_input, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, adt7411_show_input, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, adt7411_show_input, NULL, 4);
|
||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, adt7411_show_input, NULL, 5);
|
||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, adt7411_show_input, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, adt7411_show_input, NULL, 7);
|
||||
static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG);
|
||||
static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225);
|
||||
static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD);
|
||||
|
||||
static struct attribute *adt7411_attrs[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&dev_attr_in0_input.attr,
|
||||
&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_no_average.dev_attr.attr,
|
||||
&sensor_dev_attr_fast_sampling.dev_attr.attr,
|
||||
&sensor_dev_attr_adc_ref_vdd.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(adt7411);
|
||||
|
||||
static umode_t adt7411_attrs_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int index)
|
||||
static int adt7411_read_in_vdd(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
bool visible = true;
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
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;
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB,
|
||||
ADT7411_REG_VDD_MSB, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret * 7000 / 1024;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct attribute_group adt7411_group = {
|
||||
.attrs = adt7411_attrs,
|
||||
.is_visible = adt7411_attrs_visible,
|
||||
};
|
||||
__ATTRIBUTE_GROUPS(adt7411);
|
||||
static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
int ret;
|
||||
int lsb_reg, lsb_shift;
|
||||
int nr = channel - 1;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
if (time_after_eq(jiffies, data->next_update)) {
|
||||
ret = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3);
|
||||
if (ret < 0)
|
||||
goto exit_unlock;
|
||||
|
||||
if (ret & ADT7411_CFG3_REF_VDD) {
|
||||
ret = adt7411_read_in_vdd(dev, hwmon_in_input,
|
||||
&data->vref_cached);
|
||||
if (ret < 0)
|
||||
goto exit_unlock;
|
||||
} else {
|
||||
data->vref_cached = 2250;
|
||||
}
|
||||
|
||||
data->next_update = jiffies + HZ;
|
||||
}
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2);
|
||||
lsb_shift = 2 * (nr & 0x03);
|
||||
ret = adt7411_read_10_bit(client, lsb_reg,
|
||||
ADT7411_REG_EXT_TEMP_AIN1_MSB + nr,
|
||||
lsb_shift);
|
||||
if (ret < 0)
|
||||
goto exit_unlock;
|
||||
*val = ret * data->vref_cached / 1024;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
exit_unlock:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adt7411_read_in(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
if (channel == 0)
|
||||
return adt7411_read_in_vdd(dev, attr, val);
|
||||
else
|
||||
return adt7411_read_in_chan(dev, attr, channel, val);
|
||||
}
|
||||
|
||||
static int adt7411_read_temp(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct adt7411_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret, regl, regh;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
regl = channel ? ADT7411_REG_EXT_TEMP_AIN14_LSB :
|
||||
ADT7411_REG_INT_TEMP_VDD_LSB;
|
||||
regh = channel ? ADT7411_REG_EXT_TEMP_AIN1_MSB :
|
||||
ADT7411_REG_INT_TEMP_MSB;
|
||||
ret = adt7411_read_10_bit(client, regl, regh, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = ret & 0x200 ? ret - 0x400 : ret; /* 10 bit signed */
|
||||
*val = ret * 250;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int adt7411_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
return adt7411_read_in(dev, attr, channel, val);
|
||||
case hwmon_temp:
|
||||
return adt7411_read_temp(dev, attr, channel, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t adt7411_is_visible(const void *_data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct adt7411_data *data = _data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
if (channel > 0 && channel < 3)
|
||||
return data->use_ext_temp ? 0 : S_IRUGO;
|
||||
else
|
||||
return S_IRUGO;
|
||||
case hwmon_temp:
|
||||
if (channel == 1)
|
||||
return data->use_ext_temp ? S_IRUGO : 0;
|
||||
else
|
||||
return S_IRUGO;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int adt7411_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info)
|
||||
|
@ -358,6 +371,51 @@ static int adt7411_init_device(struct adt7411_data *data)
|
|||
return i2c_smbus_write_byte_data(data->client, ADT7411_REG_CFG1, val);
|
||||
}
|
||||
|
||||
static const u32 adt7411_in_config[] = {
|
||||
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 adt7411_in = {
|
||||
.type = hwmon_in,
|
||||
.config = adt7411_in_config,
|
||||
};
|
||||
|
||||
static const u32 adt7411_temp_config[] = {
|
||||
HWMON_T_INPUT,
|
||||
HWMON_T_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info adt7411_temp = {
|
||||
.type = hwmon_temp,
|
||||
.config = adt7411_temp_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *adt7411_info[] = {
|
||||
&adt7411_in,
|
||||
&adt7411_temp,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops adt7411_hwmon_ops = {
|
||||
.is_visible = adt7411_is_visible,
|
||||
.read = adt7411_read,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info adt7411_chip_info = {
|
||||
.ops = &adt7411_hwmon_ops,
|
||||
.info = adt7411_info,
|
||||
};
|
||||
|
||||
static int adt7411_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -382,9 +440,10 @@ static int adt7411_probe(struct i2c_client *client,
|
|||
/* force update on first occasion */
|
||||
data->next_update = jiffies;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||
data,
|
||||
adt7411_groups);
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
data,
|
||||
&adt7411_chip_info,
|
||||
adt7411_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
|
|
|
@ -810,8 +810,8 @@ static ssize_t set_temp_min(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -64000, 191000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000) + 64;
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->temp_min[attr->index] = temp;
|
||||
|
@ -848,8 +848,8 @@ static ssize_t set_temp_max(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -64000, 191000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000) + 64;
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->temp_max[attr->index] = temp;
|
||||
|
@ -912,9 +912,9 @@ static ssize_t set_volt_max(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp) || !x)
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, 0, 255 * x / 1000);
|
||||
temp *= 1000; /* convert mV to uV */
|
||||
temp = DIV_ROUND_CLOSEST(temp, x);
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->volt_max[attr->index] = temp;
|
||||
|
@ -954,9 +954,9 @@ static ssize_t set_volt_min(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp) || !x)
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, 0, 255 * x / 1000);
|
||||
temp *= 1000; /* convert mV to uV */
|
||||
temp = DIV_ROUND_CLOSEST(temp, x);
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->volt_min[attr->index] = temp;
|
||||
|
@ -1220,8 +1220,8 @@ static ssize_t set_pwm_hyst(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, 0, 15000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000);
|
||||
temp = clamp_val(temp, 0, 15);
|
||||
|
||||
/* package things up */
|
||||
temp &= ADT7462_PWM_HYST_MASK;
|
||||
|
@ -1306,8 +1306,8 @@ static ssize_t set_pwm_tmin(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -64000, 191000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000) + 64;
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->pwm_tmin[attr->index] = temp;
|
||||
|
|
|
@ -483,8 +483,8 @@ static ssize_t set_temp_min(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -128000, 127000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000);
|
||||
temp = clamp_val(temp, -128, 127);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->temp_min[attr->index] = temp;
|
||||
|
@ -517,8 +517,8 @@ static ssize_t set_temp_max(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -128000, 127000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000);
|
||||
temp = clamp_val(temp, -128, 127);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->temp_max[attr->index] = temp;
|
||||
|
@ -880,8 +880,8 @@ static ssize_t set_pwm_tmin(struct device *dev,
|
|||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, -128000, 127000);
|
||||
temp = DIV_ROUND_CLOSEST(temp, 1000);
|
||||
temp = clamp_val(temp, -128, 127);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->pwm_tmin[attr->index] = temp;
|
||||
|
|
|
@ -188,8 +188,8 @@ static struct amc6821_data *amc6821_update_device(struct device *dev)
|
|||
!data->valid) {
|
||||
|
||||
for (i = 0; i < TEMP_IDX_LEN; i++)
|
||||
data->temp[i] = i2c_smbus_read_byte_data(client,
|
||||
temp_reg[i]);
|
||||
data->temp[i] = (int8_t)i2c_smbus_read_byte_data(
|
||||
client, temp_reg[i]);
|
||||
|
||||
data->stat1 = i2c_smbus_read_byte_data(client,
|
||||
AMC6821_REG_STAT1);
|
||||
|
|
|
@ -51,6 +51,7 @@ static int force_tjmax;
|
|||
module_param_named(tjmax, force_tjmax, int, 0444);
|
||||
MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
|
||||
|
||||
#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */
|
||||
#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */
|
||||
#define NUM_REAL_CORES 128 /* Number of Real cores per cpu */
|
||||
#define CORETEMP_NAME_LENGTH 19 /* String Length of attrs */
|
||||
|
@ -58,7 +59,6 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
|
|||
#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1)
|
||||
#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO)
|
||||
|
||||
#define TO_PHYS_ID(cpu) (cpu_data(cpu).phys_proc_id)
|
||||
#define TO_CORE_ID(cpu) (cpu_data(cpu).cpu_core_id)
|
||||
#define TO_ATTR_NO(cpu) (TO_CORE_ID(cpu) + BASE_SYSFS_ATTR_NO)
|
||||
|
||||
|
@ -102,20 +102,17 @@ struct temp_data {
|
|||
|
||||
/* Platform Data per Physical CPU */
|
||||
struct platform_data {
|
||||
struct device *hwmon_dev;
|
||||
u16 phys_proc_id;
|
||||
struct temp_data *core_data[MAX_CORE_DATA];
|
||||
struct device *hwmon_dev;
|
||||
u16 pkg_id;
|
||||
struct cpumask cpumask;
|
||||
struct temp_data *core_data[MAX_CORE_DATA];
|
||||
struct device_attribute name_attr;
|
||||
};
|
||||
|
||||
struct pdev_entry {
|
||||
struct list_head list;
|
||||
struct platform_device *pdev;
|
||||
u16 phys_proc_id;
|
||||
};
|
||||
|
||||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
/* Keep track of how many package pointers we allocated in init() */
|
||||
static int max_packages __read_mostly;
|
||||
/* Array of package pointers. Serialized by cpu hotplug lock */
|
||||
static struct platform_device **pkg_devices;
|
||||
|
||||
static ssize_t show_label(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
|
@ -125,7 +122,7 @@ static ssize_t show_label(struct device *dev,
|
|||
struct temp_data *tdata = pdata->core_data[attr->index];
|
||||
|
||||
if (tdata->is_pkg_data)
|
||||
return sprintf(buf, "Physical id %u\n", pdata->phys_proc_id);
|
||||
return sprintf(buf, "Package id %u\n", pdata->pkg_id);
|
||||
|
||||
return sprintf(buf, "Core %u\n", tdata->cpu_core_id);
|
||||
}
|
||||
|
@ -138,7 +135,9 @@ static ssize_t show_crit_alarm(struct device *dev,
|
|||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
struct temp_data *tdata = pdata->core_data[attr->index];
|
||||
|
||||
mutex_lock(&tdata->update_lock);
|
||||
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
|
||||
mutex_unlock(&tdata->update_lock);
|
||||
|
||||
return sprintf(buf, "%d\n", (eax >> 5) & 1);
|
||||
}
|
||||
|
@ -435,18 +434,10 @@ static int chk_ucode_version(unsigned int cpu)
|
|||
|
||||
static struct platform_device *coretemp_get_pdev(unsigned int cpu)
|
||||
{
|
||||
u16 phys_proc_id = TO_PHYS_ID(cpu);
|
||||
struct pdev_entry *p;
|
||||
int pkgid = topology_logical_package_id(cpu);
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
|
||||
list_for_each_entry(p, &pdev_list, list)
|
||||
if (p->phys_proc_id == phys_proc_id) {
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return p->pdev;
|
||||
}
|
||||
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
if (pkgid >= 0 && pkgid < max_packages)
|
||||
return pkg_devices[pkgid];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -483,21 +474,11 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
|
|||
* The attr number is always core id + 2
|
||||
* The Pkgtemp will always show up as temp1_*, if available
|
||||
*/
|
||||
attr_no = pkg_flag ? 1 : TO_ATTR_NO(cpu);
|
||||
attr_no = pkg_flag ? PKG_SYSFS_ATTR_NO : TO_ATTR_NO(cpu);
|
||||
|
||||
if (attr_no > MAX_CORE_DATA - 1)
|
||||
return -ERANGE;
|
||||
|
||||
/*
|
||||
* Provide a single set of attributes for all HT siblings of a core
|
||||
* to avoid duplicate sensors (the processor ID and core ID of all
|
||||
* HT siblings of a core are the same).
|
||||
* Skip if a HT sibling of this core is already registered.
|
||||
* This is not an error.
|
||||
*/
|
||||
if (pdata->core_data[attr_no] != NULL)
|
||||
return 0;
|
||||
|
||||
tdata = init_temp_data(cpu, pkg_flag);
|
||||
if (!tdata)
|
||||
return -ENOMEM;
|
||||
|
@ -539,21 +520,14 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
|
|||
return err;
|
||||
}
|
||||
|
||||
static void coretemp_add_core(unsigned int cpu, int pkg_flag)
|
||||
static void
|
||||
coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag)
|
||||
{
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
int err;
|
||||
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
err = create_core_data(pdev, cpu, pkg_flag);
|
||||
if (err)
|
||||
if (create_core_data(pdev, cpu, pkg_flag))
|
||||
dev_err(&pdev->dev, "Adding Core %u failed\n", cpu);
|
||||
}
|
||||
|
||||
static void coretemp_remove_core(struct platform_data *pdata,
|
||||
int indx)
|
||||
static void coretemp_remove_core(struct platform_data *pdata, int indx)
|
||||
{
|
||||
struct temp_data *tdata = pdata->core_data[indx];
|
||||
|
||||
|
@ -574,7 +548,7 @@ static int coretemp_probe(struct platform_device *pdev)
|
|||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
pdata->phys_proc_id = pdev->id;
|
||||
pdata->pkg_id = pdev->id;
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
|
||||
pdata->hwmon_dev = devm_hwmon_device_register_with_groups(dev, DRVNAME,
|
||||
|
@ -602,85 +576,33 @@ static struct platform_driver coretemp_driver = {
|
|||
.remove = coretemp_remove,
|
||||
};
|
||||
|
||||
static int coretemp_device_add(unsigned int cpu)
|
||||
static struct platform_device *coretemp_device_add(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
int err, pkgid = topology_logical_package_id(cpu);
|
||||
struct platform_device *pdev;
|
||||
struct pdev_entry *pdev_entry;
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
if (pkgid < 0)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pdev = platform_device_alloc(DRVNAME, TO_PHYS_ID(cpu));
|
||||
if (!pdev) {
|
||||
err = -ENOMEM;
|
||||
pr_err("Device allocation failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);
|
||||
if (!pdev_entry) {
|
||||
err = -ENOMEM;
|
||||
goto exit_device_put;
|
||||
}
|
||||
pdev = platform_device_alloc(DRVNAME, pkgid);
|
||||
if (!pdev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
err = platform_device_add(pdev);
|
||||
if (err) {
|
||||
pr_err("Device addition failed (%d)\n", err);
|
||||
goto exit_device_free;
|
||||
platform_device_put(pdev);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
pdev_entry->pdev = pdev;
|
||||
pdev_entry->phys_proc_id = pdev->id;
|
||||
|
||||
list_add_tail(&pdev_entry->list, &pdev_list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_free:
|
||||
kfree(pdev_entry);
|
||||
exit_device_put:
|
||||
platform_device_put(pdev);
|
||||
exit:
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return err;
|
||||
pkg_devices[pkgid] = pdev;
|
||||
return pdev;
|
||||
}
|
||||
|
||||
static void coretemp_device_remove(unsigned int cpu)
|
||||
static int coretemp_cpu_online(unsigned int cpu)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
u16 phys_proc_id = TO_PHYS_ID(cpu);
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
if (p->phys_proc_id != phys_proc_id)
|
||||
continue;
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
}
|
||||
|
||||
static bool is_any_core_online(struct platform_data *pdata)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Find online cores, except pkgtemp data */
|
||||
for (i = MAX_CORE_DATA - 1; i >= 0; --i) {
|
||||
if (pdata->core_data[i] &&
|
||||
!pdata->core_data[i]->is_pkg_data) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void get_core_online(unsigned int cpu)
|
||||
{
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
int err;
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
struct platform_data *pdata;
|
||||
|
||||
/*
|
||||
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
|
||||
|
@ -688,12 +610,12 @@ static void get_core_online(unsigned int cpu)
|
|||
* without thermal sensors will be filtered out.
|
||||
*/
|
||||
if (!cpu_has(c, X86_FEATURE_DTHERM))
|
||||
return;
|
||||
return -ENODEV;
|
||||
|
||||
if (!pdev) {
|
||||
/* Check the microcode version of the CPU */
|
||||
if (chk_ucode_version(cpu))
|
||||
return;
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Alright, we have DTS support.
|
||||
|
@ -701,101 +623,100 @@ static void get_core_online(unsigned int cpu)
|
|||
* online. So, initialize per-pkg data structures and
|
||||
* then bring this core online.
|
||||
*/
|
||||
err = coretemp_device_add(cpu);
|
||||
if (err)
|
||||
return;
|
||||
pdev = coretemp_device_add(cpu);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
/*
|
||||
* Check whether pkgtemp support is available.
|
||||
* If so, add interfaces for pkgtemp.
|
||||
*/
|
||||
if (cpu_has(c, X86_FEATURE_PTS))
|
||||
coretemp_add_core(cpu, 1);
|
||||
coretemp_add_core(pdev, cpu, 1);
|
||||
}
|
||||
|
||||
pdata = platform_get_drvdata(pdev);
|
||||
/*
|
||||
* Physical CPU device already exists.
|
||||
* So, just add interfaces for this core.
|
||||
* Check whether a thread sibling is already online. If not add the
|
||||
* interface for this CPU core.
|
||||
*/
|
||||
coretemp_add_core(cpu, 0);
|
||||
if (!cpumask_intersects(&pdata->cpumask, topology_sibling_cpumask(cpu)))
|
||||
coretemp_add_core(pdev, cpu, 0);
|
||||
|
||||
cpumask_set_cpu(cpu, &pdata->cpumask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void put_core_offline(unsigned int cpu)
|
||||
static int coretemp_cpu_offline(unsigned int cpu)
|
||||
{
|
||||
int i, indx;
|
||||
struct platform_data *pdata;
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
struct platform_data *pd;
|
||||
struct temp_data *tdata;
|
||||
int indx, target;
|
||||
|
||||
/* If the physical CPU device does not exist, just return */
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
pdata = platform_get_drvdata(pdev);
|
||||
|
||||
indx = TO_ATTR_NO(cpu);
|
||||
return 0;
|
||||
|
||||
/* The core id is too big, just return */
|
||||
indx = TO_ATTR_NO(cpu);
|
||||
if (indx > MAX_CORE_DATA - 1)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
if (pdata->core_data[indx] && pdata->core_data[indx]->cpu == cpu)
|
||||
coretemp_remove_core(pdata, indx);
|
||||
pd = platform_get_drvdata(pdev);
|
||||
tdata = pd->core_data[indx];
|
||||
|
||||
cpumask_clear_cpu(cpu, &pd->cpumask);
|
||||
|
||||
/*
|
||||
* If a HT sibling of a core is taken offline, but another HT sibling
|
||||
* of the same core is still online, register the alternate sibling.
|
||||
* This ensures that exactly one set of attributes is provided as long
|
||||
* as at least one HT sibling of a core is online.
|
||||
* If this is the last thread sibling, remove the CPU core
|
||||
* interface, If there is still a sibling online, transfer the
|
||||
* target cpu of that core interface to it.
|
||||
*/
|
||||
for_each_sibling(i, cpu) {
|
||||
if (i != cpu) {
|
||||
get_core_online(i);
|
||||
/*
|
||||
* Display temperature sensor data for one HT sibling
|
||||
* per core only, so abort the loop after one such
|
||||
* sibling has been found.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu));
|
||||
if (target >= nr_cpu_ids) {
|
||||
coretemp_remove_core(pd, indx);
|
||||
} else if (tdata && tdata->cpu == cpu) {
|
||||
mutex_lock(&tdata->update_lock);
|
||||
tdata->cpu = target;
|
||||
mutex_unlock(&tdata->update_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* If all cores in this pkg are offline, remove the device.
|
||||
* coretemp_device_remove calls unregister_platform_device,
|
||||
* which in turn calls coretemp_remove. This removes the
|
||||
* pkgtemp entry and does other clean ups.
|
||||
* If all cores in this pkg are offline, remove the device. This
|
||||
* will invoke the platform driver remove function, which cleans up
|
||||
* the rest.
|
||||
*/
|
||||
if (!is_any_core_online(pdata))
|
||||
coretemp_device_remove(cpu);
|
||||
}
|
||||
|
||||
static int coretemp_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long) hcpu;
|
||||
|
||||
switch (action) {
|
||||
case CPU_ONLINE:
|
||||
case CPU_DOWN_FAILED:
|
||||
get_core_online(cpu);
|
||||
break;
|
||||
case CPU_DOWN_PREPARE:
|
||||
put_core_offline(cpu);
|
||||
break;
|
||||
if (cpumask_empty(&pd->cpumask)) {
|
||||
pkg_devices[topology_logical_package_id(cpu)] = NULL;
|
||||
platform_device_unregister(pdev);
|
||||
return 0;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
|
||||
/*
|
||||
* Check whether this core is the target for the package
|
||||
* interface. We need to assign it to some other cpu.
|
||||
*/
|
||||
tdata = pd->core_data[PKG_SYSFS_ATTR_NO];
|
||||
if (tdata && tdata->cpu == cpu) {
|
||||
target = cpumask_first(&pd->cpumask);
|
||||
mutex_lock(&tdata->update_lock);
|
||||
tdata->cpu = target;
|
||||
mutex_unlock(&tdata->update_lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block coretemp_cpu_notifier __refdata = {
|
||||
.notifier_call = coretemp_cpu_callback,
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id __initconst coretemp_ids[] = {
|
||||
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_DTHERM },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, coretemp_ids);
|
||||
|
||||
static enum cpuhp_state coretemp_hp_online;
|
||||
|
||||
static int __init coretemp_init(void)
|
||||
{
|
||||
int i, err;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
|
||||
|
@ -805,54 +726,38 @@ static int __init coretemp_init(void)
|
|||
if (!x86_match_cpu(coretemp_ids))
|
||||
return -ENODEV;
|
||||
|
||||
max_packages = topology_max_packages();
|
||||
pkg_devices = kzalloc(max_packages * sizeof(struct platform_device *),
|
||||
GFP_KERNEL);
|
||||
if (!pkg_devices)
|
||||
return -ENOMEM;
|
||||
|
||||
err = platform_driver_register(&coretemp_driver);
|
||||
if (err)
|
||||
goto exit;
|
||||
return err;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
for_each_online_cpu(i)
|
||||
get_core_online(i);
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
if (list_empty(&pdev_list)) {
|
||||
cpu_notifier_register_done();
|
||||
err = -ENODEV;
|
||||
goto exit_driver_unreg;
|
||||
}
|
||||
#endif
|
||||
|
||||
__register_hotcpu_notifier(&coretemp_cpu_notifier);
|
||||
cpu_notifier_register_done();
|
||||
err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/coretemp:online",
|
||||
coretemp_cpu_online, coretemp_cpu_offline);
|
||||
if (err < 0)
|
||||
goto outdrv;
|
||||
coretemp_hp_online = err;
|
||||
return 0;
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
exit_driver_unreg:
|
||||
outdrv:
|
||||
platform_driver_unregister(&coretemp_driver);
|
||||
#endif
|
||||
exit:
|
||||
kfree(pkg_devices);
|
||||
return err;
|
||||
}
|
||||
module_init(coretemp_init)
|
||||
|
||||
static void __exit coretemp_exit(void)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
__unregister_hotcpu_notifier(&coretemp_cpu_notifier);
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
cpu_notifier_register_done();
|
||||
cpuhp_remove_state(coretemp_hp_online);
|
||||
platform_driver_unregister(&coretemp_driver);
|
||||
kfree(pkg_devices);
|
||||
}
|
||||
module_exit(coretemp_exit)
|
||||
|
||||
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
|
||||
MODULE_DESCRIPTION("Intel Core temperature monitor");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(coretemp_init)
|
||||
module_exit(coretemp_exit)
|
||||
|
|
|
@ -166,7 +166,7 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
|||
if (res)
|
||||
return res;
|
||||
|
||||
val = (val * 10 / 625) * 8;
|
||||
val = (clamp_val(val, -128000, 128000) * 10 / 625) * 8;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp[attr->index] = val;
|
||||
|
|
|
@ -251,7 +251,7 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *da,
|
|||
if (result < 0)
|
||||
return result;
|
||||
|
||||
val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -63, 127);
|
||||
val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp_min[nr] = val;
|
||||
|
@ -273,7 +273,7 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *da,
|
|||
if (result < 0)
|
||||
return result;
|
||||
|
||||
val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -63, 127);
|
||||
val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp_max[nr] = val;
|
||||
|
|
|
@ -215,12 +215,13 @@ static ssize_t set_in(struct device *dev, struct device_attribute *devattr,
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
val = DIV_ROUND_CLOSEST(val * 0xC0, nominal_mv[nr]);
|
||||
val = clamp_val(val, 0, 255 * nominal_mv[nr] / 192);
|
||||
val = DIV_ROUND_CLOSEST(val * 192, nominal_mv[nr]);
|
||||
reg = (sf == min) ? EMC6W201_REG_IN_LOW(nr)
|
||||
: EMC6W201_REG_IN_HIGH(nr);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->in[sf][nr] = clamp_val(val, 0, 255);
|
||||
data->in[sf][nr] = val;
|
||||
err = emc6w201_write8(client, reg, data->in[sf][nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
|
@ -252,12 +253,13 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *devattr,
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
val = clamp_val(val, -127000, 127000);
|
||||
val = DIV_ROUND_CLOSEST(val, 1000);
|
||||
reg = (sf == min) ? EMC6W201_REG_TEMP_LOW(nr)
|
||||
: EMC6W201_REG_TEMP_HIGH(nr);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp[sf][nr] = clamp_val(val, -127, 127);
|
||||
data->temp[sf][nr] = val;
|
||||
err = emc6w201_write8(client, reg, data->temp[sf][nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
|
|
|
@ -193,14 +193,17 @@ static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p,
|
|||
* Convert fan RPM value from sysfs into count value for fan controller
|
||||
* register (FAN_SET_CNT).
|
||||
*/
|
||||
static inline unsigned char cnt_from_rpm(u32 rpm, u32 clk_freq, u16 p,
|
||||
static inline unsigned char cnt_from_rpm(unsigned long rpm, u32 clk_freq, u16 p,
|
||||
u8 clk_div, u8 gear_mult)
|
||||
{
|
||||
if (!rpm) /* to stop the fan, set cnt to 255 */
|
||||
unsigned long f1 = clk_freq * 30 * gear_mult;
|
||||
unsigned long f2 = p * clk_div;
|
||||
|
||||
if (!rpm) /* to stop the fan, set cnt to 255 */
|
||||
return 0xff;
|
||||
|
||||
return clamp_val(((clk_freq * 30 * gear_mult) / (rpm * p * clk_div)),
|
||||
0, 255);
|
||||
rpm = clamp_val(rpm, f1 / (255 * f2), ULONG_MAX / f2);
|
||||
return DIV_ROUND_CLOSEST(f1, rpm * f2);
|
||||
}
|
||||
|
||||
/* helper to grab and cache data, at most one time per second */
|
||||
|
|
|
@ -38,12 +38,15 @@ struct hwmon_device {
|
|||
|
||||
#define to_hwmon_device(d) container_of(d, struct hwmon_device, dev)
|
||||
|
||||
#define MAX_SYSFS_ATTR_NAME_LENGTH 32
|
||||
|
||||
struct hwmon_device_attribute {
|
||||
struct device_attribute dev_attr;
|
||||
const struct hwmon_ops *ops;
|
||||
enum hwmon_sensor_types type;
|
||||
u32 attr;
|
||||
int index;
|
||||
char name[MAX_SYSFS_ATTR_NAME_LENGTH];
|
||||
};
|
||||
|
||||
#define to_hwmon_attr(d) \
|
||||
|
@ -178,6 +181,22 @@ static ssize_t hwmon_attr_show(struct device *dev,
|
|||
return sprintf(buf, "%ld\n", val);
|
||||
}
|
||||
|
||||
static ssize_t hwmon_attr_show_string(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
char *s;
|
||||
int ret;
|
||||
|
||||
ret = hattr->ops->read_string(dev, hattr->type, hattr->attr,
|
||||
hattr->index, &s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%s\n", s);
|
||||
}
|
||||
|
||||
static ssize_t hwmon_attr_store(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
|
@ -205,6 +224,17 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static bool is_string_attr(enum hwmon_sensor_types type, u32 attr)
|
||||
{
|
||||
return (type == hwmon_temp && attr == hwmon_temp_label) ||
|
||||
(type == hwmon_in && attr == hwmon_in_label) ||
|
||||
(type == hwmon_curr && attr == hwmon_curr_label) ||
|
||||
(type == hwmon_power && attr == hwmon_power_label) ||
|
||||
(type == hwmon_energy && attr == hwmon_energy_label) ||
|
||||
(type == hwmon_humidity && attr == hwmon_humidity_label) ||
|
||||
(type == hwmon_fan && attr == hwmon_fan_label);
|
||||
}
|
||||
|
||||
static struct attribute *hwmon_genattr(struct device *dev,
|
||||
const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
|
@ -218,6 +248,7 @@ static struct attribute *hwmon_genattr(struct device *dev,
|
|||
struct attribute *a;
|
||||
umode_t mode;
|
||||
char *name;
|
||||
bool is_string = is_string_attr(type, attr);
|
||||
|
||||
/* The attribute is invisible if there is no template string */
|
||||
if (!template)
|
||||
|
@ -227,32 +258,31 @@ static struct attribute *hwmon_genattr(struct device *dev,
|
|||
if (!mode)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if ((mode & S_IRUGO) && !ops->read)
|
||||
if ((mode & S_IRUGO) && ((is_string && !ops->read_string) ||
|
||||
(!is_string && !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);
|
||||
|
||||
if (type == hwmon_chip) {
|
||||
name = (char *)template;
|
||||
} else {
|
||||
scnprintf(hattr->name, sizeof(hattr->name), template,
|
||||
index + hwmon_attr_base(type));
|
||||
name = hattr->name;
|
||||
}
|
||||
|
||||
hattr->type = type;
|
||||
hattr->attr = attr;
|
||||
hattr->index = index;
|
||||
hattr->ops = ops;
|
||||
|
||||
dattr = &hattr->dev_attr;
|
||||
dattr->show = hwmon_attr_show;
|
||||
dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show;
|
||||
dattr->store = hwmon_attr_store;
|
||||
|
||||
a = &dattr->attr;
|
||||
|
@ -263,7 +293,11 @@ static struct attribute *hwmon_genattr(struct device *dev,
|
|||
return a;
|
||||
}
|
||||
|
||||
static const char * const hwmon_chip_attr_templates[] = {
|
||||
/*
|
||||
* Chip attributes are not attribute templates but actual sysfs attributes.
|
||||
* See hwmon_genattr() for special handling.
|
||||
*/
|
||||
static const char * const hwmon_chip_attrs[] = {
|
||||
[hwmon_chip_temp_reset_history] = "temp_reset_history",
|
||||
[hwmon_chip_in_reset_history] = "in_reset_history",
|
||||
[hwmon_chip_curr_reset_history] = "curr_reset_history",
|
||||
|
@ -400,7 +434,7 @@ static const char * const hwmon_pwm_attr_templates[] = {
|
|||
};
|
||||
|
||||
static const char * const *__templates[] = {
|
||||
[hwmon_chip] = hwmon_chip_attr_templates,
|
||||
[hwmon_chip] = hwmon_chip_attrs,
|
||||
[hwmon_temp] = hwmon_temp_attr_templates,
|
||||
[hwmon_in] = hwmon_in_attr_templates,
|
||||
[hwmon_curr] = hwmon_curr_attr_templates,
|
||||
|
@ -412,7 +446,7 @@ static const char * const *__templates[] = {
|
|||
};
|
||||
|
||||
static const int __templates_size[] = {
|
||||
[hwmon_chip] = ARRAY_SIZE(hwmon_chip_attr_templates),
|
||||
[hwmon_chip] = ARRAY_SIZE(hwmon_chip_attrs),
|
||||
[hwmon_temp] = ARRAY_SIZE(hwmon_temp_attr_templates),
|
||||
[hwmon_in] = ARRAY_SIZE(hwmon_in_attr_templates),
|
||||
[hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates),
|
||||
|
@ -526,9 +560,9 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||
|
||||
hdev = &hwdev->dev;
|
||||
|
||||
if (chip && chip->ops->is_visible) {
|
||||
if (chip) {
|
||||
struct attribute **attrs;
|
||||
int ngroups = 2;
|
||||
int ngroups = 2; /* terminating NULL plus &hwdev->groups */
|
||||
|
||||
if (groups)
|
||||
for (i = 0; groups[i]; i++)
|
||||
|
@ -572,7 +606,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||
if (err)
|
||||
goto free_hwmon;
|
||||
|
||||
if (chip && chip->ops->is_visible && chip->ops->read &&
|
||||
if (chip && 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;
|
||||
|
@ -626,8 +660,8 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups);
|
|||
* @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
|
||||
* @info: pointer to hwmon chip information
|
||||
* @extra_groups: pointer to list of additional non-standard attribute groups
|
||||
*
|
||||
* hwmon_device_unregister() must be called when the device is no
|
||||
* longer needed.
|
||||
|
@ -638,12 +672,12 @@ 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)
|
||||
const struct attribute_group **extra_groups)
|
||||
{
|
||||
if (chip && (!chip->ops || !chip->info))
|
||||
if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __hwmon_device_register(dev, name, drvdata, chip, groups);
|
||||
return __hwmon_device_register(dev, name, drvdata, chip, extra_groups);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
|
||||
|
||||
|
@ -658,6 +692,9 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
|
|||
*/
|
||||
struct device *hwmon_device_register(struct device *dev)
|
||||
{
|
||||
dev_warn(dev,
|
||||
"hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info().\n");
|
||||
|
||||
return hwmon_device_register_with_groups(dev, NULL, NULL, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_device_register);
|
||||
|
|
|
@ -136,7 +136,8 @@ static const int lm85_scaling[] = { /* .001 Volts */
|
|||
#define SCALE(val, from, to) (((val) * (to) + ((from) / 2)) / (from))
|
||||
|
||||
#define INS_TO_REG(n, val) \
|
||||
clamp_val(SCALE(val, lm85_scaling[n], 192), 0, 255)
|
||||
SCALE(clamp_val(val, 0, 255 * lm85_scaling[n] / 192), \
|
||||
lm85_scaling[n], 192)
|
||||
|
||||
#define INSEXT_FROM_REG(n, val, ext) \
|
||||
SCALE(((val) << 4) + (ext), 192 << 4, lm85_scaling[n])
|
||||
|
|
|
@ -121,7 +121,7 @@ static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C };
|
|||
|
||||
#define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192)
|
||||
#define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \
|
||||
(val) * 192 >= (scale) * 255 ? 255 : \
|
||||
(val) >= (scale) * 255 / 192 ? 255 : \
|
||||
((val) * 192 + (scale) / 2) / (scale))
|
||||
|
||||
#define TEMP_FROM_REG(reg) ((reg) * 1000)
|
||||
|
@ -154,7 +154,6 @@ static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C };
|
|||
*/
|
||||
|
||||
struct lm87_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
@ -181,6 +180,8 @@ struct lm87_data {
|
|||
u16 alarms; /* register values, combined */
|
||||
u8 vid; /* register values, combined */
|
||||
u8 vrm;
|
||||
|
||||
const struct attribute_group *attr_groups[6];
|
||||
};
|
||||
|
||||
static inline int lm87_read_value(struct i2c_client *client, u8 reg)
|
||||
|
@ -195,7 +196,7 @@ static inline int lm87_write_value(struct i2c_client *client, u8 reg, u8 value)
|
|||
|
||||
static struct lm87_data *lm87_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
@ -309,7 +310,7 @@ static ssize_t show_in_max(struct device *dev,
|
|||
static ssize_t set_in_min(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -330,7 +331,7 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t set_in_max(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -396,7 +397,7 @@ static ssize_t show_temp_high(struct device *dev,
|
|||
static ssize_t set_temp_low(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -416,7 +417,7 @@ static ssize_t set_temp_low(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t set_temp_high(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -495,7 +496,7 @@ static ssize_t show_fan_div(struct device *dev,
|
|||
static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -522,7 +523,7 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int nr = to_sensor_dev_attr(attr)->index;
|
||||
long val;
|
||||
|
@ -635,7 +636,7 @@ static ssize_t show_aout(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t set_aout(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_client *client = dev_get_drvdata(dev);
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
long val;
|
||||
int err;
|
||||
|
@ -841,23 +842,18 @@ static int lm87_detect(struct i2c_client *client, struct i2c_board_info *info)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void lm87_remove_files(struct i2c_client *client)
|
||||
static void lm87_restore_config(void *arg)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct i2c_client *client = arg;
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_in6);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_fan1);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_in7);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_fan2);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_temp3);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_in0_5);
|
||||
sysfs_remove_group(&dev->kobj, &lm87_group_vid);
|
||||
lm87_write_value(client, LM87_REG_CONFIG, data->config);
|
||||
}
|
||||
|
||||
static void lm87_init_client(struct i2c_client *client)
|
||||
static int lm87_init_client(struct i2c_client *client)
|
||||
{
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
int rc;
|
||||
|
||||
if (dev_get_platdata(&client->dev)) {
|
||||
data->channel = *(u8 *)dev_get_platdata(&client->dev);
|
||||
|
@ -868,6 +864,10 @@ static void lm87_init_client(struct i2c_client *client)
|
|||
}
|
||||
data->config = lm87_read_value(client, LM87_REG_CONFIG) & 0x6F;
|
||||
|
||||
rc = devm_add_action(&client->dev, lm87_restore_config, client);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (!(data->config & 0x01)) {
|
||||
int i;
|
||||
|
||||
|
@ -895,12 +895,15 @@ static void lm87_init_client(struct i2c_client *client)
|
|||
if ((data->config & 0x09) != 0x01)
|
||||
lm87_write_value(client, LM87_REG_CONFIG,
|
||||
(data->config & 0x77) | 0x01);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct lm87_data *data;
|
||||
struct device *hwmon_dev;
|
||||
int err;
|
||||
unsigned int group_tail = 0;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct lm87_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
|
@ -910,7 +913,9 @@ static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
mutex_init(&data->update_lock);
|
||||
|
||||
/* Initialize the LM87 chip */
|
||||
lm87_init_client(client);
|
||||
err = lm87_init_client(client);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
data->in_scale[0] = 2500;
|
||||
data->in_scale[1] = 2700;
|
||||
|
@ -921,72 +926,34 @@ static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
data->in_scale[6] = 1875;
|
||||
data->in_scale[7] = 1875;
|
||||
|
||||
/* Register sysfs hooks */
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group);
|
||||
if (err)
|
||||
goto exit_stop;
|
||||
/*
|
||||
* Construct the list of attributes, the list depends on the
|
||||
* configuration of the chip
|
||||
*/
|
||||
data->attr_groups[group_tail++] = &lm87_group;
|
||||
if (data->channel & CHAN_NO_FAN(0))
|
||||
data->attr_groups[group_tail++] = &lm87_group_in6;
|
||||
else
|
||||
data->attr_groups[group_tail++] = &lm87_group_fan1;
|
||||
|
||||
if (data->channel & CHAN_NO_FAN(0)) {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_in6);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
} else {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_fan1);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
}
|
||||
if (data->channel & CHAN_NO_FAN(1))
|
||||
data->attr_groups[group_tail++] = &lm87_group_in7;
|
||||
else
|
||||
data->attr_groups[group_tail++] = &lm87_group_fan2;
|
||||
|
||||
if (data->channel & CHAN_NO_FAN(1)) {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_in7);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
} else {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_fan2);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
if (data->channel & CHAN_TEMP3) {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_temp3);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
} else {
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_in0_5);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
}
|
||||
if (data->channel & CHAN_TEMP3)
|
||||
data->attr_groups[group_tail++] = &lm87_group_temp3;
|
||||
else
|
||||
data->attr_groups[group_tail++] = &lm87_group_in0_5;
|
||||
|
||||
if (!(data->channel & CHAN_NO_VID)) {
|
||||
data->vrm = vid_which_vrm();
|
||||
err = sysfs_create_group(&client->dev.kobj, &lm87_group_vid);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
data->attr_groups[group_tail++] = &lm87_group_vid;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&client->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove:
|
||||
lm87_remove_files(client);
|
||||
exit_stop:
|
||||
lm87_write_value(client, LM87_REG_CONFIG, data->config);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lm87_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lm87_data *data = i2c_get_clientdata(client);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
lm87_remove_files(client);
|
||||
|
||||
lm87_write_value(client, LM87_REG_CONFIG, data->config);
|
||||
return 0;
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(
|
||||
&client->dev, client->name, client, data->attr_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1006,7 +973,6 @@ static struct i2c_driver lm87_driver = {
|
|||
.name = "lm87",
|
||||
},
|
||||
.probe = lm87_probe,
|
||||
.remove = lm87_remove,
|
||||
.id_table = lm87_id,
|
||||
.detect = lm87_detect,
|
||||
.address_list = normal_i2c,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* Copyright (C) 2008-2009, 2012 Freescale Semiconductor, Inc.
|
||||
* Author: Mingkai Hu <Mingkai.hu@freescale.com>
|
||||
* Reworked by Sven Schuchmann <schuchmann@schleissheimer.de>
|
||||
* DT support added by Clemens Gruber <clemens.gruber@pqgruber.com>
|
||||
*
|
||||
* This driver export the value of analog input voltage to sysfs, the
|
||||
* voltage unit is mV. Through the sysfs interface, lm-sensors tool
|
||||
|
@ -22,11 +23,13 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
/* Vdd info */
|
||||
#define MCP3021_VDD_MAX 5500
|
||||
#define MCP3021_VDD_MIN 2700
|
||||
#define MCP3021_VDD_REF 3300
|
||||
/* Vdd / reference voltage in millivolt */
|
||||
#define MCP3021_VDD_REF_MAX 5500
|
||||
#define MCP3021_VDD_REF_MIN 2700
|
||||
#define MCP3021_VDD_REF_DEFAULT 3300
|
||||
|
||||
/* output format */
|
||||
#define MCP3021_SAR_SHIFT 2
|
||||
|
@ -47,7 +50,7 @@ enum chips {
|
|||
*/
|
||||
struct mcp3021_data {
|
||||
struct device *hwmon_dev;
|
||||
u32 vdd; /* device power supply */
|
||||
u32 vdd; /* supply and reference voltage in millivolt */
|
||||
u16 sar_shift;
|
||||
u16 sar_mask;
|
||||
u8 output_res;
|
||||
|
@ -99,13 +102,14 @@ static ssize_t show_in_input(struct device *dev, struct device_attribute *attr,
|
|||
return sprintf(buf, "%d\n", in_input);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input, NULL);
|
||||
static DEVICE_ATTR(in0_input, 0444, show_in_input, NULL);
|
||||
|
||||
static int mcp3021_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int err;
|
||||
struct mcp3021_data *data = NULL;
|
||||
struct device_node *np = client->dev.of_node;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
return -ENODEV;
|
||||
|
@ -117,6 +121,21 @@ static int mcp3021_probe(struct i2c_client *client,
|
|||
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
if (np) {
|
||||
if (!of_property_read_u32(np, "reference-voltage-microvolt",
|
||||
&data->vdd))
|
||||
data->vdd /= 1000;
|
||||
else
|
||||
data->vdd = MCP3021_VDD_REF_DEFAULT;
|
||||
} else {
|
||||
u32 *pdata = dev_get_platdata(&client->dev);
|
||||
|
||||
if (pdata)
|
||||
data->vdd = *pdata;
|
||||
else
|
||||
data->vdd = MCP3021_VDD_REF_DEFAULT;
|
||||
}
|
||||
|
||||
switch (id->driver_data) {
|
||||
case mcp3021:
|
||||
data->sar_shift = MCP3021_SAR_SHIFT;
|
||||
|
@ -131,13 +150,8 @@ static int mcp3021_probe(struct i2c_client *client,
|
|||
break;
|
||||
}
|
||||
|
||||
if (dev_get_platdata(&client->dev)) {
|
||||
data->vdd = *(u32 *)dev_get_platdata(&client->dev);
|
||||
if (data->vdd > MCP3021_VDD_MAX || data->vdd < MCP3021_VDD_MIN)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
data->vdd = MCP3021_VDD_REF;
|
||||
}
|
||||
if (data->vdd > MCP3021_VDD_REF_MAX || data->vdd < MCP3021_VDD_REF_MIN)
|
||||
return -EINVAL;
|
||||
|
||||
err = sysfs_create_file(&client->dev.kobj, &dev_attr_in0_input.attr);
|
||||
if (err)
|
||||
|
@ -173,9 +187,19 @@ static const struct i2c_device_id mcp3021_id[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mcp3021_id);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id of_mcp3021_match[] = {
|
||||
{ .compatible = "microchip,mcp3021", .data = (void *)mcp3021 },
|
||||
{ .compatible = "microchip,mcp3221", .data = (void *)mcp3221 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_mcp3021_match);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver mcp3021_driver = {
|
||||
.driver = {
|
||||
.name = "mcp3021",
|
||||
.of_match_table = of_match_ptr(of_mcp3021_match),
|
||||
},
|
||||
.probe = mcp3021_probe,
|
||||
.remove = mcp3021_remove,
|
||||
|
|
|
@ -259,13 +259,15 @@ static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low,
|
|||
ret = 0;
|
||||
else if (ret)
|
||||
ret = DIV_ROUND_CLOSEST(1350000U, ret);
|
||||
else
|
||||
ret = 1350000U;
|
||||
abort:
|
||||
mutex_unlock(&data->access_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nct7802_write_fan_min(struct nct7802_data *data, u8 reg_fan_low,
|
||||
u8 reg_fan_high, unsigned int limit)
|
||||
u8 reg_fan_high, unsigned long limit)
|
||||
{
|
||||
int err;
|
||||
|
||||
|
@ -326,8 +328,8 @@ static int nct7802_write_voltage(struct nct7802_data *data, int nr, int index,
|
|||
int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr];
|
||||
int err;
|
||||
|
||||
voltage = clamp_val(voltage, 0, 0x3ff * nct7802_vmul[nr]);
|
||||
voltage = DIV_ROUND_CLOSEST(voltage, nct7802_vmul[nr]);
|
||||
voltage = clamp_val(voltage, 0, 0x3ff);
|
||||
|
||||
mutex_lock(&data->access_lock);
|
||||
err = regmap_write(data->regmap,
|
||||
|
@ -402,7 +404,7 @@ static ssize_t store_temp(struct device *dev, struct device_attribute *attr,
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127);
|
||||
val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
|
||||
|
||||
err = regmap_write(data->regmap, nr, val & 0xff);
|
||||
return err ? : count;
|
||||
|
|
|
@ -499,15 +499,27 @@ static int adm1275_probe(struct i2c_client *client,
|
|||
pindex = 2;
|
||||
tindex = 3;
|
||||
|
||||
info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT;
|
||||
info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
|
||||
|
||||
/* Enable VOUT if not enabled (it is disabled by default) */
|
||||
if (!(config & ADM1278_VOUT_EN)) {
|
||||
config |= ADM1278_VOUT_EN;
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
ADM1275_PMON_CONFIG,
|
||||
config);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"Failed to enable VOUT monitoring\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (config & ADM1278_TEMP1_EN)
|
||||
info->func[0] |=
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
if (config & ADM1278_VIN_EN)
|
||||
info->func[0] |= PMBUS_HAVE_VIN;
|
||||
if (config & ADM1278_VOUT_EN)
|
||||
info->func[0] |=
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
|
||||
break;
|
||||
case adm1293:
|
||||
case adm1294:
|
||||
|
|
|
@ -251,6 +251,7 @@ static const struct of_device_id scpi_of_match[] = {
|
|||
{.compatible = "arm,scpi-sensors"},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, scpi_of_match);
|
||||
|
||||
static struct platform_driver scpi_hwmon_platdrv = {
|
||||
.driver = {
|
||||
|
|
|
@ -77,14 +77,15 @@ static inline unsigned int IN_FROM_REG(u8 reg, int n)
|
|||
|
||||
static inline u8 IN_TO_REG(unsigned long val, int n)
|
||||
{
|
||||
return clamp_val(SCALE(val, 192, nom_mv[n]), 0, 255);
|
||||
val = clamp_val(val, 0, nom_mv[n] * 255 / 192);
|
||||
return SCALE(val, 192, nom_mv[n]);
|
||||
}
|
||||
|
||||
/*
|
||||
* TEMP: 0.001 degC units (-128C to +127C)
|
||||
* REG: 1C/bit, two's complement
|
||||
*/
|
||||
static inline s8 TEMP_TO_REG(int val)
|
||||
static inline s8 TEMP_TO_REG(long val)
|
||||
{
|
||||
return SCALE(clamp_val(val, -128000, 127000), 1, 1000);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
* tc654.c - Linux kernel modules for fan speed controller
|
||||
*
|
||||
* Copyright (C) 2016 Allied Telesis Labs NZ
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
enum tc654_regs {
|
||||
TC654_REG_RPM1 = 0x00, /* RPM Output 1 */
|
||||
TC654_REG_RPM2 = 0x01, /* RPM Output 2 */
|
||||
TC654_REG_FAN_FAULT1 = 0x02, /* Fan Fault 1 Threshold */
|
||||
TC654_REG_FAN_FAULT2 = 0x03, /* Fan Fault 2 Threshold */
|
||||
TC654_REG_CONFIG = 0x04, /* Configuration */
|
||||
TC654_REG_STATUS = 0x05, /* Status */
|
||||
TC654_REG_DUTY_CYCLE = 0x06, /* Fan Speed Duty Cycle */
|
||||
TC654_REG_MFR_ID = 0x07, /* Manufacturer Identification */
|
||||
TC654_REG_VER_ID = 0x08, /* Version Identification */
|
||||
};
|
||||
|
||||
/* Macros to easily index the registers */
|
||||
#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx))
|
||||
#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx))
|
||||
|
||||
/* Config register bits */
|
||||
#define TC654_REG_CONFIG_RES BIT(6) /* Resolution Selection */
|
||||
#define TC654_REG_CONFIG_DUTYC BIT(5) /* Duty Cycle Control */
|
||||
#define TC654_REG_CONFIG_SDM BIT(0) /* Shutdown Mode */
|
||||
|
||||
/* Status register bits */
|
||||
#define TC654_REG_STATUS_F2F BIT(1) /* Fan 2 Fault */
|
||||
#define TC654_REG_STATUS_F1F BIT(0) /* Fan 1 Fault */
|
||||
|
||||
/* RPM resolution for RPM Output registers */
|
||||
#define TC654_HIGH_RPM_RESOLUTION 25 /* 25 RPM resolution */
|
||||
#define TC654_LOW_RPM_RESOLUTION 50 /* 50 RPM resolution */
|
||||
|
||||
/* Convert to the fan fault RPM threshold from register value */
|
||||
#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50) /* 50 RPM resolution */
|
||||
|
||||
/* Convert to register value from the fan fault RPM threshold */
|
||||
#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff)
|
||||
|
||||
/* Register data is read (and cached) at most once per second. */
|
||||
#define TC654_UPDATE_INTERVAL HZ
|
||||
|
||||
struct tc654_data {
|
||||
struct i2c_client *client;
|
||||
|
||||
/* update mutex */
|
||||
struct mutex update_lock;
|
||||
|
||||
/* tc654 register cache */
|
||||
bool valid;
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
u8 rpm_output[2]; /* The fan RPM data for fans 1 and 2 is then
|
||||
* written to registers RPM1 and RPM2
|
||||
*/
|
||||
u8 fan_fault[2]; /* The Fan Fault Threshold Registers are used to
|
||||
* set the fan fault threshold levels for fan 1
|
||||
* and fan 2
|
||||
*/
|
||||
u8 config; /* The Configuration Register is an 8-bit read/
|
||||
* writable multi-function control register
|
||||
* 7: Fan Fault Clear
|
||||
* 1 = Clear Fan Fault
|
||||
* 0 = Normal Operation (default)
|
||||
* 6: Resolution Selection for RPM Output Registers
|
||||
* RPM Output Registers (RPM1 and RPM2) will be
|
||||
* set for
|
||||
* 1 = 25 RPM (9-bit) resolution
|
||||
* 0 = 50 RPM (8-bit) resolution (default)
|
||||
* 5: Duty Cycle Control Method
|
||||
* The V OUT duty cycle will be controlled via
|
||||
* 1 = the SMBus interface.
|
||||
* 0 = via the V IN analog input pin. (default)
|
||||
* 4,3: Fan 2 Pulses Per Rotation
|
||||
* 00 = 1
|
||||
* 01 = 2 (default)
|
||||
* 10 = 4
|
||||
* 11 = 8
|
||||
* 2,1: Fan 1 Pulses Per Rotation
|
||||
* 00 = 1
|
||||
* 01 = 2 (default)
|
||||
* 10 = 4
|
||||
* 11 = 8
|
||||
* 0: Shutdown Mode
|
||||
* 1 = Shutdown mode.
|
||||
* 0 = Normal operation. (default)
|
||||
*/
|
||||
u8 status; /* The Status register provides all the information
|
||||
* about what is going on within the TC654/TC655
|
||||
* devices.
|
||||
* 7,6: Unimplemented, Read as '0'
|
||||
* 5: Over-Temperature Fault Condition
|
||||
* 1 = Over-Temperature condition has occurred
|
||||
* 0 = Normal operation. V IN is less than 2.6V
|
||||
* 4: RPM2 Counter Overflow
|
||||
* 1 = Fault condition
|
||||
* 0 = Normal operation
|
||||
* 3: RPM1 Counter Overflow
|
||||
* 1 = Fault condition
|
||||
* 0 = Normal operation
|
||||
* 2: V IN Input Status
|
||||
* 1 = V IN is open
|
||||
* 0 = Normal operation. voltage present at V IN
|
||||
* 1: Fan 2 Fault
|
||||
* 1 = Fault condition
|
||||
* 0 = Normal operation
|
||||
* 0: Fan 1 Fault
|
||||
* 1 = Fault condition
|
||||
* 0 = Normal operation
|
||||
*/
|
||||
u8 duty_cycle; /* The DUTY_CYCLE register is a 4-bit read/
|
||||
* writable register used to control the duty
|
||||
* cycle of the V OUT output.
|
||||
*/
|
||||
};
|
||||
|
||||
/* helper to grab and cache data, at most one time per second */
|
||||
static struct tc654_data *tc654_update_client(struct device *dev)
|
||||
{
|
||||
struct tc654_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) &&
|
||||
likely(data->valid))
|
||||
goto out;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->rpm_output[0] = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->rpm_output[1] = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->fan_fault[0] = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->fan_fault[1] = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->config = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->status = ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
data->duty_cycle = ret & 0x0f;
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = true;
|
||||
out:
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
if (ret < 0) /* upon error, encode it in return value */
|
||||
data = ERR_PTR(ret);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs attributes
|
||||
*/
|
||||
|
||||
static ssize_t show_fan(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (data->config & TC654_REG_CONFIG_RES)
|
||||
val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION;
|
||||
else
|
||||
val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION;
|
||||
|
||||
return sprintf(buf, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t show_fan_min(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr]));
|
||||
}
|
||||
|
||||
static ssize_t set_fan_min(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
val = clamp_val(val, 0, 12750);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val);
|
||||
ret = i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr),
|
||||
data->fan_fault[nr]);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
int val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (nr == 0)
|
||||
val = !!(data->status & TC654_REG_STATUS_F1F);
|
||||
else
|
||||
val = !!(data->status & TC654_REG_STATUS_F2F);
|
||||
|
||||
return sprintf(buf, "%d\n", val);
|
||||
}
|
||||
|
||||
static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 };
|
||||
|
||||
static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
u8 val;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
val = BIT((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03);
|
||||
return sprintf(buf, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int nr = to_sensor_dev_attr(da)->index;
|
||||
struct tc654_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
u8 config;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
switch (val) {
|
||||
case 1:
|
||||
config = 0;
|
||||
break;
|
||||
case 2:
|
||||
config = 1;
|
||||
break;
|
||||
case 4:
|
||||
config = 2;
|
||||
break;
|
||||
case 8:
|
||||
config = 3;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]);
|
||||
data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]);
|
||||
ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm_mode(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
return sprintf(buf, "%d\n", !!(data->config & TC654_REG_CONFIG_DUTYC));
|
||||
}
|
||||
|
||||
static ssize_t set_pwm_mode(struct device *dev,
|
||||
struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tc654_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
if (val != 0 && val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (val)
|
||||
data->config |= TC654_REG_CONFIG_DUTYC;
|
||||
else
|
||||
data->config &= ~TC654_REG_CONFIG_DUTYC;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static const int tc654_pwm_map[16] = { 77, 88, 102, 112, 124, 136, 148, 160,
|
||||
172, 184, 196, 207, 219, 231, 243, 255};
|
||||
|
||||
static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct tc654_data *data = tc654_update_client(dev);
|
||||
int pwm;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (data->config & TC654_REG_CONFIG_SDM)
|
||||
pwm = 0;
|
||||
else
|
||||
pwm = tc654_pwm_map[data->duty_cycle];
|
||||
|
||||
return sprintf(buf, "%d\n", pwm);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tc654_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
if (val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (val == 0)
|
||||
data->config |= TC654_REG_CONFIG_SDM;
|
||||
else
|
||||
data->config &= ~TC654_REG_CONFIG_SDM;
|
||||
|
||||
data->duty_cycle = find_closest(val, tc654_pwm_map,
|
||||
ARRAY_SIZE(tc654_pwm_map));
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE,
|
||||
data->duty_cycle);
|
||||
|
||||
out:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||
set_fan_min, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||
set_fan_min, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
|
||||
set_fan_pulses, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses,
|
||||
set_fan_pulses, 1);
|
||||
static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO,
|
||||
show_pwm_mode, set_pwm_mode, 0);
|
||||
static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm,
|
||||
set_pwm, 0);
|
||||
|
||||
/* Driver data */
|
||||
static struct attribute *tc654_attrs[] = {
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_pulses.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_pulses.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm1_mode.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm1.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(tc654);
|
||||
|
||||
/*
|
||||
* device probe and removal
|
||||
*/
|
||||
|
||||
static int tc654_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct tc654_data *data;
|
||||
struct device *hwmon_dev;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->config = ret;
|
||||
|
||||
hwmon_dev =
|
||||
devm_hwmon_device_register_with_groups(dev, client->name, data,
|
||||
tc654_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tc654_id[] = {
|
||||
{"tc654", 0},
|
||||
{"tc655", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, tc654_id);
|
||||
|
||||
static struct i2c_driver tc654_driver = {
|
||||
.driver = {
|
||||
.name = "tc654",
|
||||
},
|
||||
.probe = tc654_probe,
|
||||
.id_table = tc654_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tc654_driver);
|
||||
|
||||
MODULE_AUTHOR("Allied Telesis Labs");
|
||||
MODULE_DESCRIPTION("Microchip TC654/TC655 driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,469 @@
|
|||
/* Texas Instruments TMP108 SMBus temperature sensor driver
|
||||
*
|
||||
* Copyright (C) 2016 John Muir <john@jmuir.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.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DRIVER_NAME "tmp108"
|
||||
|
||||
#define TMP108_REG_TEMP 0x00
|
||||
#define TMP108_REG_CONF 0x01
|
||||
#define TMP108_REG_TLOW 0x02
|
||||
#define TMP108_REG_THIGH 0x03
|
||||
|
||||
#define TMP108_TEMP_MIN_MC -50000 /* Minimum millicelcius. */
|
||||
#define TMP108_TEMP_MAX_MC 127937 /* Maximum millicelcius. */
|
||||
|
||||
/* Configuration register bits.
|
||||
* Note: these bit definitions are byte swapped.
|
||||
*/
|
||||
#define TMP108_CONF_M0 0x0100 /* Sensor mode. */
|
||||
#define TMP108_CONF_M1 0x0200
|
||||
#define TMP108_CONF_TM 0x0400 /* Thermostat mode. */
|
||||
#define TMP108_CONF_FL 0x0800 /* Watchdog flag - TLOW */
|
||||
#define TMP108_CONF_FH 0x1000 /* Watchdog flag - THIGH */
|
||||
#define TMP108_CONF_CR0 0x2000 /* Conversion rate. */
|
||||
#define TMP108_CONF_CR1 0x4000
|
||||
#define TMP108_CONF_ID 0x8000
|
||||
#define TMP108_CONF_HYS0 0x0010 /* Hysteresis. */
|
||||
#define TMP108_CONF_HYS1 0x0020
|
||||
#define TMP108_CONF_POL 0x0080 /* Polarity of alert. */
|
||||
|
||||
/* Defaults set by the hardware upon reset. */
|
||||
#define TMP108_CONF_DEFAULTS (TMP108_CONF_CR0 | TMP108_CONF_TM |\
|
||||
TMP108_CONF_HYS0 | TMP108_CONF_M1)
|
||||
/* These bits are read-only. */
|
||||
#define TMP108_CONF_READ_ONLY (TMP108_CONF_FL | TMP108_CONF_FH |\
|
||||
TMP108_CONF_ID)
|
||||
|
||||
#define TMP108_CONF_MODE_MASK (TMP108_CONF_M0|TMP108_CONF_M1)
|
||||
#define TMP108_MODE_SHUTDOWN 0x0000
|
||||
#define TMP108_MODE_ONE_SHOT TMP108_CONF_M0
|
||||
#define TMP108_MODE_CONTINUOUS TMP108_CONF_M1 /* Default */
|
||||
/* When M1 is set, M0 is ignored. */
|
||||
|
||||
#define TMP108_CONF_CONVRATE_MASK (TMP108_CONF_CR0|TMP108_CONF_CR1)
|
||||
#define TMP108_CONVRATE_0P25HZ 0x0000
|
||||
#define TMP108_CONVRATE_1HZ TMP108_CONF_CR0 /* Default */
|
||||
#define TMP108_CONVRATE_4HZ TMP108_CONF_CR1
|
||||
#define TMP108_CONVRATE_16HZ (TMP108_CONF_CR0|TMP108_CONF_CR1)
|
||||
|
||||
#define TMP108_CONF_HYSTERESIS_MASK (TMP108_CONF_HYS0|TMP108_CONF_HYS1)
|
||||
#define TMP108_HYSTERESIS_0C 0x0000
|
||||
#define TMP108_HYSTERESIS_1C TMP108_CONF_HYS0 /* Default */
|
||||
#define TMP108_HYSTERESIS_2C TMP108_CONF_HYS1
|
||||
#define TMP108_HYSTERESIS_4C (TMP108_CONF_HYS0|TMP108_CONF_HYS1)
|
||||
|
||||
#define TMP108_CONVERSION_TIME_MS 30 /* in milli-seconds */
|
||||
|
||||
struct tmp108 {
|
||||
struct regmap *regmap;
|
||||
u16 orig_config;
|
||||
unsigned long ready_time;
|
||||
};
|
||||
|
||||
/* convert 12-bit TMP108 register value to milliCelsius */
|
||||
static inline int tmp108_temp_reg_to_mC(s16 val)
|
||||
{
|
||||
return (val & ~0x0f) * 1000 / 256;
|
||||
}
|
||||
|
||||
/* convert milliCelsius to left adjusted 12-bit TMP108 register value */
|
||||
static inline u16 tmp108_mC_to_temp_reg(int val)
|
||||
{
|
||||
return (val * 256) / 1000;
|
||||
}
|
||||
|
||||
static int tmp108_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *temp)
|
||||
{
|
||||
struct tmp108 *tmp108 = dev_get_drvdata(dev);
|
||||
unsigned int regval;
|
||||
int err, hyst;
|
||||
|
||||
if (type == hwmon_chip) {
|
||||
if (attr == hwmon_chip_update_interval) {
|
||||
err = regmap_read(tmp108->regmap, TMP108_REG_CONF,
|
||||
®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
switch (regval & TMP108_CONF_CONVRATE_MASK) {
|
||||
case TMP108_CONVRATE_0P25HZ:
|
||||
default:
|
||||
*temp = 4000;
|
||||
break;
|
||||
case TMP108_CONVRATE_1HZ:
|
||||
*temp = 1000;
|
||||
break;
|
||||
case TMP108_CONVRATE_4HZ:
|
||||
*temp = 250;
|
||||
break;
|
||||
case TMP108_CONVRATE_16HZ:
|
||||
*temp = 63;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
/* Is it too early to return a conversion ? */
|
||||
if (time_before(jiffies, tmp108->ready_time)) {
|
||||
dev_dbg(dev, "%s: Conversion not ready yet..\n",
|
||||
__func__);
|
||||
return -EAGAIN;
|
||||
}
|
||||
err = regmap_read(tmp108->regmap, TMP108_REG_TEMP, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*temp = tmp108_temp_reg_to_mC(regval);
|
||||
break;
|
||||
case hwmon_temp_min:
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(tmp108->regmap, attr == hwmon_temp_min ?
|
||||
TMP108_REG_TLOW : TMP108_REG_THIGH, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*temp = tmp108_temp_reg_to_mC(regval);
|
||||
break;
|
||||
case hwmon_temp_min_alarm:
|
||||
case hwmon_temp_max_alarm:
|
||||
err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*temp = !!(regval & (attr == hwmon_temp_min_alarm ?
|
||||
TMP108_CONF_FL : TMP108_CONF_FH));
|
||||
break;
|
||||
case hwmon_temp_min_hyst:
|
||||
case hwmon_temp_max_hyst:
|
||||
err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
switch (regval & TMP108_CONF_HYSTERESIS_MASK) {
|
||||
case TMP108_HYSTERESIS_0C:
|
||||
default:
|
||||
hyst = 0;
|
||||
break;
|
||||
case TMP108_HYSTERESIS_1C:
|
||||
hyst = 1000;
|
||||
break;
|
||||
case TMP108_HYSTERESIS_2C:
|
||||
hyst = 2000;
|
||||
break;
|
||||
case TMP108_HYSTERESIS_4C:
|
||||
hyst = 4000;
|
||||
break;
|
||||
}
|
||||
err = regmap_read(tmp108->regmap, attr == hwmon_temp_min_hyst ?
|
||||
TMP108_REG_TLOW : TMP108_REG_THIGH, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*temp = tmp108_temp_reg_to_mC(regval);
|
||||
if (attr == hwmon_temp_min_hyst)
|
||||
*temp += hyst;
|
||||
else
|
||||
*temp -= hyst;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp108_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long temp)
|
||||
{
|
||||
struct tmp108 *tmp108 = dev_get_drvdata(dev);
|
||||
u32 regval, mask;
|
||||
int err;
|
||||
|
||||
if (type == hwmon_chip) {
|
||||
if (attr == hwmon_chip_update_interval) {
|
||||
if (temp < 156)
|
||||
mask = TMP108_CONVRATE_16HZ;
|
||||
else if (temp < 625)
|
||||
mask = TMP108_CONVRATE_4HZ;
|
||||
else if (temp < 2500)
|
||||
mask = TMP108_CONVRATE_1HZ;
|
||||
else
|
||||
mask = TMP108_CONVRATE_0P25HZ;
|
||||
return regmap_update_bits(tmp108->regmap,
|
||||
TMP108_REG_CONF,
|
||||
TMP108_CONF_CONVRATE_MASK,
|
||||
mask);
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_min:
|
||||
case hwmon_temp_max:
|
||||
temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC);
|
||||
return regmap_write(tmp108->regmap,
|
||||
attr == hwmon_temp_min ?
|
||||
TMP108_REG_TLOW : TMP108_REG_THIGH,
|
||||
tmp108_mC_to_temp_reg(temp));
|
||||
case hwmon_temp_min_hyst:
|
||||
case hwmon_temp_max_hyst:
|
||||
temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC);
|
||||
err = regmap_read(tmp108->regmap,
|
||||
attr == hwmon_temp_min_hyst ?
|
||||
TMP108_REG_TLOW : TMP108_REG_THIGH,
|
||||
®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (attr == hwmon_temp_min_hyst)
|
||||
temp -= tmp108_temp_reg_to_mC(regval);
|
||||
else
|
||||
temp = tmp108_temp_reg_to_mC(regval) - temp;
|
||||
if (temp < 500)
|
||||
mask = TMP108_HYSTERESIS_0C;
|
||||
else if (temp < 1500)
|
||||
mask = TMP108_HYSTERESIS_1C;
|
||||
else if (temp < 3000)
|
||||
mask = TMP108_HYSTERESIS_2C;
|
||||
else
|
||||
mask = TMP108_HYSTERESIS_4C;
|
||||
return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF,
|
||||
TMP108_CONF_HYSTERESIS_MASK, mask);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
if (type == hwmon_chip && attr == hwmon_chip_update_interval)
|
||||
return 0644;
|
||||
|
||||
if (type != hwmon_temp)
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_min_alarm:
|
||||
case hwmon_temp_max_alarm:
|
||||
return 0444;
|
||||
case hwmon_temp_min:
|
||||
case hwmon_temp_max:
|
||||
case hwmon_temp_min_hyst:
|
||||
case hwmon_temp_max_hyst:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 tmp108_chip_config[] = {
|
||||
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info tmp108_chip = {
|
||||
.type = hwmon_chip,
|
||||
.config = tmp108_chip_config,
|
||||
};
|
||||
|
||||
static u32 tmp108_temp_config[] = {
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_MIN_HYST
|
||||
| HWMON_T_MAX_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info tmp108_temp = {
|
||||
.type = hwmon_temp,
|
||||
.config = tmp108_temp_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *tmp108_info[] = {
|
||||
&tmp108_chip,
|
||||
&tmp108_temp,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops tmp108_hwmon_ops = {
|
||||
.is_visible = tmp108_is_visible,
|
||||
.read = tmp108_read,
|
||||
.write = tmp108_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info tmp108_chip_info = {
|
||||
.ops = &tmp108_hwmon_ops,
|
||||
.info = tmp108_info,
|
||||
};
|
||||
|
||||
static void tmp108_restore_config(void *data)
|
||||
{
|
||||
struct tmp108 *tmp108 = data;
|
||||
|
||||
regmap_write(tmp108->regmap, TMP108_REG_CONF, tmp108->orig_config);
|
||||
}
|
||||
|
||||
static bool tmp108_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
return reg != TMP108_REG_TEMP;
|
||||
}
|
||||
|
||||
static bool tmp108_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
/* Configuration register must be volatile to enable FL and FH. */
|
||||
return reg == TMP108_REG_TEMP || reg == TMP108_REG_CONF;
|
||||
}
|
||||
|
||||
static const struct regmap_config tmp108_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.max_register = TMP108_REG_THIGH,
|
||||
.writeable_reg = tmp108_is_writeable_reg,
|
||||
.volatile_reg = tmp108_is_volatile_reg,
|
||||
.val_format_endian = REGMAP_ENDIAN_BIG,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.use_single_rw = true,
|
||||
};
|
||||
|
||||
static int tmp108_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct tmp108 *tmp108;
|
||||
int err;
|
||||
u32 config;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||
dev_err(dev,
|
||||
"adapter doesn't support SMBus word transactions\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL);
|
||||
if (!tmp108)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, tmp108);
|
||||
|
||||
tmp108->regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config);
|
||||
if (IS_ERR(tmp108->regmap)) {
|
||||
err = PTR_ERR(tmp108->regmap);
|
||||
dev_err(dev, "regmap init failed: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "error reading config register: %d", err);
|
||||
return err;
|
||||
}
|
||||
tmp108->orig_config = config;
|
||||
|
||||
/* Only continuous mode is supported. */
|
||||
config &= ~TMP108_CONF_MODE_MASK;
|
||||
config |= TMP108_MODE_CONTINUOUS;
|
||||
|
||||
/* Only comparator mode is supported. */
|
||||
config &= ~TMP108_CONF_TM;
|
||||
|
||||
err = regmap_write(tmp108->regmap, TMP108_REG_CONF, config);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "error writing config register: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
tmp108->ready_time = jiffies;
|
||||
if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) ==
|
||||
TMP108_MODE_SHUTDOWN)
|
||||
tmp108->ready_time +=
|
||||
msecs_to_jiffies(TMP108_CONVERSION_TIME_MS);
|
||||
|
||||
err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108);
|
||||
if (err) {
|
||||
dev_err(dev, "add action or reset failed: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
tmp108,
|
||||
&tmp108_chip_info,
|
||||
NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static int __maybe_unused tmp108_suspend(struct device *dev)
|
||||
{
|
||||
struct tmp108 *tmp108 = dev_get_drvdata(dev);
|
||||
|
||||
return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF,
|
||||
TMP108_CONF_MODE_MASK, TMP108_MODE_SHUTDOWN);
|
||||
}
|
||||
|
||||
static int __maybe_unused tmp108_resume(struct device *dev)
|
||||
{
|
||||
struct tmp108 *tmp108 = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = regmap_update_bits(tmp108->regmap, TMP108_REG_CONF,
|
||||
TMP108_CONF_MODE_MASK, TMP108_MODE_CONTINUOUS);
|
||||
tmp108->ready_time = jiffies +
|
||||
msecs_to_jiffies(TMP108_CONVERSION_TIME_MS);
|
||||
return err;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume);
|
||||
|
||||
static const struct i2c_device_id tmp108_i2c_ids[] = {
|
||||
{ "tmp108", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id tmp108_of_ids[] = {
|
||||
{ .compatible = "ti,tmp108", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tmp108_of_ids);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver tmp108_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.pm = &tmp108_dev_pm_ops,
|
||||
.of_match_table = of_match_ptr(tmp108_of_ids),
|
||||
},
|
||||
.probe = tmp108_probe,
|
||||
.id_table = tmp108_i2c_ids,
|
||||
};
|
||||
|
||||
module_i2c_driver(tmp108_driver);
|
||||
|
||||
MODULE_AUTHOR("John Muir <john@jmuir.com>");
|
||||
MODULE_DESCRIPTION("Texas Instruments TMP108 temperature sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -220,7 +220,7 @@ struct pdev_entry {
|
|||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
|
||||
static int via_cputemp_device_add(unsigned int cpu)
|
||||
static int via_cputemp_online(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
struct platform_device *pdev;
|
||||
|
@ -261,7 +261,7 @@ static int via_cputemp_device_add(unsigned int cpu)
|
|||
return err;
|
||||
}
|
||||
|
||||
static void via_cputemp_device_remove(unsigned int cpu)
|
||||
static int via_cputemp_down_prep(unsigned int cpu)
|
||||
{
|
||||
struct pdev_entry *p;
|
||||
|
||||
|
@ -272,33 +272,13 @@ static void via_cputemp_device_remove(unsigned int cpu)
|
|||
list_del(&p->list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
kfree(p);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int via_cputemp_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long) hcpu;
|
||||
|
||||
switch (action) {
|
||||
case CPU_ONLINE:
|
||||
case CPU_DOWN_FAILED:
|
||||
via_cputemp_device_add(cpu);
|
||||
break;
|
||||
case CPU_DOWN_PREPARE:
|
||||
via_cputemp_device_remove(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block via_cputemp_cpu_notifier __refdata = {
|
||||
.notifier_call = via_cputemp_cpu_callback,
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id __initconst cputemp_ids[] = {
|
||||
{ X86_VENDOR_CENTAUR, 6, 0xa, }, /* C7 A */
|
||||
{ X86_VENDOR_CENTAUR, 6, 0xd, }, /* C7 D */
|
||||
|
@ -307,9 +287,11 @@ static const struct x86_cpu_id __initconst cputemp_ids[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, cputemp_ids);
|
||||
|
||||
static enum cpuhp_state via_temp_online;
|
||||
|
||||
static int __init via_cputemp_init(void)
|
||||
{
|
||||
int i, err;
|
||||
int err;
|
||||
|
||||
if (!x86_match_cpu(cputemp_ids))
|
||||
return -ENODEV;
|
||||
|
@ -318,58 +300,33 @@ static int __init via_cputemp_init(void)
|
|||
if (err)
|
||||
goto exit;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
for_each_online_cpu(i) {
|
||||
struct cpuinfo_x86 *c = &cpu_data(i);
|
||||
|
||||
if (c->x86 != 6)
|
||||
continue;
|
||||
|
||||
if (c->x86_model < 0x0a)
|
||||
continue;
|
||||
|
||||
if (c->x86_model > 0x0f) {
|
||||
pr_warn("Unknown CPU model 0x%x\n", c->x86_model);
|
||||
continue;
|
||||
}
|
||||
|
||||
via_cputemp_device_add(i);
|
||||
}
|
||||
err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/via:online",
|
||||
via_cputemp_online, via_cputemp_down_prep);
|
||||
if (err < 0)
|
||||
goto exit_driver_unreg;
|
||||
via_temp_online = err;
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
if (list_empty(&pdev_list)) {
|
||||
cpu_notifier_register_done();
|
||||
err = -ENODEV;
|
||||
goto exit_driver_unreg;
|
||||
goto exit_hp_unreg;
|
||||
}
|
||||
#endif
|
||||
|
||||
__register_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
||||
cpu_notifier_register_done();
|
||||
return 0;
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
exit_hp_unreg:
|
||||
cpuhp_remove_state_nocalls(via_temp_online);
|
||||
#endif
|
||||
exit_driver_unreg:
|
||||
platform_driver_unregister(&via_cputemp_driver);
|
||||
#endif
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit via_cputemp_exit(void)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
__unregister_hotcpu_notifier(&via_cputemp_cpu_notifier);
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
cpu_notifier_register_done();
|
||||
cpuhp_remove_state(via_temp_online);
|
||||
platform_driver_unregister(&via_cputemp_driver);
|
||||
}
|
||||
|
||||
|
|
|
@ -298,8 +298,8 @@ enum hwmon_pwm_attributes {
|
|||
* 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.
|
||||
* @read: Read callback for data attributes. Mandatory if readable
|
||||
* data attributes are present.
|
||||
* Parameters are:
|
||||
* @dev: Pointer to hardware monitoring device
|
||||
* @type: Sensor type
|
||||
|
@ -308,8 +308,19 @@ enum hwmon_pwm_attributes {
|
|||
* 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.
|
||||
* @read_string:
|
||||
* Read callback for string attributes. Mandatory if string
|
||||
* attributes are present.
|
||||
* Parameters are:
|
||||
* @dev: Pointer to hardware monitoring device
|
||||
* @type: Sensor type
|
||||
* @attr: Sensor attribute
|
||||
* @channel:
|
||||
* Channel number
|
||||
* @str: Pointer to returned string
|
||||
* The function returns 0 on success or a negative error number.
|
||||
* @write: Write callback for data attributes. Mandatory if writeable
|
||||
* data attributes are present.
|
||||
* Parameters are:
|
||||
* @dev: Pointer to hardware monitoring device
|
||||
* @type: Sensor type
|
||||
|
@ -324,6 +335,8 @@ struct hwmon_ops {
|
|||
u32 attr, int channel);
|
||||
int (*read)(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val);
|
||||
int (*read_string)(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, char **str);
|
||||
int (*write)(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val);
|
||||
};
|
||||
|
@ -349,7 +362,9 @@ struct hwmon_chip_info {
|
|||
const struct hwmon_channel_info **info;
|
||||
};
|
||||
|
||||
/* hwmon_device_register() is deprecated */
|
||||
struct device *hwmon_device_register(struct device *dev);
|
||||
|
||||
struct device *
|
||||
hwmon_device_register_with_groups(struct device *dev, const char *name,
|
||||
void *drvdata,
|
||||
|
@ -362,12 +377,12 @@ 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);
|
||||
const struct attribute_group **extra_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);
|
||||
const char *name, void *drvdata,
|
||||
const struct hwmon_chip_info *info,
|
||||
const struct attribute_group **extra_groups);
|
||||
|
||||
void hwmon_device_unregister(struct device *dev);
|
||||
void devm_hwmon_device_unregister(struct device *dev);
|
||||
|
|
Loading…
Reference in New Issue