mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-28 09:31:14 +00:00
hwmon updates for v5.3
New drivers for Infineon PXE1610 and IRPS5401 Minor improvements, cleanup, and fixes in several drivers -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJdJNpEAAoJEMsfJm/On5mBjlkP/1DEAEtL6pJYyl0zmyR3QrUN CAkSvvH46ZFGLbZb/ZC3XuXTaBbxFmZxeZP0NLSoOh45zmNoo0IjPzmSBHpm9R+7 ENZBLlMhtbR3M7zJO5kEb5kgOpnzf46KqEfOi72J51lZx+aabepSfkvzV1Zqf6TY PjuXwYG+VhhBobpwVNe2qixoQcHxhKbhheYvwbvfVjCA8YFqa08MkzbXjqHGcGwP mNKko/okRlLf1Qqq4rONlfixfO6rorKKN1oiDihFFRrNmJoT5n92jGaB+RLx2sHY pC8iHrOCnbe8iXXKYg8cwEVZ8OKUDfOSGL3RX2yVk6ZF7B13QZjqX7t437h3hAyD i0rWUyolKxwapAr2yUgO8QhHun6Zx/oOzpBPdGIdy0yhsbas9f9e5unGm9rSEWHh 2aIMUED5YKtXqdujos05AYaw9GgQVPEbr16xkjU8DGc/qtWrWUFeER0dJDNDnduO yLl9yDwwq0bSoSEg54bA6ib4CPHyKtD8ZNrYDfGiTMO9xZqrwWssFMguvYnWbuNZ cAh6lEQODL5bhg4vGIMfqt3Ub7EoTzjCCOnQRrOU4ERbxazaB56+A2VyYv+7LwLd sPfxldSMjyUYrnyDk+WQkTgRl0WsV/Sr4FS6Z4NOjZWyGoTCsjB8Z/m/5eEydIas VbeuAQEKoxvvJOWaRu00 =G/oG -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - New drivers for Infineon PXE1610 and IRPS5401 - Minor improvements, cleanup, and fixes in several drivers * tag 'hwmon-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (33 commits) hwmon: (ina3221) Add of_node_put() before return hwmon: (gpio-fan) fix sysfs notifications and udev events for gpio-fan alarms hwmon: (gpio-fan) move fan_alarm_init after devm_hwmon_device_register_with_groups hwmon: (lm90) Introduce function to update configuration register hwmon: (lm90) Cache configuration register value hwmon: (lm90) Fix max6658 sporadic wrong temperature reading hwmon: (nct7904) Changes comments in probe function. hwmon: (nct7904) Add error handling in probe function. hwmon: Convert remaining drivers to use SPDX identifier hwmon: (max6650) Fix unused variable warning hwmon: (pmbus/adm1275) Fix power sampling support hwmon: (lm90) simplify getting the adapter of a client hwmon: (asus_atk0110) no need to check return value of debugfs_create functions hwmon: (max6650) Fix minor formatting issues hwmon: (max6650) Improve error handling in max6650_update_device hwmon: (max6650) Read non-volatile registers only once hwmon: (max6650) Convert to use devm_hwmon_device_register_with_info hwmon: (max6650) Simplify alarm handling hwmon: (max6650) Cache alarm_en register hwmon: (max6650) Declare valid as boolean ...
This commit is contained in:
commit
64b08df460
18 changed files with 954 additions and 457 deletions
90
Documentation/hwmon/pxe1610
Normal file
90
Documentation/hwmon/pxe1610
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
Kernel driver pxe1610
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
* Infineon PXE1610
|
||||||
|
Prefix: 'pxe1610'
|
||||||
|
Addresses scanned: -
|
||||||
|
Datasheet: Datasheet is not publicly available.
|
||||||
|
|
||||||
|
* Infineon PXE1110
|
||||||
|
Prefix: 'pxe1110'
|
||||||
|
Addresses scanned: -
|
||||||
|
Datasheet: Datasheet is not publicly available.
|
||||||
|
|
||||||
|
* Infineon PXM1310
|
||||||
|
Prefix: 'pxm1310'
|
||||||
|
Addresses scanned: -
|
||||||
|
Datasheet: Datasheet is not publicly available.
|
||||||
|
|
||||||
|
Author: Vijay Khemka <vijaykhemka@fb.com>
|
||||||
|
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
PXE1610/PXE1110 are Multi-rail/Multiphase Digital Controllers
|
||||||
|
and compliant to
|
||||||
|
-- Intel VR13 DC-DC converter specifications.
|
||||||
|
-- Intel SVID protocol.
|
||||||
|
Used for Vcore power regulation for Intel VR13 based microprocessors
|
||||||
|
-- Servers, Workstations, and High-end desktops
|
||||||
|
|
||||||
|
PXM1310 is a Multi-rail Controller and it is compliant to
|
||||||
|
-- Intel VR13 DC-DC converter specifications.
|
||||||
|
-- Intel SVID protocol.
|
||||||
|
Used for DDR3/DDR4 Memory power regulation for Intel VR13 and
|
||||||
|
IMVP8 based systems
|
||||||
|
|
||||||
|
|
||||||
|
Usage Notes
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This driver does not probe for PMBus devices. You will have
|
||||||
|
to instantiate devices explicitly.
|
||||||
|
|
||||||
|
Example: the following commands will load the driver for an PXE1610
|
||||||
|
at address 0x70 on I2C bus #4:
|
||||||
|
|
||||||
|
# modprobe pxe1610
|
||||||
|
# echo pxe1610 0x70 > /sys/bus/i2c/devices/i2c-4/new_device
|
||||||
|
|
||||||
|
It can also be instantiated by declaring in device tree
|
||||||
|
|
||||||
|
|
||||||
|
Sysfs attributes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
curr1_label "iin"
|
||||||
|
curr1_input Measured input current
|
||||||
|
curr1_alarm Current high alarm
|
||||||
|
|
||||||
|
curr[2-4]_label "iout[1-3]"
|
||||||
|
curr[2-4]_input Measured output current
|
||||||
|
curr[2-4]_crit Critical maximum current
|
||||||
|
curr[2-4]_crit_alarm Current critical high alarm
|
||||||
|
|
||||||
|
in1_label "vin"
|
||||||
|
in1_input Measured input voltage
|
||||||
|
in1_crit Critical maximum input voltage
|
||||||
|
in1_crit_alarm Input voltage critical high alarm
|
||||||
|
|
||||||
|
in[2-4]_label "vout[1-3]"
|
||||||
|
in[2-4]_input Measured output voltage
|
||||||
|
in[2-4]_lcrit Critical minimum output voltage
|
||||||
|
in[2-4]_lcrit_alarm Output voltage critical low alarm
|
||||||
|
in[2-4]_crit Critical maximum output voltage
|
||||||
|
in[2-4]_crit_alarm Output voltage critical high alarm
|
||||||
|
|
||||||
|
power1_label "pin"
|
||||||
|
power1_input Measured input power
|
||||||
|
power1_alarm Input power high alarm
|
||||||
|
|
||||||
|
power[2-4]_label "pout[1-3]"
|
||||||
|
power[2-4]_input Measured output power
|
||||||
|
|
||||||
|
temp[1-3]_input Measured temperature
|
||||||
|
temp[1-3]_crit Critical high temperature
|
||||||
|
temp[1-3]_crit_alarm Chip temperature critical high alarm
|
||||||
|
temp[1-3]_max Maximum temperature
|
||||||
|
temp[1-3]_max_alarm Chip temperature high alarm
|
|
@ -10,16 +10,6 @@
|
||||||
* Very rare chip please let me know if you use it
|
* Very rare chip please let me know if you use it
|
||||||
*
|
*
|
||||||
* http://www.analog.com/UploadedFiles/Data_Sheets/ADM1029.pdf
|
* http://www.analog.com/UploadedFiles/Data_Sheets/ADM1029.pdf
|
||||||
*
|
|
||||||
*
|
|
||||||
* 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 version 2 of the License
|
|
||||||
*
|
|
||||||
* 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/module.h>
|
#include <linux/module.h>
|
||||||
|
|
|
@ -789,33 +789,16 @@ static const struct file_operations atk_debugfs_ggrp_fops = {
|
||||||
static void atk_debugfs_init(struct atk_data *data)
|
static void atk_debugfs_init(struct atk_data *data)
|
||||||
{
|
{
|
||||||
struct dentry *d;
|
struct dentry *d;
|
||||||
struct dentry *f;
|
|
||||||
|
|
||||||
data->debugfs.id = 0;
|
data->debugfs.id = 0;
|
||||||
|
|
||||||
d = debugfs_create_dir("asus_atk0110", NULL);
|
d = debugfs_create_dir("asus_atk0110", NULL);
|
||||||
if (!d || IS_ERR(d))
|
|
||||||
return;
|
|
||||||
|
|
||||||
f = debugfs_create_x32("id", 0600, d, &data->debugfs.id);
|
debugfs_create_x32("id", 0600, d, &data->debugfs.id);
|
||||||
if (!f || IS_ERR(f))
|
debugfs_create_file_unsafe("gitm", 0400, d, data, &atk_debugfs_gitm);
|
||||||
goto cleanup;
|
debugfs_create_file("ggrp", 0400, d, data, &atk_debugfs_ggrp_fops);
|
||||||
|
|
||||||
f = debugfs_create_file_unsafe("gitm", 0400, d, data,
|
|
||||||
&atk_debugfs_gitm);
|
|
||||||
if (!f || IS_ERR(f))
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
f = debugfs_create_file("ggrp", 0400, d, data,
|
|
||||||
&atk_debugfs_ggrp_fops);
|
|
||||||
if (!f || IS_ERR(f))
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
data->debugfs.root = d;
|
data->debugfs.root = d;
|
||||||
|
|
||||||
return;
|
|
||||||
cleanup:
|
|
||||||
debugfs_remove_recursive(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void atk_debugfs_cleanup(struct atk_data *data)
|
static void atk_debugfs_cleanup(struct atk_data *data)
|
||||||
|
|
|
@ -54,8 +54,8 @@ static void fan_alarm_notify(struct work_struct *ws)
|
||||||
struct gpio_fan_data *fan_data =
|
struct gpio_fan_data *fan_data =
|
||||||
container_of(ws, struct gpio_fan_data, alarm_work);
|
container_of(ws, struct gpio_fan_data, alarm_work);
|
||||||
|
|
||||||
sysfs_notify(&fan_data->dev->kobj, NULL, "fan1_alarm");
|
sysfs_notify(&fan_data->hwmon_dev->kobj, NULL, "fan1_alarm");
|
||||||
kobject_uevent(&fan_data->dev->kobj, KOBJ_CHANGE);
|
kobject_uevent(&fan_data->hwmon_dev->kobj, KOBJ_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id)
|
static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id)
|
||||||
|
@ -510,13 +510,6 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||||
platform_set_drvdata(pdev, fan_data);
|
platform_set_drvdata(pdev, fan_data);
|
||||||
mutex_init(&fan_data->lock);
|
mutex_init(&fan_data->lock);
|
||||||
|
|
||||||
/* Configure alarm GPIO if available. */
|
|
||||||
if (fan_data->alarm_gpio) {
|
|
||||||
err = fan_alarm_init(fan_data);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Configure control GPIOs if available. */
|
/* Configure control GPIOs if available. */
|
||||||
if (fan_data->gpios && fan_data->num_gpios > 0) {
|
if (fan_data->gpios && fan_data->num_gpios > 0) {
|
||||||
if (!fan_data->speed || fan_data->num_speed <= 1)
|
if (!fan_data->speed || fan_data->num_speed <= 1)
|
||||||
|
@ -524,7 +517,9 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||||
err = fan_ctrl_init(fan_data);
|
err = fan_ctrl_init(fan_data);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
devm_add_action_or_reset(dev, gpio_fan_stop, fan_data);
|
err = devm_add_action_or_reset(dev, gpio_fan_stop, fan_data);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make this driver part of hwmon class. */
|
/* Make this driver part of hwmon class. */
|
||||||
|
@ -535,6 +530,13 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(fan_data->hwmon_dev))
|
if (IS_ERR(fan_data->hwmon_dev))
|
||||||
return PTR_ERR(fan_data->hwmon_dev);
|
return PTR_ERR(fan_data->hwmon_dev);
|
||||||
|
|
||||||
|
/* Configure alarm GPIO if available. */
|
||||||
|
if (fan_data->alarm_gpio) {
|
||||||
|
err = fan_alarm_init(fan_data);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/* Optional cooling device register for Device tree platforms */
|
/* Optional cooling device register for Device tree platforms */
|
||||||
fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np,
|
fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np,
|
||||||
"gpio-fan", fan_data, &gpio_fan_cool_ops);
|
"gpio-fan", fan_data, &gpio_fan_cool_ops);
|
||||||
|
|
|
@ -651,6 +651,12 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||||
hwdev, j);
|
hwdev, j);
|
||||||
if (err) {
|
if (err) {
|
||||||
device_unregister(hdev);
|
device_unregister(hdev);
|
||||||
|
/*
|
||||||
|
* Don't worry about hwdev;
|
||||||
|
* hwmon_dev_release(), called
|
||||||
|
* from device_unregister(),
|
||||||
|
* will free it.
|
||||||
|
*/
|
||||||
goto ida_remove;
|
goto ida_remove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -713,8 +713,10 @@ static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina)
|
||||||
|
|
||||||
for_each_child_of_node(np, child) {
|
for_each_child_of_node(np, child) {
|
||||||
ret = ina3221_probe_child_from_dt(dev, child, ina);
|
ret = ina3221_probe_child_from_dt(dev, child, ina);
|
||||||
if (ret)
|
if (ret) {
|
||||||
|
of_node_put(child);
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -174,6 +174,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
||||||
#define LM90_HAVE_EMERGENCY_ALARM (1 << 5)/* emergency alarm */
|
#define LM90_HAVE_EMERGENCY_ALARM (1 << 5)/* emergency alarm */
|
||||||
#define LM90_HAVE_TEMP3 (1 << 6) /* 3rd temperature sensor */
|
#define LM90_HAVE_TEMP3 (1 << 6) /* 3rd temperature sensor */
|
||||||
#define LM90_HAVE_BROKEN_ALERT (1 << 7) /* Broken alert */
|
#define LM90_HAVE_BROKEN_ALERT (1 << 7) /* Broken alert */
|
||||||
|
#define LM90_PAUSE_FOR_CONFIG (1 << 8) /* Pause conversion for config */
|
||||||
|
|
||||||
/* LM90 status */
|
/* LM90 status */
|
||||||
#define LM90_STATUS_LTHRM (1 << 0) /* local THERM limit tripped */
|
#define LM90_STATUS_LTHRM (1 << 0) /* local THERM limit tripped */
|
||||||
|
@ -367,6 +368,7 @@ static const struct lm90_params lm90_params[] = {
|
||||||
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||||
},
|
},
|
||||||
[max6657] = {
|
[max6657] = {
|
||||||
|
.flags = LM90_PAUSE_FOR_CONFIG,
|
||||||
.alert_alarms = 0x7c,
|
.alert_alarms = 0x7c,
|
||||||
.max_convrate = 8,
|
.max_convrate = 8,
|
||||||
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||||
|
@ -457,6 +459,7 @@ struct lm90_data {
|
||||||
|
|
||||||
unsigned int update_interval; /* in milliseconds */
|
unsigned int update_interval; /* in milliseconds */
|
||||||
|
|
||||||
|
u8 config; /* Current configuration register value */
|
||||||
u8 config_orig; /* Original configuration register value */
|
u8 config_orig; /* Original configuration register value */
|
||||||
u8 convrate_orig; /* Original conversion rate register value */
|
u8 convrate_orig; /* Original conversion rate register value */
|
||||||
u16 alert_alarms; /* Which alarm bits trigger ALERT# */
|
u16 alert_alarms; /* Which alarm bits trigger ALERT# */
|
||||||
|
@ -540,6 +543,21 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl)
|
||||||
return (newh << 8) | l;
|
return (newh << 8) | l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int lm90_update_confreg(struct lm90_data *data, u8 config)
|
||||||
|
{
|
||||||
|
if (data->config != config) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = i2c_smbus_write_byte_data(data->client,
|
||||||
|
LM90_REG_W_CONFIG1,
|
||||||
|
config);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
data->config = config;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* client->update_lock must be held when calling this function (unless we are
|
* client->update_lock must be held when calling this function (unless we are
|
||||||
* in detection or initialization steps), and while a remote channel other
|
* in detection or initialization steps), and while a remote channel other
|
||||||
|
@ -548,23 +566,39 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl)
|
||||||
* various registers have different meanings as a result of selecting a
|
* various registers have different meanings as a result of selecting a
|
||||||
* non-default remote channel.
|
* non-default remote channel.
|
||||||
*/
|
*/
|
||||||
static inline int lm90_select_remote_channel(struct i2c_client *client,
|
static int lm90_select_remote_channel(struct lm90_data *data, int channel)
|
||||||
struct lm90_data *data,
|
|
||||||
int channel)
|
|
||||||
{
|
{
|
||||||
int config;
|
int err = 0;
|
||||||
|
|
||||||
if (data->kind == max6696) {
|
if (data->kind == max6696) {
|
||||||
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
u8 config = data->config & ~0x08;
|
||||||
if (config < 0)
|
|
||||||
return config;
|
|
||||||
config &= ~0x08;
|
|
||||||
if (channel)
|
if (channel)
|
||||||
config |= 0x08;
|
config |= 0x08;
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
err = lm90_update_confreg(data, config);
|
||||||
config);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm90_write_convrate(struct lm90_data *data, int val)
|
||||||
|
{
|
||||||
|
u8 config = data->config;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Save config and pause conversion */
|
||||||
|
if (data->flags & LM90_PAUSE_FOR_CONFIG) {
|
||||||
|
err = lm90_update_confreg(data, config | 0x40);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set conv rate */
|
||||||
|
err = i2c_smbus_write_byte_data(data->client, LM90_REG_W_CONVRATE, val);
|
||||||
|
|
||||||
|
/* Revert change to config */
|
||||||
|
lm90_update_confreg(data, config);
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -587,7 +621,7 @@ static int lm90_set_convrate(struct i2c_client *client, struct lm90_data *data,
|
||||||
if (interval >= update_interval * 3 / 4)
|
if (interval >= update_interval * 3 / 4)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
err = i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, i);
|
err = lm90_write_convrate(data, i);
|
||||||
data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64);
|
data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -658,7 +692,7 @@ static int lm90_update_limits(struct device *dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data->kind == max6696) {
|
if (data->kind == max6696) {
|
||||||
val = lm90_select_remote_channel(client, data, 1);
|
val = lm90_select_remote_channel(data, 1);
|
||||||
if (val < 0)
|
if (val < 0)
|
||||||
return val;
|
return val;
|
||||||
|
|
||||||
|
@ -682,7 +716,7 @@ static int lm90_update_limits(struct device *dev)
|
||||||
return val;
|
return val;
|
||||||
data->temp11[REMOTE2_HIGH] = val << 8;
|
data->temp11[REMOTE2_HIGH] = val << 8;
|
||||||
|
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(data, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -742,19 +776,19 @@ static int lm90_update_device(struct device *dev)
|
||||||
data->alarms = val; /* lower 8 bit of alarms */
|
data->alarms = val; /* lower 8 bit of alarms */
|
||||||
|
|
||||||
if (data->kind == max6696) {
|
if (data->kind == max6696) {
|
||||||
val = lm90_select_remote_channel(client, data, 1);
|
val = lm90_select_remote_channel(data, 1);
|
||||||
if (val < 0)
|
if (val < 0)
|
||||||
return val;
|
return val;
|
||||||
|
|
||||||
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||||
LM90_REG_R_REMOTE_TEMPL);
|
LM90_REG_R_REMOTE_TEMPL);
|
||||||
if (val < 0) {
|
if (val < 0) {
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(data, 0);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
data->temp11[REMOTE2_TEMP] = val;
|
data->temp11[REMOTE2_TEMP] = val;
|
||||||
|
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(data, 0);
|
||||||
|
|
||||||
val = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
val = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
||||||
if (val < 0)
|
if (val < 0)
|
||||||
|
@ -768,15 +802,9 @@ static int lm90_update_device(struct device *dev)
|
||||||
*/
|
*/
|
||||||
if (!(data->config_orig & 0x80) &&
|
if (!(data->config_orig & 0x80) &&
|
||||||
!(data->alarms & data->alert_alarms)) {
|
!(data->alarms & data->alert_alarms)) {
|
||||||
val = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
if (data->config & 0x80) {
|
||||||
if (val < 0)
|
|
||||||
return val;
|
|
||||||
|
|
||||||
if (val & 0x80) {
|
|
||||||
dev_dbg(&client->dev, "Re-enabling ALERT#\n");
|
dev_dbg(&client->dev, "Re-enabling ALERT#\n");
|
||||||
i2c_smbus_write_byte_data(client,
|
lm90_update_confreg(data, data->config & ~0x80);
|
||||||
LM90_REG_W_CONFIG1,
|
|
||||||
val & ~0x80);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -994,7 +1022,7 @@ static int lm90_set_temp11(struct lm90_data *data, int index, long val)
|
||||||
else
|
else
|
||||||
data->temp11[index] = temp_to_s8(val) << 8;
|
data->temp11[index] = temp_to_s8(val) << 8;
|
||||||
|
|
||||||
lm90_select_remote_channel(client, data, index >= 3);
|
lm90_select_remote_channel(data, index >= 3);
|
||||||
err = i2c_smbus_write_byte_data(client, regp->high,
|
err = i2c_smbus_write_byte_data(client, regp->high,
|
||||||
data->temp11[index] >> 8);
|
data->temp11[index] >> 8);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -1003,7 +1031,7 @@ static int lm90_set_temp11(struct lm90_data *data, int index, long val)
|
||||||
err = i2c_smbus_write_byte_data(client, regp->low,
|
err = i2c_smbus_write_byte_data(client, regp->low,
|
||||||
data->temp11[index] & 0xff);
|
data->temp11[index] & 0xff);
|
||||||
|
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(data, 0);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1052,9 +1080,9 @@ static int lm90_set_temp8(struct lm90_data *data, int index, long val)
|
||||||
else
|
else
|
||||||
data->temp8[index] = temp_to_s8(val);
|
data->temp8[index] = temp_to_s8(val);
|
||||||
|
|
||||||
lm90_select_remote_channel(client, data, index >= 6);
|
lm90_select_remote_channel(data, index >= 6);
|
||||||
err = i2c_smbus_write_byte_data(client, reg[index], data->temp8[index]);
|
err = i2c_smbus_write_byte_data(client, reg[index], data->temp8[index]);
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(data, 0);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -1593,8 +1621,7 @@ static void lm90_restore_conf(void *_data)
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
|
|
||||||
/* Restore initial configuration */
|
/* Restore initial configuration */
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE,
|
lm90_write_convrate(data, data->convrate_orig);
|
||||||
data->convrate_orig);
|
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
||||||
data->config_orig);
|
data->config_orig);
|
||||||
}
|
}
|
||||||
|
@ -1611,11 +1638,13 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||||
/*
|
/*
|
||||||
* Start the conversions.
|
* Start the conversions.
|
||||||
*/
|
*/
|
||||||
lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */
|
|
||||||
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||||
if (config < 0)
|
if (config < 0)
|
||||||
return config;
|
return config;
|
||||||
data->config_orig = config;
|
data->config_orig = config;
|
||||||
|
data->config = config;
|
||||||
|
|
||||||
|
lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */
|
||||||
|
|
||||||
/* Check Temperature Range Select */
|
/* Check Temperature Range Select */
|
||||||
if (data->kind == adt7461 || data->kind == tmp451) {
|
if (data->kind == adt7461 || data->kind == tmp451) {
|
||||||
|
@ -1638,8 +1667,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||||
config &= ~0x08;
|
config &= ~0x08;
|
||||||
|
|
||||||
config &= 0xBF; /* run */
|
config &= 0xBF; /* run */
|
||||||
if (config != data->config_orig) /* Only write if changed */
|
lm90_update_confreg(data, config);
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config);
|
|
||||||
|
|
||||||
return devm_add_action_or_reset(&client->dev, lm90_restore_conf, data);
|
return devm_add_action_or_reset(&client->dev, lm90_restore_conf, data);
|
||||||
}
|
}
|
||||||
|
@ -1718,7 +1746,7 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct i2c_adapter *adapter = to_i2c_adapter(dev->parent);
|
struct i2c_adapter *adapter = client->adapter;
|
||||||
struct hwmon_channel_info *info;
|
struct hwmon_channel_info *info;
|
||||||
struct regulator *regulator;
|
struct regulator *regulator;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
|
@ -1873,14 +1901,8 @@ static void lm90_alert(struct i2c_client *client, enum i2c_alert_protocol type,
|
||||||
|
|
||||||
if ((data->flags & LM90_HAVE_BROKEN_ALERT) &&
|
if ((data->flags & LM90_HAVE_BROKEN_ALERT) &&
|
||||||
(alarms & data->alert_alarms)) {
|
(alarms & data->alert_alarms)) {
|
||||||
int config;
|
|
||||||
|
|
||||||
dev_dbg(&client->dev, "Disabling ALERT#\n");
|
dev_dbg(&client->dev, "Disabling ALERT#\n");
|
||||||
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
lm90_update_confreg(data, data->config | 0x80);
|
||||||
if (config >= 0)
|
|
||||||
i2c_smbus_write_byte_data(client,
|
|
||||||
LM90_REG_W_CONFIG1,
|
|
||||||
config | 0x80);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dev_info(&client->dev, "Everything OK\n");
|
dev_info(&client->dev, "Everything OK\n");
|
||||||
|
|
|
@ -92,7 +92,8 @@ module_param(clock, int, 0444);
|
||||||
#define FAN_RPM_MIN 240
|
#define FAN_RPM_MIN 240
|
||||||
#define FAN_RPM_MAX 30000
|
#define FAN_RPM_MAX 30000
|
||||||
|
|
||||||
#define DIV_FROM_REG(reg) (1 << (reg & 7))
|
#define DIV_FROM_REG(reg) (1 << ((reg) & 7))
|
||||||
|
#define DAC_LIMIT(v12) ((v12) ? 180 : 76)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Client data (each client gets its own)
|
* Client data (each client gets its own)
|
||||||
|
@ -100,11 +101,9 @@ module_param(clock, int, 0444);
|
||||||
|
|
||||||
struct max6650_data {
|
struct max6650_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
const struct attribute_group *groups[3];
|
struct mutex update_lock; /* protect alarm register updates */
|
||||||
struct thermal_cooling_device *cooling_dev;
|
|
||||||
struct mutex update_lock;
|
|
||||||
int nr_fans;
|
int nr_fans;
|
||||||
char valid; /* zero until following fields are valid */
|
bool valid; /* false until following fields are valid */
|
||||||
unsigned long last_updated; /* in jiffies */
|
unsigned long last_updated; /* in jiffies */
|
||||||
|
|
||||||
/* register values */
|
/* register values */
|
||||||
|
@ -114,6 +113,7 @@ struct max6650_data {
|
||||||
u8 count;
|
u8 count;
|
||||||
u8 dac;
|
u8 dac;
|
||||||
u8 alarm;
|
u8 alarm;
|
||||||
|
u8 alarm_en;
|
||||||
unsigned long cooling_dev_state;
|
unsigned long cooling_dev_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,41 +137,60 @@ static const struct of_device_id __maybe_unused max6650_dt_match[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, max6650_dt_match);
|
MODULE_DEVICE_TABLE(of, max6650_dt_match);
|
||||||
|
|
||||||
|
static int dac_to_pwm(int dac, bool v12)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans.
|
||||||
|
* Lower DAC values mean higher speeds.
|
||||||
|
*/
|
||||||
|
return clamp_val(255 - (255 * dac) / DAC_LIMIT(v12), 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 pwm_to_dac(unsigned int pwm, bool v12)
|
||||||
|
{
|
||||||
|
int limit = DAC_LIMIT(v12);
|
||||||
|
|
||||||
|
return limit - (limit * pwm) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
static struct max6650_data *max6650_update_device(struct device *dev)
|
static struct max6650_data *max6650_update_device(struct device *dev)
|
||||||
{
|
{
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
|
int reg, err = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||||
data->speed = i2c_smbus_read_byte_data(client,
|
|
||||||
MAX6650_REG_SPEED);
|
|
||||||
data->config = i2c_smbus_read_byte_data(client,
|
|
||||||
MAX6650_REG_CONFIG);
|
|
||||||
for (i = 0; i < data->nr_fans; i++) {
|
for (i = 0; i < data->nr_fans; i++) {
|
||||||
data->tach[i] = i2c_smbus_read_byte_data(client,
|
reg = i2c_smbus_read_byte_data(client, tach_reg[i]);
|
||||||
tach_reg[i]);
|
if (reg < 0) {
|
||||||
|
err = reg;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
data->tach[i] = reg;
|
||||||
}
|
}
|
||||||
data->count = i2c_smbus_read_byte_data(client,
|
|
||||||
MAX6650_REG_COUNT);
|
|
||||||
data->dac = i2c_smbus_read_byte_data(client, MAX6650_REG_DAC);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Alarms are cleared on read in case the condition that
|
* Alarms are cleared on read in case the condition that
|
||||||
* caused the alarm is removed. Keep the value latched here
|
* caused the alarm is removed. Keep the value latched here
|
||||||
* for providing the register through different alarm files.
|
* for providing the register through different alarm files.
|
||||||
*/
|
*/
|
||||||
data->alarm |= i2c_smbus_read_byte_data(client,
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM);
|
||||||
MAX6650_REG_ALARM);
|
if (reg < 0) {
|
||||||
|
err = reg;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
data->alarm |= reg;
|
||||||
data->last_updated = jiffies;
|
data->last_updated = jiffies;
|
||||||
data->valid = 1;
|
data->valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
if (err)
|
||||||
|
data = ERR_PTR(err);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,26 +218,6 @@ static int max6650_set_operating_mode(struct max6650_data *data, u8 mode)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
|
||||||
int rpm;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculation details:
|
|
||||||
*
|
|
||||||
* Each tachometer counts over an interval given by the "count"
|
|
||||||
* register (0.25, 0.5, 1 or 2 seconds). This module assumes
|
|
||||||
* that the fans produce two pulses per revolution (this seems
|
|
||||||
* to be the most common).
|
|
||||||
*/
|
|
||||||
|
|
||||||
rpm = ((data->tach[attr->index] * 120) / DIV_FROM_REG(data->count));
|
|
||||||
return sprintf(buf, "%d\n", rpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set the fan speed to the specified RPM (or read back the RPM setting).
|
* Set the fan speed to the specified RPM (or read back the RPM setting).
|
||||||
* This works in closed loop mode only. Use pwm1 for open loop speed setting.
|
* This works in closed loop mode only. Use pwm1 for open loop speed setting.
|
||||||
|
@ -260,26 +259,6 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
|
||||||
* controlled.
|
* controlled.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static ssize_t fan1_target_show(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
|
||||||
int kscale, ktach, rpm;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Use the datasheet equation:
|
|
||||||
*
|
|
||||||
* FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)]
|
|
||||||
*
|
|
||||||
* then multiply by 60 to give rpm.
|
|
||||||
*/
|
|
||||||
|
|
||||||
kscale = DIV_FROM_REG(data->config);
|
|
||||||
ktach = data->speed;
|
|
||||||
rpm = 60 * kscale * clock / (256 * (ktach + 1));
|
|
||||||
return sprintf(buf, "%d\n", rpm);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int max6650_set_target(struct max6650_data *data, unsigned long rpm)
|
static int max6650_set_target(struct max6650_data *data, unsigned long rpm)
|
||||||
{
|
{
|
||||||
int kscale, ktach;
|
int kscale, ktach;
|
||||||
|
@ -308,197 +287,8 @@ static int max6650_set_target(struct max6650_data *data, unsigned long rpm)
|
||||||
data->speed);
|
data->speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t fan1_target_store(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
|
||||||
unsigned long rpm;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &rpm);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
err = max6650_set_target(data, rpm);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get/set the fan speed in open loop mode using pwm1 sysfs file.
|
* Get gpio alarm status:
|
||||||
* Speed is given as a relative value from 0 to 255, where 255 is maximum
|
|
||||||
* speed. Note that this is done by writing directly to the chip's DAC,
|
|
||||||
* it won't change the closed loop speed set by fan1_target.
|
|
||||||
* Also note that due to rounding errors it is possible that you don't read
|
|
||||||
* back exactly the value you have set.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static ssize_t pwm1_show(struct device *dev, struct device_attribute *devattr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
int pwm;
|
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans.
|
|
||||||
* Lower DAC values mean higher speeds.
|
|
||||||
*/
|
|
||||||
if (data->config & MAX6650_CFG_V12)
|
|
||||||
pwm = 255 - (255 * (int)data->dac)/180;
|
|
||||||
else
|
|
||||||
pwm = 255 - (255 * (int)data->dac)/76;
|
|
||||||
|
|
||||||
if (pwm < 0)
|
|
||||||
pwm = 0;
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", pwm);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t pwm1_store(struct device *dev,
|
|
||||||
struct device_attribute *devattr, const char *buf,
|
|
||||||
size_t count)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
unsigned long pwm;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &pwm);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
pwm = clamp_val(pwm, 0, 255);
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
if (data->config & MAX6650_CFG_V12)
|
|
||||||
data->dac = 180 - (180 * pwm)/255;
|
|
||||||
else
|
|
||||||
data->dac = 76 - (76 * pwm)/255;
|
|
||||||
err = i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return err < 0 ? err : count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get/Set controller mode:
|
|
||||||
* Possible values:
|
|
||||||
* 0 = Fan always on
|
|
||||||
* 1 = Open loop, Voltage is set according to speed, not regulated.
|
|
||||||
* 2 = Closed loop, RPM for all fans regulated by fan1 tachometer
|
|
||||||
* 3 = Fan off
|
|
||||||
*/
|
|
||||||
static ssize_t pwm1_enable_show(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
|
||||||
int mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4;
|
|
||||||
int sysfs_modes[4] = {0, 3, 2, 1};
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", sysfs_modes[mode]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t pwm1_enable_store(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
|
||||||
unsigned long mode;
|
|
||||||
int err;
|
|
||||||
const u8 max6650_modes[] = {
|
|
||||||
MAX6650_CFG_MODE_ON,
|
|
||||||
MAX6650_CFG_MODE_OPEN_LOOP,
|
|
||||||
MAX6650_CFG_MODE_CLOSED_LOOP,
|
|
||||||
MAX6650_CFG_MODE_OFF,
|
|
||||||
};
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &mode);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
if (mode >= ARRAY_SIZE(max6650_modes))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
max6650_set_operating_mode(data, max6650_modes[mode]);
|
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Read/write functions for fan1_div sysfs file. The MAX6650 has no such
|
|
||||||
* divider. We handle this by converting between divider and counttime:
|
|
||||||
*
|
|
||||||
* (counttime == k) <==> (divider == 2^k), k = 0, 1, 2, or 3
|
|
||||||
*
|
|
||||||
* Lower values of k allow to connect a faster fan without the risk of
|
|
||||||
* counter overflow. The price is lower resolution. You can also set counttime
|
|
||||||
* using the module parameter. Note that the module parameter "prescaler" also
|
|
||||||
* influences the behaviour. Unfortunately, there's no sysfs attribute
|
|
||||||
* defined for that. See the data sheet for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static ssize_t fan1_div_show(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", DIV_FROM_REG(data->count));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t fan1_div_store(struct device *dev,
|
|
||||||
struct device_attribute *devattr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
unsigned long div;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = kstrtoul(buf, 10, &div);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
switch (div) {
|
|
||||||
case 1:
|
|
||||||
data->count = 0;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
data->count = 1;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
data->count = 2;
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
data->count = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
i2c_smbus_write_byte_data(client, MAX6650_REG_COUNT, data->count);
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get alarm stati:
|
|
||||||
* Possible values:
|
* Possible values:
|
||||||
* 0 = no alarm
|
* 0 = no alarm
|
||||||
* 1 = alarm
|
* 1 = alarm
|
||||||
|
@ -509,42 +299,30 @@ static ssize_t alarm_show(struct device *dev,
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
struct max6650_data *data = max6650_update_device(dev);
|
struct max6650_data *data = max6650_update_device(dev);
|
||||||
struct i2c_client *client = data->client;
|
bool alarm;
|
||||||
int alarm = 0;
|
|
||||||
|
|
||||||
if (data->alarm & attr->index) {
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
alarm = data->alarm & attr->index;
|
||||||
|
if (alarm) {
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
alarm = 1;
|
|
||||||
data->alarm &= ~attr->index;
|
data->alarm &= ~attr->index;
|
||||||
data->alarm |= i2c_smbus_read_byte_data(client,
|
data->valid = false;
|
||||||
MAX6650_REG_ALARM);
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", alarm);
|
return sprintf(buf, "%d\n", alarm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
|
|
||||||
static DEVICE_ATTR_RW(fan1_target);
|
|
||||||
static DEVICE_ATTR_RW(fan1_div);
|
|
||||||
static DEVICE_ATTR_RW(pwm1_enable);
|
|
||||||
static DEVICE_ATTR_RW(pwm1);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan1_max_alarm, alarm, MAX6650_ALRM_MAX);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan1_min_alarm, alarm, MAX6650_ALRM_MIN);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(fan1_fault, alarm, MAX6650_ALRM_TACH);
|
|
||||||
static SENSOR_DEVICE_ATTR_RO(gpio1_alarm, alarm, MAX6650_ALRM_GPIO1);
|
static SENSOR_DEVICE_ATTR_RO(gpio1_alarm, alarm, MAX6650_ALRM_GPIO1);
|
||||||
static SENSOR_DEVICE_ATTR_RO(gpio2_alarm, alarm, MAX6650_ALRM_GPIO2);
|
static SENSOR_DEVICE_ATTR_RO(gpio2_alarm, alarm, MAX6650_ALRM_GPIO2);
|
||||||
|
|
||||||
static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a,
|
static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a,
|
||||||
int n)
|
int n)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = container_of(kobj, struct device, kobj);
|
||||||
struct max6650_data *data = dev_get_drvdata(dev);
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
u8 alarm_en = i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM_EN);
|
|
||||||
struct device_attribute *devattr;
|
struct device_attribute *devattr;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -552,12 +330,9 @@ static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
devattr = container_of(a, struct device_attribute, attr);
|
devattr = container_of(a, struct device_attribute, attr);
|
||||||
if (devattr == &sensor_dev_attr_fan1_max_alarm.dev_attr
|
if (devattr == &sensor_dev_attr_gpio1_alarm.dev_attr ||
|
||||||
|| devattr == &sensor_dev_attr_fan1_min_alarm.dev_attr
|
devattr == &sensor_dev_attr_gpio2_alarm.dev_attr) {
|
||||||
|| devattr == &sensor_dev_attr_fan1_fault.dev_attr
|
if (!(data->alarm_en & to_sensor_dev_attr(devattr)->index))
|
||||||
|| devattr == &sensor_dev_attr_gpio1_alarm.dev_attr
|
|
||||||
|| devattr == &sensor_dev_attr_gpio2_alarm.dev_attr) {
|
|
||||||
if (!(alarm_en & to_sensor_dev_attr(devattr)->index))
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,14 +340,6 @@ static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a,
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct attribute *max6650_attrs[] = {
|
static struct attribute *max6650_attrs[] = {
|
||||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
|
||||||
&dev_attr_fan1_target.attr,
|
|
||||||
&dev_attr_fan1_div.attr,
|
|
||||||
&dev_attr_pwm1_enable.attr,
|
|
||||||
&dev_attr_pwm1.attr,
|
|
||||||
&sensor_dev_attr_fan1_max_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan1_min_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan1_fault.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_gpio1_alarm.dev_attr.attr,
|
&sensor_dev_attr_gpio1_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_gpio2_alarm.dev_attr.attr,
|
&sensor_dev_attr_gpio2_alarm.dev_attr.attr,
|
||||||
NULL
|
NULL
|
||||||
|
@ -583,27 +350,17 @@ static const struct attribute_group max6650_group = {
|
||||||
.is_visible = max6650_attrs_visible,
|
.is_visible = max6650_attrs_visible,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct attribute *max6651_attrs[] = {
|
static const struct attribute_group *max6650_groups[] = {
|
||||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
&max6650_group,
|
||||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group max6651_group = {
|
|
||||||
.attrs = max6651_attrs,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Real code
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int max6650_init_client(struct max6650_data *data,
|
static int max6650_init_client(struct max6650_data *data,
|
||||||
struct i2c_client *client)
|
struct i2c_client *client)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
int config;
|
int reg;
|
||||||
int err = -EIO;
|
int err;
|
||||||
u32 voltage;
|
u32 voltage;
|
||||||
u32 prescale;
|
u32 prescale;
|
||||||
u32 target_rpm;
|
u32 target_rpm;
|
||||||
|
@ -617,21 +374,20 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
&prescale))
|
&prescale))
|
||||||
prescale = prescaler;
|
prescale = prescaler;
|
||||||
|
|
||||||
config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG);
|
||||||
|
if (reg < 0) {
|
||||||
if (config < 0) {
|
dev_err(dev, "Error reading config register, aborting.\n");
|
||||||
dev_err(dev, "Error reading config, aborting.\n");
|
return reg;
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (voltage) {
|
switch (voltage) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
config &= ~MAX6650_CFG_V12;
|
reg &= ~MAX6650_CFG_V12;
|
||||||
break;
|
break;
|
||||||
case 12:
|
case 12:
|
||||||
config |= MAX6650_CFG_V12;
|
reg |= MAX6650_CFG_V12;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(dev, "illegal value for fan_voltage (%d)\n", voltage);
|
dev_err(dev, "illegal value for fan_voltage (%d)\n", voltage);
|
||||||
|
@ -641,22 +397,22 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
config &= ~MAX6650_CFG_PRESCALER_MASK;
|
reg &= ~MAX6650_CFG_PRESCALER_MASK;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
reg = (reg & ~MAX6650_CFG_PRESCALER_MASK)
|
||||||
| MAX6650_CFG_PRESCALER_2;
|
| MAX6650_CFG_PRESCALER_2;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
reg = (reg & ~MAX6650_CFG_PRESCALER_MASK)
|
||||||
| MAX6650_CFG_PRESCALER_4;
|
| MAX6650_CFG_PRESCALER_4;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
reg = (reg & ~MAX6650_CFG_PRESCALER_MASK)
|
||||||
| MAX6650_CFG_PRESCALER_8;
|
| MAX6650_CFG_PRESCALER_8;
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
config = (config & ~MAX6650_CFG_PRESCALER_MASK)
|
reg = (reg & ~MAX6650_CFG_PRESCALER_MASK)
|
||||||
| MAX6650_CFG_PRESCALER_16;
|
| MAX6650_CFG_PRESCALER_16;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -664,16 +420,43 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(dev, "Fan voltage: %dV, prescaler: %d.\n",
|
dev_info(dev, "Fan voltage: %dV, prescaler: %d.\n",
|
||||||
(config & MAX6650_CFG_V12) ? 12 : 5,
|
(reg & MAX6650_CFG_V12) ? 12 : 5,
|
||||||
1 << (config & MAX6650_CFG_PRESCALER_MASK));
|
1 << (reg & MAX6650_CFG_PRESCALER_MASK));
|
||||||
|
|
||||||
if (i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, config)) {
|
err = i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, reg);
|
||||||
|
if (err) {
|
||||||
dev_err(dev, "Config write error, aborting.\n");
|
dev_err(dev, "Config write error, aborting.\n");
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
data->config = reg;
|
||||||
|
|
||||||
data->config = config;
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_SPEED);
|
||||||
data->count = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT);
|
if (reg < 0) {
|
||||||
|
dev_err(dev, "Failed to read speed register, aborting.\n");
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
data->speed = reg;
|
||||||
|
|
||||||
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_DAC);
|
||||||
|
if (reg < 0) {
|
||||||
|
dev_err(dev, "Failed to read DAC register, aborting.\n");
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
data->dac = reg;
|
||||||
|
|
||||||
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT);
|
||||||
|
if (reg < 0) {
|
||||||
|
dev_err(dev, "Failed to read count register, aborting.\n");
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
data->count = reg;
|
||||||
|
|
||||||
|
reg = i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM_EN);
|
||||||
|
if (reg < 0) {
|
||||||
|
dev_err(dev, "Failed to read alarm configuration, aborting.\n");
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
data->alarm_en = reg;
|
||||||
|
|
||||||
if (!of_property_read_u32(client->dev.of_node, "maxim,fan-target-rpm",
|
if (!of_property_read_u32(client->dev.of_node, "maxim,fan-target-rpm",
|
||||||
&target_rpm)) {
|
&target_rpm)) {
|
||||||
|
@ -684,8 +467,6 @@ static int max6650_init_client(struct max6650_data *data,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_THERMAL)
|
|
||||||
|
|
||||||
static int max6650_get_max_state(struct thermal_cooling_device *cdev,
|
static int max6650_get_max_state(struct thermal_cooling_device *cdev,
|
||||||
unsigned long *state)
|
unsigned long *state)
|
||||||
{
|
{
|
||||||
|
@ -715,23 +496,18 @@ static int max6650_set_cur_state(struct thermal_cooling_device *cdev,
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
if (data->config & MAX6650_CFG_V12)
|
data->dac = pwm_to_dac(state, data->config & MAX6650_CFG_V12);
|
||||||
data->dac = 180 - (180 * state)/255;
|
|
||||||
else
|
|
||||||
data->dac = 76 - (76 * state)/255;
|
|
||||||
|
|
||||||
err = i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
err = i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac);
|
||||||
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
max6650_set_operating_mode(data, state ?
|
max6650_set_operating_mode(data, state ?
|
||||||
MAX6650_CFG_MODE_OPEN_LOOP :
|
MAX6650_CFG_MODE_OPEN_LOOP :
|
||||||
MAX6650_CFG_MODE_OFF);
|
MAX6650_CFG_MODE_OFF);
|
||||||
data->cooling_dev_state = state;
|
data->cooling_dev_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return err < 0 ? err : 0;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct thermal_cooling_device_ops max6650_cooling_ops = {
|
static const struct thermal_cooling_device_ops max6650_cooling_ops = {
|
||||||
|
@ -739,11 +515,252 @@ static const struct thermal_cooling_device_ops max6650_cooling_ops = {
|
||||||
.get_cur_state = max6650_get_cur_state,
|
.get_cur_state = max6650_get_cur_state,
|
||||||
.set_cur_state = max6650_set_cur_state,
|
.set_cur_state = max6650_set_cur_state,
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
static int max6650_read(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
|
{
|
||||||
|
struct max6650_data *data = max6650_update_device(dev);
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_pwm:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_pwm_input:
|
||||||
|
*val = dac_to_pwm(data->dac,
|
||||||
|
data->config & MAX6650_CFG_V12);
|
||||||
|
break;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
/*
|
||||||
|
* Possible values:
|
||||||
|
* 0 = Fan always on
|
||||||
|
* 1 = Open loop, Voltage is set according to speed,
|
||||||
|
* not regulated.
|
||||||
|
* 2 = Closed loop, RPM for all fans regulated by fan1
|
||||||
|
* tachometer
|
||||||
|
* 3 = Fan off
|
||||||
|
*/
|
||||||
|
mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4;
|
||||||
|
*val = (4 - mode) & 3; /* {0 1 2 3} -> {0 3 2 1} */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hwmon_fan:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_fan_input:
|
||||||
|
/*
|
||||||
|
* Calculation details:
|
||||||
|
*
|
||||||
|
* Each tachometer counts over an interval given by the
|
||||||
|
* "count" register (0.25, 0.5, 1 or 2 seconds).
|
||||||
|
* The driver assumes that the fans produce two pulses
|
||||||
|
* per revolution (this seems to be the most common).
|
||||||
|
*/
|
||||||
|
*val = DIV_ROUND_CLOSEST(data->tach[channel] * 120,
|
||||||
|
DIV_FROM_REG(data->count));
|
||||||
|
break;
|
||||||
|
case hwmon_fan_div:
|
||||||
|
*val = DIV_FROM_REG(data->count);
|
||||||
|
break;
|
||||||
|
case hwmon_fan_target:
|
||||||
|
/*
|
||||||
|
* Use the datasheet equation:
|
||||||
|
* FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)]
|
||||||
|
* then multiply by 60 to give rpm.
|
||||||
|
*/
|
||||||
|
*val = 60 * DIV_FROM_REG(data->config) * clock /
|
||||||
|
(256 * (data->speed + 1));
|
||||||
|
break;
|
||||||
|
case hwmon_fan_min_alarm:
|
||||||
|
*val = !!(data->alarm & MAX6650_ALRM_MIN);
|
||||||
|
data->alarm &= ~MAX6650_ALRM_MIN;
|
||||||
|
data->valid = false;
|
||||||
|
break;
|
||||||
|
case hwmon_fan_max_alarm:
|
||||||
|
*val = !!(data->alarm & MAX6650_ALRM_MAX);
|
||||||
|
data->alarm &= ~MAX6650_ALRM_MAX;
|
||||||
|
data->valid = false;
|
||||||
|
break;
|
||||||
|
case hwmon_fan_fault:
|
||||||
|
*val = !!(data->alarm & MAX6650_ALRM_TACH);
|
||||||
|
data->alarm &= ~MAX6650_ALRM_TACH;
|
||||||
|
data->valid = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u8 max6650_pwm_modes[] = {
|
||||||
|
MAX6650_CFG_MODE_ON,
|
||||||
|
MAX6650_CFG_MODE_OPEN_LOOP,
|
||||||
|
MAX6650_CFG_MODE_CLOSED_LOOP,
|
||||||
|
MAX6650_CFG_MODE_OFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int max6650_write(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long val)
|
||||||
|
{
|
||||||
|
struct max6650_data *data = dev_get_drvdata(dev);
|
||||||
|
int ret = 0;
|
||||||
|
u8 reg;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_pwm:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_pwm_input:
|
||||||
|
reg = pwm_to_dac(clamp_val(val, 0, 255),
|
||||||
|
data->config & MAX6650_CFG_V12);
|
||||||
|
ret = i2c_smbus_write_byte_data(data->client,
|
||||||
|
MAX6650_REG_DAC, reg);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
data->dac = reg;
|
||||||
|
break;
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
if (val < 0 || val >= ARRAY_SIZE(max6650_pwm_modes)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret = max6650_set_operating_mode(data,
|
||||||
|
max6650_pwm_modes[val]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hwmon_fan:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_fan_div:
|
||||||
|
switch (val) {
|
||||||
|
case 1:
|
||||||
|
reg = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
reg = 1;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
reg = 2;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
reg = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ret = i2c_smbus_write_byte_data(data->client,
|
||||||
|
MAX6650_REG_COUNT, reg);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
data->count = reg;
|
||||||
|
break;
|
||||||
|
case hwmon_fan_target:
|
||||||
|
if (val < 0) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ret = max6650_set_target(data, val);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static umode_t max6650_is_visible(const void *_data,
|
||||||
|
enum hwmon_sensor_types type, u32 attr,
|
||||||
|
int channel)
|
||||||
|
{
|
||||||
|
const struct max6650_data *data = _data;
|
||||||
|
|
||||||
|
if (channel && (channel >= data->nr_fans || type != hwmon_fan))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_fan:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_fan_input:
|
||||||
|
return 0444;
|
||||||
|
case hwmon_fan_target:
|
||||||
|
case hwmon_fan_div:
|
||||||
|
return 0644;
|
||||||
|
case hwmon_fan_min_alarm:
|
||||||
|
if (data->alarm_en & MAX6650_ALRM_MIN)
|
||||||
|
return 0444;
|
||||||
|
break;
|
||||||
|
case hwmon_fan_max_alarm:
|
||||||
|
if (data->alarm_en & MAX6650_ALRM_MAX)
|
||||||
|
return 0444;
|
||||||
|
break;
|
||||||
|
case hwmon_fan_fault:
|
||||||
|
if (data->alarm_en & MAX6650_ALRM_TACH)
|
||||||
|
return 0444;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hwmon_pwm:
|
||||||
|
switch (attr) {
|
||||||
|
case hwmon_pwm_input:
|
||||||
|
case hwmon_pwm_enable:
|
||||||
|
return 0644;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hwmon_channel_info *max6650_info[] = {
|
||||||
|
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
|
||||||
|
HWMON_F_MIN_ALARM | HWMON_F_MAX_ALARM |
|
||||||
|
HWMON_F_FAULT,
|
||||||
|
HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT),
|
||||||
|
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_ops max6650_hwmon_ops = {
|
||||||
|
.read = max6650_read,
|
||||||
|
.write = max6650_write,
|
||||||
|
.is_visible = max6650_is_visible,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hwmon_chip_info max6650_chip_info = {
|
||||||
|
.ops = &max6650_hwmon_ops,
|
||||||
|
.info = max6650_info,
|
||||||
|
};
|
||||||
|
|
||||||
static int max6650_probe(struct i2c_client *client,
|
static int max6650_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
|
struct thermal_cooling_device *cooling_dev;
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
const struct of_device_id *of_id =
|
const struct of_device_id *of_id =
|
||||||
of_match_device(of_match_ptr(max6650_dt_match), dev);
|
of_match_device(of_match_ptr(max6650_dt_match), dev);
|
||||||
|
@ -767,37 +784,23 @@ static int max6650_probe(struct i2c_client *client,
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
data->groups[0] = &max6650_group;
|
hwmon_dev = devm_hwmon_device_register_with_info(dev,
|
||||||
/* 3 additional fan inputs for the MAX6651 */
|
client->name, data,
|
||||||
if (data->nr_fans == 4)
|
&max6650_chip_info,
|
||||||
data->groups[1] = &max6651_group;
|
max6650_groups);
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev,
|
|
||||||
client->name, data,
|
|
||||||
data->groups);
|
|
||||||
err = PTR_ERR_OR_ZERO(hwmon_dev);
|
err = PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_THERMAL)
|
if (IS_ENABLED(CONFIG_THERMAL)) {
|
||||||
data->cooling_dev =
|
cooling_dev = devm_thermal_of_cooling_device_register(dev,
|
||||||
thermal_of_cooling_device_register(client->dev.of_node,
|
dev->of_node, client->name,
|
||||||
client->name, data,
|
data, &max6650_cooling_ops);
|
||||||
&max6650_cooling_ops);
|
if (IS_ERR(cooling_dev)) {
|
||||||
if (IS_ERR(data->cooling_dev))
|
dev_warn(dev, "thermal cooling device register failed: %ld\n",
|
||||||
dev_warn(&client->dev,
|
PTR_ERR(cooling_dev));
|
||||||
"thermal cooling device register failed: %ld\n",
|
}
|
||||||
PTR_ERR(data->cooling_dev));
|
}
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int max6650_remove(struct i2c_client *client)
|
|
||||||
{
|
|
||||||
struct max6650_data *data = i2c_get_clientdata(client);
|
|
||||||
|
|
||||||
if (!IS_ERR(data->cooling_dev))
|
|
||||||
thermal_cooling_device_unregister(data->cooling_dev);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -815,7 +818,6 @@ static struct i2c_driver max6650_driver = {
|
||||||
.of_match_table = of_match_ptr(max6650_dt_match),
|
.of_match_table = of_match_ptr(max6650_dt_match),
|
||||||
},
|
},
|
||||||
.probe = max6650_probe,
|
.probe = max6650_probe,
|
||||||
.remove = max6650_remove,
|
|
||||||
.id_table = max6650_id,
|
.id_table = max6650_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
*
|
*
|
||||||
* Copyright (c) 2015 Kontron
|
* Copyright (c) 2015 Kontron
|
||||||
* Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>
|
* Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Advantech
|
||||||
|
* Author: Amy.Shih <amy.shih@advantech.com.tw>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
@ -50,6 +53,8 @@
|
||||||
#define T_CPU1_HV_REG 0xA0 /* Bank 0; 2 regs (HV/LV) per sensor */
|
#define T_CPU1_HV_REG 0xA0 /* Bank 0; 2 regs (HV/LV) per sensor */
|
||||||
|
|
||||||
#define PRTS_REG 0x03 /* Bank 2 */
|
#define PRTS_REG 0x03 /* Bank 2 */
|
||||||
|
#define PFE_REG 0x00 /* Bank 2; PECI Function Enable */
|
||||||
|
#define TSI_CTRL_REG 0x50 /* Bank 2; TSI Control Register */
|
||||||
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
|
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
|
||||||
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
|
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
|
||||||
|
|
||||||
|
@ -65,6 +70,8 @@ struct nct7904_data {
|
||||||
u32 vsen_mask;
|
u32 vsen_mask;
|
||||||
u32 tcpu_mask;
|
u32 tcpu_mask;
|
||||||
u8 fan_mode[FANCTL_MAX];
|
u8 fan_mode[FANCTL_MAX];
|
||||||
|
u8 enable_dts;
|
||||||
|
u8 has_dts;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Access functions */
|
/* Access functions */
|
||||||
|
@ -229,11 +236,15 @@ static int nct7904_read_temp(struct device *dev, u32 attr, int channel,
|
||||||
|
|
||||||
switch (attr) {
|
switch (attr) {
|
||||||
case hwmon_temp_input:
|
case hwmon_temp_input:
|
||||||
if (channel == 0)
|
if (channel == 4)
|
||||||
ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG);
|
ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG);
|
||||||
|
else if (channel < 5)
|
||||||
|
ret = nct7904_read_reg16(data, BANK_0,
|
||||||
|
TEMP_CH1_HV_REG + channel * 4);
|
||||||
else
|
else
|
||||||
ret = nct7904_read_reg16(data, BANK_0,
|
ret = nct7904_read_reg16(data, BANK_0,
|
||||||
T_CPU1_HV_REG + (channel - 1) * 2);
|
T_CPU1_HV_REG + (channel - 5)
|
||||||
|
* 2);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
temp = ((ret & 0xff00) >> 5) | (ret & 0x7);
|
||||||
|
@ -249,11 +260,11 @@ static umode_t nct7904_temp_is_visible(const void *_data, u32 attr, int channel)
|
||||||
const struct nct7904_data *data = _data;
|
const struct nct7904_data *data = _data;
|
||||||
|
|
||||||
if (attr == hwmon_temp_input) {
|
if (attr == hwmon_temp_input) {
|
||||||
if (channel == 0) {
|
if (channel < 5) {
|
||||||
if (data->vsen_mask & BIT(17))
|
if (data->tcpu_mask & BIT(channel))
|
||||||
return 0444;
|
return 0444;
|
||||||
} else {
|
} else {
|
||||||
if (data->tcpu_mask & BIT(channel - 1))
|
if (data->has_dts & BIT(channel - 5))
|
||||||
return 0444;
|
return 0444;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,6 +471,7 @@ static int nct7904_probe(struct i2c_client *client,
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
int ret, i;
|
int ret, i;
|
||||||
u32 mask;
|
u32 mask;
|
||||||
|
u8 val, bit;
|
||||||
|
|
||||||
data = devm_kzalloc(dev, sizeof(struct nct7904_data), GFP_KERNEL);
|
data = devm_kzalloc(dev, sizeof(struct nct7904_data), GFP_KERNEL);
|
||||||
if (!data)
|
if (!data)
|
||||||
|
@ -493,10 +505,65 @@ static int nct7904_probe(struct i2c_client *client,
|
||||||
data->vsen_mask = mask;
|
data->vsen_mask = mask;
|
||||||
|
|
||||||
/* CPU_TEMP attributes */
|
/* CPU_TEMP attributes */
|
||||||
ret = nct7904_read_reg16(data, BANK_0, DTS_T_CTRL0_REG);
|
ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL0_REG);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
data->tcpu_mask = ((ret >> 8) & 0xf) | ((ret & 0xf) << 4);
|
|
||||||
|
if ((ret & 0x6) == 0x6)
|
||||||
|
data->tcpu_mask |= 1; /* TR1 */
|
||||||
|
if ((ret & 0x18) == 0x18)
|
||||||
|
data->tcpu_mask |= 2; /* TR2 */
|
||||||
|
if ((ret & 0x20) == 0x20)
|
||||||
|
data->tcpu_mask |= 4; /* TR3 */
|
||||||
|
if ((ret & 0x80) == 0x80)
|
||||||
|
data->tcpu_mask |= 8; /* TR4 */
|
||||||
|
|
||||||
|
/* LTD */
|
||||||
|
ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL2_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if ((ret & 0x02) == 0x02)
|
||||||
|
data->tcpu_mask |= 0x10;
|
||||||
|
|
||||||
|
/* Multi-Function detecting for Volt and TR/TD */
|
||||||
|
ret = nct7904_read_reg(data, BANK_0, VT_ADC_MD_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
val = (ret & (0x03 << i)) >> (i * 2);
|
||||||
|
bit = (1 << i);
|
||||||
|
if (val == 0)
|
||||||
|
data->tcpu_mask &= ~bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PECI */
|
||||||
|
ret = nct7904_read_reg(data, BANK_2, PFE_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret & 0x80) {
|
||||||
|
data->enable_dts = 1; /* Enable DTS & PECI */
|
||||||
|
} else {
|
||||||
|
ret = nct7904_read_reg(data, BANK_2, TSI_CTRL_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret & 0x80)
|
||||||
|
data->enable_dts = 0x3; /* Enable DTS & TSI */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check DTS enable status */
|
||||||
|
if (data->enable_dts) {
|
||||||
|
ret = nct7904_read_reg(data, BANK_0, DTS_T_CTRL0_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
data->has_dts = ret & 0xF;
|
||||||
|
if (data->enable_dts & 0x2) {
|
||||||
|
ret = nct7904_read_reg(data, BANK_0, DTS_T_CTRL1_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
data->has_dts |= (ret & 0xF) << 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < FANCTL_MAX; i++) {
|
for (i = 0; i < FANCTL_MAX; i++) {
|
||||||
ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + i);
|
ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + i);
|
||||||
|
|
|
@ -241,6 +241,12 @@ static ssize_t occ_show_temp_1(struct device *dev,
|
||||||
val = get_unaligned_be16(&temp->sensor_id);
|
val = get_unaligned_be16(&temp->sensor_id);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
/*
|
||||||
|
* If a sensor reading has expired and couldn't be refreshed,
|
||||||
|
* OCC returns 0xFFFF for that sensor.
|
||||||
|
*/
|
||||||
|
if (temp->value == 0xFFFF)
|
||||||
|
return -EREMOTEIO;
|
||||||
val = get_unaligned_be16(&temp->value) * 1000;
|
val = get_unaligned_be16(&temp->value) * 1000;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -64,6 +64,15 @@ config SENSORS_IR38064
|
||||||
This driver can also be built as a module. If so, the module will
|
This driver can also be built as a module. If so, the module will
|
||||||
be called ir38064.
|
be called ir38064.
|
||||||
|
|
||||||
|
config SENSORS_IRPS5401
|
||||||
|
tristate "Infineon IRPS5401"
|
||||||
|
help
|
||||||
|
If you say yes here you get hardware monitoring support for the
|
||||||
|
Infineon IRPS5401 controller.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will
|
||||||
|
be called irps5401.
|
||||||
|
|
||||||
config SENSORS_ISL68137
|
config SENSORS_ISL68137
|
||||||
tristate "Intersil ISL68137"
|
tristate "Intersil ISL68137"
|
||||||
help
|
help
|
||||||
|
@ -154,6 +163,15 @@ config SENSORS_MAX8688
|
||||||
This driver can also be built as a module. If so, the module will
|
This driver can also be built as a module. If so, the module will
|
||||||
be called max8688.
|
be called max8688.
|
||||||
|
|
||||||
|
config SENSORS_PXE1610
|
||||||
|
tristate "Infineon PXE1610"
|
||||||
|
help
|
||||||
|
If you say yes here you get hardware monitoring support for Infineon
|
||||||
|
PXE1610.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will
|
||||||
|
be called pxe1610.
|
||||||
|
|
||||||
config SENSORS_TPS40422
|
config SENSORS_TPS40422
|
||||||
tristate "TI TPS40422"
|
tristate "TI TPS40422"
|
||||||
help
|
help
|
||||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
|
||||||
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
|
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
|
||||||
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
|
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
|
||||||
obj-$(CONFIG_SENSORS_IR38064) += ir38064.o
|
obj-$(CONFIG_SENSORS_IR38064) += ir38064.o
|
||||||
|
obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o
|
||||||
obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o
|
obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o
|
||||||
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
||||||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
||||||
|
@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
|
||||||
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
||||||
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
|
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
|
||||||
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
|
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
|
||||||
|
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
|
||||||
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
|
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
|
||||||
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
||||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/log2.h>
|
||||||
#include "pmbus.h"
|
#include "pmbus.h"
|
||||||
|
|
||||||
enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 };
|
enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 };
|
||||||
|
@ -69,6 +71,18 @@ enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 };
|
||||||
#define ADM1075_VAUX_OV_WARN BIT(7)
|
#define ADM1075_VAUX_OV_WARN BIT(7)
|
||||||
#define ADM1075_VAUX_UV_WARN BIT(6)
|
#define ADM1075_VAUX_UV_WARN BIT(6)
|
||||||
|
|
||||||
|
#define ADM1275_VI_AVG_SHIFT 0
|
||||||
|
#define ADM1275_VI_AVG_MASK GENMASK(ADM1275_VI_AVG_SHIFT + 2, \
|
||||||
|
ADM1275_VI_AVG_SHIFT)
|
||||||
|
#define ADM1275_SAMPLES_AVG_MAX 128
|
||||||
|
|
||||||
|
#define ADM1278_PWR_AVG_SHIFT 11
|
||||||
|
#define ADM1278_PWR_AVG_MASK GENMASK(ADM1278_PWR_AVG_SHIFT + 2, \
|
||||||
|
ADM1278_PWR_AVG_SHIFT)
|
||||||
|
#define ADM1278_VI_AVG_SHIFT 8
|
||||||
|
#define ADM1278_VI_AVG_MASK GENMASK(ADM1278_VI_AVG_SHIFT + 2, \
|
||||||
|
ADM1278_VI_AVG_SHIFT)
|
||||||
|
|
||||||
struct adm1275_data {
|
struct adm1275_data {
|
||||||
int id;
|
int id;
|
||||||
bool have_oc_fault;
|
bool have_oc_fault;
|
||||||
|
@ -80,6 +94,7 @@ struct adm1275_data {
|
||||||
bool have_pin_min;
|
bool have_pin_min;
|
||||||
bool have_pin_max;
|
bool have_pin_max;
|
||||||
bool have_temp_max;
|
bool have_temp_max;
|
||||||
|
bool have_power_sampling;
|
||||||
struct pmbus_driver_info info;
|
struct pmbus_driver_info info;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,6 +170,62 @@ static const struct coefficients adm1293_coefficients[] = {
|
||||||
[18] = { 7658, 0, -3 }, /* power, 21V, irange200 */
|
[18] = { 7658, 0, -3 }, /* power, 21V, irange200 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int adm1275_read_pmon_config(const struct adm1275_data *data,
|
||||||
|
struct i2c_client *client, bool is_power)
|
||||||
|
{
|
||||||
|
int shift, ret;
|
||||||
|
u16 mask;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The PMON configuration register is a 16-bit register only on chips
|
||||||
|
* supporting power average sampling. On other chips it is an 8-bit
|
||||||
|
* register.
|
||||||
|
*/
|
||||||
|
if (data->have_power_sampling) {
|
||||||
|
ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG);
|
||||||
|
mask = is_power ? ADM1278_PWR_AVG_MASK : ADM1278_VI_AVG_MASK;
|
||||||
|
shift = is_power ? ADM1278_PWR_AVG_SHIFT : ADM1278_VI_AVG_SHIFT;
|
||||||
|
} else {
|
||||||
|
ret = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
|
||||||
|
mask = ADM1275_VI_AVG_MASK;
|
||||||
|
shift = ADM1275_VI_AVG_SHIFT;
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return (ret & mask) >> shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adm1275_write_pmon_config(const struct adm1275_data *data,
|
||||||
|
struct i2c_client *client,
|
||||||
|
bool is_power, u16 word)
|
||||||
|
{
|
||||||
|
int shift, ret;
|
||||||
|
u16 mask;
|
||||||
|
|
||||||
|
if (data->have_power_sampling) {
|
||||||
|
ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG);
|
||||||
|
mask = is_power ? ADM1278_PWR_AVG_MASK : ADM1278_VI_AVG_MASK;
|
||||||
|
shift = is_power ? ADM1278_PWR_AVG_SHIFT : ADM1278_VI_AVG_SHIFT;
|
||||||
|
} else {
|
||||||
|
ret = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
|
||||||
|
mask = ADM1275_VI_AVG_MASK;
|
||||||
|
shift = ADM1275_VI_AVG_SHIFT;
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
word = (ret & ~mask) | ((word << shift) & mask);
|
||||||
|
if (data->have_power_sampling)
|
||||||
|
ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG,
|
||||||
|
word);
|
||||||
|
else
|
||||||
|
ret = i2c_smbus_write_byte_data(client, ADM1275_PMON_CONFIG,
|
||||||
|
word);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
|
static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
|
||||||
{
|
{
|
||||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||||
|
@ -233,6 +304,21 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
|
||||||
if (!data->have_temp_max)
|
if (!data->have_temp_max)
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
break;
|
break;
|
||||||
|
case PMBUS_VIRT_POWER_SAMPLES:
|
||||||
|
if (!data->have_power_sampling)
|
||||||
|
return -ENXIO;
|
||||||
|
ret = adm1275_read_pmon_config(data, client, true);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
ret = BIT(ret);
|
||||||
|
break;
|
||||||
|
case PMBUS_VIRT_IN_SAMPLES:
|
||||||
|
case PMBUS_VIRT_CURR_SAMPLES:
|
||||||
|
ret = adm1275_read_pmon_config(data, client, false);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
ret = BIT(ret);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = -ENODATA;
|
ret = -ENODATA;
|
||||||
break;
|
break;
|
||||||
|
@ -277,6 +363,19 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
|
||||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||||
ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0);
|
ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0);
|
||||||
break;
|
break;
|
||||||
|
case PMBUS_VIRT_POWER_SAMPLES:
|
||||||
|
if (!data->have_power_sampling)
|
||||||
|
return -ENXIO;
|
||||||
|
word = clamp_val(word, 1, ADM1275_SAMPLES_AVG_MAX);
|
||||||
|
ret = adm1275_write_pmon_config(data, client, true,
|
||||||
|
ilog2(word));
|
||||||
|
break;
|
||||||
|
case PMBUS_VIRT_IN_SAMPLES:
|
||||||
|
case PMBUS_VIRT_CURR_SAMPLES:
|
||||||
|
word = clamp_val(word, 1, ADM1275_SAMPLES_AVG_MAX);
|
||||||
|
ret = adm1275_write_pmon_config(data, client, false,
|
||||||
|
ilog2(word));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = -ENODATA;
|
ret = -ENODATA;
|
||||||
break;
|
break;
|
||||||
|
@ -430,7 +529,8 @@ static int adm1275_probe(struct i2c_client *client,
|
||||||
info->format[PSC_CURRENT_OUT] = direct;
|
info->format[PSC_CURRENT_OUT] = direct;
|
||||||
info->format[PSC_POWER] = direct;
|
info->format[PSC_POWER] = direct;
|
||||||
info->format[PSC_TEMPERATURE] = direct;
|
info->format[PSC_TEMPERATURE] = direct;
|
||||||
info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
|
info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||||
|
PMBUS_HAVE_SAMPLES;
|
||||||
|
|
||||||
info->read_word_data = adm1275_read_word_data;
|
info->read_word_data = adm1275_read_word_data;
|
||||||
info->read_byte_data = adm1275_read_byte_data;
|
info->read_byte_data = adm1275_read_byte_data;
|
||||||
|
@ -471,6 +571,7 @@ static int adm1275_probe(struct i2c_client *client,
|
||||||
data->have_vout = true;
|
data->have_vout = true;
|
||||||
data->have_pin_max = true;
|
data->have_pin_max = true;
|
||||||
data->have_temp_max = true;
|
data->have_temp_max = true;
|
||||||
|
data->have_power_sampling = true;
|
||||||
|
|
||||||
coefficients = adm1272_coefficients;
|
coefficients = adm1272_coefficients;
|
||||||
vindex = (config & ADM1275_VRANGE) ? 1 : 0;
|
vindex = (config & ADM1275_VRANGE) ? 1 : 0;
|
||||||
|
@ -556,6 +657,7 @@ static int adm1275_probe(struct i2c_client *client,
|
||||||
data->have_vout = true;
|
data->have_vout = true;
|
||||||
data->have_pin_max = true;
|
data->have_pin_max = true;
|
||||||
data->have_temp_max = true;
|
data->have_temp_max = true;
|
||||||
|
data->have_power_sampling = true;
|
||||||
|
|
||||||
coefficients = adm1278_coefficients;
|
coefficients = adm1278_coefficients;
|
||||||
vindex = 0;
|
vindex = 0;
|
||||||
|
@ -591,6 +693,7 @@ static int adm1275_probe(struct i2c_client *client,
|
||||||
data->have_pin_min = true;
|
data->have_pin_min = true;
|
||||||
data->have_pin_max = true;
|
data->have_pin_max = true;
|
||||||
data->have_mfr_vaux_status = true;
|
data->have_mfr_vaux_status = true;
|
||||||
|
data->have_power_sampling = true;
|
||||||
|
|
||||||
coefficients = adm1293_coefficients;
|
coefficients = adm1293_coefficients;
|
||||||
|
|
||||||
|
|
67
drivers/hwmon/pmbus/irps5401.c
Normal file
67
drivers/hwmon/pmbus/irps5401.c
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Hardware monitoring driver for the Infineon IRPS5401M PMIC.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 SED Systems, a division of Calian Ltd.
|
||||||
|
*
|
||||||
|
* The device supports VOUT_PEAK, IOUT_PEAK, and TEMPERATURE_PEAK, however
|
||||||
|
* this driver does not currently support them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include "pmbus.h"
|
||||||
|
|
||||||
|
#define IRPS5401_SW_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | \
|
||||||
|
PMBUS_HAVE_STATUS_INPUT | \
|
||||||
|
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \
|
||||||
|
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \
|
||||||
|
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \
|
||||||
|
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP)
|
||||||
|
|
||||||
|
#define IRPS5401_LDO_FUNC (PMBUS_HAVE_VIN | \
|
||||||
|
PMBUS_HAVE_STATUS_INPUT | \
|
||||||
|
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \
|
||||||
|
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \
|
||||||
|
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \
|
||||||
|
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP)
|
||||||
|
|
||||||
|
static struct pmbus_driver_info irps5401_info = {
|
||||||
|
.pages = 5,
|
||||||
|
.func[0] = IRPS5401_SW_FUNC,
|
||||||
|
.func[1] = IRPS5401_SW_FUNC,
|
||||||
|
.func[2] = IRPS5401_SW_FUNC,
|
||||||
|
.func[3] = IRPS5401_SW_FUNC,
|
||||||
|
.func[4] = IRPS5401_LDO_FUNC,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int irps5401_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
return pmbus_do_probe(client, id, &irps5401_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_device_id irps5401_id[] = {
|
||||||
|
{"irps5401", 0},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(i2c, irps5401_id);
|
||||||
|
|
||||||
|
static struct i2c_driver irps5401_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "irps5401",
|
||||||
|
},
|
||||||
|
.probe = irps5401_probe,
|
||||||
|
.remove = pmbus_do_remove,
|
||||||
|
.id_table = irps5401_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(irps5401_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Robert Hancock");
|
||||||
|
MODULE_DESCRIPTION("PMBus driver for Infineon IRPS5401");
|
||||||
|
MODULE_LICENSE("GPL");
|
139
drivers/hwmon/pmbus/pxe1610.c
Normal file
139
drivers/hwmon/pmbus/pxe1610.c
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Hardware monitoring driver for Infineon PXE1610
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Facebook Inc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include "pmbus.h"
|
||||||
|
|
||||||
|
#define PXE1610_NUM_PAGES 3
|
||||||
|
|
||||||
|
/* Identify chip parameters. */
|
||||||
|
static int pxe1610_identify(struct i2c_client *client,
|
||||||
|
struct pmbus_driver_info *info)
|
||||||
|
{
|
||||||
|
if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
|
||||||
|
u8 vout_mode;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Read the register with VOUT scaling value.*/
|
||||||
|
ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
vout_mode = ret & GENMASK(4, 0);
|
||||||
|
|
||||||
|
switch (vout_mode) {
|
||||||
|
case 1:
|
||||||
|
info->vrm_version = vr12;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
info->vrm_version = vr13;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pmbus_driver_info pxe1610_info = {
|
||||||
|
.pages = PXE1610_NUM_PAGES,
|
||||||
|
.format[PSC_VOLTAGE_IN] = linear,
|
||||||
|
.format[PSC_VOLTAGE_OUT] = vid,
|
||||||
|
.format[PSC_CURRENT_IN] = linear,
|
||||||
|
.format[PSC_CURRENT_OUT] = linear,
|
||||||
|
.format[PSC_TEMPERATURE] = linear,
|
||||||
|
.format[PSC_POWER] = linear,
|
||||||
|
.func[0] = PMBUS_HAVE_VIN
|
||||||
|
| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
|
||||||
|
| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
|
||||||
|
| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
|
||||||
|
| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
|
||||||
|
| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||||
|
.func[1] = PMBUS_HAVE_VIN
|
||||||
|
| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
|
||||||
|
| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
|
||||||
|
| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
|
||||||
|
| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
|
||||||
|
| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||||
|
.func[2] = PMBUS_HAVE_VIN
|
||||||
|
| PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
|
||||||
|
| PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
|
||||||
|
| PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
|
||||||
|
| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
|
||||||
|
| PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||||
|
.identify = pxe1610_identify,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pxe1610_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
struct pmbus_driver_info *info;
|
||||||
|
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!i2c_check_functionality(
|
||||||
|
client->adapter,
|
||||||
|
I2C_FUNC_SMBUS_READ_BYTE_DATA
|
||||||
|
| I2C_FUNC_SMBUS_READ_WORD_DATA
|
||||||
|
| I2C_FUNC_SMBUS_READ_BLOCK_DATA))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default this device doesn't boot to page 0, so set page 0
|
||||||
|
* to access all pmbus registers.
|
||||||
|
*/
|
||||||
|
i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||||
|
|
||||||
|
/* Read Manufacturer id */
|
||||||
|
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (ret != 2 || strncmp(buf, "XP", 2)) {
|
||||||
|
dev_err(&client->dev, "MFR_ID unrecognized\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
info = devm_kmemdup(&client->dev, &pxe1610_info,
|
||||||
|
sizeof(struct pmbus_driver_info),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!info)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return pmbus_do_probe(client, id, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_device_id pxe1610_id[] = {
|
||||||
|
{"pxe1610", 0},
|
||||||
|
{"pxe1110", 0},
|
||||||
|
{"pxm1310", 0},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(i2c, pxe1610_id);
|
||||||
|
|
||||||
|
static struct i2c_driver pxe1610_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "pxe1610",
|
||||||
|
},
|
||||||
|
.probe = pxe1610_probe,
|
||||||
|
.remove = pmbus_do_remove,
|
||||||
|
.id_table = pxe1610_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(pxe1610_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Vijay Khemka <vijaykhemka@fb.com>");
|
||||||
|
MODULE_DESCRIPTION("PMBus driver for Infineon PXE1610, PXE1110 and PXM1310");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -320,8 +320,10 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||||
dev_err(dev, "Failed to enable fan supply: %d\n", ret);
|
dev_err(dev, "Failed to enable fan supply: %d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
devm_add_action_or_reset(dev, pwm_fan_regulator_disable,
|
ret = devm_add_action_or_reset(dev, pwm_fan_regulator_disable,
|
||||||
ctx->reg_en);
|
ctx->reg_en);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->pwm_value = MAX_PWM;
|
ctx->pwm_value = MAX_PWM;
|
||||||
|
@ -337,7 +339,9 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
timer_setup(&ctx->rpm_timer, sample_timer, 0);
|
timer_setup(&ctx->rpm_timer, sample_timer, 0);
|
||||||
devm_add_action_or_reset(dev, pwm_fan_pwm_disable, ctx);
|
ret = devm_add_action_or_reset(dev, pwm_fan_pwm_disable, ctx);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr);
|
of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr);
|
||||||
ctx->pulses_per_revolution = ppr;
|
ctx->pulses_per_revolution = ppr;
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* System Control and Power Interface(SCPI) based hwmon sensor driver
|
* System Control and Power Interface(SCPI) based hwmon sensor driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2015 ARM Ltd.
|
* Copyright (C) 2015 ARM Ltd.
|
||||||
* Punit Agrawal <punit.agrawal@arm.com>
|
* Punit Agrawal <punit.agrawal@arm.com>
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License version 2 as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
||||||
* kind, whether express or implied; without even the implied warranty
|
|
||||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
|
|
|
@ -351,6 +351,8 @@ static ssize_t fan_div_store(struct device *dev,
|
||||||
tmp |= data->fan_div[2] << 4;
|
tmp |= data->fan_div[2] << 4;
|
||||||
smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);
|
smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
BUG();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preserve fan min */
|
/* Preserve fan min */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue