2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* Hardware monitoring driver for PMBus devices
|
|
|
|
*
|
|
|
|
* Copyright (c) 2010, 2011 Ericsson AB.
|
2013-01-17 02:31:32 +08:00
|
|
|
* Copyright (c) 2012 Guenter Roeck
|
2011-01-27 12:09:02 +08:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
2017-08-15 02:55:41 +08:00
|
|
|
#include <linux/debugfs.h>
|
2011-01-27 12:09:02 +08:00
|
|
|
#include <linux/kernel.h>
|
2017-11-28 07:51:55 +08:00
|
|
|
#include <linux/math64.h>
|
2011-01-27 12:09:02 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/hwmon.h>
|
|
|
|
#include <linux/hwmon-sysfs.h>
|
2012-10-10 21:25:56 +08:00
|
|
|
#include <linux/jiffies.h>
|
2017-05-22 04:34:43 +08:00
|
|
|
#include <linux/pmbus.h>
|
2014-10-16 02:55:09 +08:00
|
|
|
#include <linux/regulator/driver.h>
|
|
|
|
#include <linux/regulator/machine.h>
|
2011-01-27 12:09:02 +08:00
|
|
|
#include "pmbus.h"
|
|
|
|
|
|
|
|
/*
|
2013-01-21 13:00:01 +08:00
|
|
|
* Number of additional attribute pointers to allocate
|
|
|
|
* with each call to krealloc
|
2011-01-27 12:09:02 +08:00
|
|
|
*/
|
2013-01-21 13:00:01 +08:00
|
|
|
#define PMBUS_ATTR_ALLOC_SIZE 32
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Index into status register array, per status register group
|
|
|
|
*/
|
|
|
|
#define PB_STATUS_BASE 0
|
|
|
|
#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES)
|
|
|
|
#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES)
|
|
|
|
#define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES)
|
|
|
|
#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES)
|
2013-01-17 02:31:32 +08:00
|
|
|
#define PB_STATUS_TEMP_BASE (PB_STATUS_FAN34_BASE + PMBUS_PAGES)
|
|
|
|
#define PB_STATUS_INPUT_BASE (PB_STATUS_TEMP_BASE + PMBUS_PAGES)
|
|
|
|
#define PB_STATUS_VMON_BASE (PB_STATUS_INPUT_BASE + 1)
|
|
|
|
|
|
|
|
#define PB_NUM_STATUS_REG (PB_STATUS_VMON_BASE + 1)
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-07-09 23:58:49 +08:00
|
|
|
#define PMBUS_NAME_SIZE 24
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
struct pmbus_sensor {
|
2013-01-21 04:01:41 +08:00
|
|
|
struct pmbus_sensor *next;
|
2011-07-09 23:58:49 +08:00
|
|
|
char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */
|
2013-01-21 04:01:41 +08:00
|
|
|
struct device_attribute attribute;
|
2011-01-27 12:09:02 +08:00
|
|
|
u8 page; /* page number */
|
2011-07-09 23:30:26 +08:00
|
|
|
u16 reg; /* register */
|
2011-01-27 12:09:02 +08:00
|
|
|
enum pmbus_sensor_classes class; /* sensor class */
|
|
|
|
bool update; /* runtime sensor update needed */
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
bool convert; /* Whether or not to apply linear/vid/direct */
|
2011-01-27 12:09:02 +08:00
|
|
|
int data; /* Sensor data.
|
|
|
|
Negative if there was a read error */
|
|
|
|
};
|
2013-01-21 04:01:41 +08:00
|
|
|
#define to_pmbus_sensor(_attr) \
|
|
|
|
container_of(_attr, struct pmbus_sensor, attribute)
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
struct pmbus_boolean {
|
2011-07-09 23:58:49 +08:00
|
|
|
char name[PMBUS_NAME_SIZE]; /* sysfs boolean name */
|
2011-01-27 12:09:02 +08:00
|
|
|
struct sensor_device_attribute attribute;
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_sensor *s1;
|
|
|
|
struct pmbus_sensor *s2;
|
2011-01-27 12:09:02 +08:00
|
|
|
};
|
2013-01-21 02:05:55 +08:00
|
|
|
#define to_pmbus_boolean(_attr) \
|
|
|
|
container_of(_attr, struct pmbus_boolean, attribute)
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
struct pmbus_label {
|
2011-07-09 23:58:49 +08:00
|
|
|
char name[PMBUS_NAME_SIZE]; /* sysfs label name */
|
2013-01-21 00:13:21 +08:00
|
|
|
struct device_attribute attribute;
|
2011-07-09 23:58:49 +08:00
|
|
|
char label[PMBUS_NAME_SIZE]; /* label */
|
2011-01-27 12:09:02 +08:00
|
|
|
};
|
2013-01-21 00:13:21 +08:00
|
|
|
#define to_pmbus_label(_attr) \
|
|
|
|
container_of(_attr, struct pmbus_label, attribute)
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
struct pmbus_data {
|
2013-01-21 00:13:21 +08:00
|
|
|
struct device *dev;
|
2011-01-27 12:09:02 +08:00
|
|
|
struct device *hwmon_dev;
|
|
|
|
|
|
|
|
u32 flags; /* from platform data */
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
int exponent[PMBUS_PAGES];
|
|
|
|
/* linear mode: exponent for output voltages */
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
const struct pmbus_driver_info *info;
|
|
|
|
|
|
|
|
int max_attributes;
|
|
|
|
int num_attributes;
|
|
|
|
struct attribute_group group;
|
2013-07-07 00:47:08 +08:00
|
|
|
const struct attribute_group *groups[2];
|
2017-08-15 02:55:41 +08:00
|
|
|
struct dentry *debugfs; /* debugfs device directory */
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
struct pmbus_sensor *sensors;
|
|
|
|
|
|
|
|
struct mutex update_lock;
|
|
|
|
bool valid;
|
|
|
|
unsigned long last_updated; /* in jiffies */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A single status register covers multiple attributes,
|
|
|
|
* so we keep them all together.
|
|
|
|
*/
|
2017-08-11 05:57:47 +08:00
|
|
|
u16 status[PB_NUM_STATUS_REG];
|
2017-08-11 05:57:48 +08:00
|
|
|
|
2017-08-11 05:57:49 +08:00
|
|
|
bool has_status_word; /* device uses STATUS_WORD register */
|
2017-08-11 05:57:48 +08:00
|
|
|
int (*read_status)(struct i2c_client *client, int page);
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
u8 currpage;
|
|
|
|
};
|
|
|
|
|
2017-08-15 02:55:41 +08:00
|
|
|
struct pmbus_debugfs_entry {
|
|
|
|
struct i2c_client *client;
|
|
|
|
u8 page;
|
|
|
|
u8 reg;
|
|
|
|
};
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
static const int pmbus_fan_rpm_mask[] = {
|
|
|
|
PB_FAN_1_RPM,
|
|
|
|
PB_FAN_2_RPM,
|
|
|
|
PB_FAN_1_RPM,
|
|
|
|
PB_FAN_2_RPM,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int pmbus_fan_config_registers[] = {
|
|
|
|
PMBUS_FAN_CONFIG_12,
|
|
|
|
PMBUS_FAN_CONFIG_12,
|
|
|
|
PMBUS_FAN_CONFIG_34,
|
|
|
|
PMBUS_FAN_CONFIG_34
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int pmbus_fan_command_registers[] = {
|
|
|
|
PMBUS_FAN_COMMAND_1,
|
|
|
|
PMBUS_FAN_COMMAND_2,
|
|
|
|
PMBUS_FAN_COMMAND_3,
|
|
|
|
PMBUS_FAN_COMMAND_4,
|
|
|
|
};
|
|
|
|
|
2013-01-27 07:15:37 +08:00
|
|
|
void pmbus_clear_cache(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
data->valid = false;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_clear_cache);
|
|
|
|
|
2017-10-28 00:55:05 +08:00
|
|
|
int pmbus_set_page(struct i2c_client *client, int page)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
2017-11-20 12:42:05 +08:00
|
|
|
int rv;
|
|
|
|
|
|
|
|
if (page < 0 || page == data->currpage)
|
|
|
|
return 0;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2017-11-20 12:42:05 +08:00
|
|
|
if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) {
|
2011-01-27 12:09:02 +08:00
|
|
|
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
2017-11-20 12:42:05 +08:00
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
if (rv != page)
|
|
|
|
return -EIO;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2017-11-20 12:42:05 +08:00
|
|
|
|
|
|
|
data->currpage = page;
|
|
|
|
|
|
|
|
return 0;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_set_page);
|
|
|
|
|
2011-07-09 01:43:57 +08:00
|
|
|
int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
2017-10-28 00:55:05 +08:00
|
|
|
rv = pmbus_set_page(client, page);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
return i2c_smbus_write_byte(client, value);
|
|
|
|
}
|
2011-07-09 01:43:57 +08:00
|
|
|
EXPORT_SYMBOL_GPL(pmbus_write_byte);
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-07-30 13:08:07 +08:00
|
|
|
/*
|
|
|
|
* _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if
|
2013-09-27 20:36:04 +08:00
|
|
|
* a device specific mapping function exists and calls it if necessary.
|
2011-07-30 13:08:07 +08:00
|
|
|
*/
|
|
|
|
static int _pmbus_write_byte(struct i2c_client *client, int page, u8 value)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (info->write_byte) {
|
|
|
|
status = info->write_byte(client, page, value);
|
|
|
|
if (status != -ENODATA)
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
return pmbus_write_byte(client, page, value);
|
|
|
|
}
|
|
|
|
|
2017-10-28 00:55:05 +08:00
|
|
|
int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
|
|
|
|
u16 word)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = pmbus_set_page(client, page);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return i2c_smbus_write_word_data(client, reg, word);
|
|
|
|
}
|
2011-07-09 01:41:24 +08:00
|
|
|
EXPORT_SYMBOL_GPL(pmbus_write_word_data);
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
|
|
|
|
static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg,
|
|
|
|
u16 word)
|
|
|
|
{
|
|
|
|
int bit;
|
|
|
|
int id;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
switch (reg) {
|
|
|
|
case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4:
|
|
|
|
id = reg - PMBUS_VIRT_FAN_TARGET_1;
|
|
|
|
bit = pmbus_fan_rpm_mask[id];
|
|
|
|
rv = pmbus_update_fan(client, page, id, bit, bit, word);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rv = -ENXIO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2011-07-09 01:41:24 +08:00
|
|
|
/*
|
|
|
|
* _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if
|
|
|
|
* a device specific mapping function exists and calls it if necessary.
|
|
|
|
*/
|
|
|
|
static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
|
|
|
|
u16 word)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (info->write_word_data) {
|
|
|
|
status = info->write_word_data(client, page, reg, word);
|
|
|
|
if (status != -ENODATA)
|
|
|
|
return status;
|
|
|
|
}
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
|
2011-07-09 01:41:24 +08:00
|
|
|
if (reg >= PMBUS_VIRT_BASE)
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
return pmbus_write_virt_reg(client, page, reg, word);
|
|
|
|
|
2011-07-09 01:41:24 +08:00
|
|
|
return pmbus_write_word_data(client, page, reg, word);
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
int pmbus_update_fan(struct i2c_client *client, int page, int id,
|
|
|
|
u8 config, u8 mask, u16 command)
|
|
|
|
{
|
|
|
|
int from;
|
|
|
|
int rv;
|
|
|
|
u8 to;
|
|
|
|
|
|
|
|
from = pmbus_read_byte_data(client, page,
|
|
|
|
pmbus_fan_config_registers[id]);
|
|
|
|
if (from < 0)
|
|
|
|
return from;
|
|
|
|
|
|
|
|
to = (from & ~mask) | (config & mask);
|
|
|
|
if (to != from) {
|
|
|
|
rv = pmbus_write_byte_data(client, page,
|
|
|
|
pmbus_fan_config_registers[id], to);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _pmbus_write_word_data(client, page,
|
|
|
|
pmbus_fan_command_registers[id], command);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_update_fan);
|
|
|
|
|
2017-10-28 00:55:05 +08:00
|
|
|
int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = pmbus_set_page(client, page);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return i2c_smbus_read_word_data(client, reg);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_read_word_data);
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
int id;
|
|
|
|
|
|
|
|
switch (reg) {
|
|
|
|
case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4:
|
|
|
|
id = reg - PMBUS_VIRT_FAN_TARGET_1;
|
|
|
|
rv = pmbus_get_fan_rate_device(client, page, id, rpm);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rv = -ENXIO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2011-07-09 01:41:24 +08:00
|
|
|
/*
|
|
|
|
* _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if
|
|
|
|
* a device specific mapping function exists and calls it if necessary.
|
|
|
|
*/
|
|
|
|
static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (info->read_word_data) {
|
|
|
|
status = info->read_word_data(client, page, reg);
|
|
|
|
if (status != -ENODATA)
|
|
|
|
return status;
|
|
|
|
}
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
|
2011-07-09 23:30:26 +08:00
|
|
|
if (reg >= PMBUS_VIRT_BASE)
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
return pmbus_read_virt_reg(client, page, reg);
|
|
|
|
|
2011-07-09 01:41:24 +08:00
|
|
|
return pmbus_read_word_data(client, page, reg);
|
|
|
|
}
|
|
|
|
|
2011-07-09 22:41:01 +08:00
|
|
|
int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
2017-10-28 00:55:05 +08:00
|
|
|
rv = pmbus_set_page(client, page);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
return i2c_smbus_read_byte_data(client, reg);
|
|
|
|
}
|
2011-06-26 02:21:49 +08:00
|
|
|
EXPORT_SYMBOL_GPL(pmbus_read_byte_data);
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2014-10-16 02:55:08 +08:00
|
|
|
int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = pmbus_set_page(client, page);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return i2c_smbus_write_byte_data(client, reg, value);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_write_byte_data);
|
|
|
|
|
|
|
|
int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
|
|
|
|
u8 mask, u8 value)
|
|
|
|
{
|
|
|
|
unsigned int tmp;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = pmbus_read_byte_data(client, page, reg);
|
|
|
|
if (rv < 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
tmp = (rv & ~mask) | (value & mask);
|
|
|
|
|
|
|
|
if (tmp != rv)
|
|
|
|
rv = pmbus_write_byte_data(client, page, reg, tmp);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_update_byte_data);
|
|
|
|
|
2011-07-10 03:06:45 +08:00
|
|
|
/*
|
|
|
|
* _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if
|
|
|
|
* a device specific mapping function exists and calls it if necessary.
|
|
|
|
*/
|
|
|
|
static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (info->read_byte_data) {
|
|
|
|
status = info->read_byte_data(client, page, reg);
|
|
|
|
if (status != -ENODATA)
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
return pmbus_read_byte_data(client, page, reg);
|
|
|
|
}
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
|
|
|
|
int reg)
|
|
|
|
{
|
|
|
|
struct pmbus_sensor *sensor;
|
|
|
|
|
|
|
|
for (sensor = data->sensors; sensor; sensor = sensor->next) {
|
|
|
|
if (sensor->page == page && sensor->reg == reg)
|
|
|
|
return sensor;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id,
|
|
|
|
enum pmbus_fan_mode mode,
|
|
|
|
bool from_cache)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
bool want_rpm, have_rpm;
|
|
|
|
struct pmbus_sensor *s;
|
|
|
|
int config;
|
|
|
|
int reg;
|
|
|
|
|
|
|
|
want_rpm = (mode == rpm);
|
|
|
|
|
|
|
|
if (from_cache) {
|
|
|
|
reg = want_rpm ? PMBUS_VIRT_FAN_TARGET_1 : PMBUS_VIRT_PWM_1;
|
|
|
|
s = pmbus_find_sensor(data, page, reg + id);
|
|
|
|
if (IS_ERR(s))
|
|
|
|
return PTR_ERR(s);
|
|
|
|
|
|
|
|
return s->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
config = pmbus_read_byte_data(client, page,
|
|
|
|
pmbus_fan_config_registers[id]);
|
|
|
|
if (config < 0)
|
|
|
|
return config;
|
|
|
|
|
|
|
|
have_rpm = !!(config & pmbus_fan_rpm_mask[id]);
|
|
|
|
if (want_rpm == have_rpm)
|
|
|
|
return pmbus_read_word_data(client, page,
|
|
|
|
pmbus_fan_command_registers[id]);
|
|
|
|
|
|
|
|
/* Can't sensibly map between RPM and PWM, just return zero */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
|
|
|
|
enum pmbus_fan_mode mode)
|
|
|
|
{
|
|
|
|
return pmbus_get_fan_rate(client, page, id, mode, false);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device);
|
|
|
|
|
|
|
|
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
|
|
|
|
enum pmbus_fan_mode mode)
|
|
|
|
{
|
|
|
|
return pmbus_get_fan_rate(client, page, id, mode, true);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached);
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
static void pmbus_clear_fault_page(struct i2c_client *client, int page)
|
|
|
|
{
|
2011-07-30 13:08:07 +08:00
|
|
|
_pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void pmbus_clear_faults(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < data->info->pages; i++)
|
|
|
|
pmbus_clear_fault_page(client, i);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_clear_faults);
|
|
|
|
|
2011-07-09 22:41:01 +08:00
|
|
|
static int pmbus_check_status_cml(struct i2c_client *client)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2012-05-20 02:35:25 +08:00
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
2011-01-27 12:09:02 +08:00
|
|
|
int status, status2;
|
|
|
|
|
2017-08-11 05:57:48 +08:00
|
|
|
status = data->read_status(client, -1);
|
2011-01-27 12:09:02 +08:00
|
|
|
if (status < 0 || (status & PB_STATUS_CML)) {
|
2011-07-30 13:19:39 +08:00
|
|
|
status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
|
2011-01-27 12:09:02 +08:00
|
|
|
if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND))
|
2011-09-01 23:34:31 +08:00
|
|
|
return -EIO;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-23 08:26:46 +08:00
|
|
|
static bool pmbus_check_register(struct i2c_client *client,
|
|
|
|
int (*func)(struct i2c_client *client,
|
|
|
|
int page, int reg),
|
|
|
|
int page, int reg)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
|
2013-01-23 08:26:46 +08:00
|
|
|
rv = func(client, page, reg);
|
2011-01-27 12:09:02 +08:00
|
|
|
if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK))
|
2011-07-09 22:41:01 +08:00
|
|
|
rv = pmbus_check_status_cml(client);
|
|
|
|
pmbus_clear_fault_page(client, -1);
|
2011-01-27 12:09:02 +08:00
|
|
|
return rv >= 0;
|
|
|
|
}
|
2013-01-23 08:26:46 +08:00
|
|
|
|
2017-08-11 05:57:48 +08:00
|
|
|
static bool pmbus_check_status_register(struct i2c_client *client, int page)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
status = data->read_status(client, page);
|
|
|
|
if (status >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK) &&
|
|
|
|
(status & PB_STATUS_CML)) {
|
|
|
|
status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
|
|
|
|
if (status < 0 || (status & PB_CML_FAULT_INVALID_COMMAND))
|
|
|
|
status = -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
pmbus_clear_fault_page(client, -1);
|
|
|
|
return status >= 0;
|
|
|
|
}
|
|
|
|
|
2013-01-23 08:26:46 +08:00
|
|
|
bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg)
|
|
|
|
{
|
|
|
|
return pmbus_check_register(client, _pmbus_read_byte_data, page, reg);
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
EXPORT_SYMBOL_GPL(pmbus_check_byte_register);
|
|
|
|
|
|
|
|
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg)
|
|
|
|
{
|
2013-01-23 08:26:46 +08:00
|
|
|
return pmbus_check_register(client, _pmbus_read_word_data, page, reg);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_check_word_register);
|
|
|
|
|
|
|
|
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
return data->info;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_get_driver_info);
|
|
|
|
|
2013-01-23 08:26:46 +08:00
|
|
|
static struct _pmbus_status {
|
|
|
|
u32 func;
|
|
|
|
u16 base;
|
|
|
|
u16 reg;
|
|
|
|
} pmbus_status[] = {
|
|
|
|
{ PMBUS_HAVE_STATUS_VOUT, PB_STATUS_VOUT_BASE, PMBUS_STATUS_VOUT },
|
|
|
|
{ PMBUS_HAVE_STATUS_IOUT, PB_STATUS_IOUT_BASE, PMBUS_STATUS_IOUT },
|
|
|
|
{ PMBUS_HAVE_STATUS_TEMP, PB_STATUS_TEMP_BASE,
|
|
|
|
PMBUS_STATUS_TEMPERATURE },
|
|
|
|
{ PMBUS_HAVE_STATUS_FAN12, PB_STATUS_FAN_BASE, PMBUS_STATUS_FAN_12 },
|
|
|
|
{ PMBUS_HAVE_STATUS_FAN34, PB_STATUS_FAN34_BASE, PMBUS_STATUS_FAN_34 },
|
|
|
|
};
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
static struct pmbus_data *pmbus_update_device(struct device *dev)
|
|
|
|
{
|
2013-07-07 00:47:08 +08:00
|
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
2011-01-27 12:09:02 +08:00
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
2013-01-21 04:01:41 +08:00
|
|
|
struct pmbus_sensor *sensor;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
2013-01-23 08:26:46 +08:00
|
|
|
int i, j;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-23 08:26:46 +08:00
|
|
|
for (i = 0; i < info->pages; i++) {
|
2011-01-27 12:09:02 +08:00
|
|
|
data->status[PB_STATUS_BASE + i]
|
2017-08-11 05:57:48 +08:00
|
|
|
= data->read_status(client, i);
|
2013-01-23 08:26:46 +08:00
|
|
|
for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) {
|
|
|
|
struct _pmbus_status *s = &pmbus_status[j];
|
|
|
|
|
|
|
|
if (!(info->func[i] & s->func))
|
|
|
|
continue;
|
|
|
|
data->status[s->base + i]
|
|
|
|
= _pmbus_read_byte_data(client, i,
|
|
|
|
s->reg);
|
|
|
|
}
|
2011-03-05 23:55:10 +08:00
|
|
|
}
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
if (info->func[0] & PMBUS_HAVE_STATUS_INPUT)
|
|
|
|
data->status[PB_STATUS_INPUT_BASE]
|
2011-03-09 15:00:10 +08:00
|
|
|
= _pmbus_read_byte_data(client, 0,
|
|
|
|
PMBUS_STATUS_INPUT);
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-17 02:31:32 +08:00
|
|
|
if (info->func[0] & PMBUS_HAVE_STATUS_VMON)
|
|
|
|
data->status[PB_STATUS_VMON_BASE]
|
|
|
|
= _pmbus_read_byte_data(client, 0,
|
|
|
|
PMBUS_VIRT_STATUS_VMON);
|
|
|
|
|
2013-01-21 04:01:41 +08:00
|
|
|
for (sensor = data->sensors; sensor; sensor = sensor->next) {
|
2011-01-27 12:09:02 +08:00
|
|
|
if (!data->valid || sensor->update)
|
|
|
|
sensor->data
|
2011-07-09 01:41:24 +08:00
|
|
|
= _pmbus_read_word_data(client,
|
|
|
|
sensor->page,
|
|
|
|
sensor->reg);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
pmbus_clear_faults(client);
|
|
|
|
data->last_updated = jiffies;
|
|
|
|
data->valid = 1;
|
|
|
|
}
|
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert linear sensor values to milli- or micro-units
|
|
|
|
* depending on sensor type.
|
|
|
|
*/
|
2011-07-11 10:31:29 +08:00
|
|
|
static long pmbus_reg2data_linear(struct pmbus_data *data,
|
|
|
|
struct pmbus_sensor *sensor)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2011-03-08 10:34:50 +08:00
|
|
|
s16 exponent;
|
|
|
|
s32 mantissa;
|
2011-01-27 12:09:02 +08:00
|
|
|
long val;
|
|
|
|
|
2011-03-08 10:34:50 +08:00
|
|
|
if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */
|
2014-01-31 11:51:14 +08:00
|
|
|
exponent = data->exponent[sensor->page];
|
2011-03-08 10:34:50 +08:00
|
|
|
mantissa = (u16) sensor->data;
|
|
|
|
} else { /* LINEAR11 */
|
2011-09-29 02:36:20 +08:00
|
|
|
exponent = ((s16)sensor->data) >> 11;
|
|
|
|
mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
val = mantissa;
|
|
|
|
|
|
|
|
/* scale result to milli-units for all sensors except fans */
|
|
|
|
if (sensor->class != PSC_FAN)
|
|
|
|
val = val * 1000L;
|
|
|
|
|
|
|
|
/* scale result to micro-units for power sensors */
|
|
|
|
if (sensor->class == PSC_POWER)
|
|
|
|
val = val * 1000L;
|
|
|
|
|
|
|
|
if (exponent >= 0)
|
|
|
|
val <<= exponent;
|
|
|
|
else
|
|
|
|
val >>= -exponent;
|
|
|
|
|
2011-07-11 10:31:29 +08:00
|
|
|
return val;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert direct sensor values to milli- or micro-units
|
|
|
|
* depending on sensor type.
|
|
|
|
*/
|
2011-07-11 10:31:29 +08:00
|
|
|
static long pmbus_reg2data_direct(struct pmbus_data *data,
|
|
|
|
struct pmbus_sensor *sensor)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2017-11-28 07:51:55 +08:00
|
|
|
s64 b, val = (s16)sensor->data;
|
|
|
|
s32 m, R;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
m = data->info->m[sensor->class];
|
|
|
|
b = data->info->b[sensor->class];
|
|
|
|
R = data->info->R[sensor->class];
|
|
|
|
|
|
|
|
if (m == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* X = 1/m * (Y * 10^-R - b) */
|
|
|
|
R = -R;
|
|
|
|
/* scale result to milli-units for everything but fans */
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
2011-01-27 12:09:02 +08:00
|
|
|
R += 3;
|
|
|
|
b *= 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* scale result to micro-units for power sensors */
|
|
|
|
if (sensor->class == PSC_POWER) {
|
|
|
|
R += 3;
|
|
|
|
b *= 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (R > 0) {
|
|
|
|
val *= 10;
|
|
|
|
R--;
|
|
|
|
}
|
|
|
|
while (R < 0) {
|
2017-11-28 07:51:55 +08:00
|
|
|
val = div_s64(val + 5LL, 10L); /* round closest */
|
2011-01-27 12:09:02 +08:00
|
|
|
R++;
|
|
|
|
}
|
|
|
|
|
2017-11-28 07:51:55 +08:00
|
|
|
val = div_s64(val - b, m);
|
|
|
|
return clamp_val(val, LONG_MIN, LONG_MAX);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2011-06-26 02:21:49 +08:00
|
|
|
/*
|
|
|
|
* Convert VID sensor values to milli- or micro-units
|
|
|
|
* depending on sensor type.
|
|
|
|
*/
|
|
|
|
static long pmbus_reg2data_vid(struct pmbus_data *data,
|
|
|
|
struct pmbus_sensor *sensor)
|
|
|
|
{
|
|
|
|
long val = sensor->data;
|
2015-07-21 00:47:33 +08:00
|
|
|
long rv = 0;
|
|
|
|
|
|
|
|
switch (data->info->vrm_version) {
|
|
|
|
case vr11:
|
|
|
|
if (val >= 0x02 && val <= 0xb2)
|
|
|
|
rv = DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100);
|
|
|
|
break;
|
|
|
|
case vr12:
|
|
|
|
if (val >= 0x01)
|
|
|
|
rv = 250 + (val - 1) * 5;
|
|
|
|
break;
|
2017-08-30 04:06:21 +08:00
|
|
|
case vr13:
|
|
|
|
if (val >= 0x01)
|
|
|
|
rv = 500 + (val - 1) * 10;
|
|
|
|
break;
|
2015-07-21 00:47:33 +08:00
|
|
|
}
|
|
|
|
return rv;
|
2011-06-26 02:21:49 +08:00
|
|
|
}
|
|
|
|
|
2011-07-11 10:31:29 +08:00
|
|
|
static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2011-07-11 10:31:29 +08:00
|
|
|
long val;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
if (!sensor->convert)
|
|
|
|
return sensor->data;
|
|
|
|
|
2011-06-26 02:21:49 +08:00
|
|
|
switch (data->info->format[sensor->class]) {
|
|
|
|
case direct:
|
2011-01-27 12:09:02 +08:00
|
|
|
val = pmbus_reg2data_direct(data, sensor);
|
2011-06-26 02:21:49 +08:00
|
|
|
break;
|
|
|
|
case vid:
|
|
|
|
val = pmbus_reg2data_vid(data, sensor);
|
|
|
|
break;
|
|
|
|
case linear:
|
|
|
|
default:
|
2011-01-27 12:09:02 +08:00
|
|
|
val = pmbus_reg2data_linear(data, sensor);
|
2011-06-26 02:21:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_MANTISSA (1023 * 1000)
|
|
|
|
#define MIN_MANTISSA (511 * 1000)
|
|
|
|
|
|
|
|
static u16 pmbus_data2reg_linear(struct pmbus_data *data,
|
2014-01-31 11:51:14 +08:00
|
|
|
struct pmbus_sensor *sensor, long val)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2011-03-08 10:34:50 +08:00
|
|
|
s16 exponent = 0, mantissa;
|
2011-01-27 12:09:02 +08:00
|
|
|
bool negative = false;
|
|
|
|
|
|
|
|
/* simple case */
|
|
|
|
if (val == 0)
|
|
|
|
return 0;
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
if (sensor->class == PSC_VOLTAGE_OUT) {
|
2011-03-08 10:34:50 +08:00
|
|
|
/* LINEAR16 does not support negative voltages */
|
|
|
|
if (val < 0)
|
|
|
|
return 0;
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* For a static exponents, we don't have a choice
|
|
|
|
* but to adjust the value to it.
|
|
|
|
*/
|
2014-01-31 11:51:14 +08:00
|
|
|
if (data->exponent[sensor->page] < 0)
|
|
|
|
val <<= -data->exponent[sensor->page];
|
2011-01-27 12:09:02 +08:00
|
|
|
else
|
2014-01-31 11:51:14 +08:00
|
|
|
val >>= data->exponent[sensor->page];
|
2011-01-27 12:09:02 +08:00
|
|
|
val = DIV_ROUND_CLOSEST(val, 1000);
|
2011-03-08 10:34:50 +08:00
|
|
|
return val & 0xffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val < 0) {
|
|
|
|
negative = true;
|
|
|
|
val = -val;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Power is in uW. Convert to mW before converting. */
|
2014-01-31 11:51:14 +08:00
|
|
|
if (sensor->class == PSC_POWER)
|
2011-01-27 12:09:02 +08:00
|
|
|
val = DIV_ROUND_CLOSEST(val, 1000L);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For simplicity, convert fan data to milli-units
|
|
|
|
* before calculating the exponent.
|
|
|
|
*/
|
2014-01-31 11:51:14 +08:00
|
|
|
if (sensor->class == PSC_FAN)
|
2011-01-27 12:09:02 +08:00
|
|
|
val = val * 1000;
|
|
|
|
|
|
|
|
/* Reduce large mantissa until it fits into 10 bit */
|
|
|
|
while (val >= MAX_MANTISSA && exponent < 15) {
|
|
|
|
exponent++;
|
|
|
|
val >>= 1;
|
|
|
|
}
|
|
|
|
/* Increase small mantissa to improve precision */
|
|
|
|
while (val < MIN_MANTISSA && exponent > -15) {
|
|
|
|
exponent--;
|
|
|
|
val <<= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert mantissa from milli-units to units */
|
|
|
|
mantissa = DIV_ROUND_CLOSEST(val, 1000);
|
|
|
|
|
|
|
|
/* Ensure that resulting number is within range */
|
|
|
|
if (mantissa > 0x3ff)
|
|
|
|
mantissa = 0x3ff;
|
|
|
|
|
|
|
|
/* restore sign */
|
|
|
|
if (negative)
|
|
|
|
mantissa = -mantissa;
|
|
|
|
|
|
|
|
/* Convert to 5 bit exponent, 11 bit mantissa */
|
|
|
|
return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u16 pmbus_data2reg_direct(struct pmbus_data *data,
|
2014-01-31 11:51:14 +08:00
|
|
|
struct pmbus_sensor *sensor, long val)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2017-11-28 07:51:55 +08:00
|
|
|
s64 b, val64 = val;
|
|
|
|
s32 m, R;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
m = data->info->m[sensor->class];
|
|
|
|
b = data->info->b[sensor->class];
|
|
|
|
R = data->info->R[sensor->class];
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
/* Power is in uW. Adjust R and b. */
|
2014-01-31 11:51:14 +08:00
|
|
|
if (sensor->class == PSC_POWER) {
|
2011-01-27 12:09:02 +08:00
|
|
|
R -= 3;
|
|
|
|
b *= 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate Y = (m * X + b) * 10^R */
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
2011-01-27 12:09:02 +08:00
|
|
|
R -= 3; /* Adjust R and b for data in milli-units */
|
|
|
|
b *= 1000;
|
|
|
|
}
|
2017-11-28 07:51:55 +08:00
|
|
|
val64 = val64 * m + b;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
while (R > 0) {
|
2017-11-28 07:51:55 +08:00
|
|
|
val64 *= 10;
|
2011-01-27 12:09:02 +08:00
|
|
|
R--;
|
|
|
|
}
|
|
|
|
while (R < 0) {
|
2017-11-28 07:51:55 +08:00
|
|
|
val64 = div_s64(val64 + 5LL, 10L); /* round closest */
|
2011-01-27 12:09:02 +08:00
|
|
|
R++;
|
|
|
|
}
|
|
|
|
|
2017-11-28 07:51:55 +08:00
|
|
|
return (u16)clamp_val(val64, S16_MIN, S16_MAX);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2011-06-26 02:21:49 +08:00
|
|
|
static u16 pmbus_data2reg_vid(struct pmbus_data *data,
|
2014-01-31 11:51:14 +08:00
|
|
|
struct pmbus_sensor *sensor, long val)
|
2011-06-26 02:21:49 +08:00
|
|
|
{
|
2013-01-10 00:09:34 +08:00
|
|
|
val = clamp_val(val, 500, 1600);
|
2011-06-26 02:21:49 +08:00
|
|
|
|
|
|
|
return 2 + DIV_ROUND_CLOSEST((1600 - val) * 100, 625);
|
|
|
|
}
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
static u16 pmbus_data2reg(struct pmbus_data *data,
|
2014-01-31 11:51:14 +08:00
|
|
|
struct pmbus_sensor *sensor, long val)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
u16 regval;
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
if (!sensor->convert)
|
|
|
|
return val;
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
switch (data->info->format[sensor->class]) {
|
2011-06-26 02:21:49 +08:00
|
|
|
case direct:
|
2014-01-31 11:51:14 +08:00
|
|
|
regval = pmbus_data2reg_direct(data, sensor, val);
|
2011-06-26 02:21:49 +08:00
|
|
|
break;
|
|
|
|
case vid:
|
2014-01-31 11:51:14 +08:00
|
|
|
regval = pmbus_data2reg_vid(data, sensor, val);
|
2011-06-26 02:21:49 +08:00
|
|
|
break;
|
|
|
|
case linear:
|
|
|
|
default:
|
2014-01-31 11:51:14 +08:00
|
|
|
regval = pmbus_data2reg_linear(data, sensor, val);
|
2011-06-26 02:21:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
return regval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return boolean calculated from converted data.
|
2013-01-21 02:05:55 +08:00
|
|
|
* <index> defines a status register index and mask.
|
|
|
|
* The mask is in the lower 8 bits, the register index is in bits 8..23.
|
2011-01-27 12:09:02 +08:00
|
|
|
*
|
2013-01-21 02:05:55 +08:00
|
|
|
* The associated pmbus_boolean structure contains optional pointers to two
|
|
|
|
* sensor attributes. If specified, those attributes are compared against each
|
|
|
|
* other to determine if a limit has been exceeded.
|
2011-01-27 12:09:02 +08:00
|
|
|
*
|
2013-01-21 02:05:55 +08:00
|
|
|
* If the sensor attribute pointers are NULL, the function returns true if
|
|
|
|
* (status[reg] & mask) is true.
|
|
|
|
*
|
|
|
|
* If sensor attribute pointers are provided, a comparison against a specified
|
|
|
|
* limit has to be performed to determine the boolean result.
|
2011-01-27 12:09:02 +08:00
|
|
|
* In this case, the function returns true if v1 >= v2 (where v1 and v2 are
|
2013-01-21 02:05:55 +08:00
|
|
|
* sensor values referenced by sensor attribute pointers s1 and s2).
|
2011-01-27 12:09:02 +08:00
|
|
|
*
|
|
|
|
* To determine if an object exceeds upper limits, specify <s1,s2> = <v,limit>.
|
|
|
|
* To determine if an object exceeds lower limits, specify <s1,s2> = <limit,v>.
|
|
|
|
*
|
|
|
|
* If a negative value is stored in any of the referenced registers, this value
|
|
|
|
* reflects an error code which will be returned.
|
|
|
|
*/
|
2013-01-21 02:05:55 +08:00
|
|
|
static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b,
|
|
|
|
int index)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_sensor *s1 = b->s1;
|
|
|
|
struct pmbus_sensor *s2 = b->s2;
|
2017-08-11 05:57:47 +08:00
|
|
|
u16 reg = (index >> 16) & 0xffff;
|
|
|
|
u16 mask = index & 0xffff;
|
2012-03-29 00:14:03 +08:00
|
|
|
int ret, status;
|
2017-08-11 05:57:47 +08:00
|
|
|
u16 regval;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
status = data->status[reg];
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
regval = status & mask;
|
2013-01-21 02:05:55 +08:00
|
|
|
if (!s1 && !s2) {
|
2012-03-29 00:14:03 +08:00
|
|
|
ret = !!regval;
|
2013-01-21 02:05:55 +08:00
|
|
|
} else if (!s1 || !s2) {
|
2013-09-14 01:31:38 +08:00
|
|
|
WARN(1, "Bad boolean descriptor %p: s1=%p, s2=%p\n", b, s1, s2);
|
2013-01-21 02:05:55 +08:00
|
|
|
return 0;
|
|
|
|
} else {
|
2011-07-11 10:31:29 +08:00
|
|
|
long v1, v2;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 02:05:55 +08:00
|
|
|
if (s1->data < 0)
|
|
|
|
return s1->data;
|
|
|
|
if (s2->data < 0)
|
|
|
|
return s2->data;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 02:05:55 +08:00
|
|
|
v1 = pmbus_reg2data(data, s1);
|
|
|
|
v2 = pmbus_reg2data(data, s2);
|
2012-03-29 00:14:03 +08:00
|
|
|
ret = !!(regval && v1 >= v2);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2012-03-29 00:14:03 +08:00
|
|
|
return ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t pmbus_show_boolean(struct device *dev,
|
|
|
|
struct device_attribute *da, char *buf)
|
|
|
|
{
|
|
|
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_boolean *boolean = to_pmbus_boolean(attr);
|
2011-01-27 12:09:02 +08:00
|
|
|
struct pmbus_data *data = pmbus_update_device(dev);
|
|
|
|
int val;
|
|
|
|
|
2013-01-21 02:05:55 +08:00
|
|
|
val = pmbus_get_boolean(data, boolean, attr->index);
|
2012-03-29 00:14:03 +08:00
|
|
|
if (val < 0)
|
|
|
|
return val;
|
2011-01-27 12:09:02 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t pmbus_show_sensor(struct device *dev,
|
2013-01-21 04:01:41 +08:00
|
|
|
struct device_attribute *devattr, char *buf)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_data *data = pmbus_update_device(dev);
|
2013-01-21 04:01:41 +08:00
|
|
|
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
if (sensor->data < 0)
|
|
|
|
return sensor->data;
|
|
|
|
|
2011-07-11 10:31:29 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%ld\n", pmbus_reg2data(data, sensor));
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t pmbus_set_sensor(struct device *dev,
|
|
|
|
struct device_attribute *devattr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
2013-07-07 00:47:08 +08:00
|
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
2011-01-27 12:09:02 +08:00
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
2013-01-21 04:01:41 +08:00
|
|
|
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
|
2011-01-27 12:09:02 +08:00
|
|
|
ssize_t rv = count;
|
|
|
|
long val = 0;
|
|
|
|
int ret;
|
|
|
|
u16 regval;
|
|
|
|
|
2012-01-17 02:14:54 +08:00
|
|
|
if (kstrtol(buf, 10, &val) < 0)
|
2011-01-27 12:09:02 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mutex_lock(&data->update_lock);
|
2014-01-31 11:51:14 +08:00
|
|
|
regval = pmbus_data2reg(data, sensor, val);
|
2011-07-09 01:41:24 +08:00
|
|
|
ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval);
|
2011-01-27 12:09:02 +08:00
|
|
|
if (ret < 0)
|
|
|
|
rv = ret;
|
|
|
|
else
|
2013-01-21 04:01:41 +08:00
|
|
|
sensor->data = regval;
|
2011-01-27 12:09:02 +08:00
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t pmbus_show_label(struct device *dev,
|
|
|
|
struct device_attribute *da, char *buf)
|
|
|
|
{
|
2013-01-21 00:13:21 +08:00
|
|
|
struct pmbus_label *label = to_pmbus_label(da);
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", label->label);
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 13:00:01 +08:00
|
|
|
static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr)
|
|
|
|
{
|
|
|
|
if (data->num_attributes >= data->max_attributes - 1) {
|
2013-03-14 21:30:20 +08:00
|
|
|
int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE;
|
|
|
|
void *new_attrs = krealloc(data->group.attrs,
|
|
|
|
new_max_attrs * sizeof(void *),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!new_attrs)
|
2013-01-21 13:00:01 +08:00
|
|
|
return -ENOMEM;
|
2013-03-14 21:30:20 +08:00
|
|
|
data->group.attrs = new_attrs;
|
|
|
|
data->max_attributes = new_max_attrs;
|
2013-01-21 13:00:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
data->group.attrs[data->num_attributes++] = attr;
|
|
|
|
data->group.attrs[data->num_attributes] = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static void pmbus_dev_attr_init(struct device_attribute *dev_attr,
|
|
|
|
const char *name,
|
|
|
|
umode_t mode,
|
|
|
|
ssize_t (*show)(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf),
|
|
|
|
ssize_t (*store)(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count))
|
|
|
|
{
|
|
|
|
sysfs_attr_init(&dev_attr->attr);
|
|
|
|
dev_attr->attr.name = name;
|
|
|
|
dev_attr->attr.mode = mode;
|
|
|
|
dev_attr->show = show;
|
|
|
|
dev_attr->store = store;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2013-01-20 12:59:04 +08:00
|
|
|
static void pmbus_attr_init(struct sensor_device_attribute *a,
|
|
|
|
const char *name,
|
|
|
|
umode_t mode,
|
|
|
|
ssize_t (*show)(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf),
|
|
|
|
ssize_t (*store)(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count),
|
|
|
|
int idx)
|
|
|
|
{
|
2013-01-21 00:13:21 +08:00
|
|
|
pmbus_dev_attr_init(&a->dev_attr, name, mode, show, store);
|
2013-01-20 12:59:04 +08:00
|
|
|
a->index = idx;
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_boolean(struct pmbus_data *data,
|
|
|
|
const char *name, const char *type, int seq,
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_sensor *s1,
|
|
|
|
struct pmbus_sensor *s2,
|
2017-08-11 05:57:47 +08:00
|
|
|
u16 reg, u16 mask)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_boolean *boolean;
|
2013-01-20 12:59:04 +08:00
|
|
|
struct sensor_device_attribute *a;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL);
|
|
|
|
if (!boolean)
|
|
|
|
return -ENOMEM;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-20 12:59:04 +08:00
|
|
|
a = &boolean->attribute;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s",
|
|
|
|
name, seq, type);
|
2013-01-21 02:05:55 +08:00
|
|
|
boolean->s1 = s1;
|
|
|
|
boolean->s2 = s2;
|
2013-01-20 12:59:04 +08:00
|
|
|
pmbus_attr_init(a, boolean->name, S_IRUGO, pmbus_show_boolean, NULL,
|
2017-08-11 05:57:47 +08:00
|
|
|
(reg << 16) | mask);
|
2013-01-21 00:13:21 +08:00
|
|
|
|
2013-01-21 13:00:01 +08:00
|
|
|
return pmbus_add_attribute(data, &a->dev_attr.attr);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2013-01-21 02:05:55 +08:00
|
|
|
static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
|
|
|
const char *name, const char *type,
|
|
|
|
int seq, int page, int reg,
|
|
|
|
enum pmbus_sensor_classes class,
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
bool update, bool readonly,
|
|
|
|
bool convert)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_sensor *sensor;
|
2013-01-21 04:01:41 +08:00
|
|
|
struct device_attribute *a;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 04:01:41 +08:00
|
|
|
sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL);
|
|
|
|
if (!sensor)
|
|
|
|
return NULL;
|
2013-01-20 12:59:04 +08:00
|
|
|
a = &sensor->attribute;
|
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
if (type)
|
|
|
|
snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
|
|
|
|
name, seq, type);
|
|
|
|
else
|
|
|
|
snprintf(sensor->name, sizeof(sensor->name), "%s%d",
|
|
|
|
name, seq);
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
sensor->page = page;
|
|
|
|
sensor->reg = reg;
|
|
|
|
sensor->class = class;
|
|
|
|
sensor->update = update;
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
sensor->convert = convert;
|
2013-01-21 04:01:41 +08:00
|
|
|
pmbus_dev_attr_init(a, sensor->name,
|
|
|
|
readonly ? S_IRUGO : S_IRUGO | S_IWUSR,
|
|
|
|
pmbus_show_sensor, pmbus_set_sensor);
|
2013-01-20 12:59:04 +08:00
|
|
|
|
2013-01-21 13:00:01 +08:00
|
|
|
if (pmbus_add_attribute(data, &a->attr))
|
|
|
|
return NULL;
|
|
|
|
|
2013-01-21 04:01:41 +08:00
|
|
|
sensor->next = data->sensors;
|
|
|
|
data->sensors = sensor;
|
2013-01-21 02:05:55 +08:00
|
|
|
|
|
|
|
return sensor;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_label(struct pmbus_data *data,
|
|
|
|
const char *name, int seq,
|
|
|
|
const char *lstring, int index)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_label *label;
|
2013-01-21 00:13:21 +08:00
|
|
|
struct device_attribute *a;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL);
|
|
|
|
if (!label)
|
|
|
|
return -ENOMEM;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-20 12:59:04 +08:00
|
|
|
a = &label->attribute;
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq);
|
|
|
|
if (!index)
|
|
|
|
strncpy(label->label, lstring, sizeof(label->label) - 1);
|
|
|
|
else
|
|
|
|
snprintf(label->label, sizeof(label->label), "%s%d", lstring,
|
|
|
|
index);
|
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
pmbus_dev_attr_init(a, label->name, S_IRUGO, pmbus_show_label, NULL);
|
2013-01-21 13:00:01 +08:00
|
|
|
return pmbus_add_attribute(data, &a->attr);
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Search for attributes. Allocate sensors, booleans, and labels as needed.
|
|
|
|
*/
|
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
/*
|
|
|
|
* The pmbus_limit_attr structure describes a single limit attribute
|
|
|
|
* and its associated alarm attribute.
|
|
|
|
*/
|
|
|
|
struct pmbus_limit_attr {
|
2011-07-09 23:30:26 +08:00
|
|
|
u16 reg; /* Limit register */
|
2013-01-23 08:26:46 +08:00
|
|
|
u16 sbit; /* Alarm attribute status bit */
|
2011-07-09 23:30:26 +08:00
|
|
|
bool update; /* True if register needs updates */
|
2011-09-10 21:02:12 +08:00
|
|
|
bool low; /* True if low limit; for limits with compare
|
|
|
|
functions only */
|
2011-03-12 10:09:33 +08:00
|
|
|
const char *attr; /* Attribute name */
|
|
|
|
const char *alarm; /* Alarm attribute name */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The pmbus_sensor_attr structure describes one sensor attribute. This
|
|
|
|
* description includes a reference to the associated limit attributes.
|
|
|
|
*/
|
|
|
|
struct pmbus_sensor_attr {
|
2013-01-17 02:31:32 +08:00
|
|
|
u16 reg; /* sensor register */
|
2017-08-11 05:57:47 +08:00
|
|
|
u16 gbit; /* generic status bit */
|
2013-01-23 08:26:46 +08:00
|
|
|
u8 nlimit; /* # of limit registers */
|
2011-03-12 10:09:33 +08:00
|
|
|
enum pmbus_sensor_classes class;/* sensor class */
|
|
|
|
const char *label; /* sensor label */
|
|
|
|
bool paged; /* true if paged sensor */
|
|
|
|
bool update; /* true if update needed */
|
|
|
|
bool compare; /* true if compare function needed */
|
|
|
|
u32 func; /* sensor mask */
|
|
|
|
u32 sfunc; /* sensor status mask */
|
|
|
|
int sbase; /* status base register */
|
|
|
|
const struct pmbus_limit_attr *limit;/* limit registers */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a set of limit attributes and, if supported, the associated
|
|
|
|
* alarm attributes.
|
2013-01-21 00:13:21 +08:00
|
|
|
* returns 0 if no alarm register found, 1 if an alarm register was found,
|
|
|
|
* < 0 on errors.
|
2011-03-12 10:09:33 +08:00
|
|
|
*/
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_limit_attrs(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data,
|
|
|
|
const struct pmbus_driver_info *info,
|
|
|
|
const char *name, int index, int page,
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_sensor *base,
|
2013-01-21 00:13:21 +08:00
|
|
|
const struct pmbus_sensor_attr *attr)
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
|
|
|
const struct pmbus_limit_attr *l = attr->limit;
|
|
|
|
int nlimit = attr->nlimit;
|
2013-01-21 00:13:21 +08:00
|
|
|
int have_alarm = 0;
|
2013-01-21 02:05:55 +08:00
|
|
|
int i, ret;
|
|
|
|
struct pmbus_sensor *curr;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
|
|
|
for (i = 0; i < nlimit; i++) {
|
|
|
|
if (pmbus_check_word_register(client, page, l->reg)) {
|
2013-01-21 02:05:55 +08:00
|
|
|
curr = pmbus_add_sensor(data, name, l->attr, index,
|
|
|
|
page, l->reg, attr->class,
|
|
|
|
attr->update || l->update,
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
false, true);
|
2013-01-21 04:01:41 +08:00
|
|
|
if (!curr)
|
|
|
|
return -ENOMEM;
|
2011-07-09 23:30:26 +08:00
|
|
|
if (l->sbit && (info->func[page] & attr->sfunc)) {
|
2013-01-21 02:05:55 +08:00
|
|
|
ret = pmbus_add_boolean(data, name,
|
|
|
|
l->alarm, index,
|
|
|
|
attr->compare ? l->low ? curr : base
|
|
|
|
: NULL,
|
|
|
|
attr->compare ? l->low ? base : curr
|
|
|
|
: NULL,
|
|
|
|
attr->sbase + page, l->sbit);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2013-01-21 00:13:21 +08:00
|
|
|
have_alarm = 1;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
l++;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
return have_alarm;
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data,
|
|
|
|
const struct pmbus_driver_info *info,
|
|
|
|
const char *name,
|
|
|
|
int index, int page,
|
|
|
|
const struct pmbus_sensor_attr *attr)
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
2013-01-21 02:05:55 +08:00
|
|
|
struct pmbus_sensor *base;
|
2017-08-11 05:57:49 +08:00
|
|
|
bool upper = !!(attr->gbit & 0xff00); /* need to check STATUS_WORD */
|
2013-01-21 00:13:21 +08:00
|
|
|
int ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
if (attr->label) {
|
|
|
|
ret = pmbus_add_label(data, name, index, attr->label,
|
|
|
|
attr->paged ? page + 1 : 0);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2013-01-21 02:05:55 +08:00
|
|
|
base = pmbus_add_sensor(data, name, "input", index, page, attr->reg,
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
attr->class, true, true, true);
|
2013-01-21 04:01:41 +08:00
|
|
|
if (!base)
|
|
|
|
return -ENOMEM;
|
2011-03-12 10:09:33 +08:00
|
|
|
if (attr->sfunc) {
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_limit_attrs(client, data, info, name,
|
2013-01-21 02:05:55 +08:00
|
|
|
index, page, base, attr);
|
2013-01-21 00:13:21 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* Add generic alarm attribute only if there are no individual
|
2011-07-10 03:06:45 +08:00
|
|
|
* alarm attributes, if there is a global alarm bit, and if
|
2017-08-11 05:57:49 +08:00
|
|
|
* the generic status register (word or byte, depending on
|
|
|
|
* which global bit is set) for this page is accessible.
|
2011-01-27 12:09:02 +08:00
|
|
|
*/
|
2013-01-21 00:13:21 +08:00
|
|
|
if (!ret && attr->gbit &&
|
2017-08-11 05:57:49 +08:00
|
|
|
(!upper || (upper && data->has_status_word)) &&
|
2017-08-11 05:57:48 +08:00
|
|
|
pmbus_check_status_register(client, page)) {
|
2013-01-21 02:05:55 +08:00
|
|
|
ret = pmbus_add_boolean(data, name, "alarm", index,
|
|
|
|
NULL, NULL,
|
|
|
|
PB_STATUS_BASE + page,
|
|
|
|
attr->gbit);
|
2013-01-21 00:13:21 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2013-01-21 00:13:21 +08:00
|
|
|
return 0;
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_sensor_attrs(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data,
|
|
|
|
const char *name,
|
|
|
|
const struct pmbus_sensor_attr *attrs,
|
|
|
|
int nattrs)
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int index, i;
|
2013-01-21 00:13:21 +08:00
|
|
|
int ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
index = 1;
|
|
|
|
for (i = 0; i < nattrs; i++) {
|
|
|
|
int page, pages;
|
|
|
|
|
|
|
|
pages = attrs->paged ? info->pages : 1;
|
|
|
|
for (page = 0; page < pages; page++) {
|
|
|
|
if (!(info->func[page] & attrs->func))
|
|
|
|
continue;
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_sensor_attrs_one(client, data, info,
|
|
|
|
name, index, page,
|
|
|
|
attrs);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
index++;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
attrs++;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2013-01-21 00:13:21 +08:00
|
|
|
return 0;
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_limit_attr vin_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_VIN_UV_WARN_LIMIT,
|
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIN_UV_FAULT_LIMIT,
|
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIN_OV_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIN_OV_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_FAULT,
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VIN_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VIN_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "lowest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VIN_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_VIN_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-03-12 10:09:33 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2013-01-17 02:31:32 +08:00
|
|
|
static const struct pmbus_limit_attr vmon_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_VIRT_VMON_UV_WARN_LIMIT,
|
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_VMON_UV_FAULT_LIMIT,
|
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_VMON_OV_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_VMON_OV_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_FAULT,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_limit_attr vout_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_VOUT_UV_WARN_LIMIT,
|
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VOUT_UV_FAULT_LIMIT,
|
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_UV_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VOUT_OV_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VOUT_OV_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_VOLTAGE_OV_FAULT,
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VOUT_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VOUT_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "lowest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VOUT_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_VOUT_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_sensor_attr voltage_attributes[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_READ_VIN,
|
|
|
|
.class = PSC_VOLTAGE_IN,
|
|
|
|
.label = "vin",
|
|
|
|
.func = PMBUS_HAVE_VIN,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_INPUT,
|
|
|
|
.sbase = PB_STATUS_INPUT_BASE,
|
|
|
|
.gbit = PB_STATUS_VIN_UV,
|
|
|
|
.limit = vin_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(vin_limit_attrs),
|
2013-01-17 02:31:32 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_VMON,
|
|
|
|
.class = PSC_VOLTAGE_IN,
|
|
|
|
.label = "vmon",
|
|
|
|
.func = PMBUS_HAVE_VMON,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_VMON,
|
|
|
|
.sbase = PB_STATUS_VMON_BASE,
|
|
|
|
.limit = vmon_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(vmon_limit_attrs),
|
2011-03-12 10:09:33 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_VCAP,
|
|
|
|
.class = PSC_VOLTAGE_IN,
|
|
|
|
.label = "vcap",
|
|
|
|
.func = PMBUS_HAVE_VCAP,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_VOUT,
|
|
|
|
.class = PSC_VOLTAGE_OUT,
|
|
|
|
.label = "vout",
|
|
|
|
.paged = true,
|
|
|
|
.func = PMBUS_HAVE_VOUT,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_VOUT,
|
|
|
|
.sbase = PB_STATUS_VOUT_BASE,
|
|
|
|
.gbit = PB_STATUS_VOUT_OV,
|
|
|
|
.limit = vout_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(vout_limit_attrs),
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
/* Current attributes */
|
|
|
|
|
|
|
|
static const struct pmbus_limit_attr iin_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_IIN_OC_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_IIN_OC_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_IIN_OC_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_IIN_OC_FAULT,
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IIN_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IIN_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "lowest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IIN_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_IIN_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_limit_attr iout_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_IOUT_OC_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_IOUT_OC_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_IOUT_UC_FAULT_LIMIT,
|
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_IOUT_UC_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_IOUT_OC_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_IOUT_OC_FAULT,
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IOUT_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IOUT_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "lowest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_IOUT_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_IOUT_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_sensor_attr current_attributes[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_READ_IIN,
|
|
|
|
.class = PSC_CURRENT_IN,
|
|
|
|
.label = "iin",
|
|
|
|
.func = PMBUS_HAVE_IIN,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_INPUT,
|
|
|
|
.sbase = PB_STATUS_INPUT_BASE,
|
2017-08-11 05:57:49 +08:00
|
|
|
.gbit = PB_STATUS_INPUT,
|
2011-03-12 10:09:33 +08:00
|
|
|
.limit = iin_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(iin_limit_attrs),
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_IOUT,
|
|
|
|
.class = PSC_CURRENT_OUT,
|
|
|
|
.label = "iout",
|
|
|
|
.paged = true,
|
|
|
|
.func = PMBUS_HAVE_IOUT,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_IOUT,
|
|
|
|
.sbase = PB_STATUS_IOUT_BASE,
|
|
|
|
.gbit = PB_STATUS_IOUT_OC,
|
|
|
|
.limit = iout_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(iout_limit_attrs),
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
/* Power attributes */
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_limit_attr pin_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_PIN_OP_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "alarm",
|
|
|
|
.sbit = PB_PIN_OP_WARNING,
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_PIN_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
2015-07-06 04:45:43 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_PIN_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "input_lowest",
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_PIN_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "input_highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_PIN_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_limit_attr pout_limit_attrs[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_POUT_MAX,
|
|
|
|
.attr = "cap",
|
|
|
|
.alarm = "cap_alarm",
|
|
|
|
.sbit = PB_POWER_LIMITING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_POUT_OP_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_POUT_OP_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_POUT_OP_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_POUT_OP_FAULT,
|
2012-02-24 11:33:55 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_POUT_AVG,
|
|
|
|
.update = true,
|
|
|
|
.attr = "average",
|
2015-07-06 04:45:43 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_POUT_MIN,
|
|
|
|
.update = true,
|
|
|
|
.attr = "input_lowest",
|
2012-02-24 11:33:55 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_POUT_MAX,
|
|
|
|
.update = true,
|
|
|
|
.attr = "input_highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_POUT_HISTORY,
|
|
|
|
.attr = "reset_history",
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
|
|
|
};
|
2011-03-07 02:56:52 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_sensor_attr power_attributes[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_READ_PIN,
|
|
|
|
.class = PSC_POWER,
|
|
|
|
.label = "pin",
|
|
|
|
.func = PMBUS_HAVE_PIN,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_INPUT,
|
|
|
|
.sbase = PB_STATUS_INPUT_BASE,
|
2017-08-11 05:57:49 +08:00
|
|
|
.gbit = PB_STATUS_INPUT,
|
2011-03-12 10:09:33 +08:00
|
|
|
.limit = pin_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(pin_limit_attrs),
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_POUT,
|
|
|
|
.class = PSC_POWER,
|
|
|
|
.label = "pout",
|
|
|
|
.paged = true,
|
|
|
|
.func = PMBUS_HAVE_POUT,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_IOUT,
|
|
|
|
.sbase = PB_STATUS_IOUT_BASE,
|
|
|
|
.limit = pout_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(pout_limit_attrs),
|
|
|
|
}
|
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
/* Temperature atributes */
|
|
|
|
|
|
|
|
static const struct pmbus_limit_attr temp_limit_attrs[] = {
|
2011-07-09 23:30:26 +08:00
|
|
|
{
|
|
|
|
.reg = PMBUS_UT_WARN_LIMIT,
|
2011-09-10 21:02:12 +08:00
|
|
|
.low = true,
|
2011-07-09 23:30:26 +08:00
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_UT_FAULT_LIMIT,
|
2011-09-10 21:02:12 +08:00
|
|
|
.low = true,
|
2011-07-09 23:30:26 +08:00
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP_MIN,
|
|
|
|
.attr = "lowest",
|
2012-02-24 11:33:55 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP_AVG,
|
|
|
|
.attr = "average",
|
2011-07-09 23:30:26 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP_MAX,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_TEMP_HISTORY,
|
|
|
|
.attr = "reset_history",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-09-11 03:59:15 +08:00
|
|
|
static const struct pmbus_limit_attr temp_limit_attrs2[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_UT_WARN_LIMIT,
|
|
|
|
.low = true,
|
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_UT_FAULT_LIMIT,
|
|
|
|
.low = true,
|
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP2_MIN,
|
|
|
|
.attr = "lowest",
|
2012-02-24 11:33:55 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP2_AVG,
|
|
|
|
.attr = "average",
|
2011-09-11 03:59:15 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_READ_TEMP2_MAX,
|
|
|
|
.attr = "highest",
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY,
|
|
|
|
.attr = "reset_history",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pmbus_limit_attr temp_limit_attrs3[] = {
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
|
|
|
.reg = PMBUS_UT_WARN_LIMIT,
|
2011-09-10 21:02:12 +08:00
|
|
|
.low = true,
|
2011-03-12 10:09:33 +08:00
|
|
|
.attr = "min",
|
|
|
|
.alarm = "min_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_UT_FAULT_LIMIT,
|
2011-09-10 21:02:12 +08:00
|
|
|
.low = true,
|
2011-03-12 10:09:33 +08:00
|
|
|
.attr = "lcrit",
|
|
|
|
.alarm = "lcrit_alarm",
|
|
|
|
.sbit = PB_TEMP_UT_FAULT,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_WARN_LIMIT,
|
|
|
|
.attr = "max",
|
|
|
|
.alarm = "max_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_WARNING,
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_OT_FAULT_LIMIT,
|
|
|
|
.attr = "crit",
|
|
|
|
.alarm = "crit_alarm",
|
|
|
|
.sbit = PB_TEMP_OT_FAULT,
|
|
|
|
}
|
|
|
|
};
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
static const struct pmbus_sensor_attr temp_attributes[] = {
|
|
|
|
{
|
|
|
|
.reg = PMBUS_READ_TEMPERATURE_1,
|
|
|
|
.class = PSC_TEMPERATURE,
|
|
|
|
.paged = true,
|
|
|
|
.update = true,
|
|
|
|
.compare = true,
|
|
|
|
.func = PMBUS_HAVE_TEMP,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_TEMP,
|
|
|
|
.sbase = PB_STATUS_TEMP_BASE,
|
|
|
|
.gbit = PB_STATUS_TEMPERATURE,
|
|
|
|
.limit = temp_limit_attrs,
|
|
|
|
.nlimit = ARRAY_SIZE(temp_limit_attrs),
|
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_TEMPERATURE_2,
|
|
|
|
.class = PSC_TEMPERATURE,
|
|
|
|
.paged = true,
|
|
|
|
.update = true,
|
|
|
|
.compare = true,
|
|
|
|
.func = PMBUS_HAVE_TEMP2,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_TEMP,
|
|
|
|
.sbase = PB_STATUS_TEMP_BASE,
|
|
|
|
.gbit = PB_STATUS_TEMPERATURE,
|
2011-09-11 03:59:15 +08:00
|
|
|
.limit = temp_limit_attrs2,
|
|
|
|
.nlimit = ARRAY_SIZE(temp_limit_attrs2),
|
2011-03-12 10:09:33 +08:00
|
|
|
}, {
|
|
|
|
.reg = PMBUS_READ_TEMPERATURE_3,
|
|
|
|
.class = PSC_TEMPERATURE,
|
|
|
|
.paged = true,
|
|
|
|
.update = true,
|
|
|
|
.compare = true,
|
|
|
|
.func = PMBUS_HAVE_TEMP3,
|
|
|
|
.sfunc = PMBUS_HAVE_STATUS_TEMP,
|
|
|
|
.sbase = PB_STATUS_TEMP_BASE,
|
|
|
|
.gbit = PB_STATUS_TEMPERATURE,
|
2011-09-11 03:59:15 +08:00
|
|
|
.limit = temp_limit_attrs3,
|
|
|
|
.nlimit = ARRAY_SIZE(temp_limit_attrs3),
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const int pmbus_fan_registers[] = {
|
|
|
|
PMBUS_READ_FAN_SPEED_1,
|
|
|
|
PMBUS_READ_FAN_SPEED_2,
|
|
|
|
PMBUS_READ_FAN_SPEED_3,
|
|
|
|
PMBUS_READ_FAN_SPEED_4
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int pmbus_fan_status_registers[] = {
|
|
|
|
PMBUS_STATUS_FAN_12,
|
|
|
|
PMBUS_STATUS_FAN_12,
|
|
|
|
PMBUS_STATUS_FAN_34,
|
|
|
|
PMBUS_STATUS_FAN_34
|
|
|
|
};
|
|
|
|
|
|
|
|
static const u32 pmbus_fan_flags[] = {
|
|
|
|
PMBUS_HAVE_FAN12,
|
|
|
|
PMBUS_HAVE_FAN12,
|
|
|
|
PMBUS_HAVE_FAN34,
|
|
|
|
PMBUS_HAVE_FAN34
|
|
|
|
};
|
|
|
|
|
|
|
|
static const u32 pmbus_fan_status_flags[] = {
|
|
|
|
PMBUS_HAVE_STATUS_FAN12,
|
|
|
|
PMBUS_HAVE_STATUS_FAN12,
|
|
|
|
PMBUS_HAVE_STATUS_FAN34,
|
|
|
|
PMBUS_HAVE_STATUS_FAN34
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Fans */
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
|
|
|
|
/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */
|
|
|
|
static int pmbus_add_fan_ctrl(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data, int index, int page, int id,
|
|
|
|
u8 config)
|
|
|
|
{
|
|
|
|
struct pmbus_sensor *sensor;
|
|
|
|
|
|
|
|
sensor = pmbus_add_sensor(data, "fan", "target", index, page,
|
|
|
|
PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
|
|
|
|
false, false, true);
|
|
|
|
|
|
|
|
if (!sensor)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (!((data->info->func[page] & PMBUS_HAVE_PWM12) ||
|
|
|
|
(data->info->func[page] & PMBUS_HAVE_PWM34)))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
|
|
|
|
PMBUS_VIRT_PWM_1 + id, PSC_PWM,
|
|
|
|
false, false, true);
|
|
|
|
|
|
|
|
if (!sensor)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
sensor = pmbus_add_sensor(data, "pwm", "enable", index, page,
|
|
|
|
PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
|
|
|
|
true, false, false);
|
|
|
|
|
|
|
|
if (!sensor)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_add_fan_attributes(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data)
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
int index = 1;
|
|
|
|
int page;
|
2013-01-21 00:13:21 +08:00
|
|
|
int ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
for (page = 0; page < info->pages; page++) {
|
2011-03-05 23:55:10 +08:00
|
|
|
int f;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2011-03-05 23:55:10 +08:00
|
|
|
for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) {
|
2011-01-27 12:09:02 +08:00
|
|
|
int regval;
|
|
|
|
|
2011-03-05 23:55:10 +08:00
|
|
|
if (!(info->func[page] & pmbus_fan_flags[f]))
|
|
|
|
break;
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
if (!pmbus_check_word_register(client, page,
|
2011-03-09 23:23:54 +08:00
|
|
|
pmbus_fan_registers[f]))
|
2011-01-27 12:09:02 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip fan if not installed.
|
|
|
|
* Each fan configuration register covers multiple fans,
|
|
|
|
* so we have to do some magic.
|
|
|
|
*/
|
2011-03-09 23:23:54 +08:00
|
|
|
regval = _pmbus_read_byte_data(client, page,
|
2011-01-27 12:09:02 +08:00
|
|
|
pmbus_fan_config_registers[f]);
|
|
|
|
if (regval < 0 ||
|
|
|
|
(!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4)))))
|
|
|
|
continue;
|
|
|
|
|
2013-01-21 04:01:41 +08:00
|
|
|
if (pmbus_add_sensor(data, "fan", "input", index,
|
|
|
|
page, pmbus_fan_registers[f],
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
PSC_FAN, true, true, true) == NULL)
|
2013-01-21 04:01:41 +08:00
|
|
|
return -ENOMEM;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
hwmon: (pmbus) Add fan control support
Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.
Fans in a PMBus device are driven by the configuration of two registers,
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding configuration in FAN_CONFIG_x_y.
The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers to
facilitate the necessary side-effects of each access:
1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x
Some complexity arises with the fanX_target and pwmX attributes both mapping
onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and
RPM, so we can't display values in either attribute in terms of the other
(which in my mind is the intuitive, if impossible, behaviour). This problem
also affects the pwmX_enable attribute which allows userspace to switch between
full speed, manual PWM and a number of automatic control modes, possibly
including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to
reach a RPM target, the behaviour of fanX_target).
The next most intuitive behaviour is for fanX_target and pwmX to simply be
independent, to retain their most recently set value even if that value is not
active on the hardware (due to switching to the alternative control mode). This
property of retaining the value independent of the hardware state has useful
results for both userspace and the kernel: Userspace always sees a sensible
value in the attribute (the last thing it was set to, as opposed to 0 or
receiving an error on read), and the kernel can use the attributes as a value
cache. This latter point eases the implementation of pwmX_enable, which can
look up the associated pmbus_sensor object, take its cached value and apply it
to hardware on changing control mode. This ensures we will not arbitrarily set
a PWM value as an RPM value or vice versa, and we can assume that the RPM or
PWM value set was sensible at least at some point in the past.
Finally, the DIRECT mode coefficients of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the
necessary coefficients. As pmbus core had no PWM support previously PSC_FAN
continues to be used to capture the RPM DIRECT coefficients, but in order to
avoid falsely applying RPM scaling to PWM values I have introduced the
PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow
drivers to explicitly declare PWM support in order to have the attributes
exposed.
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2017-11-20 12:42:03 +08:00
|
|
|
/* Fan control */
|
|
|
|
if (pmbus_check_word_register(client, page,
|
|
|
|
pmbus_fan_command_registers[f])) {
|
|
|
|
ret = pmbus_add_fan_ctrl(client, data, index,
|
|
|
|
page, f, regval);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* Each fan status register covers multiple fans,
|
|
|
|
* so we have to do some magic.
|
|
|
|
*/
|
2011-03-05 23:55:10 +08:00
|
|
|
if ((info->func[page] & pmbus_fan_status_flags[f]) &&
|
|
|
|
pmbus_check_byte_register(client,
|
|
|
|
page, pmbus_fan_status_registers[f])) {
|
2011-01-27 12:09:02 +08:00
|
|
|
int base;
|
|
|
|
|
|
|
|
if (f > 1) /* fan 3, 4 */
|
2011-03-05 23:55:10 +08:00
|
|
|
base = PB_STATUS_FAN34_BASE + page;
|
2011-01-27 12:09:02 +08:00
|
|
|
else
|
|
|
|
base = PB_STATUS_FAN_BASE + page;
|
2013-01-21 02:05:55 +08:00
|
|
|
ret = pmbus_add_boolean(data, "fan",
|
|
|
|
"alarm", index, NULL, NULL, base,
|
2011-01-27 12:09:02 +08:00
|
|
|
PB_FAN_FAN1_WARNING >> (f & 1));
|
2013-01-21 00:13:21 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2013-01-21 02:05:55 +08:00
|
|
|
ret = pmbus_add_boolean(data, "fan",
|
|
|
|
"fault", index, NULL, NULL, base,
|
2011-01-27 12:09:02 +08:00
|
|
|
PB_FAN_FAN1_FAULT >> (f & 1));
|
2013-01-21 00:13:21 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2011-03-12 10:09:33 +08:00
|
|
|
index++;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
}
|
2013-01-21 00:13:21 +08:00
|
|
|
return 0;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
static int pmbus_find_attributes(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data)
|
2011-03-12 10:09:33 +08:00
|
|
|
{
|
2013-01-21 00:13:21 +08:00
|
|
|
int ret;
|
|
|
|
|
2011-03-12 10:09:33 +08:00
|
|
|
/* Voltage sensors */
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_sensor_attrs(client, data, "in", voltage_attributes,
|
|
|
|
ARRAY_SIZE(voltage_attributes));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
|
|
|
/* Current sensors */
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes,
|
|
|
|
ARRAY_SIZE(current_attributes));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
|
|
|
/* Power sensors */
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_sensor_attrs(client, data, "power", power_attributes,
|
|
|
|
ARRAY_SIZE(power_attributes));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
|
|
|
/* Temperature sensors */
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_sensor_attrs(client, data, "temp", temp_attributes,
|
|
|
|
ARRAY_SIZE(temp_attributes));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
|
|
|
|
/* Fans */
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_add_fan_attributes(client, data);
|
|
|
|
return ret;
|
2011-03-12 10:09:33 +08:00
|
|
|
}
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* Identify chip parameters.
|
|
|
|
* This function is called for all chips.
|
|
|
|
*/
|
|
|
|
static int pmbus_identify_common(struct i2c_client *client,
|
2014-01-31 11:51:14 +08:00
|
|
|
struct pmbus_data *data, int page)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
2011-09-29 02:36:20 +08:00
|
|
|
int vout_mode = -1;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
if (pmbus_check_byte_register(client, page, PMBUS_VOUT_MODE))
|
|
|
|
vout_mode = _pmbus_read_byte_data(client, page,
|
|
|
|
PMBUS_VOUT_MODE);
|
2011-03-02 05:49:18 +08:00
|
|
|
if (vout_mode >= 0 && vout_mode != 0xff) {
|
2011-01-27 12:09:02 +08:00
|
|
|
/*
|
|
|
|
* Not all chips support the VOUT_MODE command,
|
|
|
|
* so a failure to read it is not an error.
|
|
|
|
*/
|
|
|
|
switch (vout_mode >> 5) {
|
|
|
|
case 0: /* linear mode */
|
2011-06-26 02:21:49 +08:00
|
|
|
if (data->info->format[PSC_VOLTAGE_OUT] != linear)
|
2011-01-27 12:09:02 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
data->exponent[page] = ((s8)(vout_mode << 3)) >> 3;
|
2011-01-27 12:09:02 +08:00
|
|
|
break;
|
2011-06-26 02:21:49 +08:00
|
|
|
case 1: /* VID mode */
|
|
|
|
if (data->info->format[PSC_VOLTAGE_OUT] != vid)
|
|
|
|
return -ENODEV;
|
|
|
|
break;
|
2011-01-27 12:09:02 +08:00
|
|
|
case 2: /* direct mode */
|
2011-06-26 02:21:49 +08:00
|
|
|
if (data->info->format[PSC_VOLTAGE_OUT] != direct)
|
2011-01-27 12:09:02 +08:00
|
|
|
return -ENODEV;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
pmbus_clear_fault_page(client, page);
|
2011-01-27 12:09:02 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-11 05:57:48 +08:00
|
|
|
static int pmbus_read_status_byte(struct i2c_client *client, int page)
|
|
|
|
{
|
|
|
|
return _pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pmbus_read_status_word(struct i2c_client *client, int page)
|
|
|
|
{
|
|
|
|
return _pmbus_read_word_data(client, page, PMBUS_STATUS_WORD);
|
|
|
|
}
|
|
|
|
|
2012-05-20 02:35:25 +08:00
|
|
|
static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
|
|
|
struct pmbus_driver_info *info)
|
|
|
|
{
|
|
|
|
struct device *dev = &client->dev;
|
2014-01-31 11:51:14 +08:00
|
|
|
int page, ret;
|
2012-05-20 02:35:25 +08:00
|
|
|
|
|
|
|
/*
|
2017-08-11 05:57:48 +08:00
|
|
|
* Some PMBus chips don't support PMBUS_STATUS_WORD, so try
|
|
|
|
* to use PMBUS_STATUS_BYTE instead if that is the case.
|
2012-05-20 02:35:25 +08:00
|
|
|
* Bail out if both registers are not supported.
|
|
|
|
*/
|
2017-08-11 05:57:48 +08:00
|
|
|
data->read_status = pmbus_read_status_word;
|
|
|
|
ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD);
|
|
|
|
if (ret < 0 || ret == 0xffff) {
|
|
|
|
data->read_status = pmbus_read_status_byte;
|
|
|
|
ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE);
|
|
|
|
if (ret < 0 || ret == 0xff) {
|
2012-05-20 02:35:25 +08:00
|
|
|
dev_err(dev, "PMBus status register not found\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2017-08-11 05:57:49 +08:00
|
|
|
} else {
|
|
|
|
data->has_status_word = true;
|
2012-05-20 02:35:25 +08:00
|
|
|
}
|
|
|
|
|
2015-08-18 07:26:21 +08:00
|
|
|
/* Enable PEC if the controller supports it */
|
|
|
|
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
|
|
|
|
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK))
|
|
|
|
client->flags |= I2C_CLIENT_PEC;
|
|
|
|
|
2012-05-20 02:35:25 +08:00
|
|
|
pmbus_clear_faults(client);
|
|
|
|
|
|
|
|
if (info->identify) {
|
|
|
|
ret = (*info->identify)(client, info);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Chip identification failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->pages <= 0 || info->pages > PMBUS_PAGES) {
|
|
|
|
dev_err(dev, "Bad number of PMBus pages: %d\n", info->pages);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2014-01-31 11:51:14 +08:00
|
|
|
for (page = 0; page < info->pages; page++) {
|
|
|
|
ret = pmbus_identify_common(client, data, page);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Failed to identify chip capabilities\n");
|
|
|
|
return ret;
|
|
|
|
}
|
2012-05-20 02:35:25 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-16 02:55:09 +08:00
|
|
|
#if IS_ENABLED(CONFIG_REGULATOR)
|
|
|
|
static int pmbus_regulator_is_enabled(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
struct device *dev = rdev_get_dev(rdev);
|
|
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
|
|
|
u8 page = rdev_get_id(rdev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return !!(ret & PB_OPERATION_CONTROL_ON);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable)
|
|
|
|
{
|
|
|
|
struct device *dev = rdev_get_dev(rdev);
|
|
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
|
|
|
u8 page = rdev_get_id(rdev);
|
|
|
|
|
|
|
|
return pmbus_update_byte_data(client, page, PMBUS_OPERATION,
|
|
|
|
PB_OPERATION_CONTROL_ON,
|
|
|
|
enable ? PB_OPERATION_CONTROL_ON : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pmbus_regulator_enable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
return _pmbus_regulator_on_off(rdev, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pmbus_regulator_disable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
return _pmbus_regulator_on_off(rdev, 0);
|
|
|
|
}
|
|
|
|
|
2015-07-10 13:00:08 +08:00
|
|
|
const struct regulator_ops pmbus_regulator_ops = {
|
2014-10-16 02:55:09 +08:00
|
|
|
.enable = pmbus_regulator_enable,
|
|
|
|
.disable = pmbus_regulator_disable,
|
|
|
|
.is_enabled = pmbus_regulator_is_enabled,
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_regulator_ops);
|
|
|
|
|
|
|
|
static int pmbus_regulator_register(struct pmbus_data *data)
|
|
|
|
{
|
|
|
|
struct device *dev = data->dev;
|
|
|
|
const struct pmbus_driver_info *info = data->info;
|
|
|
|
const struct pmbus_platform_data *pdata = dev_get_platdata(dev);
|
|
|
|
struct regulator_dev *rdev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < info->num_regulators; i++) {
|
|
|
|
struct regulator_config config = { };
|
|
|
|
|
|
|
|
config.dev = dev;
|
|
|
|
config.driver_data = data;
|
|
|
|
|
|
|
|
if (pdata && pdata->reg_init_data)
|
|
|
|
config.init_data = &pdata->reg_init_data[i];
|
|
|
|
|
|
|
|
rdev = devm_regulator_register(dev, &info->reg_desc[i],
|
|
|
|
&config);
|
|
|
|
if (IS_ERR(rdev)) {
|
|
|
|
dev_err(dev, "Failed to register %s regulator\n",
|
|
|
|
info->reg_desc[i].name);
|
|
|
|
return PTR_ERR(rdev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static int pmbus_regulator_register(struct pmbus_data *data)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-08-15 02:55:41 +08:00
|
|
|
static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */
|
|
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
|
|
static int pmbus_debugfs_get(void *data, u64 *val)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct pmbus_debugfs_entry *entry = data;
|
|
|
|
|
|
|
|
rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
*val = rc;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL,
|
|
|
|
"0x%02llx\n");
|
|
|
|
|
|
|
|
static int pmbus_debugfs_get_status(void *data, u64 *val)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct pmbus_debugfs_entry *entry = data;
|
|
|
|
struct pmbus_data *pdata = i2c_get_clientdata(entry->client);
|
|
|
|
|
|
|
|
rc = pdata->read_status(entry->client, entry->page);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
*val = rc;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status,
|
|
|
|
NULL, "0x%04llx\n");
|
|
|
|
|
|
|
|
static int pmbus_init_debugfs(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data)
|
|
|
|
{
|
|
|
|
int i, idx = 0;
|
|
|
|
char name[PMBUS_NAME_SIZE];
|
|
|
|
struct pmbus_debugfs_entry *entries;
|
|
|
|
|
|
|
|
if (!pmbus_debugfs_dir)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the debugfs directory for this device. Use the hwmon device
|
|
|
|
* name to avoid conflicts (hwmon numbers are globally unique).
|
|
|
|
*/
|
|
|
|
data->debugfs = debugfs_create_dir(dev_name(data->hwmon_dev),
|
|
|
|
pmbus_debugfs_dir);
|
|
|
|
if (IS_ERR_OR_NULL(data->debugfs)) {
|
|
|
|
data->debugfs = NULL;
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate the max possible entries we need. */
|
|
|
|
entries = devm_kzalloc(data->dev,
|
|
|
|
sizeof(*entries) * (data->info->pages * 10),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!entries)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (i = 0; i < data->info->pages; ++i) {
|
|
|
|
/* Check accessibility of status register if it's not page 0 */
|
|
|
|
if (!i || pmbus_check_status_register(client, i)) {
|
|
|
|
/* No need to set reg as we have special read op. */
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_VOUT) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_VOUT;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_vout", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_IOUT) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_IOUT;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_iout", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_INPUT) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_INPUT;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_input", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_TEMP) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_TEMPERATURE;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_temp", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmbus_check_byte_register(client, i, PMBUS_STATUS_CML)) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_CML;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_cml", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmbus_check_byte_register(client, i, PMBUS_STATUS_OTHER)) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_OTHER;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_other", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pmbus_check_byte_register(client, i,
|
|
|
|
PMBUS_STATUS_MFR_SPECIFIC)) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_MFR_SPECIFIC;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_mfr", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN12) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_FAN_12;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan12", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN34) {
|
|
|
|
entries[idx].client = client;
|
|
|
|
entries[idx].page = i;
|
|
|
|
entries[idx].reg = PMBUS_STATUS_FAN_34;
|
|
|
|
scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan34", i);
|
|
|
|
debugfs_create_file(name, 0444, data->debugfs,
|
|
|
|
&entries[idx++],
|
|
|
|
&pmbus_debugfs_ops);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static int pmbus_init_debugfs(struct i2c_client *client,
|
|
|
|
struct pmbus_data *data)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
|
|
|
struct pmbus_driver_info *info)
|
|
|
|
{
|
2013-01-21 00:23:16 +08:00
|
|
|
struct device *dev = &client->dev;
|
2013-07-30 16:13:06 +08:00
|
|
|
const struct pmbus_platform_data *pdata = dev_get_platdata(dev);
|
2011-01-27 12:09:02 +08:00
|
|
|
struct pmbus_data *data;
|
|
|
|
int ret;
|
|
|
|
|
2013-01-17 02:20:15 +08:00
|
|
|
if (!info)
|
2011-01-27 12:09:02 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE
|
|
|
|
| I2C_FUNC_SMBUS_BYTE_DATA
|
|
|
|
| I2C_FUNC_SMBUS_WORD_DATA))
|
|
|
|
return -ENODEV;
|
|
|
|
|
2013-01-21 00:23:16 +08:00
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
2013-01-17 02:20:15 +08:00
|
|
|
if (!data)
|
2011-01-27 12:09:02 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
i2c_set_clientdata(client, data);
|
|
|
|
mutex_init(&data->update_lock);
|
2013-01-21 00:13:21 +08:00
|
|
|
data->dev = dev;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
if (pdata)
|
|
|
|
data->flags = pdata->flags;
|
|
|
|
data->info = info;
|
|
|
|
|
2012-05-20 02:35:25 +08:00
|
|
|
ret = pmbus_init_common(client, data, info);
|
|
|
|
if (ret < 0)
|
2012-02-23 00:56:43 +08:00
|
|
|
return ret;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
2013-01-21 00:13:21 +08:00
|
|
|
ret = pmbus_find_attributes(client, data);
|
|
|
|
if (ret)
|
2013-01-21 13:00:01 +08:00
|
|
|
goto out_kfree;
|
2011-01-27 12:09:02 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If there are no attributes, something is wrong.
|
|
|
|
* Bail out instead of trying to register nothing.
|
|
|
|
*/
|
|
|
|
if (!data->num_attributes) {
|
2013-01-21 00:23:16 +08:00
|
|
|
dev_err(dev, "No attributes found\n");
|
2013-01-21 13:00:01 +08:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto out_kfree;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
|
2013-07-07 00:47:08 +08:00
|
|
|
data->groups[0] = &data->group;
|
|
|
|
data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
|
|
|
data, data->groups);
|
2011-01-27 12:09:02 +08:00
|
|
|
if (IS_ERR(data->hwmon_dev)) {
|
|
|
|
ret = PTR_ERR(data->hwmon_dev);
|
2013-01-21 00:23:16 +08:00
|
|
|
dev_err(dev, "Failed to register hwmon device\n");
|
2013-07-07 00:47:08 +08:00
|
|
|
goto out_kfree;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
2014-10-16 02:55:09 +08:00
|
|
|
|
|
|
|
ret = pmbus_regulator_register(data);
|
|
|
|
if (ret)
|
|
|
|
goto out_unregister;
|
|
|
|
|
2017-08-15 02:55:41 +08:00
|
|
|
ret = pmbus_init_debugfs(client, data);
|
|
|
|
if (ret)
|
|
|
|
dev_warn(dev, "Failed to register debugfs\n");
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
return 0;
|
|
|
|
|
2014-10-16 02:55:09 +08:00
|
|
|
out_unregister:
|
|
|
|
hwmon_device_unregister(data->hwmon_dev);
|
2013-01-21 13:00:01 +08:00
|
|
|
out_kfree:
|
|
|
|
kfree(data->group.attrs);
|
2011-01-27 12:09:02 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_do_probe);
|
|
|
|
|
2012-02-23 00:56:44 +08:00
|
|
|
int pmbus_do_remove(struct i2c_client *client)
|
2011-01-27 12:09:02 +08:00
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
2017-08-15 02:55:41 +08:00
|
|
|
|
|
|
|
debugfs_remove_recursive(data->debugfs);
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
hwmon_device_unregister(data->hwmon_dev);
|
2013-01-21 13:00:01 +08:00
|
|
|
kfree(data->group.attrs);
|
2012-02-23 00:56:44 +08:00
|
|
|
return 0;
|
2011-01-27 12:09:02 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_do_remove);
|
|
|
|
|
2017-12-12 05:32:49 +08:00
|
|
|
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
return data->debugfs;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir);
|
|
|
|
|
2017-08-15 02:55:41 +08:00
|
|
|
static int __init pmbus_core_init(void)
|
|
|
|
{
|
|
|
|
pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL);
|
|
|
|
if (IS_ERR(pmbus_debugfs_dir))
|
|
|
|
pmbus_debugfs_dir = NULL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit pmbus_core_exit(void)
|
|
|
|
{
|
|
|
|
debugfs_remove_recursive(pmbus_debugfs_dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(pmbus_core_init);
|
|
|
|
module_exit(pmbus_core_exit);
|
|
|
|
|
2011-01-27 12:09:02 +08:00
|
|
|
MODULE_AUTHOR("Guenter Roeck");
|
|
|
|
MODULE_DESCRIPTION("PMBus core driver");
|
|
|
|
MODULE_LICENSE("GPL");
|