Skip to content

Commit

Permalink
thinkpad-acpi: support the second fan on the X61
Browse files Browse the repository at this point in the history
Support reading the tachometer of the auxiliary fan of a X60/X61.

It was found out by sheer luck, that bit 0 of EC register 0x31
(formely HBRV) selects which fan is active for tachometer readings
through EC 0x84/0x085: 0 for fan1, 1 for fan2.

Many thanks to Christoph Kl??nter, to Whoopie, and to weasel, who
helped confirm that behaviour.

Fan control through EC HFSP applies to both fans equally, regardless
of the state of bit 0 of EC 0x31.  That matches the way the DSDT uses
HFSP.

In order to better support the secondary fan, export a second
tachometer over hwmon, and add defensive measures to make sure we are
reading the correct tachometer.

Support for the second fan is whitelist-based, as I have not found
anything obvious to look for in the DSDT to detect the presence of
the second fan.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
  • Loading branch information
Henrique de Moraes Holschuh authored and Len Brown committed Jun 18, 2009
1 parent d7880f1 commit d737724
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 2 deletions.
10 changes: 9 additions & 1 deletion Documentation/laptops/thinkpad-acpi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@ Fan control and monitoring: fan speed, fan enable/disable

procfs: /proc/acpi/ibm/fan
sysfs device attributes: (hwmon "thinkpad") fan1_input, pwm1,
pwm1_enable
pwm1_enable, fan2_input
sysfs hwmon driver attributes: fan_watchdog

NOTE NOTE NOTE: fan control operations are disabled by default for
Expand All @@ -1282,6 +1282,9 @@ from the hardware registers of the embedded controller. This is known
to work on later R, T, X and Z series ThinkPads but may show a bogus
value on other models.

Some Lenovo ThinkPads support a secondary fan. This fan cannot be
controlled separately, it shares the main fan control.

Fan levels:

Most ThinkPad fans work in "levels" at the firmware interface. Level 0
Expand Down Expand Up @@ -1412,6 +1415,11 @@ hwmon device attribute fan1_input:
which can take up to two minutes. May return rubbish on older
ThinkPads.

hwmon device attribute fan2_input:
Fan tachometer reading, in RPM, for the secondary fan.
Available only on some ThinkPads. If the secondary fan is
not installed, will always read 0.

hwmon driver attribute fan_watchdog:
Fan safety watchdog timer interval, in seconds. Minimum is
1 second, maximum is 120 seconds. 0 disables the watchdog.
Expand Down
122 changes: 121 additions & 1 deletion drivers/platform/x86/thinkpad_acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ static struct {
u32 wan:1;
u32 uwb:1;
u32 fan_ctrl_status_undef:1;
u32 second_fan:1;
u32 beep_needs_two_args:1;
u32 input_device_registered:1;
u32 platform_drv_registered:1;
Expand Down Expand Up @@ -6298,6 +6299,21 @@ static struct ibm_struct volume_driver_data = {
* For firmware bugs, refer to:
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
*
* ----
*
* ThinkPad EC register 0x31 bit 0 (only on select models)
*
* When bit 0 of EC register 0x31 is zero, the tachometer registers
* show the speed of the main fan. When bit 0 of EC register 0x31
* is one, the tachometer registers show the speed of the auxiliary
* fan.
*
* Fan control seems to affect both fans, regardless of the state
* of this bit.
*
* So far, only the firmware for the X60/X61 non-tablet versions
* seem to support this (firmware TP-7M).
*
* TPACPI_FAN_WR_ACPI_FANS:
* ThinkPad X31, X40, X41. Not available in the X60.
*
Expand All @@ -6324,6 +6340,8 @@ enum { /* Fan control constants */
fan_status_offset = 0x2f, /* EC register 0x2f */
fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM)
* 0x84 must be read before 0x85 */
fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M)
bit 0 selects which fan is active */

TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
Expand Down Expand Up @@ -6417,6 +6435,38 @@ static void fan_quirk1_handle(u8 *fan_status)
}
}

/* Select main fan on X60/X61, NOOP on others */
static bool fan_select_fan1(void)
{
if (tp_features.second_fan) {
u8 val;

if (ec_read(fan_select_offset, &val) < 0)
return false;
val &= 0xFEU;
if (ec_write(fan_select_offset, val) < 0)
return false;
}
return true;
}

/* Select secondary fan on X60/X61 */
static bool fan_select_fan2(void)
{
u8 val;

if (!tp_features.second_fan)
return false;

if (ec_read(fan_select_offset, &val) < 0)
return false;
val |= 0x01U;
if (ec_write(fan_select_offset, val) < 0)
return false;

return true;
}

/*
* Call with fan_mutex held
*/
Expand Down Expand Up @@ -6494,6 +6544,8 @@ static int fan_get_speed(unsigned int *speed)
switch (fan_status_access_mode) {
case TPACPI_FAN_RD_TPEC:
/* all except 570, 600e/x, 770e, 770x */
if (unlikely(!fan_select_fan1()))
return -EIO;
if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
!acpi_ec_read(fan_rpm_offset + 1, &hi)))
return -EIO;
Expand All @@ -6510,6 +6562,34 @@ static int fan_get_speed(unsigned int *speed)
return 0;
}

static int fan2_get_speed(unsigned int *speed)
{
u8 hi, lo;
bool rc;

switch (fan_status_access_mode) {
case TPACPI_FAN_RD_TPEC:
/* all except 570, 600e/x, 770e, 770x */
if (unlikely(!fan_select_fan2()))
return -EIO;
rc = !acpi_ec_read(fan_rpm_offset, &lo) ||
!acpi_ec_read(fan_rpm_offset + 1, &hi);
fan_select_fan1(); /* play it safe */
if (rc)
return -EIO;

if (likely(speed))
*speed = (hi << 8) | lo;

break;

default:
return -ENXIO;
}

return 0;
}

static int fan_set_level(int level)
{
if (!fan_control_allowed)
Expand Down Expand Up @@ -6915,6 +6995,25 @@ static struct device_attribute dev_attr_fan_fan1_input =
__ATTR(fan1_input, S_IRUGO,
fan_fan1_input_show, NULL);

/* sysfs fan fan2_input ------------------------------------------------ */
static ssize_t fan_fan2_input_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int res;
unsigned int speed;

res = fan2_get_speed(&speed);
if (res < 0)
return res;

return snprintf(buf, PAGE_SIZE, "%u\n", speed);
}

static struct device_attribute dev_attr_fan_fan2_input =
__ATTR(fan2_input, S_IRUGO,
fan_fan2_input_show, NULL);

/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
char *buf)
Expand Down Expand Up @@ -6948,28 +7047,38 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
static struct attribute *fan_attributes[] = {
&dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
&dev_attr_fan_fan1_input.attr,
NULL, /* for fan2_input */
NULL
};

static const struct attribute_group fan_attr_group = {
.attrs = fan_attributes,
};

#define TPACPI_FAN_Q1 0x0001
#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */
#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */

#define TPACPI_FAN_QI(__id1, __id2, __quirks) \
{ .vendor = PCI_VENDOR_ID_IBM, \
.bios = TPACPI_MATCH_ANY, \
.ec = TPID(__id1, __id2), \
.quirks = __quirks }

#define TPACPI_FAN_QL(__id1, __id2, __quirks) \
{ .vendor = PCI_VENDOR_ID_LENOVO, \
.bios = TPACPI_MATCH_ANY, \
.ec = TPID(__id1, __id2), \
.quirks = __quirks }

static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1),
TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1),
TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1),
TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1),
TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN),
};

#undef TPACPI_FAN_QL
#undef TPACPI_FAN_QI

static int __init fan_init(struct ibm_init_struct *iibm)
Expand All @@ -6986,6 +7095,7 @@ static int __init fan_init(struct ibm_init_struct *iibm)
fan_control_commands = 0;
fan_watchdog_maxinterval = 0;
tp_features.fan_ctrl_status_undef = 0;
tp_features.second_fan = 0;
fan_control_desired_level = 7;

TPACPI_ACPIHANDLE_INIT(fans);
Expand All @@ -7006,6 +7116,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
if (quirks & TPACPI_FAN_Q1)
fan_quirk1_setup();
if (quirks & TPACPI_FAN_2FAN) {
tp_features.second_fan = 1;
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
"secondary fan support enabled\n");
}
} else {
printk(TPACPI_ERR
"ThinkPad ACPI EC access misbehaving, "
Expand Down Expand Up @@ -7061,6 +7176,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)

if (fan_status_access_mode != TPACPI_FAN_NONE ||
fan_control_access_mode != TPACPI_FAN_WR_NONE) {
if (tp_features.second_fan) {
/* attach second fan tachometer */
fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
&dev_attr_fan_fan2_input.attr;
}
rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
&fan_attr_group);
if (rc < 0)
Expand Down

0 comments on commit d737724

Please sign in to comment.