Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 186409
b: refs/heads/master
c: 4f3f51b
h: refs/heads/master
i:
  186407: bad22b4
v: v3
  • Loading branch information
Jean Delvare committed Mar 5, 2010
1 parent bc85a97 commit 19f18c4
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 5 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 404a552d8ae6847ae17f3c749bd5d312da08efe4
refs/heads/master: 4f3f51bc21d434f8d91a79438a1957ec0baa9e30
27 changes: 25 additions & 2 deletions trunk/Documentation/hwmon/it87
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,34 @@ Fan speed control
-----------------

The fan speed control features are limited to manual PWM mode. Automatic
"Smart Guardian" mode control handling is not implemented. However
if you want to go for "manual mode" just write 1 to pwmN_enable.
"Smart Guardian" mode control handling is only implemented for older chips
(see below.) However if you want to go for "manual mode" just write 1 to
pwmN_enable.

If you are only able to control the fan speed with very small PWM values,
try lowering the PWM base frequency (pwm1_freq). Depending on the fan,
it may give you a somewhat greater control range. The same frequency is
used to drive all fan outputs, which is why pwm2_freq and pwm3_freq are
read-only.


Automatic fan speed control (old interface)
-------------------------------------------

The driver supports the old interface to automatic fan speed control
which is implemented by IT8705F chips up to revision F and IT8712F
chips up to revision G.

This interface implements 4 temperature vs. PWM output trip points.
The PWM output of trip point 4 is always the maximum value (fan running
at full speed) while the PWM output of the other 3 trip points can be
freely chosen. The temperature of all 4 trip points can be freely chosen.
Additionally, trip point 1 has an hysteresis temperature attached, to
prevent fast switching between fan on and off.

The chip automatically computes the PWM output value based on the input
temperature, based on this simple rule: if the temperature value is
between trip point N and trip point N+1 then the PWM output value is
the one of trip point N. The automatic control mode is less flexible
than the manual control mode, but it reacts faster, is more robust and
doesn't use CPU cycles.
182 changes: 180 additions & 2 deletions trunk/drivers/hwmon/it87.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 };

#define IT87_REG_CHIPID 0x58

#define IT87_REG_AUTO_TEMP(nr, i) (0x60 + (nr) * 8 + (i))
#define IT87_REG_AUTO_PWM(nr, i) (0x65 + (nr) * 8 + (i))

#define IN_TO_REG(val) (SENSORS_LIMIT((((val) + 8)/16),0,255))
#define IN_FROM_REG(val) ((val) * 16)

Expand Down Expand Up @@ -293,6 +296,10 @@ struct it87_data {
u8 pwm_ctrl[3]; /* Register value */
u8 pwm_duty[3]; /* Manual PWM value set by user (bit 6-0) */
u8 pwm_temp_map[3]; /* PWM to temp. chan. mapping (bits 1-0) */

/* Automatic fan speed control registers */
u8 auto_pwm[3][4]; /* [nr][3] is hard-coded */
s8 auto_temp[3][5]; /* [nr][0] is point1_temp_hyst */
};

static inline int has_16bit_fans(const struct it87_data *data)
Expand All @@ -307,6 +314,15 @@ static inline int has_16bit_fans(const struct it87_data *data)
|| data->type == it8720;
}

static inline int has_old_autopwm(const struct it87_data *data)
{
/* The old automatic fan speed control interface is implemented
by IT8705F chips up to revision F and IT8712F chips up to
revision G. */
return (data->type == it87 && data->revision < 0x03)
|| (data->type == it8712 && data->revision < 0x08);
}

static int it87_probe(struct platform_device *pdev);
static int __devexit it87_remove(struct platform_device *pdev);

Expand Down Expand Up @@ -813,6 +829,13 @@ static ssize_t set_pwm_temp_map(struct device *dev,
long val;
u8 reg;

/* This check can go away if we ever support automatic fan speed
control on newer chips. */
if (!has_old_autopwm(data)) {
dev_notice(dev, "Mapping change disabled for safety reasons\n");
return -EINVAL;
}

if (strict_strtol(buf, 10, &val) < 0)
return -EINVAL;

Expand Down Expand Up @@ -842,6 +865,72 @@ static ssize_t set_pwm_temp_map(struct device *dev,
return count;
}

static ssize_t show_auto_pwm(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct it87_data *data = it87_update_device(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int point = sensor_attr->index;

return sprintf(buf, "%d\n", PWM_FROM_REG(data->auto_pwm[nr][point]));
}

static ssize_t set_auto_pwm(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct it87_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int point = sensor_attr->index;
long val;

if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 255)
return -EINVAL;

mutex_lock(&data->update_lock);
data->auto_pwm[nr][point] = PWM_TO_REG(val);
it87_write_value(data, IT87_REG_AUTO_PWM(nr, point),
data->auto_pwm[nr][point]);
mutex_unlock(&data->update_lock);
return count;
}

static ssize_t show_auto_temp(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct it87_data *data = it87_update_device(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int point = sensor_attr->index;

return sprintf(buf, "%d\n", TEMP_FROM_REG(data->auto_temp[nr][point]));
}

static ssize_t set_auto_temp(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct it87_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int point = sensor_attr->index;
long val;

if (strict_strtol(buf, 10, &val) < 0 || val < -128000 || val > 127000)
return -EINVAL;

mutex_lock(&data->update_lock);
data->auto_temp[nr][point] = TEMP_TO_REG(val);
it87_write_value(data, IT87_REG_AUTO_TEMP(nr, point),
data->auto_temp[nr][point]);
mutex_unlock(&data->update_lock);
return count;
}

#define show_fan_offset(offset) \
static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \
show_fan, NULL, offset - 1); \
Expand All @@ -863,8 +952,34 @@ static DEVICE_ATTR(pwm##offset##_freq, \
(offset == 1 ? S_IRUGO | S_IWUSR : S_IRUGO), \
show_pwm_freq, (offset == 1 ? set_pwm_freq : NULL)); \
static SENSOR_DEVICE_ATTR(pwm##offset##_auto_channels_temp, \
S_IRUGO, show_pwm_temp_map, set_pwm_temp_map, \
offset - 1);
S_IRUGO | S_IWUSR, show_pwm_temp_map, set_pwm_temp_map, \
offset - 1); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_pwm, \
S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \
offset - 1, 0); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_pwm, \
S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \
offset - 1, 1); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_pwm, \
S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \
offset - 1, 2); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_pwm, \
S_IRUGO, show_auto_pwm, NULL, offset - 1, 3); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp, \
S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \
offset - 1, 1); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp_hyst, \
S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \
offset - 1, 0); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_temp, \
S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \
offset - 1, 2); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_temp, \
S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \
offset - 1, 3); \
static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_temp, \
S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \
offset - 1, 4);

show_pwm_offset(1);
show_pwm_offset(2);
Expand Down Expand Up @@ -1219,6 +1334,47 @@ static const struct attribute_group it87_group_pwm[3] = {
{ .attrs = it87_attributes_pwm[2] },
};

static struct attribute *it87_attributes_autopwm[3][9+1] = { {
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point1_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point1_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr,
NULL
} };

static const struct attribute_group it87_group_autopwm[3] = {
{ .attrs = it87_attributes_autopwm[0] },
{ .attrs = it87_attributes_autopwm[1] },
{ .attrs = it87_attributes_autopwm[2] },
};

static struct attribute *it87_attributes_fan_beep[] = {
&sensor_dev_attr_fan1_beep.dev_attr.attr,
&sensor_dev_attr_fan2_beep.dev_attr.attr,
Expand Down Expand Up @@ -1382,6 +1538,9 @@ static void it87_remove_files(struct device *dev)
if (sio_data->skip_pwm & (1 << 0))
continue;
sysfs_remove_group(&dev->kobj, &it87_group_pwm[i]);
if (has_old_autopwm(data))
sysfs_remove_group(&dev->kobj,
&it87_group_autopwm[i]);
}
if (!sio_data->skip_vid)
sysfs_remove_group(&dev->kobj, &it87_group_vid);
Expand Down Expand Up @@ -1491,6 +1650,13 @@ static int __devinit it87_probe(struct platform_device *pdev)
&it87_group_pwm[i]);
if (err)
goto ERROR4;

if (!has_old_autopwm(data))
continue;
err = sysfs_create_group(&dev->kobj,
&it87_group_autopwm[i]);
if (err)
goto ERROR4;
}
}

Expand Down Expand Up @@ -1624,6 +1790,7 @@ static void __devinit it87_init_device(struct platform_device *pdev)
for (i = 0; i < 3; i++) {
data->pwm_temp_map[i] = i;
data->pwm_duty[i] = 0x7f; /* Full speed */
data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
}

/* Some chips seem to have default value 0xff for all limit
Expand Down Expand Up @@ -1703,6 +1870,17 @@ static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
else /* Manual mode */
data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;

if (has_old_autopwm(data)) {
int i;

for (i = 0; i < 5 ; i++)
data->auto_temp[nr][i] = it87_read_value(data,
IT87_REG_AUTO_TEMP(nr, i));
for (i = 0; i < 3 ; i++)
data->auto_pwm[nr][i] = it87_read_value(data,
IT87_REG_AUTO_PWM(nr, i));
}
}

static struct it87_data *it87_update_device(struct device *dev)
Expand Down

0 comments on commit 19f18c4

Please sign in to comment.