|
@@ -65,6 +65,7 @@ struct pmbus_sensor {
|
|
|
u16 reg; /* register */
|
|
|
enum pmbus_sensor_classes class; /* sensor class */
|
|
|
bool update; /* runtime sensor update needed */
|
|
|
+ bool convert; /* Whether or not to apply linear/vid/direct */
|
|
|
int data; /* Sensor data.
|
|
|
Negative if there was a read error */
|
|
|
};
|
|
@@ -129,6 +130,27 @@ struct pmbus_debugfs_entry {
|
|
|
u8 reg;
|
|
|
};
|
|
|
|
|
|
+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,
|
|
|
+};
|
|
|
+
|
|
|
void pmbus_clear_cache(struct i2c_client *client)
|
|
|
{
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
@@ -140,18 +162,27 @@ EXPORT_SYMBOL_GPL(pmbus_clear_cache);
|
|
|
int pmbus_set_page(struct i2c_client *client, int page)
|
|
|
{
|
|
|
struct pmbus_data *data = i2c_get_clientdata(client);
|
|
|
- int rv = 0;
|
|
|
- int newpage;
|
|
|
+ int rv;
|
|
|
|
|
|
- if (page >= 0 && page != data->currpage) {
|
|
|
+ if (page < 0 || page == data->currpage)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) {
|
|
|
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
|
|
- newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
|
|
- if (newpage != page)
|
|
|
- rv = -EIO;
|
|
|
- else
|
|
|
- data->currpage = page;
|
|
|
+ if (rv < 0)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
|
|
+ if (rv < 0)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ if (rv != page)
|
|
|
+ return -EIO;
|
|
|
}
|
|
|
- return rv;
|
|
|
+
|
|
|
+ data->currpage = page;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pmbus_set_page);
|
|
|
|
|
@@ -198,6 +229,28 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pmbus_write_word_data);
|
|
|
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* _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.
|
|
@@ -214,11 +267,38 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
|
|
|
if (status != -ENODATA)
|
|
|
return status;
|
|
|
}
|
|
|
+
|
|
|
if (reg >= PMBUS_VIRT_BASE)
|
|
|
- return -ENXIO;
|
|
|
+ return pmbus_write_virt_reg(client, page, reg, word);
|
|
|
+
|
|
|
return pmbus_write_word_data(client, page, reg, word);
|
|
|
}
|
|
|
|
|
|
+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);
|
|
|
+
|
|
|
int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
|
|
|
{
|
|
|
int rv;
|
|
@@ -231,6 +311,24 @@ int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pmbus_read_word_data);
|
|
|
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* _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.
|
|
@@ -246,8 +344,10 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
|
|
|
if (status != -ENODATA)
|
|
|
return status;
|
|
|
}
|
|
|
+
|
|
|
if (reg >= PMBUS_VIRT_BASE)
|
|
|
- return -ENXIO;
|
|
|
+ return pmbus_read_virt_reg(client, page, reg);
|
|
|
+
|
|
|
return pmbus_read_word_data(client, page, reg);
|
|
|
}
|
|
|
|
|
@@ -312,6 +412,68 @@ static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
|
|
|
return pmbus_read_byte_data(client, page, reg);
|
|
|
}
|
|
|
|
|
|
+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);
|
|
|
+
|
|
|
static void pmbus_clear_fault_page(struct i2c_client *client, int page)
|
|
|
{
|
|
|
_pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
|
|
@@ -513,7 +675,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data,
|
|
|
/* X = 1/m * (Y * 10^-R - b) */
|
|
|
R = -R;
|
|
|
/* scale result to milli-units for everything but fans */
|
|
|
- if (sensor->class != PSC_FAN) {
|
|
|
+ if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
|
|
R += 3;
|
|
|
b *= 1000;
|
|
|
}
|
|
@@ -568,6 +730,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)
|
|
|
{
|
|
|
long val;
|
|
|
|
|
|
+ if (!sensor->convert)
|
|
|
+ return sensor->data;
|
|
|
+
|
|
|
switch (data->info->format[sensor->class]) {
|
|
|
case direct:
|
|
|
val = pmbus_reg2data_direct(data, sensor);
|
|
@@ -672,7 +837,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data,
|
|
|
}
|
|
|
|
|
|
/* Calculate Y = (m * X + b) * 10^R */
|
|
|
- if (sensor->class != PSC_FAN) {
|
|
|
+ if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
|
|
|
R -= 3; /* Adjust R and b for data in milli-units */
|
|
|
b *= 1000;
|
|
|
}
|
|
@@ -703,6 +868,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
|
|
|
{
|
|
|
u16 regval;
|
|
|
|
|
|
+ if (!sensor->convert)
|
|
|
+ return val;
|
|
|
+
|
|
|
switch (data->info->format[sensor->class]) {
|
|
|
case direct:
|
|
|
regval = pmbus_data2reg_direct(data, sensor, val);
|
|
@@ -915,7 +1083,8 @@ 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,
|
|
|
- bool update, bool readonly)
|
|
|
+ bool update, bool readonly,
|
|
|
+ bool convert)
|
|
|
{
|
|
|
struct pmbus_sensor *sensor;
|
|
|
struct device_attribute *a;
|
|
@@ -925,12 +1094,18 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
|
|
return NULL;
|
|
|
a = &sensor->attribute;
|
|
|
|
|
|
- snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
|
|
|
- name, seq, type);
|
|
|
+ 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);
|
|
|
+
|
|
|
sensor->page = page;
|
|
|
sensor->reg = reg;
|
|
|
sensor->class = class;
|
|
|
sensor->update = update;
|
|
|
+ sensor->convert = convert;
|
|
|
pmbus_dev_attr_init(a, sensor->name,
|
|
|
readonly ? S_IRUGO : S_IRUGO | S_IWUSR,
|
|
|
pmbus_show_sensor, pmbus_set_sensor);
|
|
@@ -1029,7 +1204,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client,
|
|
|
curr = pmbus_add_sensor(data, name, l->attr, index,
|
|
|
page, l->reg, attr->class,
|
|
|
attr->update || l->update,
|
|
|
- false);
|
|
|
+ false, true);
|
|
|
if (!curr)
|
|
|
return -ENOMEM;
|
|
|
if (l->sbit && (info->func[page] & attr->sfunc)) {
|
|
@@ -1068,7 +1243,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
|
|
|
return ret;
|
|
|
}
|
|
|
base = pmbus_add_sensor(data, name, "input", index, page, attr->reg,
|
|
|
- attr->class, true, true);
|
|
|
+ attr->class, true, true, true);
|
|
|
if (!base)
|
|
|
return -ENOMEM;
|
|
|
if (attr->sfunc) {
|
|
@@ -1592,13 +1767,6 @@ static const int pmbus_fan_registers[] = {
|
|
|
PMBUS_READ_FAN_SPEED_4
|
|
|
};
|
|
|
|
|
|
-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_status_registers[] = {
|
|
|
PMBUS_STATUS_FAN_12,
|
|
|
PMBUS_STATUS_FAN_12,
|
|
@@ -1621,6 +1789,42 @@ static const u32 pmbus_fan_status_flags[] = {
|
|
|
};
|
|
|
|
|
|
/* Fans */
|
|
|
+
|
|
|
+/* 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;
|
|
|
+}
|
|
|
+
|
|
|
static int pmbus_add_fan_attributes(struct i2c_client *client,
|
|
|
struct pmbus_data *data)
|
|
|
{
|
|
@@ -1655,9 +1859,18 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,
|
|
|
|
|
|
if (pmbus_add_sensor(data, "fan", "input", index,
|
|
|
page, pmbus_fan_registers[f],
|
|
|
- PSC_FAN, true, true) == NULL)
|
|
|
+ PSC_FAN, true, true, true) == NULL)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
+ /* 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;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Each fan status register covers multiple fans,
|
|
|
* so we have to do some magic.
|
|
@@ -2168,6 +2381,14 @@ int pmbus_do_remove(struct i2c_client *client)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pmbus_do_remove);
|
|
|
|
|
|
+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);
|
|
|
+
|
|
|
static int __init pmbus_core_init(void)
|
|
|
{
|
|
|
pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL);
|