5836 lines
162 KiB
Perl
5836 lines
162 KiB
Perl
#!/usr/bin/perl -w
|
|
#
|
|
# sensors-detect - Detect hardware monitoring chips
|
|
# Copyright (C) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>
|
|
# Copyright (C) 2004 - 2009 Jean Delvare <khali@linux-fr.org>
|
|
#
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301 USA.
|
|
#
|
|
|
|
require 5.004;
|
|
|
|
use strict;
|
|
use Fcntl;
|
|
use File::Basename;
|
|
|
|
# We will call modprobe, which typically lives in either /sbin,
|
|
# /usr/sbin or /usr/local/bin. So make sure these are all in the PATH.
|
|
foreach ('/usr/sbin', '/usr/local/sbin', '/sbin') {
|
|
$ENV{PATH} = "$_:".$ENV{PATH}
|
|
unless $ENV{PATH} =~ m/(^|:)$_\/?(:|$)/;
|
|
}
|
|
|
|
#########################
|
|
# CONSTANT DECLARATIONS #
|
|
#########################
|
|
|
|
use constant NO_CACHE => 1;
|
|
use vars qw(@pci_adapters @chip_ids @ipmi_ifs @non_hwmon_chip_ids
|
|
$i2c_addresses_to_scan $revision @i2c_byte_cache);
|
|
|
|
$revision = '$Revision: 1.1 $ ($Date: 2009/11/10 14:28:19 $)';
|
|
$revision =~ s/\$\w+: (.*?) \$/$1/g;
|
|
$revision =~ s/ \([^()]*\)//;
|
|
|
|
# This is the list of SMBus or I2C adapters we recognize by their PCI
|
|
# signature. This is an easy and fast way to determine which SMBus or I2C
|
|
# adapters should be present.
|
|
# Each entry must have a vendid (Vendor ID), devid (Device ID) and
|
|
# procid (Device name) and driver (Device driver).
|
|
@pci_adapters = (
|
|
{
|
|
vendid => 0x8086,
|
|
devid => 0x7113,
|
|
procid => "Intel 82371AB PIIX4 ACPI",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x7603,
|
|
procid => "Intel 82372FB PIIX5 ACPI",
|
|
driver => "to-be-tested",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x719b,
|
|
procid => "Intel 82443MX Mobile",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x2413,
|
|
procid => "Intel 82801AA ICH",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x2423,
|
|
procid => "Intel 82801AB ICH0",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x2443,
|
|
procid => "Intel 82801BA ICH2",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x2483,
|
|
procid => "Intel 82801CA/CAM ICH3",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x24C3,
|
|
procid => "Intel 82801DB ICH4",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x24D3,
|
|
procid => "Intel 82801EB ICH5",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x25A4,
|
|
procid => "Intel 6300ESB",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x269B,
|
|
procid => "Intel Enterprise Southbridge - ESB2",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x266A,
|
|
procid => "Intel 82801FB ICH6",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x27DA,
|
|
procid => "Intel 82801G ICH7",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x283E,
|
|
procid => "Intel 82801H ICH8",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x2930,
|
|
procid => "Intel ICH9",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x5032,
|
|
procid => "Intel Tolapai",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x3A30,
|
|
procid => "Intel ICH10",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x3A60,
|
|
procid => "Intel ICH10",
|
|
driver => "i2c-i801",
|
|
}, {
|
|
vendid => 0x8086,
|
|
devid => 0x8119,
|
|
procid => "Intel SCH",
|
|
driver => "i2c-isch",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3040,
|
|
procid => "VIA Technologies VT82C586B Apollo ACPI",
|
|
driver => "i2c-via",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3050,
|
|
procid => "VIA Technologies VT82C596 Apollo ACPI",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3051,
|
|
procid => "VIA Technologies VT82C596B ACPI",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3057,
|
|
procid => "VIA Technologies VT82C686 Apollo ACPI",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3074,
|
|
procid => "VIA Technologies VT8233 VLink South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3147,
|
|
procid => "VIA Technologies VT8233A South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3177,
|
|
procid => "VIA Technologies VT8233A/8235 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3227,
|
|
procid => "VIA Technologies VT8237 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3337,
|
|
procid => "VIA Technologies VT8237A South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x8235,
|
|
procid => "VIA Technologies VT8231 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x3287,
|
|
procid => "VIA Technologies VT8251 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x8324,
|
|
procid => "VIA Technologies CX700 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1106,
|
|
devid => 0x8353,
|
|
procid => "VIA Technologies VX800/VX820 South Bridge",
|
|
driver => "i2c-viapro",
|
|
}, {
|
|
vendid => 0x1039,
|
|
devid => 0x0008,
|
|
procid => "Silicon Integrated Systems SIS5595",
|
|
driver => "i2c-sis5595",
|
|
}, {
|
|
vendid => 0x1039,
|
|
devid => 0x0630,
|
|
procid => "Silicon Integrated Systems SIS630",
|
|
driver => "i2c-sis630",
|
|
}, {
|
|
vendid => 0x1039,
|
|
devid => 0x0730,
|
|
procid => "Silicon Integrated Systems SIS730",
|
|
driver => "i2c-sis630",
|
|
}, {
|
|
vendid => 0x1039,
|
|
devid => 0x0016,
|
|
procid => "Silicon Integrated Systems SMBus Controller",
|
|
driver => "i2c-sis96x",
|
|
}, {
|
|
# Both Ali chips below have same PCI ID. Can't be helped. Only one should load.
|
|
vendid => 0x10b9,
|
|
devid => 0x7101,
|
|
procid => "Acer Labs 1533/1543",
|
|
driver => "i2c-ali15x3",
|
|
}, {
|
|
vendid => 0x10b9,
|
|
devid => 0x7101,
|
|
procid => "Acer Labs 1535",
|
|
driver => "i2c-ali1535",
|
|
}, {
|
|
vendid => 0x10b9,
|
|
devid => 0x1563,
|
|
procid => "Acer Labs 1563",
|
|
driver => "i2c-ali1563",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x740b,
|
|
procid => "AMD-756 Athlon ACPI",
|
|
driver => "i2c-amd756",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x7413,
|
|
procid => "AMD-766 Athlon ACPI",
|
|
driver => "i2c-amd756",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x7443,
|
|
procid => "AMD-768 System Management",
|
|
driver => "i2c-amd756",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x746b,
|
|
procid => "AMD-8111 ACPI",
|
|
driver => "i2c-amd756",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x746a,
|
|
procid => "AMD-8111 SMBus 2.0",
|
|
driver => "i2c-amd8111",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x01b4,
|
|
procid => "nVidia nForce SMBus",
|
|
driver => "i2c-amd756",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0064,
|
|
procid => "nVidia Corporation nForce2 SMBus (MCP)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0084,
|
|
procid => "nVidia Corporation nForce2 Ultra 400 SMBus (MCP)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x00D4,
|
|
procid => "nVidia Corporation nForce3 Pro150 SMBus (MCP)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x00E4,
|
|
procid => "nVidia Corporation nForce3 250Gb SMBus (MCP)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0052,
|
|
procid => "nVidia Corporation nForce4 SMBus (MCP)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0034,
|
|
procid => "nVidia Corporation nForce4 SMBus (MCP-04)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0264,
|
|
procid => "nVidia Corporation nForce SMBus (MCP51)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0368,
|
|
procid => "nVidia Corporation nForce SMBus (MCP55)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x03eb,
|
|
procid => "nVidia Corporation nForce SMBus (MCP61)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0446,
|
|
procid => "nVidia Corporation nForce SMBus (MCP65)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0542,
|
|
procid => "nVidia Corporation nForce SMBus (MCP67)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x07d8,
|
|
procid => "nVidia Corporation nForce SMBus (MCP73)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0752,
|
|
procid => "nVidia Corporation nForce SMBus (MCP78S)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x10de,
|
|
devid => 0x0aa2,
|
|
procid => "nVidia Corporation nForce SMBus (MCP79)",
|
|
driver => "i2c-nforce2",
|
|
}, {
|
|
vendid => 0x1166,
|
|
devid => 0x0200,
|
|
procid => "ServerWorks OSB4 South Bridge",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1055,
|
|
devid => 0x9463,
|
|
procid => "SMSC Victory66 South Bridge",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1166,
|
|
devid => 0x0201,
|
|
procid => "ServerWorks CSB5 South Bridge",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1166,
|
|
devid => 0x0203,
|
|
procid => "ServerWorks CSB6 South Bridge",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1166,
|
|
devid => 0x0205,
|
|
procid => "ServerWorks HT-1000 South Bridge",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1002,
|
|
devid => 0x4353,
|
|
procid => "ATI Technologies Inc ATI SMBus",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1002,
|
|
devid => 0x4363,
|
|
procid => "ATI Technologies Inc ATI SMBus",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1002,
|
|
devid => 0x4372,
|
|
procid => "ATI Technologies Inc IXP SB400 SMBus Controller",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1002,
|
|
devid => 0x4385,
|
|
procid => "ATI Technologies Inc SB600/SB700/SB800 SMBus",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x780b,
|
|
procid => "AMD Hudson-2 SMBus",
|
|
driver => "i2c-piix4",
|
|
}, {
|
|
vendid => 0x100B,
|
|
devid => 0x0500,
|
|
procid => "SCx200 Bridge",
|
|
driver => "scx200_acb",
|
|
}, {
|
|
vendid => 0x100B,
|
|
devid => 0x0510,
|
|
procid => "SC1100 Bridge",
|
|
driver => "scx200_acb",
|
|
}, {
|
|
vendid => 0x100B,
|
|
devid => 0x002B,
|
|
procid => "CS5535 ISA bridge",
|
|
driver => "scx200_acb",
|
|
}, {
|
|
vendid => 0x1022,
|
|
devid => 0x2090,
|
|
procid => "CS5536 [Geode companion] ISA",
|
|
driver => "scx200_acb",
|
|
}
|
|
);
|
|
|
|
# Look-up table to find out an I2C bus' driver based on the bus name.
|
|
# The match field should contain a regular expression matching the I2C
|
|
# bus name as it would appear in sysfs.
|
|
# Note that new drivers probably don't need to be added to this table
|
|
# if they bind to their device, as we will be able to get the driver name
|
|
# from sysfs directly.
|
|
use vars qw(@i2c_adapter_names);
|
|
@i2c_adapter_names = (
|
|
{ driver => "i2c-piix4", match => qr/^SMBus PIIX4 adapter at / },
|
|
{ driver => "i2c-i801", match => qr/^SMBus I801 adapter at / },
|
|
{ driver => "i2c-via", match => qr/^VIA i2c/ },
|
|
{ driver => "i2c-viapro", match => qr/^SMBus V(IA|ia) Pro adapter at / },
|
|
{ driver => "i2c-sis5595", match => qr/^SMBus SIS5595 adapter at / },
|
|
{ driver => "i2c-sis630", match => qr/^SMBus SIS630 adapter at / },
|
|
{ driver => "i2c-sis96x", match => qr/^SiS96x SMBus adapter at / },
|
|
{ driver => "i2c-ali15x3", match => qr/^SMBus ALI15X3 adapter at / },
|
|
{ driver => "i2c-ali1535", match => qr/^SMBus ALI1535 adapter at/ },
|
|
{ driver => "i2c-ali1563", match => qr/^SMBus ALi 1563 Adapter @ / },
|
|
{ driver => "i2c-amd756", match => qr/^SMBus (AMD756|AMD766|AMD768|AMD8111|nVidia nForce) adapter at / },
|
|
{ driver => "i2c-amd8111", match => qr/^SMBus2 AMD8111 adapter at / },
|
|
{ driver => "i2c-nforce2", match => qr/^SMBus nForce2 adapter at / },
|
|
{ driver => "scx200_acb", match => qr/^(NatSemi SCx200 ACCESS\.bus|SCx200 ACB\d+|CS553[56] ACB\d+)/ },
|
|
);
|
|
|
|
# This is a list of all recognized I2C and ISA chips.
|
|
# Each entry must have the following fields:
|
|
# name: The full chip name
|
|
# driver: The driver name. Put in exactly:
|
|
# * "to-be-written" if it is not yet available
|
|
# * "use-isa-instead" if no i2c driver will be written
|
|
# i2c_addrs (optional): For I2C chips, the list of I2C addresses to
|
|
# probe.
|
|
# i2c_detect (optional): For I2C chips, the function to call to detect
|
|
# this chip. The function will be passed two parameters: an open file
|
|
# descriptor to access the bus, and the I2C address to probe.
|
|
# isa_addrs (optional): For ISA chips, the list of port addresses to
|
|
# probe.
|
|
# isa_detect (optional): For ISA chips, the function to call to detect
|
|
# this chip. The function will be passed one parameter: the ISA address
|
|
# to probe.
|
|
# alias_detect (optional): For chips which can be both on the ISA and the
|
|
# I2C bus, a function which detects whether two entries are the same.
|
|
# The function will be passed three parameters: the ISA address, an
|
|
# open file descriptor to access the I2C bus, and the I2C address.
|
|
@chip_ids = (
|
|
{
|
|
name => "Myson MTP008",
|
|
driver => "mtp008",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { mtp008_detect(@_); },
|
|
}, {
|
|
name => "National Semiconductor LM78",
|
|
driver => "lm78",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { lm78_detect(@_, 0); },
|
|
isa_addrs => [0x290],
|
|
isa_detect => sub { lm78_isa_detect(@_, 0); },
|
|
alias_detect => sub { winbond_alias_detect(@_, 0x2b, 0x3d); },
|
|
}, {
|
|
name => "National Semiconductor LM79",
|
|
driver => "lm78",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { lm78_detect(@_, 2); },
|
|
isa_addrs => [0x290],
|
|
isa_detect => sub { lm78_isa_detect(@_, 2); },
|
|
alias_detect => sub { winbond_alias_detect(@_, 0x2b, 0x3d); },
|
|
}, {
|
|
name => "National Semiconductor LM75",
|
|
driver => "lm75",
|
|
i2c_addrs => [0x48..0x4f],
|
|
i2c_detect => sub { lm75_detect(@_, 0); },
|
|
}, {
|
|
name => "Dallas Semiconductor DS75",
|
|
driver => "lm75",
|
|
i2c_addrs => [0x48..0x4f],
|
|
i2c_detect => sub { lm75_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM77",
|
|
driver => "lm77",
|
|
i2c_addrs => [0x48..0x4b],
|
|
i2c_detect => sub { lm77_detect(@_); },
|
|
}, {
|
|
name => "National Semiconductor LM80",
|
|
driver => "lm80",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { lm80_detect(@_); },
|
|
}, {
|
|
name => "National Semiconductor LM85",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 0); },
|
|
}, {
|
|
name => "National Semiconductor LM96000 or PC8374L",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADM1027",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 2); },
|
|
}, {
|
|
name => "Analog Devices ADT7460 or ADT7463",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 3); },
|
|
}, {
|
|
name => "SMSC EMC6D100 or EMC6D101",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 4); },
|
|
}, {
|
|
name => "SMSC EMC6D102",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 5); },
|
|
}, {
|
|
name => "SMSC EMC6D103",
|
|
driver => "lm85",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 6); },
|
|
}, {
|
|
name => "Winbond WPCD377I",
|
|
driver => "not-a-sensor",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm85_detect(@_, 7); },
|
|
}, {
|
|
name => "Analog Devices ADT7462",
|
|
driver => "adt7462",
|
|
i2c_addrs => [0x5c, 0x58],
|
|
i2c_detect => sub { adt7467_detect(@_, 2); },
|
|
}, {
|
|
name => "Analog Devices ADT7466",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { adt7467_detect(@_, 3); },
|
|
}, {
|
|
name => "Analog Devices ADT7467 or ADT7468",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2e],
|
|
i2c_detect => sub { adt7467_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADT7470",
|
|
driver => "adt7470",
|
|
i2c_addrs => [0x2c, 0x2e, 0x2f],
|
|
i2c_detect => sub { adt7467_detect(@_, 4); },
|
|
}, {
|
|
name => "Analog Devices ADT7473",
|
|
driver => "adt7473",
|
|
i2c_addrs => [0x2e],
|
|
i2c_detect => sub { adt7473_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADT7475",
|
|
driver => "adt7475",
|
|
i2c_addrs => [0x2e],
|
|
i2c_detect => sub { adt7473_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADT7476",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adt7467_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADT7490",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adt7490_detect(@_); },
|
|
}, {
|
|
name => "Andigilog aSC7511",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { andigilog_aSC7511_detect(@_); },
|
|
}, {
|
|
name => "Andigilog aSC7512",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x58],
|
|
i2c_detect => sub { andigilog_detect(@_, 0); },
|
|
}, {
|
|
name => "Andigilog aSC7611",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { andigilog_detect(@_, 1); },
|
|
}, {
|
|
name => "Andigilog aSC7621",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { andigilog_detect(@_, 2); },
|
|
}, {
|
|
name => "National Semiconductor LM87",
|
|
driver => "lm87",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm87_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADM1024",
|
|
driver => "lm87",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm87_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM93",
|
|
driver => "lm93",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { lm93_detect(@_); },
|
|
}, {
|
|
name => "Winbond W83781D",
|
|
driver => "w83781d",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 0); },
|
|
isa_addrs => [0x290],
|
|
isa_detect => sub { w83781d_isa_detect(@_, 0); },
|
|
alias_detect => sub { winbond_alias_detect(@_, 0x2b, 0x3d); },
|
|
}, {
|
|
name => "Winbond W83782D",
|
|
driver => "w83781d",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 1); },
|
|
isa_addrs => [0x290],
|
|
isa_detect => sub { w83781d_isa_detect(@_, 1); },
|
|
alias_detect => sub { winbond_alias_detect(@_, 0x2b, 0x3d); },
|
|
}, {
|
|
name => "Winbond W83783S",
|
|
driver => "w83781d",
|
|
i2c_addrs => [0x2d],
|
|
i2c_detect => sub { w83781d_detect(@_, 2); },
|
|
}, {
|
|
name => "Winbond W83791D",
|
|
driver => "w83791d",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 7); },
|
|
}, {
|
|
name => "Winbond W83792D",
|
|
driver => "w83792d",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 8); },
|
|
}, {
|
|
name => "Winbond W83793R/G",
|
|
driver => "w83793",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { w83793_detect(@_); },
|
|
}, {
|
|
name => "Nuvoton W83795G/ADG",
|
|
driver => "w83795",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { w83795_detect(@_); },
|
|
}, {
|
|
name => "Winbond W83627HF",
|
|
driver => "use-isa-instead",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 3); },
|
|
}, {
|
|
name => "Winbond W83627EHF",
|
|
driver => "use-isa-instead",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 9); },
|
|
}, {
|
|
name => "Winbond W83627DHG/W83667HG/W83677HG",
|
|
driver => "use-isa-instead",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 10); },
|
|
}, {
|
|
name => "Asus AS99127F (rev.1)",
|
|
driver => "w83781d",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 4); },
|
|
}, {
|
|
name => "Asus AS99127F (rev.2)",
|
|
driver => "w83781d",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 5); },
|
|
}, {
|
|
name => "Asus ASB100 Bach",
|
|
driver => "asb100",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { w83781d_detect(@_, 6); },
|
|
}, {
|
|
name => "Asus Mozart-2",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x77],
|
|
i2c_detect => sub { mozart_detect(@_); },
|
|
}, {
|
|
name => "Winbond W83L784R/AR/G",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2d],
|
|
i2c_detect => sub { w83l784r_detect(@_, 0); },
|
|
}, {
|
|
name => "Winbond W83L785R/G",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2d],
|
|
i2c_detect => sub { w83l784r_detect(@_, 1); },
|
|
}, {
|
|
name => "Winbond W83L786NR/NG/R/G",
|
|
driver => "w83l786ng",
|
|
i2c_addrs => [0x2e, 0x2f],
|
|
i2c_detect => sub { w83l784r_detect(@_, 2); },
|
|
}, {
|
|
name => "Winbond W83L785TS-S",
|
|
driver => "w83l785ts",
|
|
i2c_addrs => [0x2e],
|
|
i2c_detect => sub { w83l784r_detect(@_, 3); },
|
|
}, {
|
|
name => "Genesys Logic GL518SM",
|
|
driver => "gl518sm",
|
|
i2c_addrs => [0x2c, 0x2d],
|
|
i2c_detect => sub { gl518sm_detect(@_, 0); },
|
|
}, {
|
|
name => "Genesys Logic GL520SM",
|
|
driver => "gl520sm",
|
|
i2c_addrs => [0x2c, 0x2d],
|
|
i2c_detect => sub { gl518sm_detect(@_, 1); },
|
|
}, {
|
|
name => "Genesys Logic GL525SM",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2d],
|
|
i2c_detect => sub { gl525sm_detect(@_); },
|
|
}, {
|
|
name => "Analog Devices ADM9240",
|
|
driver => "adm9240",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { adm9240_detect(@_, 0); },
|
|
}, {
|
|
name => "Dallas Semiconductor DS1621/DS1631",
|
|
driver => "ds1621",
|
|
i2c_addrs => [0x48..0x4f],
|
|
i2c_detect => sub { ds1621_detect(@_); },
|
|
}, {
|
|
name => "Dallas Semiconductor DS1780",
|
|
driver => "adm9240",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { adm9240_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM81",
|
|
driver => "adm9240",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { adm9240_detect(@_, 2); },
|
|
}, {
|
|
name => "Analog Devices ADM1026",
|
|
driver => "adm1026",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1026_detect(@_); },
|
|
}, {
|
|
name => "Analog Devices ADM1025",
|
|
driver => "adm1025",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1025_detect(@_, 0); },
|
|
}, {
|
|
name => "Philips NE1619",
|
|
driver => "adm1025",
|
|
i2c_addrs => [0x2c..0x2d],
|
|
i2c_detect => sub { adm1025_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADM1021",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADM1021A/ADM1023",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 1); },
|
|
}, {
|
|
name => "Maxim MAX1617",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 2); },
|
|
}, {
|
|
name => "Maxim MAX1617A",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 3); },
|
|
}, {
|
|
name => "Maxim MAX1668",
|
|
driver => "max1668",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max1668_detect(@_, 0); },
|
|
}, {
|
|
name => "Maxim MAX1805",
|
|
driver => "max1668",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max1668_detect(@_, 1); },
|
|
}, {
|
|
name => "Maxim MAX1989",
|
|
driver => "max1668",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max1668_detect(@_, 2); },
|
|
}, {
|
|
name => "Maxim MAX6650/MAX6651",
|
|
driver => "max6650",
|
|
i2c_addrs => [0x1b, 0x1f, 0x48, 0x4b],
|
|
i2c_detect => sub { max6650_detect(@_); },
|
|
}, {
|
|
name => "Maxim MAX6655/MAX6656",
|
|
driver => "max6655",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max6655_detect(@_); },
|
|
}, {
|
|
name => "TI THMC10",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 4); },
|
|
}, {
|
|
name => "National Semiconductor LM84",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 5); },
|
|
}, {
|
|
name => "Genesys Logic GL523SM",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 6); },
|
|
}, {
|
|
name => "Onsemi MC1066",
|
|
driver => "adm1021",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { adm1021_detect(@_, 7); },
|
|
}, {
|
|
name => "Maxim MAX1618",
|
|
driver => "max1619",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max1619_detect(@_, 1); },
|
|
}, {
|
|
name => "Maxim MAX1619",
|
|
driver => "max1619",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { max1619_detect(@_, 0); },
|
|
}, {
|
|
name => "National Semiconductor LM82/LM83",
|
|
driver => "lm83",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { lm83_detect(@_); },
|
|
}, {
|
|
name => "National Semiconductor LM90",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm90_detect(@_, 0); },
|
|
}, {
|
|
name => "National Semiconductor LM89/LM99",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c..0x4d],
|
|
i2c_detect => sub { lm90_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM86",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm90_detect(@_, 2); },
|
|
}, {
|
|
name => "Analog Devices ADM1032",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c..0x4d],
|
|
i2c_detect => sub { lm90_detect(@_, 3); },
|
|
}, {
|
|
name => "Maxim MAX6654/MAX6690",
|
|
driver => "to-be-written", # probably lm90
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { lm90_detect(@_, 4); },
|
|
}, {
|
|
name => "Maxim MAX6657/MAX6658/MAX6659",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { max6657_detect(@_); },
|
|
}, {
|
|
name => "Maxim MAX6659",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4d..0x4e], # 0x4c is handled above
|
|
i2c_detect => sub { max6657_detect(@_); },
|
|
}, {
|
|
name => "Maxim MAX6646",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4d],
|
|
i2c_detect => sub { lm90_detect(@_, 6); },
|
|
}, {
|
|
name => "Maxim MAX6647",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4e],
|
|
i2c_detect => sub { lm90_detect(@_, 6); },
|
|
}, {
|
|
name => "Maxim MAX6648/MAX6649/MAX6692",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm90_detect(@_, 6); },
|
|
}, {
|
|
name => "Maxim MAX6680/MAX6681",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e],
|
|
i2c_detect => sub { lm90_detect(@_, 7); },
|
|
}, {
|
|
name => "Winbond W83L771W/G",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm90_detect(@_, 8); },
|
|
}, {
|
|
name => "Texas Instruments TMP401",
|
|
driver => "tmp401",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm90_detect(@_, 9); },
|
|
}, {
|
|
name => "Texas Instruments TMP411",
|
|
driver => "tmp401",
|
|
i2c_addrs => [0x4c..0x4e],
|
|
i2c_detect => sub { lm90_detect(@_, 10); },
|
|
}, {
|
|
name => "Texas Instruments TMP421",
|
|
driver => "tmp421",
|
|
i2c_addrs => [0x2a, 0x4c..0x4f], # 0x1c-0x1f not probed.
|
|
i2c_detect => sub { tmp42x_detect(@_, 0); },
|
|
}, {
|
|
name => "Texas Instruments TMP422",
|
|
driver => "tmp421",
|
|
i2c_addrs => [0x4c..0x4f],
|
|
i2c_detect => sub { tmp42x_detect(@_, 1); },
|
|
}, {
|
|
name => "Texas Instruments TMP423",
|
|
driver => "tmp421",
|
|
i2c_addrs => [0x4c, 0x4d],
|
|
i2c_detect => sub { tmp42x_detect(@_, 2); },
|
|
}, {
|
|
name => "National Semiconductor LM95231",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2b, 0x19, 0x2a],
|
|
i2c_detect => sub { lm95231_detect(@_, 0); },
|
|
}, {
|
|
name => "National Semiconductor LM95241",
|
|
driver => "lm95241",
|
|
i2c_addrs => [0x2b, 0x19, 0x2a],
|
|
i2c_detect => sub { lm95231_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM63",
|
|
driver => "lm63",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm63_detect(@_, 1); },
|
|
}, {
|
|
name => "National Semiconductor LM64",
|
|
driver => "to-be-written", # lm63
|
|
i2c_addrs => [0x18, 0x4e],
|
|
i2c_detect => sub { lm63_detect(@_, 3); },
|
|
}, {
|
|
name => "Fintek F75363SG",
|
|
driver => "lm63", # Not yet
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { lm63_detect(@_, 2); },
|
|
}, {
|
|
name => "National Semiconductor LM73",
|
|
driver => "lm73",
|
|
i2c_addrs => [0x48..0x4a, 0x4c..0x4e],
|
|
i2c_detect => sub { lm73_detect(@_); },
|
|
}, {
|
|
name => "National Semiconductor LM92",
|
|
driver => "lm92",
|
|
i2c_addrs => [0x48..0x4b],
|
|
i2c_detect => sub { lm92_detect(@_, 0); },
|
|
}, {
|
|
name => "National Semiconductor LM76",
|
|
driver => "lm92",
|
|
i2c_addrs => [0x48..0x4b],
|
|
i2c_detect => sub { lm92_detect(@_, 1); },
|
|
}, {
|
|
name => "Maxim MAX6633/MAX6634/MAX6635",
|
|
driver => "lm92",
|
|
i2c_addrs => [0x48..0x4f], # The MAX6633 can also use 0x40-0x47 but we
|
|
# don't want to probe these addresses, it's
|
|
# dangerous.
|
|
i2c_detect => sub { lm92_detect(@_, 2); },
|
|
}, {
|
|
name => "Analog Devices ADT7461",
|
|
driver => "lm90",
|
|
i2c_addrs => [0x4c..0x4d],
|
|
i2c_detect => sub { lm90_detect(@_, 5); },
|
|
}, {
|
|
name => "Analog Devices ADT7481",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4c, 0x4b],
|
|
i2c_detect => sub { adt7481_detect(@_); },
|
|
}, {
|
|
name => "Analog Devices ADM1029",
|
|
driver => "adm1029",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { adm1029_detect(@_); },
|
|
}, {
|
|
name => "Analog Devices ADM1030",
|
|
driver => "adm1031",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1031_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADM1031",
|
|
driver => "adm1031",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1031_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADM1033",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x50..0x53],
|
|
i2c_detect => sub { adm1034_detect(@_, 0); },
|
|
}, {
|
|
name => "Analog Devices ADM1034",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x50..0x53],
|
|
i2c_detect => sub { adm1034_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADM1022",
|
|
driver => "thmc50",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1022_detect(@_, 0); },
|
|
}, {
|
|
name => "Texas Instruments THMC50",
|
|
driver => "thmc50",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { adm1022_detect(@_, 1); },
|
|
}, {
|
|
name => "Analog Devices ADM1028",
|
|
driver => "thmc50",
|
|
i2c_addrs => [0x2e],
|
|
i2c_detect => sub { adm1022_detect(@_, 2); },
|
|
}, {
|
|
name => "Texas Instruments THMC51",
|
|
driver => "to-be-written", # thmc50
|
|
i2c_addrs => [0x2e], # At least (no datasheet)
|
|
i2c_detect => sub { adm1022_detect(@_, 3); },
|
|
}, {
|
|
name => "VIA VT1211 (I2C)",
|
|
driver => "use-isa-instead",
|
|
i2c_addrs => [0x2d],
|
|
i2c_detect => sub { vt1211_i2c_detect(@_); },
|
|
}, {
|
|
name => "ITE IT8712F",
|
|
driver => "it87",
|
|
i2c_addrs => [0x28..0x2f],
|
|
i2c_detect => sub { it8712_i2c_detect(@_); },
|
|
}, {
|
|
name => "FSC Poseidon I",
|
|
driver => sub { kernel_version_at_least(2, 6, 24) ? "fschmd" : "fscpos" },
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 0); },
|
|
}, {
|
|
name => "FSC Poseidon II",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 1); },
|
|
}, {
|
|
name => "FSC Scylla",
|
|
driver => "fschmd",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 2); },
|
|
}, {
|
|
name => "FSC Hermes",
|
|
driver => sub { kernel_version_at_least(2, 6, 24) ? "fschmd" : "fscher" },
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 3); },
|
|
}, {
|
|
name => "FSC Heimdal",
|
|
driver => "fschmd",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 4); },
|
|
}, {
|
|
name => "FSC Heracles",
|
|
driver => "fschmd",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 5); },
|
|
}, {
|
|
name => "FSC Hades",
|
|
driver => "fschmd",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 6); },
|
|
}, {
|
|
name => "FSC Syleus",
|
|
driver => "fschmd",
|
|
i2c_addrs => [0x73],
|
|
i2c_detect => sub { fsc_detect(@_, 7); },
|
|
}, {
|
|
name => "ALi M5879",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2c..0x2d],
|
|
i2c_detect => sub { m5879_detect(@_); },
|
|
}, {
|
|
name => "SMSC LPC47M15x/192/292/997",
|
|
driver => "smsc47m192",
|
|
i2c_addrs => [0x2c..0x2d],
|
|
i2c_detect => sub { smsc47m192_detect(@_); },
|
|
}, {
|
|
name => "SMSC DME1737",
|
|
driver => "dme1737",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { dme1737_detect(@_, 1); },
|
|
}, {
|
|
name => "SMSC SCH5027D-NW",
|
|
driver => "dme1737",
|
|
i2c_addrs => [0x2c..0x2e],
|
|
i2c_detect => sub { dme1737_detect(@_, 2); },
|
|
}, {
|
|
name => "Fintek F75121R/F75122R/RG (VID+GPIO)",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4e], # 0x37 not probed
|
|
i2c_detect => sub { fintek_detect(@_, 2); },
|
|
}, {
|
|
name => "Fintek F75373S/SG",
|
|
driver => "f75375s",
|
|
i2c_addrs => [0x2d..0x2e],
|
|
i2c_detect => sub { fintek_detect(@_, 3); },
|
|
}, {
|
|
name => "Fintek F75375S/SP",
|
|
driver => "f75375s",
|
|
i2c_addrs => [0x2d..0x2e],
|
|
i2c_detect => sub { fintek_detect(@_, 4); },
|
|
}, {
|
|
name => "Fintek F75387SG/RG",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2d..0x2e],
|
|
i2c_detect => sub { fintek_detect(@_, 5); },
|
|
}, {
|
|
name => "Fintek F75383S/M",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4c],
|
|
i2c_detect => sub { fintek_detect(@_, 6); },
|
|
}, {
|
|
name => "Fintek F75384S/M",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x4d],
|
|
i2c_detect => sub { fintek_detect(@_, 6); },
|
|
}, {
|
|
name => "Fintek custom power control IC",
|
|
driver => "to-be-written",
|
|
i2c_addrs => [0x2f],
|
|
i2c_detect => sub { fintek_detect(@_, 7); },
|
|
}, {
|
|
name => "Smart Battery",
|
|
driver => "sbs", # ACPI driver, not sure if it always works
|
|
i2c_addrs => [0x0b],
|
|
i2c_detect => sub { smartbatt_detect(@_); },
|
|
}
|
|
);
|
|
|
|
# IPMI interfaces have their own array now
|
|
@ipmi_ifs = (
|
|
{
|
|
name => "IPMI BMC KCS",
|
|
driver => "ipmisensors",
|
|
isa_addrs => [0x0ca0],
|
|
isa_detect => sub { ipmi_detect(@_); },
|
|
}, {
|
|
name => "IPMI BMC SMIC",
|
|
driver => "ipmisensors",
|
|
isa_addrs => [0x0ca8],
|
|
isa_detect => sub { ipmi_detect(@_); },
|
|
}
|
|
);
|
|
|
|
# Here is a similar list, but for devices which are not hardware monitoring
|
|
# chips. We only list popular devices which happen to live at the same I2C
|
|
# address as recognized hardware monitoring chips. The idea is to make it
|
|
# clear that the chip in question is of no interest for lm-sensors.
|
|
@non_hwmon_chip_ids = (
|
|
{
|
|
name => "Winbond W83791SD",
|
|
i2c_addrs => [0x2c..0x2f],
|
|
i2c_detect => sub { w83791sd_detect(@_); },
|
|
}, {
|
|
name => "Fintek F75111R/RG/N (GPIO)",
|
|
i2c_addrs => [0x37, 0x4e],
|
|
i2c_detect => sub { fintek_detect(@_, 1); },
|
|
}, {
|
|
name => "ITE IT8201R/IT8203R/IT8206R/IT8266R",
|
|
i2c_addrs => [0x4e],
|
|
i2c_detect => sub { ite_overclock_detect(@_); },
|
|
}, {
|
|
name => "SPD EEPROM",
|
|
i2c_addrs => [0x50..0x57],
|
|
i2c_detect => sub { eeprom_detect(@_); },
|
|
}, {
|
|
name => "EDID EEPROM",
|
|
i2c_addrs => [0x50],
|
|
i2c_detect => sub { ddcmonitor_detect(@_); },
|
|
}
|
|
);
|
|
|
|
# This is a list of all recognized superio chips.
|
|
# Each entry must have the following fields:
|
|
# name: The full chip name
|
|
# driver: The driver name. Put in exactly:
|
|
# * "to-be-written" if it is not yet available
|
|
# * "not-a-sensor" if the chip doesn't have hardware monitoring
|
|
# capabilities (listing such chips here removes the need of manual
|
|
# lookup when people report them)
|
|
# * "via-smbus-only" if this is a Super-I/O chip whose hardware
|
|
# monitoring registers can only be accessed via the SMBus
|
|
# devid: The device ID we have to match (base device)
|
|
# devid_mask (optional): Bitmask to apply before checking the device ID
|
|
# logdev: The logical device containing the sensors
|
|
# check (optional): A function to refine the detection. Will be passed
|
|
# the index and data ports as parameters. Must return 1 for a matching
|
|
# device, 0 otherwise.
|
|
# features (optional): Features supported by this device, amongst:
|
|
# * FEAT_IN
|
|
# * FEAT_FAN
|
|
# * FEAT_TEMP
|
|
use vars qw(@superio_ids_natsemi @superio_ids_smsc @superio_ids_smsc_ns
|
|
@superio_ids_winbond @superio_ids_ite @superio_ids);
|
|
|
|
use constant FEAT_IN => (1 << 0);
|
|
use constant FEAT_FAN => (1 << 1);
|
|
use constant FEAT_TEMP => (1 << 2);
|
|
use constant FEAT_SMBUS => (1 << 7);
|
|
|
|
@superio_ids_natsemi = (
|
|
{
|
|
name => "Nat. Semi. PC8374L Super IO Sensors", # Also Nuvoton WPCD374L
|
|
driver => "to-be-written",
|
|
devid => 0xf1,
|
|
check => sub {
|
|
outb($_[0], 0x27);
|
|
# Guess work; seen so far: 0x11
|
|
return (inb($_[1]) < 0x80);
|
|
},
|
|
logdev => 0x08,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Nuvoton WPCD377I Super IO", # Also WPCD376I
|
|
driver => "not-a-sensor",
|
|
devid => 0xf1,
|
|
check => sub {
|
|
outb($_[0], 0x27);
|
|
# Guess work; seen so far: 0x91 (twice)
|
|
return (inb($_[1]) >= 0x80);
|
|
},
|
|
}, {
|
|
name => "Nat. Semi. PC87351 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0xe2,
|
|
logdev => 0x08,
|
|
}, {
|
|
name => "Nat. Semi. PC87360 Super IO Fan Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe1,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87363 Super IO Fan Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe8,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87364 Super IO Fan Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe4,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87365 Super IO Fan Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe5,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87365 Super IO Voltage Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe5,
|
|
logdev => 0x0d,
|
|
features => FEAT_IN,
|
|
}, {
|
|
name => "Nat. Semi. PC87365 Super IO Thermal Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe5,
|
|
logdev => 0x0e,
|
|
features => FEAT_TEMP,
|
|
}, {
|
|
name => "Nat. Semi. PC87366 Super IO Fan Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe9,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87366 Super IO Voltage Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe9,
|
|
logdev => 0x0d,
|
|
features => FEAT_IN,
|
|
}, {
|
|
name => "Nat. Semi. PC87366 Super IO Thermal Sensors",
|
|
driver => "pc87360",
|
|
devid => 0xe9,
|
|
logdev => 0x0e,
|
|
features => FEAT_TEMP,
|
|
}, {
|
|
name => "Nat. Semi. PC87372 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0xf0,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87373 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0xf3,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87591 Super IO",
|
|
driver => "to-be-written",
|
|
devid => 0xec,
|
|
logdev => 0x0f,
|
|
}, {
|
|
name => "Nat. Semi. PC87317 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0xd0,
|
|
}, {
|
|
name => "Nat. Semi. PC97317 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0xdf,
|
|
}, {
|
|
name => "Nat. Semi. PC8739x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0xea,
|
|
}, {
|
|
name => "Nat. Semi. PC8741x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0xee,
|
|
}, {
|
|
name => "Nat. Semi. PC87427 Super IO Fan Sensors",
|
|
driver => "pc87427",
|
|
devid => 0xf2,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "Nat. Semi. PC87427 Super IO Health Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0xf2,
|
|
logdev => 0x14,
|
|
features => FEAT_IN | FEAT_TEMP,
|
|
}
|
|
);
|
|
|
|
@superio_ids_smsc = (
|
|
{
|
|
name => "SMSC DME1737 Super IO",
|
|
# Hardware monitoring features are accessed on the SMBus
|
|
driver => "via-smbus-only",
|
|
devid => 0x78,
|
|
}, {
|
|
name => "SMSC DME1737 Super IO",
|
|
# The DME1737 shows up twice in this list because it can return either
|
|
# 0x78 or 0x77 as its device ID.
|
|
# Hardware monitoring features are accessed on the SMBus
|
|
driver => "via-smbus-only",
|
|
devid => 0x77,
|
|
}, {
|
|
name => "SMSC EMC2700LPC Super IO",
|
|
# no datasheet
|
|
devid => 0x67,
|
|
}, {
|
|
name => "SMSC FDC37B72x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x4c,
|
|
}, {
|
|
name => "SMSC FDC37B78x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x44,
|
|
}, {
|
|
name => "SMSC FDC37C672 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x40,
|
|
}, {
|
|
name => "SMSC FDC37M707 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x42,
|
|
}, {
|
|
name => "SMSC FDC37M81x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x4d,
|
|
}, {
|
|
name => "SMSC LPC47B27x Super IO Fan Sensors",
|
|
driver => "smsc47m1",
|
|
devid => 0x51,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47B34x Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x56,
|
|
}, {
|
|
name => "SMSC LPC47B357/M967 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x5d,
|
|
}, {
|
|
name => "SMSC LPC47B367-NC Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x6d,
|
|
}, {
|
|
name => "SMSC LPC47B37x Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x52,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47B397-NC Super IO",
|
|
driver => "smsc47b397",
|
|
devid => 0x6f,
|
|
logdev => 0x08,
|
|
features => FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC LPC47M10x/112/13x Super IO Fan Sensors",
|
|
driver => "smsc47m1",
|
|
devid => 0x59,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M14x Super IO Fan Sensors",
|
|
driver => "smsc47m1",
|
|
devid => 0x5f,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M15x/192/997 Super IO Fan Sensors",
|
|
driver => "smsc47m1",
|
|
devid => 0x60,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M172 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x14,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M182 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x74,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M233 Super IO Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x6b80,
|
|
devid_mask => 0xff80,
|
|
logdev => 0x0a,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC LPC47M292 Super IO Fan Sensors",
|
|
driver => "smsc47m1",
|
|
devid => 0x6b00,
|
|
devid_mask => 0xff80,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47M584-NC Super IO",
|
|
# No datasheet
|
|
devid => 0x76,
|
|
}, {
|
|
name => "SMSC LPC47N252 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x0e,
|
|
logdev => 0x09,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47S42x Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x57,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47S45x Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x62,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC LPC47U33x Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x54,
|
|
logdev => 0x0a,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC SCH3112 Super IO",
|
|
driver => "dme1737",
|
|
devid => 0x7c,
|
|
logdev => 0x0a,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH3114 Super IO",
|
|
driver => "dme1737",
|
|
devid => 0x7d,
|
|
logdev => 0x0a,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH3116 Super IO",
|
|
driver => "dme1737",
|
|
devid => 0x7f,
|
|
logdev => 0x0a,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH4307 Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x90,
|
|
logdev => 0x08,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "SMSC SCH5027D-NW Super IO",
|
|
# Hardware monitoring features are accessed on the SMBus
|
|
driver => "via-smbus-only",
|
|
devid => 0x89,
|
|
}, {
|
|
name => "SMSC SCH5127 Super IO",
|
|
driver => "dme1737",
|
|
devid => 0x86,
|
|
logdev => 0x0a,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH5307-NS Super IO",
|
|
driver => "smsc47b397",
|
|
devid => 0x81,
|
|
logdev => 0x08,
|
|
features => FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH5317 Super IO",
|
|
driver => "smsc47b397",
|
|
devid => 0x85,
|
|
logdev => 0x08,
|
|
features => FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH5317 Super IO",
|
|
# The SCH5317 shows up twice in this list because it can return either
|
|
# 0x85 or 0x8c as its device ID.
|
|
driver => "smsc47b397",
|
|
devid => 0x8c,
|
|
logdev => 0x08,
|
|
features => FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "SMSC SCH5504-NS Super IO",
|
|
# No datasheet
|
|
driver => "not-a-sensor",
|
|
devid => 0x79,
|
|
}, {
|
|
name => "SMSC SCH5514D-NS Super IO",
|
|
# No datasheet
|
|
driver => "not-a-sensor",
|
|
devid => 0x83,
|
|
}
|
|
);
|
|
|
|
# Non-standard SMSC chip list. These chips differ from the standard ones
|
|
# listed above in that the device ID register address is 0x0d instead of
|
|
# 0x20 (as specified by the ISA PNP spec).
|
|
@superio_ids_smsc_ns = (
|
|
{
|
|
name => "SMSC FDC37C665 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x65,
|
|
}, {
|
|
name => "SMSC FDC37C666 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x66,
|
|
}, {
|
|
name => "SMSC FDC37C669 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x03,
|
|
}, {
|
|
name => "SMSC FDC37N769 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x28,
|
|
}, {
|
|
name => "SMSC LPC47N227 Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x5a,
|
|
}
|
|
);
|
|
|
|
@superio_ids_winbond = (
|
|
{
|
|
name => "VIA VT1211 Super IO Sensors",
|
|
driver => "vt1211",
|
|
devid => 0x3c,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "VIA VT1212 Super IO Lite", # in 100 pin TQFP package
|
|
driver => "not-a-sensor",
|
|
devid => 0x3e,
|
|
}, {
|
|
name => "VIA VT1212 Super IO Lite", # in 48 pin LQFP package
|
|
driver => "not-a-sensor",
|
|
devid => 0x3f,
|
|
}, {
|
|
name => "Winbond W83627HF/F/HG/G Super IO Sensors",
|
|
driver => "w83627hf",
|
|
devid => 0x52,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83627THF/THG Super IO Sensors",
|
|
driver => "w83627hf",
|
|
devid => 0x82,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83637HF/HG Super IO Sensors",
|
|
driver => "w83627hf",
|
|
devid => 0x70,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83687THF Super IO Sensors",
|
|
driver => "w83627hf",
|
|
devid => 0x85,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83697HF/F/HG Super IO Sensors",
|
|
driver => "w83627hf",
|
|
devid => 0x60,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83697SF/UF/UG Super IO PWM",
|
|
driver => "to-be-written",
|
|
devid => 0x68,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83627EHF/EF/EHG/EG Super IO Sensors",
|
|
driver => "w83627ehf",
|
|
# W83627EHF datasheet says 0x886x but 0x8853 was seen, thus the
|
|
# broader mask. W83627EHG was seen with ID 0x8863.
|
|
devid => 0x8840,
|
|
devid_mask => 0xFFC0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83627DHG Super IO Sensors",
|
|
driver => "w83627ehf",
|
|
devid => 0xA020,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83627DHG-P Super IO Sensors",
|
|
driver => "w83627ehf",
|
|
devid => 0xB070,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83627UHG Super IO Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0xA230,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83667HG Super IO Sensors",
|
|
driver => "w83627ehf",
|
|
devid => 0xA510,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Nuvoton W83667HG-B Super IO Sensors",
|
|
driver => "to-be-written", # Probably w83627ehf
|
|
devid => 0xB350,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Nuvoton W83677HG-I Super IO Sensors",
|
|
driver => "to-be-written", # Probably w83627ehf
|
|
devid => 0xB470,
|
|
devid_mask => 0xFFF0,
|
|
logdev => 0x0b,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Winbond W83L517D Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x61,
|
|
}, {
|
|
name => "Fintek F71805F/FG Super IO Sensors",
|
|
driver => "f71805f",
|
|
devid => 0x0406,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71862FG Super IO Sensors",
|
|
driver => "f71882fg",
|
|
devid => 0x0601,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71869FG Super IO Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x0814,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71806FG/F71872FG Super IO Sensors",
|
|
driver => "f71805f",
|
|
devid => 0x0341,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71858DG Super IO Sensors",
|
|
driver => "f71882fg",
|
|
devid => 0x0507,
|
|
logdev => 0x02,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71882FG/F71883FG Super IO Sensors",
|
|
driver => "f71882fg",
|
|
devid => 0x0541,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F71889FG Super IO Sensors",
|
|
driver => "f71882fg",
|
|
devid => 0x0723,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "Fintek F81216D Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x0208,
|
|
}, {
|
|
name => "Fintek F81218D Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x0206,
|
|
}, {
|
|
name => "Asus F8000 Super IO",
|
|
driver => "f71882fg",
|
|
devid => 0x0581,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
# Shouldn't be in this family, but seems to be still.
|
|
name => "ITE IT8708F Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x8708,
|
|
}, {
|
|
# Shouldn't be in this family, but seems to be still.
|
|
name => "ITE IT8710F Super IO",
|
|
driver => "not-a-sensor",
|
|
devid => 0x8710,
|
|
}
|
|
);
|
|
|
|
@superio_ids_ite = (
|
|
{
|
|
name => "ITE IT8702F Super IO Fan Sensors",
|
|
driver => "to-be-written",
|
|
devid => 0x8702,
|
|
logdev => 0x04,
|
|
features => FEAT_FAN,
|
|
}, {
|
|
name => "ITE IT8705F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8705,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "ITE IT8712F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8712,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "ITE IT8716F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8716,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "ITE IT8718F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8718,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "ITE IT8720F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8720,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}, {
|
|
name => "ITE IT8726F Super IO Sensors",
|
|
driver => "it87",
|
|
devid => 0x8726,
|
|
logdev => 0x04,
|
|
features => FEAT_IN | FEAT_FAN | FEAT_TEMP,
|
|
}
|
|
);
|
|
|
|
# Entries are grouped by family. Each family entry has the following fields:
|
|
# family: The family name
|
|
# guess (optional): Typical logical device address. This lets us do
|
|
# generic probing if we fail to recognize the chip.
|
|
# enter: The password sequence to write to the address register
|
|
# chips: Array of chips
|
|
# The order of families matters, because we stop as soon as one family
|
|
# succeeds. So we have to list families with shorter password sequences
|
|
# first.
|
|
@superio_ids = (
|
|
{
|
|
family => "National Semiconductor",
|
|
enter =>
|
|
{
|
|
0x2e => [],
|
|
0x4e => [],
|
|
},
|
|
chips => \@superio_ids_natsemi,
|
|
}, {
|
|
family => "SMSC",
|
|
enter =>
|
|
{
|
|
0x2e => [0x55],
|
|
0x4e => [0x55],
|
|
},
|
|
chips => \@superio_ids_smsc,
|
|
ns_detect => \&smsc_ns_detect_superio,
|
|
ns_chips => \@superio_ids_smsc_ns,
|
|
}, {
|
|
family => "VIA/Winbond/Nuvoton/Fintek",
|
|
guess => 0x290,
|
|
enter =>
|
|
{
|
|
0x2e => [0x87, 0x87],
|
|
0x4e => [0x87, 0x87],
|
|
},
|
|
chips => \@superio_ids_winbond,
|
|
}, {
|
|
family => "ITE",
|
|
guess => 0x290,
|
|
enter =>
|
|
{
|
|
0x2e => [0x87, 0x01, 0x55, 0x55],
|
|
0x4e => [0x87, 0x01, 0x55, 0xaa],
|
|
},
|
|
chips => \@superio_ids_ite,
|
|
}
|
|
);
|
|
|
|
# Drivers for bridge, CPU and memory embedded sensors
|
|
# Each entry must have the following fields:
|
|
# name: The device name
|
|
# driver: The driver name. Put "to-be-written" if no driver is available.
|
|
# detect: Detection callback function. No parameter will be passed to
|
|
# this function, it must use global lists of PCI devices, CPU,
|
|
# etc. It must return a confidence value, undef if no supported
|
|
# CPU is found.
|
|
use vars qw(@cpu_ids);
|
|
|
|
@cpu_ids = (
|
|
{
|
|
name => "Silicon Integrated Systems SIS5595",
|
|
driver => "sis5595",
|
|
detect => \&sis5595_pci_detect,
|
|
}, {
|
|
name => "VIA VT82C686 Integrated Sensors",
|
|
driver => "via686a",
|
|
detect => \&via686a_pci_detect,
|
|
}, {
|
|
name => "VIA VT8231 Integrated Sensors",
|
|
driver => "vt8231",
|
|
detect => \&via8231_pci_detect,
|
|
}, {
|
|
name => "AMD K8 thermal sensors",
|
|
driver => "k8temp",
|
|
detect => \&k8temp_pci_detect,
|
|
}, {
|
|
name => "AMD Family 11h thermal sensors",
|
|
driver => "to-be-written",
|
|
detect => \&fam11h_pci_detect,
|
|
}, {
|
|
name => "Intel Core family thermal sensor",
|
|
driver => "coretemp",
|
|
detect => \&coretemp_detect,
|
|
}, {
|
|
name => "Intel AMB FB-DIMM thermal sensor",
|
|
driver => "i5k_amb",
|
|
detect => \&intel_amb_detect,
|
|
}, {
|
|
name => "VIA C7 thermal and voltage sensors",
|
|
driver => "c7temp",
|
|
detect => \&c7temp_detect,
|
|
}
|
|
);
|
|
|
|
#######################
|
|
# AUXILIARY FUNCTIONS #
|
|
#######################
|
|
|
|
# $_[0] is the sought value
|
|
# $_[1..] is the list to seek in
|
|
# Returns: 1 if found, 0 if not.
|
|
sub contains
|
|
{
|
|
my $sought = shift;
|
|
local $_;
|
|
|
|
foreach (@_) {
|
|
return 1 if $sought eq $_;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# Address can be decimal or hexadecimal
|
|
sub valid_address
|
|
{
|
|
my $value = shift;
|
|
|
|
if ($value !~ m/^(0x[0-9a-f]+|[0-9]+)$/i) {
|
|
print "$value is not a valid address, sorry.\n";
|
|
exit -1;
|
|
}
|
|
$value = oct($value) if $value =~ m/^0x/i;
|
|
|
|
return $value;
|
|
}
|
|
|
|
sub parse_not_to_scan
|
|
{
|
|
my ($min, $max, $to_parse) = @_;
|
|
my @ranges = split /\s*, \s*/, $to_parse;
|
|
my @res;
|
|
my $range;
|
|
|
|
foreach $range (@ranges) {
|
|
my ($start, $end) = split /\s*-\s*/, $range;
|
|
$start = valid_address($start);
|
|
if (defined $end) {
|
|
$end = valid_address($end);
|
|
if ($end <= $start) {
|
|
print "$start-$end is not a valid range, sorry.\n";
|
|
exit -1;
|
|
}
|
|
$start = $min if $start < $min;
|
|
$end = $max if $end > $max;
|
|
push @res, ($start..$end);
|
|
} else {
|
|
push @res, $start if $start >= $min and $start <= $max;
|
|
}
|
|
}
|
|
|
|
return sort { $a <=> $b } @res;
|
|
}
|
|
|
|
# $_[0]: Reference to list 1
|
|
# $_[1]: Reference to list 2
|
|
# Result: 0 if they have no elements in common, 1 if they have
|
|
# Elements must be numeric.
|
|
sub any_list_match
|
|
{
|
|
my ($list1, $list2) = @_;
|
|
my ($el1, $el2);
|
|
|
|
foreach $el1 (@$list1) {
|
|
foreach $el2 (@$list2) {
|
|
return 1 if $el1 == $el2;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
###################
|
|
# I/O PORT ACCESS #
|
|
###################
|
|
|
|
sub initialize_ioports
|
|
{
|
|
sysopen(IOPORTS, "/dev/port", O_RDWR)
|
|
or die "/dev/port: $!\n";
|
|
binmode(IOPORTS);
|
|
}
|
|
|
|
sub close_ioports
|
|
{
|
|
close(IOPORTS);
|
|
}
|
|
|
|
# $_[0]: port to read
|
|
# Returns: -1 on failure, read value on success.
|
|
sub inb
|
|
{
|
|
my ($res, $nrchars);
|
|
sysseek(IOPORTS, $_[0], 0) or return -1;
|
|
$nrchars = sysread(IOPORTS, $res, 1);
|
|
return -1 if not defined $nrchars or $nrchars != 1;
|
|
$res = unpack("C", $res);
|
|
return $res;
|
|
}
|
|
|
|
# $_[0]: port to write
|
|
# $_[1]: value to write
|
|
# We assume this can't fail.
|
|
sub outb
|
|
{
|
|
sysseek(IOPORTS, $_[0], 0);
|
|
syswrite(IOPORTS, pack("C", $_[1]), 1);
|
|
}
|
|
|
|
# $_[0]: Address register
|
|
# $_[1]: Data register
|
|
# $_[2]: Register to read
|
|
# Returns: read value
|
|
sub isa_read_byte
|
|
{
|
|
outb($_[0], $_[2]);
|
|
return inb($_[1]);
|
|
}
|
|
|
|
# $_[0]: Base address
|
|
# $_[1]: Register to read
|
|
# Returns: read value
|
|
# This one can be used for any ISA chip with index register at
|
|
# offset 5 and data register at offset 6.
|
|
sub isa_read_i5d6
|
|
{
|
|
my ($addr, $reg) = @_;
|
|
return isa_read_byte($addr + 5, $addr + 6, $reg);
|
|
}
|
|
|
|
#################
|
|
# AUTODETECTION #
|
|
#################
|
|
|
|
use vars qw($dev_i2c $sysfs_root);
|
|
|
|
sub initialize_conf
|
|
{
|
|
my $use_devfs = 0;
|
|
open(local *INPUTFILE, "/proc/mounts") or die "Can't access /proc/mounts!";
|
|
local $_;
|
|
while (<INPUTFILE>) {
|
|
if (m@^\w+ /dev devfs @) {
|
|
$use_devfs = 1;
|
|
$dev_i2c = '/dev/i2c/';
|
|
}
|
|
if (m@^\S+ (/\w+) sysfs @) {
|
|
$sysfs_root = $1;
|
|
}
|
|
}
|
|
close(INPUTFILE);
|
|
|
|
# We need sysfs for many things
|
|
if (!defined $sysfs_root) {
|
|
print "Sysfs not mounted?\n";
|
|
exit -1;
|
|
}
|
|
|
|
my $use_udev = 0;
|
|
if (open(*INPUTFILE, '/etc/udev/udev.conf')) {
|
|
while (<INPUTFILE>) {
|
|
next unless m/^\s*udev_db\s*=\s*\"([^"]*)\"/
|
|
|| m/^\s*udev_db\s*=\s*(\S+)/;
|
|
if (-e $1) {
|
|
$use_udev = 1;
|
|
$dev_i2c = '/dev/i2c-';
|
|
}
|
|
last;
|
|
}
|
|
close(INPUTFILE);
|
|
}
|
|
|
|
if (!$use_udev) {
|
|
# Try some known default udev db locations, just in case
|
|
if (-e '/dev/.udev.tdb' || -e '/dev/.udev'
|
|
|| -e '/dev/.udevdb') {
|
|
$use_udev = 1;
|
|
$dev_i2c = '/dev/i2c-';
|
|
}
|
|
}
|
|
|
|
if (!($use_devfs || $use_udev)) {
|
|
if (! -c '/dev/i2c-0' && -x '/sbin/MAKEDEV') {
|
|
system("/sbin/MAKEDEV i2c");
|
|
}
|
|
if (! -c '/dev/i2c-0' && -x '/dev/MAKEDEV') {
|
|
system("/dev/MAKEDEV i2c");
|
|
}
|
|
if (-c '/dev/i2c-0') {
|
|
$dev_i2c = '/dev/i2c-';
|
|
} else { # default
|
|
print "No i2c device files found.\n";
|
|
exit -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# [0] -> VERSION
|
|
# [1] -> PATCHLEVEL
|
|
# [2] -> SUBLEVEL
|
|
# [3] -> EXTRAVERSION
|
|
#
|
|
use vars qw(@kernel_version $kernel_arch);
|
|
|
|
sub initialize_kernel_version
|
|
{
|
|
`uname -r` =~ /(\d+)\.(\d+)\.(\d+)(.*)/;
|
|
@kernel_version = ($1, $2, $3, $4);
|
|
chomp($kernel_arch = `uname -m`);
|
|
|
|
# We only support kernels >= 2.6.5
|
|
if (!kernel_version_at_least(2, 6, 5)) {
|
|
print "Kernel version is unsupported (too old, >= 2.6.5 needed)\n";
|
|
exit -1;
|
|
}
|
|
}
|
|
|
|
sub kernel_version_at_least
|
|
{
|
|
my ($vers, $plvl, $slvl) = @_;
|
|
return 1 if ($kernel_version[0] > $vers ||
|
|
($kernel_version[0] == $vers &&
|
|
($kernel_version[1] > $plvl ||
|
|
($kernel_version[1] == $plvl &&
|
|
($kernel_version[2] >= $slvl)))));
|
|
return 0;
|
|
}
|
|
|
|
# @cpu is a list of reference to hashes, one hash per CPU.
|
|
# Each entry has the following keys: vendor_id, cpu family, model,
|
|
# model name and stepping, directly taken from /proc/cpuinfo.
|
|
use vars qw(@cpu);
|
|
|
|
sub initialize_cpu_list
|
|
{
|
|
local $_;
|
|
my $entry;
|
|
|
|
open(local *INPUTFILE, "/proc/cpuinfo") or die "Can't access /proc/cpuinfo!";
|
|
while (<INPUTFILE>) {
|
|
if (m/^processor\s*:\s*(\d+)/) {
|
|
push @cpu, $entry if scalar keys(%{$entry}); # Previous entry
|
|
$entry = {}; # New entry
|
|
next;
|
|
}
|
|
if (m/^(vendor_id|cpu family|model|model name|stepping)\s*:\s*(.+)$/) {
|
|
my $k = $1;
|
|
my $v = $2;
|
|
$v =~ s/\s+/ /g; # Merge multiple spaces
|
|
$v =~ s/ $//; # Trim trailing space
|
|
$entry->{$k} = $v;
|
|
next;
|
|
}
|
|
}
|
|
close(INPUTFILE);
|
|
push @cpu, $entry if scalar keys(%{$entry}); # Last entry
|
|
}
|
|
|
|
# @i2c_adapters is a list of references to hashes, one hash per I2C/SMBus
|
|
# adapter present on the system. Each entry has the following keys: path,
|
|
# parent, name (directly taken from sysfs), driver and autoload.
|
|
use vars qw(@i2c_adapters);
|
|
|
|
# Find out whether the driver would be automatically loaded by the modalias
|
|
# mechanism.
|
|
sub device_driver_autoloads
|
|
{
|
|
my $device = shift;
|
|
|
|
my $modalias = sysfs_device_attribute($device, "modalias");
|
|
return 0 unless defined($modalias);
|
|
|
|
# At the moment we only handle PCI and USB drivers. Other driver
|
|
# types, most notably platform and i2c drivers, do not follow the
|
|
# device driver model to the letter, and often create their own
|
|
# devices. Such drivers appear to be autoloaded when they are not.
|
|
return 0 unless $modalias =~ m/^(pci|usb):/;
|
|
|
|
my $result = `modprobe -n -v --first-time $modalias 2>&1`;
|
|
return ($result =~ m/^insmod/ ||
|
|
$result =~ m/\balready in kernel\b/);
|
|
}
|
|
|
|
sub initialize_i2c_adapters_list
|
|
{
|
|
my ($entry, $base_dir, $have_i2c_adapter_class);
|
|
local $_;
|
|
|
|
if (-d "${sysfs_root}/class/i2c-adapter") {
|
|
$base_dir = "${sysfs_root}/class/i2c-adapter";
|
|
$have_i2c_adapter_class = 1;
|
|
} else {
|
|
$base_dir = "${sysfs_root}/bus/i2c/devices";
|
|
$have_i2c_adapter_class = 0;
|
|
}
|
|
opendir(local *ADAPTERS, $base_dir) or return;
|
|
|
|
while (defined($_ = readdir(ADAPTERS))) {
|
|
next unless m/^i2c-(\d+)$/;
|
|
my $nr = $1;
|
|
$entry = {}; # New entry
|
|
|
|
# The layout in sysfs has changed over the years
|
|
if ($have_i2c_adapter_class) {
|
|
my $link = readlink("${base_dir}/i2c-$nr/device");
|
|
if (!defined $link) {
|
|
$entry->{path} = "${base_dir}/i2c-$nr";
|
|
$entry->{parent} = "${base_dir}/i2c-$nr";
|
|
} elsif ($link =~ m/^(.*)\/i2c-$nr$/) {
|
|
$entry->{path} = "${base_dir}/i2c-$nr/device";
|
|
$entry->{parent} = "${base_dir}/i2c-$nr/$1";
|
|
} else {
|
|
$entry->{path} = "${base_dir}/i2c-$nr";
|
|
$entry->{parent} = "$entry->{path}/device";
|
|
}
|
|
} else {
|
|
my $link = readlink("${base_dir}/i2c-$nr");
|
|
$link =~ s/\/i2c-$nr$//;
|
|
$entry->{path} = "${base_dir}/i2c-$nr";
|
|
$entry->{parent} = "${base_dir}/$link";
|
|
}
|
|
|
|
$entry->{name} = sysfs_device_attribute($entry->{path}, "name");
|
|
next if $entry->{name} eq "ISA main adapter";
|
|
|
|
# First try to get the I2C adapter driver name from sysfs,
|
|
# and if it fails, fall back to searching our list of known
|
|
# I2C adapters.
|
|
$entry->{driver} = sysfs_device_driver($entry->{parent})
|
|
|| find_i2c_adapter_driver($entry->{name})
|
|
|| 'UNKNOWN';
|
|
|
|
$entry->{autoload} = device_driver_autoloads($entry->{parent});
|
|
$i2c_adapters[$nr] = $entry;
|
|
}
|
|
closedir(ADAPTERS);
|
|
}
|
|
|
|
# %hwmon_autoloaded is a list of hwmon drivers which are autoloaded
|
|
# (typically by udev.) We don't need to load these drivers ourselves.
|
|
use vars qw(%hwmon_autoloaded);
|
|
|
|
sub initialize_hwmon_autoloaded
|
|
{
|
|
my $class_dir = "${sysfs_root}/class/hwmon";
|
|
opendir(local *HWMON, $class_dir) or return;
|
|
|
|
my ($hwmon, $driver);
|
|
while (defined($hwmon = readdir(HWMON))) {
|
|
next unless $hwmon =~ m/^hwmon\d+$/;
|
|
|
|
$driver = sysfs_device_driver("${class_dir}/$hwmon/device");
|
|
next unless defined($driver);
|
|
|
|
if (device_driver_autoloads("${class_dir}/$hwmon/device")) {
|
|
$driver =~ tr/-/_/;
|
|
$hwmon_autoloaded{$driver}++
|
|
}
|
|
}
|
|
closedir(HWMON);
|
|
}
|
|
|
|
sub hwmon_is_autoloaded
|
|
{
|
|
my $driver = shift;
|
|
$driver =~ tr/-/_/;
|
|
return exists($hwmon_autoloaded{$driver});
|
|
}
|
|
|
|
###########
|
|
# MODULES #
|
|
###########
|
|
|
|
use vars qw(%modules_list %modules_supported @modules_we_loaded);
|
|
|
|
sub initialize_modules_list
|
|
{
|
|
local $_;
|
|
|
|
open(local *INPUTFILE, "/proc/modules") or return;
|
|
while (<INPUTFILE>) {
|
|
tr/-/_/; # Probably not needed
|
|
$modules_list{$1} = 1 if m/^(\S*)/;
|
|
}
|
|
}
|
|
|
|
sub is_module_loaded
|
|
{
|
|
my $module = shift;
|
|
$module =~ tr/-/_/;
|
|
return exists $modules_list{$module}
|
|
}
|
|
|
|
sub load_module
|
|
{
|
|
my $module = shift;
|
|
|
|
return if is_module_loaded($module);
|
|
|
|
system("modprobe", $module);
|
|
if (($? >> 8) != 0) {
|
|
print "Failed to load module $module.\n";
|
|
return -1;
|
|
}
|
|
|
|
print "Module $module loaded successfully.\n";
|
|
push @modules_we_loaded, $module;
|
|
|
|
# Update the list of loaded modules
|
|
my $normalized = $module;
|
|
$normalized =~ tr/-/_/;
|
|
$modules_list{$normalized} = 1;
|
|
}
|
|
|
|
sub initialize_modules_supported
|
|
{
|
|
foreach my $chip (@chip_ids) {
|
|
next if $chip->{driver} eq "to-be-written";
|
|
next if $chip->{driver} eq "use-isa-instead";
|
|
|
|
my $normalized = $chip->{driver};
|
|
$normalized =~ tr/-/_/;
|
|
$modules_supported{$normalized}++;
|
|
}
|
|
}
|
|
|
|
sub unload_modules
|
|
{
|
|
return unless @modules_we_loaded;
|
|
|
|
# Attempt to unload all kernel drivers we loaded ourselves
|
|
while (my $module = pop @modules_we_loaded) {
|
|
print "Unloading $module... ";
|
|
system("modprobe -r $module 2> /dev/null");
|
|
if (($? >> 8) == 0) {
|
|
print "OK\n";
|
|
} else {
|
|
print "failed\n";
|
|
}
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
############
|
|
# DMI DATA #
|
|
############
|
|
|
|
use vars qw(%dmi $dmidecode_ok);
|
|
|
|
# Returns: 1 if dmidecode is recent enough, 0 if not
|
|
# Cache the result in case it is needed again later.
|
|
sub check_dmidecode_version
|
|
{
|
|
return $dmidecode_ok if defined $dmidecode_ok;
|
|
|
|
my $version;
|
|
if (open(local *DMIDECODE, "dmidecode --version 2>/dev/null |")) {
|
|
$version = <DMIDECODE>;
|
|
close(DMIDECODE);
|
|
chomp $version if defined $version;
|
|
}
|
|
|
|
if (!defined $version
|
|
|| !($version =~ m/^(\d+).(\d+)$/)
|
|
|| !(($1 == 2 && $2 >= 7) || $1 > 2)) {
|
|
print "# DMI data unavailable, please consider installing dmidecode 2.7\n".
|
|
"# or later for better results.\n";
|
|
$dmidecode_ok = 0;
|
|
} else {
|
|
$dmidecode_ok = 1;
|
|
}
|
|
|
|
return $dmidecode_ok;
|
|
}
|
|
|
|
sub initialize_dmi_data
|
|
{
|
|
my %items = (
|
|
# sysfs file name => dmidecode string name
|
|
sys_vendor => 'system-manufacturer',
|
|
product_name => 'system-product-name',
|
|
product_version => 'system-version',
|
|
board_vendor => 'baseboard-manufacturer',
|
|
board_name => 'baseboard-product-name',
|
|
board_version => 'baseboard-product-name',
|
|
chassis_type => 'chassis-type',
|
|
);
|
|
# Many BIOS have broken DMI data, filter it out
|
|
my %fake = (
|
|
'System Manufacturer' => 1,
|
|
'System Name' => 1,
|
|
);
|
|
my $dmi_id_dir;
|
|
|
|
# First try reading the strings from sysfs if available
|
|
if (-d ($dmi_id_dir = "$sysfs_root/devices/virtual/dmi/id") # kernel >= 2.6.28
|
|
|| -d ($dmi_id_dir = "$sysfs_root/class/dmi/id")) {
|
|
foreach my $k (keys %items) {
|
|
$dmi{$k} = sysfs_device_attribute($dmi_id_dir, $k);
|
|
}
|
|
} else {
|
|
# Else fallback on calling dmidecode
|
|
return unless check_dmidecode_version();
|
|
|
|
foreach my $k (keys %items) {
|
|
next unless open(local *DMIDECODE,
|
|
"dmidecode -s $items{$k} 2>/dev/null |");
|
|
$dmi{$k} = <DMIDECODE>;
|
|
close(DMIDECODE);
|
|
}
|
|
}
|
|
|
|
# Strip trailing white-space, discard empty field
|
|
foreach my $k (keys %dmi) {
|
|
if (!defined $dmi{$k}) {
|
|
delete $dmi{$k};
|
|
next;
|
|
}
|
|
$dmi{$k} =~ s/\s*$//;
|
|
delete $dmi{$k} if $dmi{$k} eq '' || exists $fake{$dmi{$k}};
|
|
}
|
|
}
|
|
|
|
sub print_dmi_summary
|
|
{
|
|
my ($system, $board);
|
|
if (defined $dmi{sys_vendor} && defined $dmi{product_name}) {
|
|
$system = "$dmi{sys_vendor} $dmi{product_name}";
|
|
}
|
|
if (defined $dmi{board_vendor} && defined $dmi{board_name}) {
|
|
$board = "$dmi{board_vendor} $dmi{board_name}";
|
|
}
|
|
|
|
if (defined $system) {
|
|
print "# System: $system";
|
|
print " (laptop)" if (is_laptop());
|
|
print "\n";
|
|
}
|
|
print "# Board: $board\n" if defined $board
|
|
&& (!defined $system || $system ne $board);
|
|
}
|
|
|
|
sub dmi_match
|
|
{
|
|
my $key = shift;
|
|
my $value;
|
|
|
|
return unless defined $dmi{$key};
|
|
while (defined ($value = shift)) {
|
|
return 1 if $dmi{$key} =~ m/\b$value\b/i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub is_laptop
|
|
{
|
|
return 0 unless $dmi{chassis_type};
|
|
return 1 if $dmi{chassis_type} =~ m/(Laptop|Notebook|Hand Held)/i;
|
|
return 1 if $dmi{chassis_type} =~ m/^(9|10|11|14)$/;
|
|
return 0;
|
|
}
|
|
|
|
#################
|
|
# SYSFS HELPERS #
|
|
#################
|
|
|
|
# From a sysfs device path, return the driver (module) name, or undef
|
|
sub sysfs_device_driver
|
|
{
|
|
my $device = shift;
|
|
|
|
my $link = readlink("$device/driver/module");
|
|
return unless defined $link;
|
|
return basename($link);
|
|
}
|
|
|
|
# From a sysfs device path, return the subsystem name, or undef
|
|
sub sysfs_device_subsystem
|
|
{
|
|
my $device = shift;
|
|
|
|
my $link = readlink("$device/subsystem");
|
|
return unless defined $link;
|
|
return basename($link);
|
|
}
|
|
|
|
# From a sysfs device path and an attribute name, return the attribute
|
|
# value, or undef
|
|
sub sysfs_device_attribute
|
|
{
|
|
my ($device, $attr) = @_;
|
|
my $value;
|
|
|
|
open(local *FILE, "$device/$attr") or return;
|
|
$value = <FILE>;
|
|
close(FILE);
|
|
return unless defined $value;
|
|
|
|
chomp($value);
|
|
return $value;
|
|
}
|
|
|
|
##############
|
|
# PCI ACCESS #
|
|
##############
|
|
|
|
use vars qw(%pci_list);
|
|
|
|
# This function returns a list of hashes. Each hash has some PCI information:
|
|
# 'domain', 'bus', 'slot' and 'func' uniquely identify a PCI device in a
|
|
# computer; 'vendid' and 'devid' uniquely identify a type of device.
|
|
# 'class' lets us spot unknown SMBus adapters.
|
|
sub read_sys_dev_pci
|
|
{
|
|
my $devices = shift;
|
|
my ($dev, @pci_list);
|
|
|
|
opendir(local *DEVICES, "$devices")
|
|
or die "$devices: $!";
|
|
|
|
while (defined($dev = readdir(DEVICES))) {
|
|
my %record;
|
|
next unless $dev =~
|
|
m/^(?:([\da-f]+):)?([\da-f]+):([\da-f]+)\.([\da-f]+)$/;
|
|
|
|
$record{domain} = hex $1;
|
|
$record{bus} = hex $2;
|
|
$record{slot} = hex $3;
|
|
$record{func} = hex $4;
|
|
|
|
$record{vendid} = oct sysfs_device_attribute("$devices/$dev",
|
|
"vendor");
|
|
$record{devid} = oct sysfs_device_attribute("$devices/$dev",
|
|
"device");
|
|
$record{class} = (oct sysfs_device_attribute("$devices/$dev",
|
|
"class")) >> 8;
|
|
|
|
push @pci_list, \%record;
|
|
}
|
|
|
|
return \@pci_list;
|
|
}
|
|
|
|
sub initialize_pci
|
|
{
|
|
my $pci_list;
|
|
local $_;
|
|
|
|
$pci_list = read_sys_dev_pci("$sysfs_root/bus/pci/devices");
|
|
|
|
# Note that we lose duplicate devices at this point, but we don't
|
|
# really care. What matters to us is which unique devices are present,
|
|
# not how many of each.
|
|
%pci_list = map {
|
|
sprintf("%04x:%04x", $_->{vendid}, $_->{devid}) => $_
|
|
} @{$pci_list};
|
|
}
|
|
|
|
#####################
|
|
# ADAPTER DETECTION #
|
|
#####################
|
|
|
|
# Build and return a PCI device's bus ID
|
|
sub pci_busid
|
|
{
|
|
my $device = shift;
|
|
my $busid;
|
|
|
|
$busid = sprintf("\%02x:\%02x.\%x",
|
|
$device->{bus}, $device->{slot}, $device->{func});
|
|
$busid = sprintf("\%04x:", $device->{domain}) . $busid
|
|
if defined $device->{domain};
|
|
|
|
return $busid;
|
|
}
|
|
|
|
sub adapter_pci_detection
|
|
{
|
|
my ($key, $device, $try, %smbus, $count);
|
|
|
|
# Build a list of detected SMBus devices
|
|
foreach $key (keys %pci_list) {
|
|
$device = $pci_list{$key};
|
|
$smbus{$key}++
|
|
if exists $device->{class} &&
|
|
($device->{class} == 0x0c01 || # Access Bus
|
|
$device->{class} == 0x0c05); # SMBus
|
|
}
|
|
|
|
# Loop over the known I2C/SMBus adapters
|
|
foreach $try (@pci_adapters) {
|
|
$key = sprintf("%04x:%04x", $try->{vendid}, $try->{devid});
|
|
next unless exists $pci_list{$key};
|
|
|
|
$device = $pci_list{$key};
|
|
if ($try->{driver} eq "to-be-tested") {
|
|
print "\nWe are currently looking for testers for this adapter!\n".
|
|
"Please check http://www.lm-sensors.org/wiki/Devices\n".
|
|
"and/or contact us if you want to help.\n\n".
|
|
"Continue... ";
|
|
<STDIN>;
|
|
print "\n";
|
|
}
|
|
|
|
if ($try->{driver} =~ m/^to-be-/) {
|
|
printf "No known driver for device \%s: \%s\n",
|
|
pci_busid($device), $try->{procid};
|
|
} else {
|
|
printf "Using driver `\%s' for device \%s: \%s\n",
|
|
$try->{driver}, pci_busid($device),
|
|
$try->{procid};
|
|
$count++;
|
|
load_module($try->{driver});
|
|
}
|
|
|
|
# Delete from detected SMBus device list
|
|
delete $smbus{$key};
|
|
}
|
|
|
|
# Now see if there are unknown SMBus devices left
|
|
foreach $key (keys %smbus) {
|
|
$device = $pci_list{$key};
|
|
printf "Found unknown SMBus adapter \%04x:\%04x at \%s.\n",
|
|
$device->{vendid}, $device->{devid}, pci_busid($device);
|
|
}
|
|
|
|
print "Sorry, no supported PCI bus adapters found.\n"
|
|
unless $count;
|
|
}
|
|
|
|
# $_[0]: Adapter description as found in sysfs
|
|
sub find_i2c_adapter_driver
|
|
{
|
|
my $name = shift;
|
|
my $entry;
|
|
|
|
foreach $entry (@i2c_adapter_names) {
|
|
return $entry->{driver}
|
|
if $name =~ $entry->{match};
|
|
}
|
|
}
|
|
|
|
#############################
|
|
# I2C AND SMBUS /DEV ACCESS #
|
|
#############################
|
|
|
|
# This should really go into a separate module/package.
|
|
|
|
# These are copied from <linux/i2c-dev.h>
|
|
|
|
use constant IOCTL_I2C_SLAVE => 0x0703;
|
|
use constant IOCTL_I2C_FUNCS => 0x0705;
|
|
use constant IOCTL_I2C_SMBUS => 0x0720;
|
|
|
|
use constant SMBUS_READ => 1;
|
|
use constant SMBUS_WRITE => 0;
|
|
|
|
use constant SMBUS_QUICK => 0;
|
|
use constant SMBUS_BYTE => 1;
|
|
use constant SMBUS_BYTE_DATA => 2;
|
|
use constant SMBUS_WORD_DATA => 3;
|
|
|
|
use constant I2C_FUNC_SMBUS_QUICK => 0x00010000;
|
|
use constant I2C_FUNC_SMBUS_READ_BYTE => 0x00020000;
|
|
use constant I2C_FUNC_SMBUS_READ_BYTE_DATA => 0x00080000;
|
|
|
|
# Get the i2c adapter's functionalities
|
|
# $_[0]: Reference to an opened filehandle
|
|
# Returns: -1 on failure, functionality bitfield on success.
|
|
sub i2c_get_funcs
|
|
{
|
|
my $file = shift;
|
|
my $funcs = pack("L", 0); # Allocate space
|
|
|
|
ioctl($file, IOCTL_I2C_FUNCS, $funcs) or return -1;
|
|
$funcs = unpack("L", $funcs);
|
|
|
|
return $funcs;
|
|
}
|
|
|
|
# Select the device to communicate with through its address.
|
|
# $_[0]: Reference to an opened filehandle
|
|
# $_[1]: Address to select
|
|
# Returns: 0 on failure, 1 on success.
|
|
sub i2c_set_slave_addr
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
# Reset register data cache
|
|
@i2c_byte_cache = ();
|
|
|
|
$addr += 0; # Make sure it's a number not a string
|
|
ioctl($file, IOCTL_I2C_SLAVE, $addr) or return 0;
|
|
return 1;
|
|
}
|
|
|
|
# i2c_smbus_access is based upon the corresponding C function (see
|
|
# <linux/i2c-dev.h>). You should not need to call this directly.
|
|
# $_[0]: Reference to an opened filehandle
|
|
# $_[1]: SMBUS_READ for reading, SMBUS_WRITE for writing
|
|
# $_[2]: Command (usually register number)
|
|
# $_[3]: Transaction kind (SMBUS_BYTE, SMBUS_BYTE_DATA, etc.)
|
|
# $_[4]: Reference to an array used for input/output of data
|
|
# Returns: 0 on failure, 1 on success.
|
|
# Note that we need to get back to Integer boundaries through the 'x2'
|
|
# in the pack. This is very compiler-dependent; I wish there was some other
|
|
# way to do this.
|
|
sub i2c_smbus_access
|
|
{
|
|
my ($file, $read_write, $command, $size, $data) = @_;
|
|
my $data_array = pack("C32", @$data);
|
|
my $ioctl_data = pack("C2x2Ip", $read_write, $command, $size,
|
|
$data_array);
|
|
|
|
ioctl($file, IOCTL_I2C_SMBUS, $ioctl_data) or return 0;
|
|
@{$_[4]} = unpack("C32", $data_array);
|
|
return 1;
|
|
}
|
|
|
|
# $_[0]: Reference to an opened filehandle
|
|
# Returns: -1 on failure, the read byte on success.
|
|
sub i2c_smbus_read_byte
|
|
{
|
|
my ($file) = @_;
|
|
my @data;
|
|
|
|
i2c_smbus_access($file, SMBUS_READ, 0, SMBUS_BYTE, \@data)
|
|
or return -1;
|
|
return $data[0];
|
|
}
|
|
|
|
# $_[0]: Reference to an opened filehandle
|
|
# $_[1]: Command byte (usually register number)
|
|
# Returns: -1 on failure, the read byte on success.
|
|
# Read byte data values are cached by default. As we keep reading the
|
|
# same registers over and over again in the detection functions, and
|
|
# SMBus can be slow, caching results in a big performance boost.
|
|
sub i2c_smbus_read_byte_data
|
|
{
|
|
my ($file, $command, $nocache) = @_;
|
|
my @data;
|
|
|
|
return $i2c_byte_cache[$command]
|
|
if !$nocache && exists $i2c_byte_cache[$command];
|
|
|
|
i2c_smbus_access($file, SMBUS_READ, $command, SMBUS_BYTE_DATA, \@data)
|
|
or return -1;
|
|
return ($i2c_byte_cache[$command] = $data[0]);
|
|
}
|
|
|
|
# $_[0]: Reference to an opened filehandle
|
|
# $_[1]: Command byte (usually register number)
|
|
# Returns: -1 on failure, the read word on success.
|
|
# Use this function with care, some devices don't like word reads,
|
|
# so you should do as much of the detection as possible using byte reads,
|
|
# and only start using word reads when there is a good chance that
|
|
# the detection will succeed.
|
|
# Note: some devices use the wrong endianness.
|
|
sub i2c_smbus_read_word_data
|
|
{
|
|
my ($file, $command) = @_;
|
|
my @data;
|
|
i2c_smbus_access($file, SMBUS_READ, $command, SMBUS_WORD_DATA, \@data)
|
|
or return -1;
|
|
return $data[0] + 256 * $data[1];
|
|
}
|
|
|
|
# $_[0]: Reference to an opened filehandle
|
|
# $_[1]: Address
|
|
# $_[2]: Functionalities of this i2c adapter
|
|
# Returns: 1 on successful probing, 0 else.
|
|
# This function is meant to prevent AT24RF08 corruption and write-only
|
|
# chips locks. This is done by choosing the best probing method depending
|
|
# on the address range.
|
|
sub i2c_probe
|
|
{
|
|
my ($file, $addr, $funcs) = @_;
|
|
|
|
if (($addr >= 0x50 && $addr <= 0x5F)
|
|
|| ($addr >= 0x30 && $addr <= 0x37)) {
|
|
# This covers all EEPROMs we know of, including page protection
|
|
# addresses. Note that some page protection addresses will not
|
|
# reveal themselves with this, because they ack on write only,
|
|
# but this is probably better since some EEPROMs write-protect
|
|
# themselves permanently on almost any write to their page
|
|
# protection address.
|
|
return 0 unless ($funcs & I2C_FUNC_SMBUS_READ_BYTE);
|
|
return i2c_smbus_access($file, SMBUS_READ, 0, SMBUS_BYTE, []);
|
|
} elsif ($addr == 0x73) {
|
|
# Special case for FSC chips, as at least the Syleus locks
|
|
# up with our regular probe code. Note that to our current
|
|
# knowledge only FSC chips live on this address, and for them
|
|
# this probe method is safe.
|
|
return 0 unless ($funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA);
|
|
return i2c_smbus_access($file, SMBUS_READ, 0, SMBUS_BYTE_DATA, []);
|
|
} else {
|
|
return 0 unless ($funcs & I2C_FUNC_SMBUS_QUICK);
|
|
return i2c_smbus_access($file, SMBUS_WRITE, 0, SMBUS_QUICK, []);
|
|
}
|
|
}
|
|
|
|
# $_[0]: Reference to an opened file handle
|
|
# Returns: 1 if the device is safe to access, 0 else.
|
|
# This function is meant to prevent access to 1-register-only devices,
|
|
# which are designed to be accessed with SMBus receive byte and SMBus send
|
|
# byte transactions (i.e. short reads and short writes) and treat SMBus
|
|
# read byte as a real write followed by a read. The device detection
|
|
# routines would write random values to the chip with possibly very nasty
|
|
# results for the hardware. Note that this function won't catch all such
|
|
# chips, as it assumes that reads and writes relate to the same register,
|
|
# but that's the best we can do.
|
|
sub i2c_safety_check
|
|
{
|
|
my ($file) = @_;
|
|
my $data;
|
|
|
|
# First we receive a byte from the chip, and remember it.
|
|
$data = i2c_smbus_read_byte($file);
|
|
return 1 if ($data < 0);
|
|
|
|
# We receive a byte again; very likely to be the same for
|
|
# 1-register-only devices.
|
|
return 1 if (i2c_smbus_read_byte($file) != $data);
|
|
|
|
# Then we try a standard byte read, with a register offset equal to
|
|
# the byte we received; we should receive the same byte value in return.
|
|
return 1 if (i2c_smbus_read_byte_data($file, $data) != $data);
|
|
|
|
# Then we try a standard byte read, with a slightly different register
|
|
# offset; we should again receive the same byte value in return.
|
|
return 1 if (i2c_smbus_read_byte_data($file, $data ^ 1) != ($data ^ 1));
|
|
|
|
# Apprently this is a 1-register-only device, restore the original
|
|
# register value and leave it alone.
|
|
i2c_smbus_read_byte_data($file, $data);
|
|
return 0;
|
|
}
|
|
|
|
####################
|
|
# ADAPTER SCANNING #
|
|
####################
|
|
|
|
use vars qw(%chips_detected);
|
|
|
|
# We will build a complicated structure %chips_detected here, being a hash
|
|
# where keys are driver names and values are detected chip information in
|
|
# the form of a list of hashes of type 'detect_data'.
|
|
|
|
# Type detect_data:
|
|
# A hash
|
|
# with field 'i2c_devnr', contianing the /dev/i2c-* number of this
|
|
# adapter (if this is an I2C detection)
|
|
# with field 'i2c_addr', containing the I2C address of the detection;
|
|
# (if this is an I2C detection)
|
|
# with field 'i2c_sub_addrs', containing a reference to a list of
|
|
# other I2C addresses (if this is an I2C detection)
|
|
# with field 'isa_addr' containing the ISA address this chip is on
|
|
# (if this is an ISA detection)
|
|
# with field 'conf', containing the confidence level of this detection
|
|
# with field 'chipname', containing the chip name
|
|
# with optional field 'alias_detect', containing a reference to an alias
|
|
# detection function for this chip
|
|
|
|
# This adds a detection to the above structure.
|
|
# Not all possibilities of i2c_addr and i2c_sub_addrs are exhausted.
|
|
# In all normal cases, it should be all right.
|
|
# $_[0]: chip driver
|
|
# $_[1]: reference to data hash
|
|
# Returns: Nothing
|
|
sub add_i2c_to_chips_detected
|
|
{
|
|
my ($chipdriver, $datahash) = @_;
|
|
my ($i, $new_detected_ref, $detected_ref, $detected_entry,
|
|
$put_in_detected, @hash_addrs, @entry_addrs);
|
|
|
|
# First determine where the hash has to be added.
|
|
$chips_detected{$chipdriver} = []
|
|
unless exists $chips_detected{$chipdriver};
|
|
$new_detected_ref = $chips_detected{$chipdriver};
|
|
|
|
# Find out whether our new entry should go into the detected list
|
|
# or not. We compare all i2c addresses; if at least one matches,
|
|
# but our confidence value is lower, we assume this is a misdetection,
|
|
# in which case we simply discard our new entry.
|
|
@hash_addrs = ($datahash->{i2c_addr});
|
|
push @hash_addrs, @{$datahash->{i2c_sub_addrs}}
|
|
if exists $datahash->{i2c_sub_addrs};
|
|
$put_in_detected = 1;
|
|
FIND_LOOP:
|
|
foreach $detected_ref (values %chips_detected) {
|
|
foreach $detected_entry (@{$detected_ref}) {
|
|
next unless defined $detected_entry->{i2c_addr};
|
|
@entry_addrs = ($detected_entry->{i2c_addr});
|
|
push @entry_addrs, @{$detected_entry->{i2c_sub_addrs}}
|
|
if exists $detected_entry->{i2c_sub_addrs};
|
|
if ($detected_entry->{i2c_devnr} == $datahash->{i2c_devnr} &&
|
|
any_list_match(\@entry_addrs, \@hash_addrs)) {
|
|
if ($detected_entry->{conf} >= $datahash->{conf}) {
|
|
$put_in_detected = 0;
|
|
}
|
|
last FIND_LOOP;
|
|
}
|
|
}
|
|
}
|
|
|
|
return unless $put_in_detected;
|
|
|
|
# Here, we discard all entries which match at least in one main or
|
|
# sub address. This may not be the best idea to do, as it may remove
|
|
# detections without replacing them with second-best ones. Too bad.
|
|
foreach $detected_ref (values %chips_detected) {
|
|
for ($i = @$detected_ref-1; $i >=0; $i--) {
|
|
next unless defined $detected_ref->[$i]->{i2c_addr};
|
|
@entry_addrs = ($detected_ref->[$i]->{i2c_addr});
|
|
push @entry_addrs, @{$detected_ref->[$i]->{i2c_sub_addrs}}
|
|
if exists $detected_ref->[$i]->{i2c_sub_addrs};
|
|
if ($detected_ref->[$i]->{i2c_devnr} == $datahash->{i2c_devnr} &&
|
|
any_list_match(\@entry_addrs, \@hash_addrs)) {
|
|
splice @$detected_ref, $i, 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Now add the new entry to detected
|
|
push @$new_detected_ref, $datahash;
|
|
}
|
|
|
|
# This adds a detection to the above structure.
|
|
# $_[0]: chip driver
|
|
# $_[1]: reference to data hash
|
|
sub add_isa_to_chips_detected
|
|
{
|
|
my ($chipdriver, $datahash) = @_;
|
|
my ($i, $new_detected_ref, $detected_ref);
|
|
|
|
# First determine where the hash has to be added.
|
|
$chips_detected{$chipdriver} = []
|
|
unless exists $chips_detected{$chipdriver};
|
|
$new_detected_ref = $chips_detected{$chipdriver};
|
|
|
|
# Find out whether our new entry should go into the detected list
|
|
# or not. We only compare main isa_addr here, of course.
|
|
foreach $detected_ref (values %chips_detected) {
|
|
for ($i = 0; $i < @{$detected_ref}; $i++) {
|
|
if (exists $detected_ref->[$i]->{isa_addr} and
|
|
exists $datahash->{isa_addr} and
|
|
$detected_ref->[$i]->{isa_addr} == $datahash->{isa_addr}) {
|
|
if ($detected_ref->[$i]->{conf} < $datahash->{conf}) {
|
|
splice @$detected_ref, $i, 1;
|
|
push @$new_detected_ref, $datahash;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Not found? OK, put it in the detected list
|
|
push @$new_detected_ref, $datahash;
|
|
}
|
|
|
|
# $_[0]: reference to an array of chips which may be aliases of each other
|
|
sub find_aliases
|
|
{
|
|
my $detected = shift;
|
|
my ($isa, $i2c, $dev, $alias_detect, $is_alias);
|
|
|
|
for ($isa = 0; $isa < @{$detected}; $isa++) {
|
|
# Look for chips with an ISA address but no I2C address
|
|
next unless defined $detected->[$isa];
|
|
next unless $detected->[$isa]->{isa_addr};
|
|
next if defined $detected->[$isa]->{i2c_addr};
|
|
# Additionally, the chip must possibly have I2C aliases
|
|
next unless defined $detected->[$isa]->{alias_detect};
|
|
|
|
for ($i2c = 0; $i2c < @{$detected}; $i2c++) {
|
|
# Look for chips with an I2C address but no ISA address
|
|
next unless defined $detected->[$i2c];
|
|
next unless defined $detected->[$i2c]->{i2c_addr};
|
|
next if $detected->[$i2c]->{isa_addr};
|
|
# Additionally, it must have the same chip name
|
|
next unless $detected->[$i2c]->{chipname} eq
|
|
$detected->[$isa]->{chipname};
|
|
|
|
# We have potential aliases, check if they actually are
|
|
$dev = $dev_i2c.$detected->[$i2c]->{i2c_devnr};
|
|
open(local *FILE, $dev) or
|
|
print("Can't open $dev\n"),
|
|
next;
|
|
binmode(FILE);
|
|
i2c_set_slave_addr(\*FILE, $detected->[$i2c]->{i2c_addr}) or
|
|
print("Can't set I2C address for $dev\n"),
|
|
next;
|
|
|
|
initialize_ioports();
|
|
$alias_detect = $detected->[$isa]->{alias_detect};
|
|
$is_alias = &$alias_detect($detected->[$isa]->{isa_addr},
|
|
\*FILE,
|
|
$detected->[$i2c]->{i2c_addr});
|
|
close(FILE);
|
|
close_ioports();
|
|
|
|
next unless $is_alias;
|
|
# This is an alias: copy the I2C data into the ISA
|
|
# entry, then discard the I2C entry
|
|
$detected->[$isa]->{i2c_devnr} = $detected->[$i2c]->{i2c_devnr};
|
|
$detected->[$isa]->{i2c_addr} = $detected->[$i2c]->{i2c_addr};
|
|
$detected->[$isa]->{i2c_sub_addr} = $detected->[$i2c]->{i2c_sub_addr};
|
|
undef $detected->[$i2c];
|
|
last;
|
|
}
|
|
}
|
|
|
|
# The loops above may have made the chip list sparse, make it
|
|
# compact again
|
|
for ($isa = 0; $isa < @{$detected}; ) {
|
|
if (defined($detected->[$isa])) {
|
|
$isa++;
|
|
} else {
|
|
splice @{$detected}, $isa, 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# From the list of known I2C/SMBus devices, build a list of I2C addresses
|
|
# which are worth probing. There's no point in probing an address for which
|
|
# we don't know a single device, and probing some addresses has caused
|
|
# random trouble in the past.
|
|
sub i2c_addresses_to_scan
|
|
{
|
|
my @used;
|
|
my @addresses;
|
|
my $addr;
|
|
|
|
foreach my $chip (@chip_ids) {
|
|
next unless defined $chip->{i2c_addrs};
|
|
foreach $addr (@{$chip->{i2c_addrs}}) {
|
|
$used[$addr]++;
|
|
}
|
|
}
|
|
|
|
for ($addr = 0x03; $addr <= 0x77; $addr++) {
|
|
push @addresses, $addr if $used[$addr];
|
|
}
|
|
return \@addresses;
|
|
}
|
|
|
|
# $_[0]: The number of the adapter to scan
|
|
# $_[1]: Address
|
|
sub add_busy_i2c_address
|
|
{
|
|
my ($adapter_nr, $addr) = @_;
|
|
# If the address is busy, we can normally find out which driver
|
|
# requested it (if the kernel is recent enough, at least 2.6.16 and
|
|
# later are known to work), and we assume it is the right one.
|
|
my ($device, $driver, $new_hash);
|
|
|
|
$device = sprintf("$sysfs_root/bus/i2c/devices/\%d-\%04x",
|
|
$adapter_nr, $addr);
|
|
$driver = sysfs_device_driver($device);
|
|
|
|
if (!defined($driver)) {
|
|
printf("Client at address 0x%02x can not be probed - ".
|
|
"unload all client drivers first!\n", $addr);
|
|
return;
|
|
}
|
|
|
|
$new_hash = {
|
|
conf => 6, # Arbitrary confidence
|
|
i2c_addr => $addr,
|
|
chipname => sysfs_device_attribute($device, "name")
|
|
|| "unknown",
|
|
i2c_devnr => $adapter_nr,
|
|
};
|
|
|
|
printf "Client found at address 0x\%02x\n", $addr;
|
|
printf "Handled by driver `\%s' (already loaded), chip type `\%s'\n",
|
|
$driver, $new_hash->{chipname};
|
|
|
|
# Only add it to the list if this is something we would have detected,
|
|
# else we end up with random i2c chip drivers listed (for example
|
|
# media/video drivers.)
|
|
if (exists $modules_supported{$driver}) {
|
|
add_i2c_to_chips_detected($driver, $new_hash);
|
|
} else {
|
|
print " (note: this is probably NOT a sensor chip!)\n";
|
|
}
|
|
}
|
|
|
|
# $_[0]: The number of the adapter to scan
|
|
# $_[1]: Address
|
|
# $_[2]: Chip being probed
|
|
sub probe_free_i2c_address
|
|
{
|
|
my ($adapter_nr, $addr, $chip) = @_;
|
|
my ($conf, @other_addr, $new_hash);
|
|
|
|
printf("\%-60s", sprintf("Probing for `\%s'... ", $chip->{name}));
|
|
if (($conf, @other_addr) = &{$chip->{i2c_detect}} (\*FILE, $addr)) {
|
|
if ($chip->{driver} eq "not-a-sensor") {
|
|
print "Yes\n",
|
|
" (confidence $conf, not a hardware monitoring chip";
|
|
} else {
|
|
print "Success!\n",
|
|
" (confidence $conf, driver `$chip->{driver}'";
|
|
}
|
|
if (@other_addr) {
|
|
print ", other addresses:";
|
|
@other_addr = sort @other_addr;
|
|
foreach my $other_addr (@other_addr) {
|
|
printf(" 0x%02x", $other_addr);
|
|
}
|
|
}
|
|
print ")\n";
|
|
|
|
return if ($chip->{driver} eq "not-a-sensor"
|
|
|| $chip->{driver} eq "use-isa-instead");
|
|
|
|
$new_hash = {
|
|
conf => $conf,
|
|
i2c_addr => $addr,
|
|
chipname => $chip->{name},
|
|
i2c_devnr => $adapter_nr,
|
|
};
|
|
if (@other_addr) {
|
|
my @other_addr_copy = @other_addr;
|
|
$new_hash->{i2c_sub_addrs} = \@other_addr_copy;
|
|
}
|
|
add_i2c_to_chips_detected($chip->{driver}, $new_hash);
|
|
} else {
|
|
print "No\n";
|
|
}
|
|
}
|
|
|
|
# $_[0]: The device to check (PCI or not)
|
|
# Returns: PCI class of the adapter if available, 0 if not
|
|
sub get_pci_class
|
|
{
|
|
my ($device) = @_;
|
|
my ($subsystem, $class);
|
|
|
|
$subsystem = sysfs_device_subsystem($device);
|
|
return 0 unless defined $subsystem && $subsystem eq "pci";
|
|
|
|
$class = sysfs_device_attribute($device, "class");
|
|
return 0 unless defined $class;
|
|
$class = oct($class) if $class =~ /^0/;
|
|
return $class >> 8;
|
|
}
|
|
|
|
# $_[0]: The number of the adapter to scan
|
|
sub scan_i2c_adapter
|
|
{
|
|
my ($adapter_nr, $smbus_default) = @_;
|
|
my ($funcs, $chip, $addr, $class, $default, $input, @not_to_scan);
|
|
|
|
$class = get_pci_class($i2c_adapters[$adapter_nr]->{parent});
|
|
if (($class & 0xff00) == 0x0400) {
|
|
# Do not probe adapters on PCI multimedia cards by default
|
|
$default = 0;
|
|
} elsif ($class == 0x0c01 || $class == 0x0c05
|
|
|| find_i2c_adapter_driver($i2c_adapters[$adapter_nr]->{name})) {
|
|
$default = $smbus_default;
|
|
} else {
|
|
$default = 1;
|
|
}
|
|
|
|
printf "Next adapter: $i2c_adapters[$adapter_nr]->{name} (i2c-$adapter_nr)\n".
|
|
"Do you want to scan it? (\%s/selectively): ",
|
|
$default ? "YES/no" : "yes/NO";
|
|
|
|
$input = <STDIN>;
|
|
if ($input =~ /^\s*n/i
|
|
|| (!$default && $input !~ /^\s*[ys]/i)) {
|
|
print "\n";
|
|
return;
|
|
}
|
|
|
|
if ($input =~ /^\s*s/i) {
|
|
print "Please enter one or more addresses not to scan. Separate them with commas.\n",
|
|
"You can specify a range by using dashes. Example: 0x58-0x5f,0x69.\n",
|
|
"Addresses: ";
|
|
$input = <STDIN>;
|
|
chomp($input);
|
|
@not_to_scan = parse_not_to_scan(0x03, 0x77, $input);
|
|
}
|
|
|
|
open(local *FILE, "$dev_i2c$adapter_nr") or
|
|
(print "Can't open $dev_i2c$adapter_nr\n"), return;
|
|
binmode(FILE);
|
|
|
|
# Can we probe this adapter?
|
|
$funcs = i2c_get_funcs(\*FILE);
|
|
if ($funcs < 0) {
|
|
print "Adapter failed to provide its functionalities, skipping.\n";
|
|
return;
|
|
}
|
|
if (!($funcs & (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA))) {
|
|
print "Adapter cannot be probed, skipping.\n";
|
|
return;
|
|
}
|
|
if (~$funcs & (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
|
|
print "Adapter doesn't support all probing functions.\n",
|
|
"Some addresses won't be probed.\n";
|
|
}
|
|
|
|
# Now scan each address in turn
|
|
foreach $addr (@{$i2c_addresses_to_scan}) {
|
|
# As the not_to_scan list is sorted, we can check it fast
|
|
shift @not_to_scan # User skipped an address which we didn't intend to probe anyway
|
|
while (@not_to_scan and $not_to_scan[0] < $addr);
|
|
if (@not_to_scan and $not_to_scan[0] == $addr) {
|
|
shift @not_to_scan;
|
|
next;
|
|
}
|
|
|
|
if (!i2c_set_slave_addr(\*FILE, $addr)) {
|
|
add_busy_i2c_address($adapter_nr, $addr);
|
|
next;
|
|
}
|
|
|
|
next unless i2c_probe(\*FILE, $addr, $funcs);
|
|
printf "Client found at address 0x%02x\n", $addr;
|
|
if (!i2c_safety_check(\*FILE)) {
|
|
print "Seems to be a 1-register-only device, skipping.\n";
|
|
next;
|
|
}
|
|
|
|
$| = 1;
|
|
foreach $chip (@chip_ids, @non_hwmon_chip_ids) {
|
|
next unless exists $chip->{i2c_addrs}
|
|
&& contains($addr, @{$chip->{i2c_addrs}});
|
|
probe_free_i2c_address($adapter_nr, $addr, $chip);
|
|
}
|
|
$| = 0;
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
sub scan_isa_bus
|
|
{
|
|
my $chip_list_ref = shift;
|
|
my ($chip, $addr, $conf);
|
|
|
|
$| = 1;
|
|
foreach $chip (@{$chip_list_ref}) {
|
|
next if not exists $chip->{isa_addrs} or not exists $chip->{isa_detect};
|
|
foreach $addr (@{$chip->{isa_addrs}}) {
|
|
printf("\%-60s", sprintf("Probing for `\%s' at 0x\%x... ",
|
|
$chip->{name}, $addr));
|
|
$conf = &{$chip->{isa_detect}} ($addr);
|
|
print("No\n"), next if not defined $conf;
|
|
print "Success!\n";
|
|
printf " (confidence %d, driver `%s')\n", $conf, $chip->{driver};
|
|
my $new_hash = {
|
|
conf => $conf,
|
|
isa_addr => $addr,
|
|
chipname => $chip->{name},
|
|
alias_detect => $chip->{alias_detect},
|
|
};
|
|
add_isa_to_chips_detected($chip->{driver}, $new_hash);
|
|
}
|
|
}
|
|
$| = 0;
|
|
}
|
|
|
|
use vars qw(%superio);
|
|
|
|
# The following are taken from the PNP ISA spec (so it's supposed
|
|
# to be common to all Super I/O chips):
|
|
# devidreg: The device ID register(s)
|
|
# logdevreg: The logical device register
|
|
# actreg: The activation register within the logical device
|
|
# actmask: The activation bit in the activation register
|
|
# basereg: The I/O base register within the logical device
|
|
%superio = (
|
|
devidreg => 0x20,
|
|
logdevreg => 0x07,
|
|
actreg => 0x30,
|
|
actmask => 0x01,
|
|
basereg => 0x60,
|
|
);
|
|
|
|
sub exit_superio
|
|
{
|
|
my ($addrreg, $datareg) = @_;
|
|
|
|
# Some chips (SMSC, Winbond) want this
|
|
outb($addrreg, 0xaa);
|
|
|
|
# Return to "Wait For Key" state (PNP-ISA spec)
|
|
outb($addrreg, 0x02);
|
|
outb($datareg, 0x02);
|
|
}
|
|
|
|
# Guess if an unknown Super-I/O chip has sensors
|
|
sub guess_superio_ld
|
|
{
|
|
my ($addrreg, $datareg, $typical_addr) = @_;
|
|
my ($oldldn, $ldn, $addr);
|
|
|
|
# Save logical device number
|
|
outb($addrreg, $superio{logdevreg});
|
|
$oldldn = inb($datareg);
|
|
|
|
for ($ldn = 0; $ldn < 16; $ldn++) {
|
|
# Select logical device
|
|
outb($addrreg, $superio{logdevreg});
|
|
outb($datareg, $ldn);
|
|
|
|
# Read base I/O address
|
|
outb($addrreg, $superio{basereg});
|
|
$addr = inb($datareg) << 8;
|
|
outb($addrreg, $superio{basereg} + 1);
|
|
$addr |= inb($datareg);
|
|
next unless ($addr & 0xfff8) == $typical_addr;
|
|
|
|
printf " (logical device \%X has address 0x\%x, could be sensors)\n",
|
|
$ldn, $addr;
|
|
last;
|
|
}
|
|
|
|
# Be nice, restore original logical device
|
|
outb($addrreg, $superio{logdevreg});
|
|
outb($datareg, $oldldn);
|
|
}
|
|
|
|
# Returns: features bitmask if device added to chips_detected, 0 if not
|
|
sub probe_superio
|
|
{
|
|
my ($addrreg, $datareg, $chip) = @_;
|
|
my ($val, $addr);
|
|
|
|
if (exists $chip->{check}) {
|
|
return 0 unless $chip->{check}($addrreg, $datareg);
|
|
}
|
|
|
|
printf "\%-60s", "Found `$chip->{name}'";
|
|
|
|
# Does it have hardware monitoring capabilities?
|
|
if (!exists $chip->{driver}) {
|
|
print "\n (no information available)\n";
|
|
return 0;
|
|
}
|
|
if ($chip->{driver} eq "not-a-sensor") {
|
|
print "\n (no hardware monitoring capabilities)\n";
|
|
return 0;
|
|
}
|
|
if ($chip->{driver} eq "via-smbus-only") {
|
|
print "\n (hardware monitoring capabilities accessible via SMBus only)\n";
|
|
return FEAT_SMBUS;
|
|
}
|
|
|
|
# Switch to the sensor logical device
|
|
outb($addrreg, $superio{logdevreg});
|
|
outb($datareg, $chip->{logdev});
|
|
|
|
# Get the IO base address
|
|
outb($addrreg, $superio{basereg});
|
|
$addr = inb($datareg);
|
|
outb($addrreg, $superio{basereg} + 1);
|
|
$addr = ($addr << 8) | inb($datareg);
|
|
|
|
# Check the activation register and base address
|
|
outb($addrreg, $superio{actreg});
|
|
$val = inb($datareg);
|
|
if (!($val & $superio{actmask})) {
|
|
if ($addr) {
|
|
printf "\n (address 0x\%x, but not activated)\n", $addr;
|
|
} else {
|
|
print "\n (but not activated)\n";
|
|
}
|
|
return 0;
|
|
}
|
|
if ($addr == 0) {
|
|
print "\n (but no address specified)\n";
|
|
return 0;
|
|
}
|
|
|
|
print "Success!\n";
|
|
printf " (address 0x\%x, driver `%s')\n", $addr, $chip->{driver};
|
|
my $new_hash = {
|
|
conf => 9,
|
|
isa_addr => $addr,
|
|
chipname => $chip->{name}
|
|
};
|
|
add_isa_to_chips_detected($chip->{driver}, $new_hash);
|
|
return $chip->{features};
|
|
}
|
|
|
|
# Detection routine for non-standard SMSC Super I/O chips
|
|
# $_[0]: Super I/O LPC config/index port
|
|
# $_[1]: Super I/O LPC data port
|
|
# $_[2]: Reference to array of non-standard chips
|
|
# Return values: 1 if non-standard chip found, 0 otherwise
|
|
sub smsc_ns_detect_superio
|
|
{
|
|
my ($addrreg, $datareg, $ns_chips) = @_;
|
|
my ($val, $chip);
|
|
|
|
# read alternate device ID register
|
|
outb($addrreg, 0x0d);
|
|
$val = inb($datareg);
|
|
return 0 if $val == 0x00 || $val == 0xff;
|
|
|
|
print "Yes\n";
|
|
|
|
foreach $chip (@{$ns_chips}) {
|
|
if ($chip->{devid} == $val) {
|
|
probe_superio($addrreg, $datareg, $chip);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
printf("Found unknown non-standard chip with ID 0x%02x\n", $val);
|
|
return 1;
|
|
}
|
|
|
|
# Returns: features supported by the device added, if any
|
|
sub scan_superio
|
|
{
|
|
my ($addrreg, $datareg) = @_;
|
|
my ($val, $found);
|
|
my $features = 0;
|
|
|
|
printf("Probing for Super-I/O at 0x\%x/0x\%x\n", $addrreg, $datareg);
|
|
|
|
$| = 1;
|
|
# reset state to avoid false positives
|
|
exit_superio($addrreg, $datareg);
|
|
foreach my $family (@superio_ids) {
|
|
printf("\%-60s", "Trying family `$family->{family}'... ");
|
|
# write the password
|
|
foreach $val (@{$family->{enter}->{$addrreg}}) {
|
|
outb($addrreg, $val);
|
|
}
|
|
# call the non-standard detection routine first if it exists
|
|
if (defined($family->{ns_detect}) &&
|
|
&{$family->{ns_detect}}($addrreg, $datareg, $family->{ns_chips})) {
|
|
last;
|
|
}
|
|
|
|
# did it work?
|
|
outb($addrreg, $superio{devidreg});
|
|
$val = inb($datareg);
|
|
outb($addrreg, $superio{devidreg} + 1);
|
|
$val = ($val << 8) | inb($datareg);
|
|
if ($val == 0x0000 || $val == 0xffff) {
|
|
print "No\n";
|
|
next;
|
|
}
|
|
print "Yes\n";
|
|
|
|
$found = 0;
|
|
foreach my $chip (@{$family->{chips}}) {
|
|
if (($chip->{devid} > 0xff &&
|
|
($val & ($chip->{devid_mask} || 0xffff)) == $chip->{devid})
|
|
|| ($chip->{devid} <= 0xff &&
|
|
($val >> 8) == $chip->{devid})) {
|
|
$features |= probe_superio($addrreg, $datareg, $chip);
|
|
$found++;
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
printf("Found unknown chip with ID 0x%04x\n", $val);
|
|
# Guess if a logical device could correspond to sensors
|
|
guess_superio_ld($addrreg, $datareg, $family->{guess})
|
|
if defined $family->{guess};
|
|
}
|
|
last;
|
|
}
|
|
exit_superio($addrreg, $datareg);
|
|
$| = 0;
|
|
return $features;
|
|
}
|
|
|
|
sub scan_cpu
|
|
{
|
|
my $entry = shift;
|
|
my $confidence;
|
|
|
|
printf("\%-60s", "$entry->{name}... ");
|
|
if (defined ($confidence = $entry->{detect}())) {
|
|
print "Success!\n";
|
|
printf " (driver `%s')\n", $entry->{driver};
|
|
my $new_hash = {
|
|
conf => $confidence,
|
|
chipname => $entry->{name},
|
|
};
|
|
add_isa_to_chips_detected($entry->{driver}, $new_hash);
|
|
} else {
|
|
print "No\n";
|
|
}
|
|
}
|
|
|
|
##################
|
|
# CHIP DETECTION #
|
|
##################
|
|
|
|
# This routine allows you to dynamically update the chip detection list.
|
|
# The most common use is to allow for different chip to driver mappings
|
|
# based on different linux kernels
|
|
sub chip_special_cases
|
|
{
|
|
# Some chip to driver mappings depend on the environment
|
|
foreach my $chip (@chip_ids) {
|
|
if (ref($chip->{driver}) eq 'CODE') {
|
|
$chip->{driver} = $chip->{driver}->();
|
|
}
|
|
}
|
|
|
|
# Also fill the fake driver name of non-hwmon chips
|
|
foreach my $chip (@non_hwmon_chip_ids) {
|
|
$chip->{driver} = "not-a-sensor";
|
|
}
|
|
}
|
|
|
|
# Each function returns a confidence value. The higher this value, the more
|
|
# sure we are about this chip. This may help overrule false positives,
|
|
# although we also attempt to prevent false positives in the first place.
|
|
|
|
# Each function returns a list. The first element is the confidence value;
|
|
# Each element after it is an SMBus address. In this way, we can detect
|
|
# chips with several SMBus addresses. The SMBus address for which the
|
|
# function was called is never returned.
|
|
|
|
# All I2C detection functions below take at least 2 parameters:
|
|
# $_[0]: Reference to the file descriptor to access the chip
|
|
# $_[1]: Address
|
|
# Some of these functions which can detect more than one type of device,
|
|
# take a third parameter:
|
|
# $_[2]: Chip to detect
|
|
|
|
# Registers used: 0x58
|
|
sub mtp008_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return if i2c_smbus_read_byte_data($file, 0x58) != 0xac;
|
|
return 3;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM78, 2 = LM79
|
|
# Registers used:
|
|
# 0x40: Configuration
|
|
# 0x48: Full I2C Address
|
|
# 0x49: Device ID
|
|
sub lm78_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $reg;
|
|
|
|
return unless i2c_smbus_read_byte_data($file, 0x48) == $addr;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x40) & 0x80) == 0x00;
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x49);
|
|
return if $chip == 0 && ($reg != 0x00 && $reg != 0x20 && $reg != 0x40);
|
|
return if $chip == 2 && ($reg & 0xfe) != 0xc0;
|
|
|
|
# Explicitly prevent misdetection of Winbond chips
|
|
$reg = i2c_smbus_read_byte_data($file, 0x4f);
|
|
return if $reg == 0xa3 || $reg == 0x5c;
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM75, 1 = DS75
|
|
# Registers used:
|
|
# 0x00: Temperature
|
|
# 0x01: Configuration
|
|
# 0x02: Hysteresis
|
|
# 0x03: Overtemperature Shutdown
|
|
# 0x04-0x07: No registers
|
|
# The first detection step is based on the fact that the LM75 has only
|
|
# four registers, and cycles addresses over 8-byte boundaries. We use the
|
|
# 0x04-0x07 addresses (unused) to improve the reliability. These are not
|
|
# real registers and will always return the last returned value. This isn't
|
|
# documented.
|
|
# Note that register 0x00 may change, so we can't use the modulo trick on it.
|
|
# The DS75 is a bit different, it doesn't cycle over 8-byte boundaries, and
|
|
# all register addresses from 0x04 to 0x0f behave like 0x04-0x07 do for
|
|
# the LM75.
|
|
# Not all devices enjoy SMBus read word transactions, so we use read byte
|
|
# transactions even for the 16-bit registers. The low bits aren't very
|
|
# useful for detection anyway.
|
|
sub lm75_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $i;
|
|
my $cur = i2c_smbus_read_byte_data($file, 0x00);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x01);
|
|
|
|
my $hyst = i2c_smbus_read_byte_data($file, 0x02, NO_CACHE);
|
|
my $maxreg = $chip == 1 ? 0x0f : 0x07;
|
|
for $i (0x04 .. $maxreg) {
|
|
return if i2c_smbus_read_byte_data($file, $i, NO_CACHE) != $hyst;
|
|
}
|
|
|
|
my $os = i2c_smbus_read_byte_data($file, 0x03, NO_CACHE);
|
|
for $i (0x04 .. $maxreg) {
|
|
return if i2c_smbus_read_byte_data($file, $i, NO_CACHE) != $os;
|
|
}
|
|
|
|
if ($chip == 0) {
|
|
for ($i = 8; $i <= 248; $i += 40) {
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x01) != $conf
|
|
or i2c_smbus_read_byte_data($file, $i + 0x02) != $hyst
|
|
or i2c_smbus_read_byte_data($file, $i + 0x03) != $os;
|
|
}
|
|
}
|
|
|
|
# All registers hold the same value, obviously a misdetection
|
|
return if $conf == $cur and $cur == $hyst and $cur == $os;
|
|
|
|
# Unused bits
|
|
return if $chip == 0 and ($conf & 0xe0);
|
|
return if $chip == 1 and ($conf & 0x80);
|
|
|
|
# Most probable value ranges
|
|
return 6 if $cur <= 100 and ($hyst >= 10 && $hyst <= 125)
|
|
and ($os >= 20 && $os <= 127) and $hyst < $os;
|
|
return 3;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00: Temperature
|
|
# 0x01: Configuration
|
|
# 0x02: High Limit
|
|
# 0x03: Low Limit
|
|
# 0x04: Status
|
|
# 0x07: Manufacturer ID and Product ID
|
|
sub lm73_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x01);
|
|
my $status = i2c_smbus_read_byte_data($file, 0x04);
|
|
|
|
# Bits that always return 0
|
|
return if ($conf & 0x0c) or ($status & 0x10);
|
|
|
|
return if i2c_smbus_read_word_data($file, 0x07) != 0x9001;
|
|
|
|
# Make sure the chip supports SMBus read word transactions
|
|
my $cur = i2c_smbus_read_word_data($file, 0x00);
|
|
return if $cur < 0;
|
|
my $high = i2c_smbus_read_word_data($file, 0x02);
|
|
return if $high < 0;
|
|
my $low = i2c_smbus_read_word_data($file, 0x03);
|
|
return if $low < 0;
|
|
return if ($cur & 0x0300) or (($high | $low) & 0x1f00);
|
|
|
|
return 3;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00: Temperature
|
|
# 0x01: Configuration
|
|
# 0x02: Hysteresis
|
|
# 0x03: Overtemperature Shutdown
|
|
# 0x04: Low limit
|
|
# 0x05: High limit
|
|
# 0x06-0x07: No registers
|
|
# The first detection step is based on the fact that the LM77 has only
|
|
# six registers, and cycles addresses over 8-byte boundaries. We use the
|
|
# 0x06-0x07 addresses (unused) to improve the reliability. These are not
|
|
# real registers and will always return the last returned value. This isn't
|
|
# documented.
|
|
# Note that register 0x00 may change, so we can't use the modulo trick on it.
|
|
# Not all devices enjoy SMBus read word transactions, so we use read byte
|
|
# transactions even for the 16-bit registers at first. We only use read word
|
|
# transactions in the end when we are already almost certain that we have an
|
|
# LM77 chip.
|
|
sub lm77_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $i;
|
|
my $cur = i2c_smbus_read_byte_data($file, 0x00);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x01);
|
|
my $hyst = i2c_smbus_read_byte_data($file, 0x02);
|
|
my $os = i2c_smbus_read_byte_data($file, 0x03);
|
|
|
|
my $low = i2c_smbus_read_byte_data($file, 0x04, NO_CACHE);
|
|
return if i2c_smbus_read_byte_data($file, 0x06, NO_CACHE) != $low;
|
|
return if i2c_smbus_read_byte_data($file, 0x07, NO_CACHE) != $low;
|
|
|
|
my $high = i2c_smbus_read_byte_data($file, 0x05, NO_CACHE);
|
|
return if i2c_smbus_read_byte_data($file, 0x06, NO_CACHE) != $high;
|
|
return if i2c_smbus_read_byte_data($file, 0x07, NO_CACHE) != $high;
|
|
|
|
for ($i = 8; $i <= 248; $i += 40) {
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x01) != $conf;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x02) != $hyst;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x03) != $os;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x04) != $low;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x05) != $high;
|
|
}
|
|
|
|
# All registers hold the same value, obviously a misdetection
|
|
return if $conf == $cur and $cur == $hyst
|
|
and $cur == $os and $cur == $low and $cur == $high;
|
|
|
|
# Unused bits
|
|
return if ($conf & 0xe0)
|
|
or (($cur >> 4) != 0 && ($cur >> 4) != 0xf)
|
|
or (($hyst >> 4) != 0 && ($hyst >> 4) != 0xf)
|
|
or (($os >> 4) != 0 && ($os >> 4) != 0xf)
|
|
or (($low >> 4) != 0 && ($low >> 4) != 0xf)
|
|
or (($high >> 4) != 0 && ($high >> 4) != 0xf);
|
|
|
|
# Make sure the chip supports SMBus read word transactions
|
|
foreach $i (0x00, 0x02, 0x03, 0x04, 0x05) {
|
|
return if i2c_smbus_read_word_data($file, $i) < 0;
|
|
}
|
|
|
|
return 3;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM92, 1 = LM76, 2 = MAX6633/MAX6634/MAX6635
|
|
# Registers used:
|
|
# 0x01: Configuration (National Semiconductor only)
|
|
# 0x02: Hysteresis
|
|
# 0x03: Critical Temp
|
|
# 0x04: Low Limit
|
|
# 0x05: High Limit
|
|
# 0x07: Manufacturer ID (LM92 only)
|
|
# One detection step is based on the fact that the LM92 and clones have a
|
|
# limited number of registers, which cycle modulo 16 address values.
|
|
# Note that register 0x00 may change, so we can't use the modulo trick on it.
|
|
# Not all devices enjoy SMBus read word transactions, so we use read byte
|
|
# transactions even for the 16-bit registers at first. We only use read
|
|
# word transactions in the end when we are already almost certain that we
|
|
# have an LM92 chip or compatible.
|
|
sub lm92_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x01);
|
|
my $hyst = i2c_smbus_read_byte_data($file, 0x02);
|
|
my $crit = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $low = i2c_smbus_read_byte_data($file, 0x04);
|
|
my $high = i2c_smbus_read_byte_data($file, 0x05);
|
|
|
|
return if $conf == 0 and $hyst == 0 and $crit == 0
|
|
and $low == 0 and $high == 0;
|
|
|
|
# Unused bits
|
|
return if ($chip == 0 || $chip == 1)
|
|
and ($conf & 0xE0);
|
|
|
|
for (my $i = 0; $i <= 240; $i += 16) {
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x01) != $conf;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x02) != $hyst;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x03) != $crit;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x04) != $low;
|
|
return if i2c_smbus_read_byte_data($file, $i + 0x05) != $high;
|
|
}
|
|
|
|
return if $chip == 0
|
|
and i2c_smbus_read_word_data($file, 0x07) != 0x0180;
|
|
|
|
# Make sure the chip supports SMBus read word transactions
|
|
$hyst = i2c_smbus_read_word_data($file, 0x02);
|
|
return if $hyst < 0;
|
|
$crit = i2c_smbus_read_word_data($file, 0x03);
|
|
return if $crit < 0;
|
|
$low = i2c_smbus_read_word_data($file, 0x04);
|
|
return if $low < 0;
|
|
$high = i2c_smbus_read_word_data($file, 0x05);
|
|
return if $high < 0;
|
|
|
|
foreach my $temp ($hyst, $crit, $low, $high) {
|
|
return if $chip == 2 and ($temp & 0x7F00);
|
|
return if $chip != 2 and ($temp & 0x0700);
|
|
}
|
|
|
|
return ($chip == 0) ? 4 : 2;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0xAA: Temperature
|
|
# 0xA1: High limit
|
|
# 0xA2: Low limit
|
|
# 0xA8: Counter
|
|
# 0xA9: Slope
|
|
# 0xAC: Configuration
|
|
# Detection is weak. We check if bit 4 (NVB) is clear, because it is
|
|
# unlikely to be set (would mean that EEPROM is currently being accessed).
|
|
# We also check the value of the counter and slope registers, the datasheet
|
|
# doesn't mention the possible values but the conversion formula together
|
|
# with experimental evidence suggest possible sanity checks.
|
|
# Not all devices enjoy SMBus read word transactions, so we do as much as
|
|
# possible with read byte transactions first, and only use read word
|
|
# transactions second.
|
|
sub ds1621_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
my $conf = i2c_smbus_read_byte_data($file, 0xAC);
|
|
return if ($conf & 0x10);
|
|
|
|
my $temp = i2c_smbus_read_word_data($file, 0xAA);
|
|
return if $temp < 0 || ($temp & 0x0f00);
|
|
# On the DS1631, the following two checks are too strict in theory,
|
|
# but in practice I very much doubt that anyone will set temperature
|
|
# limits not a multiple of 0.5 degrees C.
|
|
my $high = i2c_smbus_read_word_data($file, 0xA1);
|
|
return if $high < 0 || ($high & 0x7f00);
|
|
my $low = i2c_smbus_read_word_data($file, 0xA2);
|
|
return if $low < 0 || ($low & 0x7f00);
|
|
|
|
return if ($temp == 0 && $high == 0 && $low == 0 && $conf == 0);
|
|
|
|
# Old versions of the DS1621 apparently don't have the counter and
|
|
# slope registers (or they return crap)
|
|
my $counter = i2c_smbus_read_byte_data($file, 0xA8);
|
|
my $slope = i2c_smbus_read_byte_data($file, 0xA9);
|
|
return ($slope == 0x10 && $counter <= $slope) ? 3 : 2;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00: Configuration register
|
|
# 0x02: Interrupt state register
|
|
# 0x2a-0x3d: Limits registers
|
|
# This one is easily misdetected since it doesn't provide identification
|
|
# registers. So we have to use some tricks:
|
|
# - 6-bit addressing, so limits readings modulo 0x40 should be unchanged
|
|
# - positive temperature limits
|
|
# - limits order correctness
|
|
# Hopefully this should limit the rate of false positives, without increasing
|
|
# the rate of false negatives.
|
|
# Thanks to Lennard Klein for testing on a non-LM80 chip, which was
|
|
# previously misdetected, and isn't anymore. For reference, it scored
|
|
# a final confidence of 0, and changing from strict limit comparisons
|
|
# to loose comparisons did not change the score.
|
|
sub lm80_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my ($i, $reg);
|
|
|
|
return if (i2c_smbus_read_byte_data($file, 0x00) & 0x80) != 0;
|
|
return if (i2c_smbus_read_byte_data($file, 0x02) & 0xc0) != 0;
|
|
|
|
for ($i = 0x2a; $i <= 0x3d; $i++) {
|
|
$reg = i2c_smbus_read_byte_data($file, $i);
|
|
return if i2c_smbus_read_byte_data($file, $i+0x40) != $reg;
|
|
return if i2c_smbus_read_byte_data($file, $i+0x80) != $reg;
|
|
return if i2c_smbus_read_byte_data($file, $i+0xc0) != $reg;
|
|
}
|
|
|
|
# Refine a bit by checking whether limits are in the correct order
|
|
# (min<max for voltages, hyst<max for temperature). Since it is still
|
|
# possible that the chip is an LM80 with limits not properly set,
|
|
# a few "errors" are tolerated.
|
|
my $confidence = 0;
|
|
for ($i = 0x2a; $i <= 0x3a; $i++) {
|
|
$confidence++
|
|
if i2c_smbus_read_byte_data($file, $i) < i2c_smbus_read_byte_data($file, $i+1);
|
|
}
|
|
# hot temp<OS temp
|
|
$confidence++
|
|
if i2c_smbus_read_byte_data($file, 0x38) < i2c_smbus_read_byte_data($file, 0x3a);
|
|
|
|
# Negative temperature limits are unlikely.
|
|
for ($i = 0x3a; $i <= 0x3d; $i++) {
|
|
$confidence++ if (i2c_smbus_read_byte_data($file, $i) & 0x80) == 0;
|
|
}
|
|
|
|
# $confidence is between 0 and 14
|
|
$confidence = ($confidence >> 1) - 4;
|
|
# $confidence is now between -4 and 3
|
|
|
|
return unless $confidence > 0;
|
|
|
|
return $confidence;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x02: Status 1
|
|
# 0x03: Configuration
|
|
# 0x04: Company ID of LM84
|
|
# 0x35: Status 2
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: Chip ID / die revision
|
|
# We can use the LM84 Company ID register because the LM83 and the LM82 are
|
|
# compatible with the LM84.
|
|
# The LM83 chip ID is missing from the datasheet and was contributed by
|
|
# Magnus Forsstrom: 0x03.
|
|
# At least some revisions of the LM82 seem to be repackaged LM83, so they
|
|
# have the same chip ID, and temp2/temp4 will be stuck in "OPEN" state.
|
|
# For this reason, we don't even try to distinguish between both chips.
|
|
# Thanks to Ben Gardner for reporting.
|
|
sub lm83_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return if i2c_smbus_read_byte_data($file, 0xfe) != 0x01;
|
|
my $chipid = i2c_smbus_read_byte_data($file, 0xff);
|
|
return if $chipid != 0x01 && $chipid != 0x03;
|
|
|
|
my $confidence = 4;
|
|
$confidence++
|
|
if (i2c_smbus_read_byte_data($file, 0x02) & 0xa8) == 0x00;
|
|
$confidence++
|
|
if (i2c_smbus_read_byte_data($file, 0x03) & 0x41) == 0x00;
|
|
$confidence++
|
|
if i2c_smbus_read_byte_data($file, 0x04) == 0x00;
|
|
$confidence++
|
|
if (i2c_smbus_read_byte_data($file, 0x35) & 0x48) == 0x00;
|
|
|
|
return $confidence;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM90, 1 = LM89/LM99, 2 = LM86, 3 = ADM1032,
|
|
# 4 = MAX6654/MAX6690, 5 = ADT7461,
|
|
# 6 = MAX6646/MAX6647/MAX6648/MAX6649/MAX6692,
|
|
# 7 = MAX6680/MAX6681, 8 = W83L771W/G, 9 = TMP401, 10 = TMP411
|
|
# Registers used:
|
|
# 0x03: Configuration
|
|
# 0x04: Conversion rate
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: Chip ID / die revision
|
|
sub lm90_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0xff);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $rate = i2c_smbus_read_byte_data($file, 0x04);
|
|
|
|
if ($chip == 0) {
|
|
return if ($conf & 0x2a) != 0;
|
|
return if $rate > 0x09;
|
|
return if $mid != 0x01; # National Semiconductor
|
|
return 8 if $cid == 0x21; # LM90
|
|
return 6 if ($cid & 0x0f) == 0x20;
|
|
}
|
|
if ($chip == 1) {
|
|
return if ($conf & 0x2a) != 0;
|
|
return if $rate > 0x09;
|
|
return if $mid != 0x01; # National Semiconductor
|
|
return 8 if $addr == 0x4c and $cid == 0x31; # LM89/LM99
|
|
return 8 if $addr == 0x4d and $cid == 0x34; # LM89-1/LM99-1
|
|
return 6 if ($cid & 0x0f) == 0x30;
|
|
}
|
|
if ($chip == 2) {
|
|
return if ($conf & 0x2a) != 0;
|
|
return if $rate > 0x09;
|
|
return if $mid != 0x01; # National Semiconductor
|
|
return 8 if $cid == 0x11; # LM86
|
|
return 6 if ($cid & 0xf0) == 0x10;
|
|
}
|
|
if ($chip == 3) {
|
|
return if ($conf & 0x3f) != 0;
|
|
return if $rate > 0x0a;
|
|
return if $mid != 0x41; # Analog Devices
|
|
return 6 if ($cid & 0xf0) == 0x40; # ADM1032
|
|
}
|
|
if ($chip == 4) {
|
|
return if ($conf & 0x07) != 0;
|
|
return if $rate > 0x07;
|
|
return if $mid != 0x4d; # Maxim
|
|
return 8 if $cid == 0x08; # MAX6654/MAX6690
|
|
}
|
|
if ($chip == 5) {
|
|
return if ($conf & 0x1b) != 0;
|
|
return if $rate > 0x0a;
|
|
return if $mid != 0x41; # Analog Devices
|
|
return 8 if $cid == 0x51; # ADT7461
|
|
}
|
|
if ($chip == 6) {
|
|
return if ($conf & 0x3f) != 0;
|
|
return if $rate > 0x07;
|
|
return if $mid != 0x4d; # Maxim
|
|
return 8 if $cid == 0x59; # MAX6648/MAX6692
|
|
}
|
|
if ($chip == 7) {
|
|
return if ($conf & 0x03) != 0;
|
|
return if $rate > 0x07;
|
|
return if $mid != 0x4d; # Maxim
|
|
return 8 if $cid == 0x01; # MAX6680/MAX6681
|
|
}
|
|
if ($chip == 8) {
|
|
return if ($conf & 0x2a) != 0;
|
|
return if $rate > 0x09;
|
|
return if $mid != 0x5c; # Winbond
|
|
return 6 if $cid == 0x00; # W83L771W/G
|
|
}
|
|
if ($chip == 9) {
|
|
return if ($conf & 0x1B) != 0;
|
|
return if $rate > 0x0F;
|
|
return if $mid != 0x55; # Texas Instruments
|
|
return 8 if $cid == 0x11; # TMP401
|
|
}
|
|
if ($chip == 10) {
|
|
return if ($conf & 0x1B) != 0;
|
|
return if $rate > 0x0F;
|
|
return if $mid != 0x55; # Texas Instruments
|
|
return 6 if ($addr == 0x4c && $cid == 0x12); # TMP411A
|
|
return 6 if ($addr == 0x4d && $cid == 0x13); # TMP411B
|
|
return 6 if ($addr == 0x4e && $cid == 0x10); # TMP411C
|
|
}
|
|
return;
|
|
}
|
|
|
|
# Chip to detect: 0 = TMP421, 1 = TMP422, 2 = TMP423
|
|
# Registers used:
|
|
# 0xfe: Manufactorer ID
|
|
# 0xff: Device ID
|
|
sub tmp42x_detect()
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0xff);
|
|
|
|
return if ($mid != 0x55);
|
|
|
|
return 6 if ($chip == 0 && $cid == 0x21); # TMP421
|
|
return 6 if ($chip == 1 && $cid == 0x22); # TMP422
|
|
return 6 if ($chip == 2 && $cid == 0x23); # TMP423
|
|
|
|
return;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x03: Configuration (no low nibble, returns the previous low nibble)
|
|
# 0x04: Conversion rate
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: no register
|
|
sub max6657_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe, NO_CACHE);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0xff, NO_CACHE);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x03, NO_CACHE);
|
|
|
|
return if $mid != 0x4d; # Maxim
|
|
return if ($conf & 0x1f) != 0x0d;
|
|
return if $cid != 0x4d; # No register, returns previous value
|
|
|
|
my $rate = i2c_smbus_read_byte_data($file, 0x04, NO_CACHE);
|
|
return if $rate > 0x09;
|
|
|
|
$cid = i2c_smbus_read_byte_data($file, 0xff, NO_CACHE);
|
|
$conf = i2c_smbus_read_byte_data($file, 0x03, NO_CACHE);
|
|
return if ($conf & 0x0f) != $rate;
|
|
return if $cid != $rate; # No register, returns previous value
|
|
|
|
return 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM95231, 1 = LM95241
|
|
# Registers used:
|
|
# 0x02: Status (3 unused bits)
|
|
# 0x03: Configuration (3 unused bits)
|
|
# 0x06: Remote diode filter control (6 unused bits)
|
|
# 0x30: Remote diode model type select (6 unused bits)
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: Revision ID
|
|
sub lm95231_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0xff);
|
|
|
|
return if $mid != 0x01; # National Semiconductor
|
|
return if $chip == 0 && $cid != 0xa1; # LM95231
|
|
return if $chip == 1 && $cid != 0xa4; # LM95231
|
|
|
|
return if i2c_smbus_read_byte_data($file, 0x02) & 0x70;
|
|
return if i2c_smbus_read_byte_data($file, 0x03) & 0x89;
|
|
return if i2c_smbus_read_byte_data($file, 0x06) & 0xfa;
|
|
return if i2c_smbus_read_byte_data($file, 0x30) & 0xfa;
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x03: Configuration 1
|
|
# 0x24: Configuration 2
|
|
# 0x3d: Manufacturer ID
|
|
# 0x3e: Device ID
|
|
sub adt7481_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3d);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $conf1 = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $conf2 = i2c_smbus_read_byte_data($file, 0x24);
|
|
|
|
return if ($conf1 & 0x10) != 0;
|
|
return if ($conf2 & 0x7f) != 0;
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if $cid != 0x81; # ADT7481
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Chip to detect: 1 = LM63, 2 = F75363SG, 3 = LM64
|
|
# Registers used:
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: Chip ID / die revision
|
|
# 0x03: Configuration (two or three unused bits)
|
|
# 0x16: Alert mask (two or three unused bits)
|
|
sub lm63_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0xff);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $mask = i2c_smbus_read_byte_data($file, 0x16);
|
|
|
|
if ($chip == 1) {
|
|
return if $mid != 0x01 # National Semiconductor
|
|
|| $cid != 0x41; # LM63
|
|
return if ($conf & 0x18) != 0x00
|
|
|| ($mask & 0xa4) != 0xa4;
|
|
} elsif ($chip == 2) {
|
|
return if $mid != 0x23 # Fintek
|
|
|| $cid != 0x20; # F75363SG
|
|
return if ($conf & 0x1a) != 0x00
|
|
|| ($mask & 0x84) != 0x00;
|
|
} elsif ($chip == 3) {
|
|
return if $mid != 0x01 # National Semiconductor
|
|
|| $cid != 0x51; # LM64
|
|
return if ($conf & 0x18) != 0x00
|
|
|| ($mask & 0xa4) != 0xa4;
|
|
}
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x02, 0x03: Fan support
|
|
# 0x06: Temperature support
|
|
# 0x07, 0x08, 0x09: Fan config
|
|
# 0x0d: Manufacturer ID
|
|
# 0x0e: Chip ID / die revision
|
|
sub adm1029_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x0d);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x0e);
|
|
my $cfg;
|
|
|
|
return unless $mid == 0x41; # Analog Devices
|
|
return unless ($cid & 0xF0) == 0x00; # ADM1029
|
|
|
|
# Extra check on unused bits
|
|
$cfg = i2c_smbus_read_byte_data($file, 0x02);
|
|
return unless $cfg == 0x03;
|
|
$cfg = i2c_smbus_read_byte_data($file, 0x06);
|
|
return unless ($cfg & 0xF9) == 0x01;
|
|
foreach my $reg (0x03, 0x07, 0x08, 0x09) {
|
|
$cfg = i2c_smbus_read_byte_data($file, $reg);
|
|
return unless ($cfg & 0xFC) == 0x00;
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM1030, 1 = ADM1031
|
|
# Registers used:
|
|
# 0x01: Config 2
|
|
# 0x03: Status 2
|
|
# 0x0d, 0x0e, 0x0f: Temperature offsets
|
|
# 0x22: Fan speed config
|
|
# 0x3d: Chip ID
|
|
# 0x3e: Manufacturer ID
|
|
# 0x3f: Die revision
|
|
sub adm1031_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3d);
|
|
my $drev = i2c_smbus_read_byte_data($file, 0x3f);
|
|
my $conf2 = i2c_smbus_read_byte_data($file, 0x01);
|
|
my $stat2 = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $fsc = i2c_smbus_read_byte_data($file, 0x22);
|
|
my $lto = i2c_smbus_read_byte_data($file, 0x0d);
|
|
my $r1to = i2c_smbus_read_byte_data($file, 0x0e);
|
|
my $r2to = i2c_smbus_read_byte_data($file, 0x0f);
|
|
my $confidence = 3;
|
|
|
|
if ($chip == 0) {
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if $cid != 0x30; # ADM1030
|
|
$confidence++ if ($drev & 0x70) == 0x00;
|
|
$confidence++ if ($conf2 & 0x4A) == 0x00;
|
|
$confidence++ if ($stat2 & 0x3F) == 0x00;
|
|
$confidence++ if ($fsc & 0xF0) == 0x00;
|
|
$confidence++ if ($lto & 0x70) == 0x00;
|
|
$confidence++ if ($r1to & 0x70) == 0x00;
|
|
return $confidence;
|
|
}
|
|
if ($chip == 1) {
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if $cid != 0x31; # ADM1031
|
|
$confidence++ if ($drev & 0x70) == 0x00;
|
|
$confidence++ if ($lto & 0x70) == 0x00;
|
|
$confidence++ if ($r1to & 0x70) == 0x00;
|
|
$confidence++ if ($r2to & 0x70) == 0x00;
|
|
return $confidence;
|
|
}
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM1033, 1 = ADM1034
|
|
# Registers used:
|
|
# 0x3d: Chip ID
|
|
# 0x3e: Manufacturer ID
|
|
# 0x3f: Die revision
|
|
sub adm1034_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3d);
|
|
my $drev = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
if ($chip == 0) {
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if $cid != 0x33; # ADM1033
|
|
return if ($drev & 0xf8) != 0x00;
|
|
return 6 if $drev == 0x02;
|
|
return 4;
|
|
}
|
|
if ($chip == 1) {
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if $cid != 0x34; # ADM1034
|
|
return if ($drev & 0xf8) != 0x00;
|
|
return 6 if $drev == 0x02;
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
# Chip to detect: 0 = ADT7467/ADT7468, 1 = ADT7476, 2 = ADT7462, 3 = ADT7466,
|
|
# 4 = ADT7470
|
|
# Registers used:
|
|
# 0x3d: Chip ID
|
|
# 0x3e: Manufacturer ID
|
|
# 0x3f: Die revision
|
|
sub adt7467_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3d);
|
|
my $drev = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
return if $mid != 0x41; # Analog Devices
|
|
|
|
if ($chip == 0) {
|
|
return if $cid != 0x68; # ADT7467
|
|
return if ($drev & 0xf0) != 0x70;
|
|
return 7 if $drev == 0x71 || $drev == 0x72;
|
|
return 5;
|
|
}
|
|
if ($chip == 1) {
|
|
return if $cid != 0x76; # ADT7476
|
|
return if ($drev & 0xf0) != 0x60;
|
|
return 7 if $drev == 0x69;
|
|
return 5;
|
|
}
|
|
if ($chip == 2) {
|
|
return if $cid != 0x62; # ADT7462
|
|
return if ($drev & 0xf0) != 0x00;
|
|
return 7 if $drev == 0x04;
|
|
return 5;
|
|
}
|
|
if ($chip == 3) {
|
|
return if $cid != 0x66; # ADT7466
|
|
return if ($drev & 0xf0) != 0x00;
|
|
return 7 if $drev == 0x02;
|
|
return 5;
|
|
}
|
|
if ($chip == 4) {
|
|
return if $cid != 0x70; # ADT7470
|
|
return if ($drev & 0xf0) != 0x00;
|
|
return 7 if $drev == 0x00;
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
# Chip to detect: 0 = ADT7473, 1 = ADT7475
|
|
# Registers used:
|
|
# 0x3d: Chip ID
|
|
# 0x3e: Manufacturer ID
|
|
sub adt7473_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3d);
|
|
|
|
return if $mid != 0x41; # Analog Devices
|
|
|
|
return if $chip == 0 && $cid != 0x73; # ADT7473
|
|
return if $chip == 1 && $cid != 0x75; # ADT7475
|
|
return 5;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x3e: Manufacturer ID
|
|
# 0x3f: Chip ID
|
|
sub adt7490_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
return if $mid != 0x41; # Analog Devices
|
|
return if ($cid & 0xfc) != 0x6c; # ADT7490
|
|
return 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = aSC7512, 1 = aSC7611, 2 = aSC7621
|
|
# Registers used:
|
|
# 0x3e: Manufacturer ID
|
|
# 0x3f: Version
|
|
sub andigilog_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
return if $mid != 0x61; # Andigilog
|
|
|
|
return if $chip == 0 && $cid != 0x62;
|
|
return if $chip == 1 && $cid != 0x69;
|
|
return if $chip == 2 && ($cid != 0x6C && $cid != 0x6D);
|
|
return 5;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0xfe: Manufacturer ID
|
|
# 0xff: Die Code
|
|
sub andigilog_aSC7511_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $mid = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $die = i2c_smbus_read_byte_data($file, 0xff);
|
|
|
|
return if $mid != 0x61; # Andigilog
|
|
return if $die != 0x00;
|
|
return 3;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM85, 1 = LM96000, 2 = ADM1027, 3 = ADT7463,
|
|
# 4 = EMC6D100/101, 5 = EMC6D102, 6 = EMC6D103,
|
|
# 7 = WPCD377I (no sensors)
|
|
# Registers used:
|
|
# 0x3e: Vendor register
|
|
# 0x3d: Device ID register (Analog Devices only)
|
|
# 0x3f: Version/Stepping register
|
|
sub lm85_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $vendor = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $verstep = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
if ($chip == 0) {
|
|
return if $vendor != 0x01; # National Semiconductor
|
|
return if $verstep != 0x60 # LM85 C
|
|
&& $verstep != 0x62; # LM85 B
|
|
} elsif ($chip == 1 || $chip == 7) {
|
|
return if $vendor != 0x01; # National Semiconductor
|
|
return if $verstep != 0x68 # LM96000
|
|
&& $verstep != 0x69; # LM96000
|
|
} elsif ($chip == 2) {
|
|
return if $vendor != 0x41; # Analog Devices
|
|
return if $verstep != 0x60; # ADM1027
|
|
} elsif ($chip == 3) {
|
|
return if $vendor != 0x41; # Analog Devices
|
|
return if $verstep != 0x62 # ADT7463
|
|
&& $verstep != 0x6a; # ADT7463 C
|
|
} elsif ($chip == 4) {
|
|
return if $vendor != 0x5c; # SMSC
|
|
return if $verstep != 0x60 # EMC6D100/101 A0
|
|
&& $verstep != 0x61; # EMC6D100/101 A1
|
|
} elsif ($chip == 5) {
|
|
return if $vendor != 0x5c; # SMSC
|
|
return if $verstep != 0x65; # EMC6D102
|
|
} elsif ($chip == 6) {
|
|
return if $vendor != 0x5c; # SMSC
|
|
return if $verstep != 0x68; # EMC6D103
|
|
}
|
|
|
|
if ($vendor == 0x41) { # Analog Devices
|
|
return if i2c_smbus_read_byte_data($file, 0x3d) != 0x27;
|
|
return 8;
|
|
}
|
|
|
|
if ($chip == 1 || $chip == 7) {
|
|
# Differenciate between real LM96000 and Winbond WPCD377I.
|
|
# The latter emulate the former except that it has no
|
|
# hardware monitoring function so the readings are always
|
|
# 0.
|
|
my ($in_temp, $fan, $i);
|
|
|
|
for ($i = 0; $i < 8; $i++) {
|
|
$in_temp = i2c_smbus_read_byte_data($file, 0x20 + $i);
|
|
$fan = i2c_smbus_read_byte_data($file, 0x28 + $i);
|
|
if ($in_temp != 0x00 or $fan != 0xff) {
|
|
return 7 if $chip == 1;
|
|
return;
|
|
}
|
|
}
|
|
return 7 if $chip == 7;
|
|
return;
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Chip to detect: 0 = LM87, 1 = ADM1024
|
|
# Registers used:
|
|
# 0x3e: Company ID
|
|
# 0x3f: Revision
|
|
# 0x40: Configuration
|
|
sub lm87_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $cid = i2c_smbus_read_byte_data($file, 0x3e);
|
|
my $rev = i2c_smbus_read_byte_data($file, 0x3f);
|
|
|
|
if ($chip == 0) {
|
|
return if $cid != 0x02; # National Semiconductor
|
|
return if ($rev & 0xfc) != 0x04; # LM87
|
|
}
|
|
if ($chip == 1) {
|
|
return if $cid != 0x41; # Analog Devices
|
|
return if ($rev & 0xf0) != 0x10; # ADM1024
|
|
}
|
|
|
|
my $cfg = i2c_smbus_read_byte_data($file, 0x40);
|
|
return if ($cfg & 0x80) != 0x00;
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Chip to detect: 0 = W83781D, 1 = W83782D, 2 = W83783S, 3 = W83627HF,
|
|
# 4 = AS99127F (rev.1), 5 = AS99127F (rev.2), 6 = ASB100,
|
|
# 7 = W83791D, 8 = W83792D, 9 = W83627EHF,
|
|
# 10 = W83627DHG/W83667HG/W83677HG
|
|
# Registers used:
|
|
# 0x48: Full I2C Address
|
|
# 0x4a: I2C addresses of emulated LM75 chips
|
|
# 0x4e: Vendor ID byte selection, and bank selection
|
|
# 0x4f: Vendor ID
|
|
# 0x58: Device ID (only when in bank 0)
|
|
# Note: Fails if the W8378xD is not in bank 0!
|
|
# Note: Asus chips do not have their I2C address at register 0x48?
|
|
# AS99127F rev.1 and ASB100 have 0x00, confirmation wanted for
|
|
# AS99127F rev.2.
|
|
sub w83781d_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my ($reg1, $reg2, @res);
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x48) == $addr)
|
|
or ($chip >= 4 && $chip <= 6);
|
|
|
|
$reg1 = i2c_smbus_read_byte_data($file, 0x4e);
|
|
$reg2 = i2c_smbus_read_byte_data($file, 0x4f);
|
|
if ($chip == 4) { # Asus AS99127F (rev.1)
|
|
return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0xc3) or
|
|
(($reg1 & 0x80) == 0x80 and $reg2 == 0x12);
|
|
} elsif ($chip == 6) { # Asus ASB100
|
|
return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0x94) or
|
|
(($reg1 & 0x80) == 0x80 and $reg2 == 0x06);
|
|
} else { # Winbond and Asus AS99127F (rev.2)
|
|
return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0xa3) or
|
|
(($reg1 & 0x80) == 0x80 and $reg2 == 0x5c);
|
|
}
|
|
|
|
return unless ($reg1 & 0x07) == 0x00;
|
|
|
|
$reg1 = i2c_smbus_read_byte_data($file, 0x58);
|
|
return if $chip == 0 and ($reg1 != 0x10 && $reg1 != 0x11);
|
|
return if $chip == 1 and $reg1 != 0x30;
|
|
return if $chip == 2 and $reg1 != 0x40;
|
|
return if $chip == 3 and $reg1 != 0x21;
|
|
return if $chip == 4 and $reg1 != 0x31;
|
|
return if $chip == 5 and $reg1 != 0x31;
|
|
return if $chip == 6 and $reg1 != 0x31;
|
|
return if $chip == 7 and $reg1 != 0x71;
|
|
return if $chip == 8 and $reg1 != 0x7a;
|
|
return if $chip == 9 and ($reg1 != 0x88 && $reg1 != 0xa1);
|
|
return if $chip == 10 and $reg1 != 0xc1;
|
|
# Default address is 0x2d
|
|
@res = ($addr != 0x2d) ? (7) : (8);
|
|
return @res if $chip >= 9; # No subclients
|
|
|
|
$reg1 = i2c_smbus_read_byte_data($file, 0x4a);
|
|
push @res, ($reg1 & 0x07) + 0x48 unless $reg1 & 0x08;
|
|
push @res, (($reg1 & 0x70) >> 4) + 0x48
|
|
unless ($reg1 & 0x80 or $chip == 2);
|
|
return @res;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x0b: Full I2C Address
|
|
# 0x0c: I2C addresses of emulated LM75 chips
|
|
# 0x00: Vendor ID byte selection, and bank selection(Bank 0, 1, 2)
|
|
# 0x0d: Vendor ID(Bank 0, 1, 2)
|
|
# 0x0e: Device ID(Bank 0, 1, 2)
|
|
sub w83793_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my ($bank, $reg, @res);
|
|
|
|
$bank = i2c_smbus_read_byte_data($file, 0x00);
|
|
$reg = i2c_smbus_read_byte_data($file, 0x0d);
|
|
|
|
return unless (($bank & 0x80) == 0x00 and $reg == 0xa3) or
|
|
(($bank & 0x80) == 0x80 and $reg == 0x5c);
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x0e);
|
|
return if $reg != 0x7b;
|
|
|
|
# If bank 0 is selected, we can do more checks
|
|
return 6 unless ($bank & 0x07) == 0;
|
|
$reg = i2c_smbus_read_byte_data($file, 0x0b);
|
|
return unless ($reg == ($addr << 1));
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x0c);
|
|
@res = (8);
|
|
push @res, ($reg & 0x07) + 0x48 unless $reg & 0x08;
|
|
push @res, (($reg & 0x70) >> 4) + 0x48 unless $reg & 0x80;
|
|
return @res;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0xfc: Full I2C Address
|
|
# 0x00: Vendor ID byte selection, and bank selection(Bank 0, 1, 2)
|
|
# 0xfd: Vendor ID(Bank 0, 1, 2)
|
|
# 0xfe: Device ID(Bank 0, 1, 2)
|
|
sub w83795_detect
|
|
{
|
|
my ($bank, $reg);
|
|
my ($file, $addr) = @_;
|
|
|
|
$bank = i2c_smbus_read_byte_data($file, 0x00);
|
|
$reg = i2c_smbus_read_byte_data($file, 0xfd);
|
|
|
|
return unless (($bank & 0x80) == 0x00 and $reg == 0xa3) or
|
|
(($bank & 0x80) == 0x80 and $reg == 0x5c);
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0xfe);
|
|
return if $reg != 0x79;
|
|
|
|
# If bank 0 is selected, we can do more checks
|
|
return 6 unless ($bank & 0x07) == 0;
|
|
$reg = i2c_smbus_read_byte_data($file, 0xfc) & 0x7f;
|
|
return unless ($reg == $addr);
|
|
|
|
return 8;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x48: Full I2C Address
|
|
# 0x4e: Vendor ID byte selection
|
|
# 0x4f: Vendor ID
|
|
# 0x58: Device ID
|
|
# Note that the datasheet was useless and this detection routine
|
|
# is based on dumps we received from users. Also, the W83781SD is *NOT*
|
|
# a hardware monitoring chip as far as we know, but we still want to
|
|
# detect it so that people won't keep reporting it as an unknown chip
|
|
# we should investigate about.
|
|
sub w83791sd_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my ($reg1, $reg2);
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x48) == $addr);
|
|
|
|
$reg1 = i2c_smbus_read_byte_data($file, 0x4e);
|
|
$reg2 = i2c_smbus_read_byte_data($file, 0x4f);
|
|
return unless (!($reg1 & 0x80) && $reg2 == 0xa3)
|
|
|| (($reg1 & 0x80) && $reg2 == 0x5c);
|
|
|
|
$reg1 = i2c_smbus_read_byte_data($file, 0x58);
|
|
return unless $reg1 == 0x72;
|
|
|
|
return 3;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x4e: Vendor ID high byte
|
|
# 0x4f: Vendor ID low byte
|
|
# 0x58: Device ID
|
|
# Note: The values were given by Alex van Kaam, we don't have datasheets
|
|
# to confirm.
|
|
sub mozart_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my ($vid, $dev);
|
|
|
|
$vid = (i2c_smbus_read_byte_data($file, 0x4e) << 8)
|
|
+ i2c_smbus_read_byte_data($file, 0x4f);
|
|
$dev = i2c_smbus_read_byte_data($file, 0x58);
|
|
|
|
return unless ($dev == 0x56 && $vid == 0x9436) # ASM58
|
|
|| ($dev == 0x56 && $vid == 0x9406) # AS2K129R
|
|
|| ($dev == 0x10 && $vid == 0x5ca3);
|
|
|
|
return 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = GL518SM, 1 = GL520SM
|
|
# Registers used:
|
|
# 0x00: Device ID
|
|
# 0x01: Revision ID
|
|
# 0x03: Configuration
|
|
sub gl518sm_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $reg;
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x00);
|
|
return if $chip == 0 && $reg != 0x80;
|
|
return if $chip == 1 && $reg != 0x20;
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x03) & 0x80) == 0x00;
|
|
$reg = i2c_smbus_read_byte_data($file, 0x01);
|
|
return unless $reg == 0x00 or $reg == 0x80;
|
|
return 6;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00: Device ID
|
|
# 0x03: Configuration
|
|
# Mediocre detection
|
|
sub gl525sm_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return unless i2c_smbus_read_byte_data($file, 0x00) == 0x25;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x03) & 0x80) == 0x00;
|
|
return 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM9240, 1 = DS1780, 2 = LM81
|
|
# Registers used:
|
|
# 0x3e: Company ID
|
|
# 0x40: Configuration
|
|
# 0x48: Full I2C Address
|
|
sub adm9240_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $reg;
|
|
$reg = i2c_smbus_read_byte_data($file, 0x3e);
|
|
return unless ($chip == 0 and $reg == 0x23) or
|
|
($chip == 1 and $reg == 0xda) or
|
|
($chip == 2 and $reg == 0x01);
|
|
return unless (i2c_smbus_read_byte_data($file, 0x40) & 0x80) == 0x00;
|
|
return unless i2c_smbus_read_byte_data($file, 0x48) == $addr;
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM1022, 1 = THMC50, 2 = ADM1028, 3 = THMC51
|
|
# Registers used:
|
|
# 0x3e: Company ID
|
|
# 0x3f: Revision
|
|
# 0x40: Configuration
|
|
sub adm1022_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $reg;
|
|
$reg = i2c_smbus_read_byte_data($file, 0x3e);
|
|
return unless ($chip == 0 and $reg == 0x41) or
|
|
($chip == 1 and $reg == 0x49) or
|
|
($chip == 2 and $reg == 0x41) or
|
|
($chip == 3 and $reg == 0x49);
|
|
$reg = i2c_smbus_read_byte_data($file, 0x40);
|
|
return if ($reg & 0x10); # Soft Reset always reads 0
|
|
return if ($chip != 0 and ($reg & 0x80)); # Reserved on THMC50 and ADM1028
|
|
$reg = i2c_smbus_read_byte_data($file, 0x3f) & 0xf0;
|
|
return unless ($chip == 0 and $reg == 0xc0) or
|
|
($chip == 1 and $reg == 0xc0) or
|
|
($chip == 2 and $reg == 0xd0) or
|
|
($chip == 3 and $reg == 0xd0);
|
|
return 8;
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM1025, 1 = NE1619
|
|
# Registers used:
|
|
# 0x3e: Company ID
|
|
# 0x3f: Revision
|
|
# 0x40: Configuration
|
|
# 0x41: Status 1
|
|
# 0x42: Status 2
|
|
sub adm1025_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $reg;
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x3e);
|
|
return if ($chip == 0) and ($reg != 0x41);
|
|
return if ($chip == 1) and ($reg != 0xA1);
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x40) & 0x80) == 0x00;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x41) & 0xC0) == 0x00;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x42) & 0xBC) == 0x00;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x3f) & 0xf0) == 0x20;
|
|
|
|
return 8;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x16: Company ID
|
|
# 0x17: Revision
|
|
sub adm1026_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $reg;
|
|
$reg = i2c_smbus_read_byte_data($file, 0x16);
|
|
return unless ($reg == 0x41);
|
|
return unless (i2c_smbus_read_byte_data($file, 0x17) & 0xf0) == 0x40;
|
|
return 8;
|
|
}
|
|
|
|
# Chip to detect: 0 = ADM1021, 1 = ADM1021A/ADM1023, 2 = MAX1617, 3 = MAX1617A,
|
|
# 4 = THMC10, 5 = LM84, 6 = GL523, 7 = MC1066
|
|
# Registers used:
|
|
# 0x04: Company ID (LM84 only)
|
|
# 0xfe: Company ID (all but LM84 and MAX1617)
|
|
# 0xff: Revision (ADM1021, ADM1021A/ADM1023 and MAX1617A)
|
|
# 0x02: Status
|
|
# 0x03: Configuration
|
|
# 0x04: Conversion rate
|
|
# 0x00-0x01, 0x05-0x08: Temperatures (MAX1617 and LM84)
|
|
# Note: Especially the MAX1617 has very bad detection; we give it a low
|
|
# confidence value.
|
|
sub adm1021_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $man_id = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $rev = i2c_smbus_read_byte_data($file, 0xff);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $status = i2c_smbus_read_byte_data($file, 0x02);
|
|
my $convrate = i2c_smbus_read_byte_data($file, 0x04);
|
|
|
|
# Check manufacturer IDs and product revisions when available
|
|
return if $chip == 0 and $man_id != 0x41 || ($rev & 0xf0) != 0x00;
|
|
return if $chip == 1 and $man_id != 0x41 || ($rev & 0xf0) != 0x30;
|
|
return if $chip == 3 and $man_id != 0x4d || $rev != 0x01;
|
|
return if $chip == 4 and $man_id != 0x49;
|
|
return if $chip == 5 and $convrate != 0x00;
|
|
return if $chip == 6 and $man_id != 0x23;
|
|
return if $chip == 7 and $man_id != 0x54;
|
|
|
|
# Check unused bits
|
|
if ($chip == 5) { # LM84
|
|
return if ($status & 0xab) != 0;
|
|
return if ($conf & 0x7f) != 0;
|
|
} else {
|
|
return if ($status & 0x03) != 0;
|
|
return if ($conf & 0x3f) != 0;
|
|
return if ($convrate & 0xf8) != 0;
|
|
}
|
|
|
|
# Extra checks for MAX1617 and LM84, since those are often misdetected.
|
|
# We verify several assertions (6 for the MAX1617, 4 for the LM84) and
|
|
# discard the chip if any fail. Note that these checks are not done
|
|
# by the adm1021 driver.
|
|
if ($chip == 2 || $chip == 5) {
|
|
my $lte = i2c_smbus_read_byte_data($file, 0x00);
|
|
my $rte = i2c_smbus_read_byte_data($file, 0x01);
|
|
my $lhi = i2c_smbus_read_byte_data($file, 0x05);
|
|
my $rhi = i2c_smbus_read_byte_data($file, 0x07);
|
|
my $llo = i2c_smbus_read_byte_data($file, 0x06);
|
|
my $rlo = i2c_smbus_read_byte_data($file, 0x08);
|
|
|
|
# If all registers hold the same value, it has to be a misdetection
|
|
return if $lte == $rte and $lte == $lhi and $lte == $rhi
|
|
and $lte == $llo and $lte == $rlo;
|
|
|
|
# Negative temperatures
|
|
return if ($lte & 0x80) or ($rte & 0x80);
|
|
# Negative high limits
|
|
return if ($lhi & 0x80) or ($rhi & 0x80);
|
|
# Low limits over high limits
|
|
if ($chip == 2) {
|
|
$llo -= 256 if ($llo & 0x80);
|
|
$rlo -= 256 if ($rlo & 0x80);
|
|
return if ($llo > $lhi) or ($rlo > $rhi);
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
return ($chip <= 3) ? 7 : 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = MAX1668, 1 = MAX1805, 2 = MAX1989
|
|
# Registers used:
|
|
# 0xfe: Company ID
|
|
# 0xff: Device ID
|
|
sub max1668_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $man_id = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $dev_id = i2c_smbus_read_byte_data($file, 0xff);
|
|
|
|
return if $man_id != 0x4d;
|
|
return if $chip == 0 and $dev_id != 0x03;
|
|
return if $chip == 1 and $dev_id != 0x05;
|
|
return if $chip == 2 and $dev_id != 0x0b;
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Chip to detect: 0 = MAX1619, 1 = MAX1618
|
|
# Registers used:
|
|
# 0xfe: Company ID
|
|
# 0xff: Device ID
|
|
# 0x02: Status
|
|
# 0x03: Configuration
|
|
# 0x04: Conversion rate
|
|
sub max1619_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $man_id = i2c_smbus_read_byte_data($file, 0xfe);
|
|
my $dev_id = i2c_smbus_read_byte_data($file, 0xff);
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x03);
|
|
my $status = i2c_smbus_read_byte_data($file, 0x02);
|
|
my $convrate = i2c_smbus_read_byte_data($file, 0x04);
|
|
|
|
return if $man_id != 0x4D; # Maxim
|
|
|
|
if ($chip == 0) { # MAX1619
|
|
return if $dev_id != 0x04
|
|
or ($conf & 0x03)
|
|
or ($status & 0x61)
|
|
or $convrate >= 8;
|
|
}
|
|
if ($chip == 1) { # MAX1618
|
|
return if $dev_id != 0x02
|
|
or ($conf & 0x07)
|
|
or ($status & 0x63);
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x28: User ID
|
|
# 0x29: User ID2
|
|
sub ite_overclock_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
my $uid1 = i2c_smbus_read_byte_data($file, 0x28);
|
|
my $uid2 = i2c_smbus_read_byte_data($file, 0x29);
|
|
return if $uid1 != 0x83 || $uid2 != 0x12;
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00: Configuration
|
|
# 0x48: Full I2C Address
|
|
# 0x58: Mfr ID
|
|
# 0x5b: Device ID
|
|
sub it8712_i2c_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $reg;
|
|
return unless i2c_smbus_read_byte_data($file, 0x48) == $addr;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x00) & 0x90) == 0x10;
|
|
return unless i2c_smbus_read_byte_data($file, 0x58) == 0x90;
|
|
return if i2c_smbus_read_byte_data($file, 0x5b) != 0x12;
|
|
return 7 + ($addr == 0x2d);
|
|
}
|
|
|
|
# Registers used:
|
|
# 0-63: SPD Data and Checksum
|
|
sub eeprom_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
my $checksum = 0;
|
|
|
|
# Check the checksum for validity (works for most DIMMs and RIMMs)
|
|
for (my $i = 0; $i <= 62; $i++) {
|
|
$checksum += i2c_smbus_read_byte_data($file, $i);
|
|
}
|
|
$checksum &= 255;
|
|
|
|
return 8 if $checksum == i2c_smbus_read_byte_data($file, 63);
|
|
return;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x00..0x07: DDC signature
|
|
sub ddcmonitor_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
return unless
|
|
i2c_smbus_read_byte_data($file, 0x00) == 0x00 and
|
|
i2c_smbus_read_byte_data($file, 0x01) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x02) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x03) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x04) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x05) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x06) == 0xFF and
|
|
i2c_smbus_read_byte_data($file, 0x07) == 0x00;
|
|
|
|
return 8;
|
|
}
|
|
|
|
# Chip to detect: 0 = Poseidon I, 1 = Poseidon II, 2 = Scylla,
|
|
# 3 = Hermes, 4 = Heimdal, 5 = Heracles
|
|
# Registers used:
|
|
# 0x00-0x02: Identification (3 capital ASCII letters)
|
|
sub fsc_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $id;
|
|
|
|
$id = chr(i2c_smbus_read_byte_data($file, 0x00))
|
|
. chr(i2c_smbus_read_byte_data($file, 0x01))
|
|
. chr(i2c_smbus_read_byte_data($file, 0x02));
|
|
|
|
return if $chip == 0 and $id ne 'PEG'; # Pegasus? aka Poseidon I
|
|
return if $chip == 1 and $id ne 'POS'; # Poseidon II
|
|
return if $chip == 2 and $id ne 'SCY'; # Scylla
|
|
return if $chip == 3 and $id ne 'HER'; # Hermes
|
|
return if $chip == 4 and $id ne 'HMD'; # Heimdal
|
|
return if $chip == 5 and $id ne 'HRC'; # Heracles
|
|
return if $chip == 6 and $id ne 'HDS'; # Hades
|
|
return if $chip == 7 and $id ne 'SYL'; # Syleus
|
|
|
|
return 8;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x3E: Manufacturer ID
|
|
# 0x3F: Version/Stepping
|
|
sub lm93_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return unless i2c_smbus_read_byte_data($file, 0x3E) == 0x01
|
|
and i2c_smbus_read_byte_data($file, 0x3F) == 0x73;
|
|
return 5;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x3F: Revision ID
|
|
# 0x48: Address
|
|
# 0x4A, 0x4B, 0x4F, 0x57, 0x58: Reserved bits.
|
|
# We do not use 0x49's reserved bits on purpose. The register is named
|
|
# "VID4/Device ID" so it is doubtful bits 7-1 are really unused.
|
|
sub m5879_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
return unless i2c_smbus_read_byte_data($file, 0x3F) == 0x01;
|
|
return unless i2c_smbus_read_byte_data($file, 0x48) == $addr;
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x4A) & 0x06) == 0
|
|
and (i2c_smbus_read_byte_data($file, 0x4B) & 0xFC) == 0
|
|
and (i2c_smbus_read_byte_data($file, 0x4F) & 0xFC) == 0
|
|
and (i2c_smbus_read_byte_data($file, 0x57) & 0xFE) == 0
|
|
and (i2c_smbus_read_byte_data($file, 0x58) & 0xEF) == 0;
|
|
|
|
return 7;
|
|
}
|
|
|
|
# Registers used:
|
|
# 0x3E: Manufacturer ID
|
|
# 0x3F: Version/Stepping
|
|
# 0x47: VID (3 reserved bits)
|
|
# 0x49: VID4 (7 reserved bits)
|
|
sub smsc47m192_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return unless i2c_smbus_read_byte_data($file, 0x3E) == 0x55
|
|
and (i2c_smbus_read_byte_data($file, 0x3F) & 0xF0) == 0x20
|
|
and (i2c_smbus_read_byte_data($file, 0x47) & 0x70) == 0x00
|
|
and (i2c_smbus_read_byte_data($file, 0x49) & 0xFE) == 0x80;
|
|
return ($addr == 0x2d ? 6 : 5);
|
|
}
|
|
|
|
# Chip to detect: 1 = DME1737, 2 = SCH5027
|
|
# Registers used:
|
|
# 0x3E: Manufacturer ID
|
|
# 0x3F: Version/Stepping
|
|
# 0x73: Read-only test register (4 test bits)
|
|
# 0x8A: Read-only test register (7 test bits)
|
|
# 0xBA: Read-only test register (8 test bits)
|
|
sub dme1737_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $vendor = i2c_smbus_read_byte_data($file, 0x3E);
|
|
my $verstep = i2c_smbus_read_byte_data($file, 0x3F);
|
|
|
|
return unless $vendor == 0x5C; # SMSC
|
|
|
|
if ($chip == 1) { # DME1737
|
|
return unless ($verstep & 0xF8) == 0x88 and
|
|
(i2c_smbus_read_byte_data($file, 0x73) & 0x0F) == 0x09 and
|
|
(i2c_smbus_read_byte_data($file, 0x8A) & 0x7F) == 0x4D;
|
|
} elsif ($chip == 2) { # SCH5027
|
|
return unless $verstep >= 0x69 and $verstep <= 0x6F and
|
|
i2c_smbus_read_byte_data($file, 0xBA) == 0x0F;
|
|
}
|
|
|
|
return ($addr == 0x2e ? 6 : 5);
|
|
}
|
|
|
|
# Chip to detect: 1 = F75111R/RG/N, 2 = F75121R/F75122R/RG, 3 = F75373S/SG,
|
|
# 4 = F75375S/SP, 5 = F75387SG/RG, 6 = F75383M/S/F75384M/S,
|
|
# 7 = custom power control IC
|
|
# Registers used:
|
|
# 0x5A-0x5B: Chip ID
|
|
# 0x5D-0x5E: Vendor ID
|
|
sub fintek_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my $chipid = (i2c_smbus_read_byte_data($file, 0x5A) << 8)
|
|
| i2c_smbus_read_byte_data($file, 0x5B);
|
|
my $vendid = (i2c_smbus_read_byte_data($file, 0x5D) << 8)
|
|
| i2c_smbus_read_byte_data($file, 0x5E);
|
|
|
|
return unless $vendid == 0x1934; # Fintek ID
|
|
|
|
if ($chip == 1) { # F75111R/RG/N
|
|
return unless $chipid == 0x0300;
|
|
} elsif ($chip == 2) { # F75121R/F75122R/RG
|
|
return unless $chipid == 0x0301;
|
|
} elsif ($chip == 3) { # F75373S/SG
|
|
return unless $chipid == 0x0204;
|
|
} elsif ($chip == 4) { # F75375S/SP
|
|
return unless $chipid == 0x0306;
|
|
} elsif ($chip == 5) { # F75387SG/RG
|
|
return unless $chipid == 0x0410;
|
|
} elsif ($chip == 6) { # F75383M/S/F75384M/S
|
|
# The datasheet has 0x0303, but Fintek say 0x0413 is also
|
|
# possible
|
|
return unless $chipid == 0x0303 || $chipid == 0x0413;
|
|
} elsif ($chip == 7) { # custom power control IC
|
|
return unless $chipid == 0x0302;
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
# This checks for non-FFFF values for temperature, voltage, and current.
|
|
# The address (0x0b) is specified by the SMBus standard so it's likely
|
|
# that this really is a smart battery.
|
|
sub smartbatt_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
return if i2c_smbus_read_word_data($file, 0x08) == 0xffff
|
|
|| i2c_smbus_read_word_data($file, 0x09) == 0xffff
|
|
|| i2c_smbus_read_word_data($file, 0x0a) == 0xffff;
|
|
return 5;
|
|
}
|
|
|
|
# Chip to detect: 0 = W83L784R/AR/G, 1 = W83L785R/G, 2 = W83L786NR/NG/R/G,
|
|
# 3 = W83L785TS-S
|
|
# Registers used:
|
|
# 0x40: Configuration
|
|
# 0x4a: Full I2C Address (W83L784R only)
|
|
# 0x4b: I2C addresses of emulated LM75 chips (W83L784R only)
|
|
# 0x4c: Winbond Vendor ID (Low Byte)
|
|
# 0x4d: Winbond Vendor ID (High Byte)
|
|
# 0x4e: Chip ID
|
|
sub w83l784r_detect
|
|
{
|
|
my ($file, $addr, $chip) = @_;
|
|
my ($reg, @res);
|
|
|
|
return unless (i2c_smbus_read_byte_data($file, 0x40) & 0x80) == 0x00;
|
|
return if $chip == 0
|
|
and i2c_smbus_read_byte_data($file, 0x4a) != $addr;
|
|
return unless i2c_smbus_read_byte_data($file, 0x4c) == 0xa3;
|
|
return unless i2c_smbus_read_byte_data($file, 0x4d) == 0x5c;
|
|
|
|
$reg = i2c_smbus_read_byte_data($file, 0x4e);
|
|
return if $chip == 0 and $reg != 0x50;
|
|
return if $chip == 1 and $reg != 0x60;
|
|
return if $chip == 2 and $reg != 0x80;
|
|
return if $chip == 3 and $reg != 0x70;
|
|
|
|
return 8 if $chip != 0; # No subclients
|
|
|
|
@res = (8);
|
|
$reg = i2c_smbus_read_byte_data($file, 0x4b);
|
|
push @res, ($reg & 0x07) + 0x48 unless $reg & 0x08;
|
|
push @res, (($reg & 0x70) >> 4) + 0x48 unless $reg & 0x80;
|
|
return @res;
|
|
}
|
|
|
|
# The max6650 has no device ID register. However, a few registers have
|
|
# spare bits, which are documented as being always zero on read. We read
|
|
# all of these registers check the spare bits. Any non-zero means this
|
|
# is not a max6650/1.
|
|
#
|
|
# The always zero bits are:
|
|
# configuration byte register (0x02) - top 2 bits
|
|
# gpio status register (0x14) - top 3 bits
|
|
# alarm enable register (0x08) - top 3 bits
|
|
# alarm status register (0x0A) - top 3 bits
|
|
# tachometer count time register (0x16) - top 6 bits
|
|
# Additionally, not all values are possible for lower 3 bits of
|
|
# the configuration register.
|
|
sub max6650_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
my $conf = i2c_smbus_read_byte_data($file, 0x02);
|
|
|
|
return if i2c_smbus_read_byte_data($file, 0x16) & 0xFC;
|
|
return if i2c_smbus_read_byte_data($file, 0x0A) & 0xE0;
|
|
return if i2c_smbus_read_byte_data($file, 0x08) & 0xE0;
|
|
return if i2c_smbus_read_byte_data($file, 0x14) & 0xE0;
|
|
return if ($conf & 0xC0) or ($conf & 0x07) > 4;
|
|
|
|
return 2;
|
|
}
|
|
|
|
sub max6655_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
|
|
# checking RDID (Device ID)
|
|
return unless i2c_smbus_read_byte_data($file, 0xfe) == 0x0a;
|
|
# checking RDRV (Manufacturer ID)
|
|
return unless i2c_smbus_read_byte_data($file, 0xff) == 0x4d;
|
|
# checking unused bits (conversion rate, extended temperature)
|
|
return unless i2c_smbus_read_byte_data($file, 0x04) & 0xf8;
|
|
return unless i2c_smbus_read_byte_data($file, 0x10) & 0x1f;
|
|
return unless i2c_smbus_read_byte_data($file, 0x11) & 0x1f;
|
|
return unless i2c_smbus_read_byte_data($file, 0x12) & 0x1f;
|
|
|
|
return 6;
|
|
}
|
|
|
|
# This isn't very good detection.
|
|
# Verify the i2c address, and the stepping ID (which is 0xb0 on
|
|
# my chip but could be different for others...
|
|
sub vt1211_i2c_detect
|
|
{
|
|
my ($file, $addr) = @_;
|
|
return unless (i2c_smbus_read_byte_data($file, 0x48) & 0x7f) == $addr;
|
|
return unless i2c_smbus_read_byte_data($file, 0x3f) == 0xb0;
|
|
return 2;
|
|
}
|
|
|
|
# All ISA detection functions below take at least 1 parameter:
|
|
# $_[0]: Address
|
|
# Some of these functions which can detect more than one type of device,
|
|
# take a second parameter:
|
|
# $_[1]: Chip to detect
|
|
|
|
# Chip to detect: 0 = LM78, 2 = LM79
|
|
sub lm78_isa_detect
|
|
{
|
|
my ($addr, $chip) = @_;
|
|
my $val = inb($addr + 1);
|
|
return if inb($addr + 2) != $val or inb($addr + 3) != $val or
|
|
inb($addr + 7) != $val;
|
|
|
|
$val = inb($addr + 5);
|
|
outb($addr + 5, ~$val & 0x7f);
|
|
if ((inb($addr+5) & 0x7f) != (~ $val & 0x7f)) {
|
|
outb($addr+5, $val);
|
|
return;
|
|
}
|
|
|
|
return unless (isa_read_i5d6($addr, 0x40) & 0x80) == 0x00;
|
|
my $reg = isa_read_i5d6($addr, 0x49);
|
|
return if $chip == 0 && ($reg != 0x00 && $reg != 0x20 && $reg != 0x40);
|
|
return if $chip == 2 && ($reg & 0xfe) != 0xc0;
|
|
|
|
# Explicitly prevent misdetection of Winbond chips
|
|
$reg = isa_read_i5d6($addr, 0x4f);
|
|
return if $reg == 0xa3 || $reg == 0x5c;
|
|
|
|
# Explicitly prevent misdetection of ITE chips
|
|
$reg = isa_read_i5d6($addr, 0x58);
|
|
return if $reg == 0x90;
|
|
|
|
return 6;
|
|
}
|
|
|
|
# Chip to detect: 0 = W83781D, 1 = W83782D
|
|
sub w83781d_isa_detect
|
|
{
|
|
my ($addr, $chip) = @_;
|
|
my ($reg1, $reg2);
|
|
my $val = inb($addr + 1);
|
|
return if inb($addr + 2) != $val or inb($addr + 3) != $val or
|
|
inb($addr + 7) != $val;
|
|
|
|
$val = inb($addr + 5);
|
|
outb($addr+5, ~$val & 0x7f);
|
|
if ((inb($addr+5) & 0x7f) != (~ $val & 0x7f)) {
|
|
outb($addr+5, $val);
|
|
return;
|
|
}
|
|
|
|
$reg1 = isa_read_i5d6($addr, 0x4e);
|
|
$reg2 = isa_read_i5d6($addr, 0x4f);
|
|
return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0xa3) or
|
|
(($reg1 & 0x80) == 0x80 and $reg2 == 0x5c);
|
|
return unless ($reg1 & 0x07) == 0x00;
|
|
$reg1 = isa_read_i5d6($addr, 0x58);
|
|
return if $chip == 0 && ($reg1 & 0xfe) != 0x10;
|
|
return if $chip == 1 && ($reg1 & 0xfe) != 0x30;
|
|
|
|
return 8;
|
|
}
|
|
|
|
########
|
|
# IPMI #
|
|
########
|
|
|
|
# Returns: number of IPMI interfaces found
|
|
sub ipmi_from_smbios
|
|
{
|
|
my ($version, $if, @ipmi_if);
|
|
|
|
return 0 unless check_dmidecode_version();
|
|
|
|
# Parse the output of dmidecode into an array of IPMI interfaces.
|
|
# Each entry is a hash with the following keys: type and addr.
|
|
$if = -1;
|
|
open(local *DMIDECODE, "dmidecode -t 38 2>/dev/null |") or return 0;
|
|
while (<DMIDECODE>) {
|
|
if (m/^IPMI Device Information/) {
|
|
$if++;
|
|
next;
|
|
}
|
|
next unless $if >= 0;
|
|
|
|
if (m/^\tInterface Type: (.*)$/) {
|
|
$ipmi_if[$if]->{type} = "IPMI BMC $1";
|
|
$ipmi_if[$if]->{type} =~ s/ \(.*//;
|
|
next;
|
|
}
|
|
if (m/^\tBase Address: (0x[0-9A-Fa-f]+) \(I\/O\)$/) {
|
|
$ipmi_if[$if]->{addr} = oct($1);
|
|
next;
|
|
}
|
|
}
|
|
close(DMIDECODE);
|
|
|
|
foreach $if (@ipmi_if) {
|
|
if (exists $if->{addr}) {
|
|
printf("\%-60s", sprintf("Found `\%s' at 0x\%x... ",
|
|
$if->{type}, $if->{addr}));
|
|
} else {
|
|
printf("\%-60s", sprintf("Found `\%s'... ",
|
|
$if->{type}));
|
|
}
|
|
print "Success!\n".
|
|
" (confidence 8, driver `ipmisensors')\n";
|
|
my $new_hash = {
|
|
conf => 8,
|
|
isa_addr => $if->{addr} || 0,
|
|
chipname => $if->{type},
|
|
};
|
|
add_isa_to_chips_detected("ipmisensors", $new_hash);
|
|
}
|
|
|
|
return scalar @ipmi_if;
|
|
}
|
|
|
|
# We simply look for a register at standard locations.
|
|
# For KCS, use the STATUS register. For SMIC, use the FLAGS register.
|
|
# Incidentally they live at the same offset.
|
|
sub ipmi_detect
|
|
{
|
|
my ($addr) = @_;
|
|
return if inb($addr + 3) == 0xff;
|
|
return 4;
|
|
}
|
|
|
|
###################
|
|
# ALIAS DETECTION #
|
|
###################
|
|
|
|
# These functions take at least 3 parameters:
|
|
# $_[0]: ISA/LPC address
|
|
# $_[1]: I2C file handle
|
|
# $_[2]: I2C address
|
|
# Some of these functions may take extra parameters.
|
|
# They return 1 if both devices are the same, 0 if not.
|
|
|
|
# Extra parameters:
|
|
# $_[3]: First limit register to compare
|
|
# $_[4]: Last limit register to compare
|
|
sub winbond_alias_detect
|
|
{
|
|
my ($isa_addr, $file, $i2c_addr, $first, $last) = @_;
|
|
my $i;
|
|
|
|
return 0 unless isa_read_i5d6($isa_addr, 0x48) == $i2c_addr;
|
|
for ($i = $first; $i <= $last; $i++) {
|
|
return 0 unless isa_read_i5d6($isa_addr, $i) ==
|
|
i2c_smbus_read_byte_data($file, $i);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
############################
|
|
# PCI CHIP / CPU DETECTION #
|
|
############################
|
|
|
|
sub sis5595_pci_detect
|
|
{
|
|
return unless exists $pci_list{'1039:0008'};
|
|
return 9;
|
|
}
|
|
|
|
sub via686a_pci_detect
|
|
{
|
|
return unless exists $pci_list{'1106:3057'};
|
|
return 9;
|
|
}
|
|
|
|
sub via8231_pci_detect
|
|
{
|
|
return unless exists $pci_list{'1106:8235'};
|
|
return 9;
|
|
}
|
|
|
|
sub k8temp_pci_detect
|
|
{
|
|
return unless exists $pci_list{'1022:1103'};
|
|
return 9;
|
|
}
|
|
|
|
sub fam11h_pci_detect
|
|
{
|
|
return unless exists $pci_list{'1022:1303'};
|
|
return 9;
|
|
}
|
|
|
|
sub intel_amb_detect
|
|
{
|
|
if ((exists $pci_list{'8086:25f0'}) || # Intel 5000
|
|
(exists $pci_list{'8086:4030'})) { # Intel 5400
|
|
return 9;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub coretemp_detect
|
|
{
|
|
my $probecpu;
|
|
foreach $probecpu (@cpu) {
|
|
if ($probecpu->{vendor_id} eq 'GenuineIntel' &&
|
|
$probecpu->{'cpu family'} == 6 &&
|
|
($probecpu->{model} == 14 || # Pentium M DC
|
|
$probecpu->{model} == 15 || # Core 2 DC 65nm
|
|
$probecpu->{model} == 0x16 || # Core 2 SC 65nm
|
|
$probecpu->{model} == 0x17 || # Penryn 45nm
|
|
$probecpu->{model} == 0x1a || # Nehalem
|
|
$probecpu->{model} == 0x1c)) { # Atom
|
|
return 9;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub c7temp_detect
|
|
{
|
|
my $probecpu;
|
|
foreach $probecpu (@cpu) {
|
|
if ($probecpu->{vendor_id} eq 'CentaurHauls' &&
|
|
$probecpu->{'cpu family'} == 6 &&
|
|
($probecpu->{model} == 0xa ||
|
|
$probecpu->{model} == 0xd)) {
|
|
return 9;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#################
|
|
# SPECIAL MODES #
|
|
#################
|
|
|
|
sub show_i2c_stats
|
|
{
|
|
my ($chip, $addr, %histo, $chips);
|
|
|
|
# Gather the data
|
|
foreach $chip (@chip_ids) {
|
|
next unless defined $chip->{i2c_addrs};
|
|
$chips++;
|
|
foreach my $addr (@{$chip->{i2c_addrs}}) {
|
|
$histo{$addr}++;
|
|
}
|
|
}
|
|
|
|
# Display the data
|
|
printf "\%d I2C chips known, \%d I2C addresses probed\n\n",
|
|
$chips, scalar keys %histo;
|
|
print " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n".
|
|
"00: ";
|
|
for (my $addr = 0x03; $addr <= 0x77; $addr++) {
|
|
printf("\n\%02x:", $addr) if ($addr % 16) == 0;
|
|
if (defined $histo{$addr}) {
|
|
printf ' %02d', $histo{$addr};
|
|
} else {
|
|
print ' --';
|
|
}
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
################
|
|
# MAIN PROGRAM #
|
|
################
|
|
|
|
# $_[0]: reference to a list of chip hashes
|
|
sub print_chips_report
|
|
{
|
|
my ($listref) = @_;
|
|
my $data;
|
|
|
|
foreach $data (@$listref) {
|
|
my $is_i2c = exists $data->{i2c_addr};
|
|
my $is_isa = exists $data->{isa_addr};
|
|
print " * ";
|
|
if ($is_i2c) {
|
|
printf "Bus `%s'\n", $i2c_adapters[$data->{i2c_devnr}]->{name};
|
|
printf " Busdriver `%s', I2C address 0x%02x",
|
|
$i2c_adapters[$data->{i2c_devnr}]->{driver}, $data->{i2c_addr};
|
|
if (exists $data->{i2c_sub_addrs}) {
|
|
print " (and";
|
|
my $sub_addr;
|
|
foreach $sub_addr (@{$data->{i2c_sub_addrs}}) {
|
|
printf " 0x%02x", $sub_addr;
|
|
}
|
|
print ")"
|
|
}
|
|
print "\n ";
|
|
}
|
|
if ($is_isa) {
|
|
print "ISA bus";
|
|
if ($data->{isa_addr}) {
|
|
printf ", address 0x%x", $data->{isa_addr};
|
|
}
|
|
print " (Busdriver `i2c-isa')"
|
|
unless kernel_version_at_least(2, 6, 18);
|
|
print "\n ";
|
|
}
|
|
printf "Chip `%s' (confidence: %d)\n",
|
|
$data->{chipname}, $data->{conf};
|
|
}
|
|
}
|
|
|
|
sub generate_modprobes
|
|
{
|
|
my ($driver, $detection, $adap);
|
|
my ($configfile, %bus_modules, %hwmon_modules);
|
|
|
|
foreach $driver (keys %chips_detected) {
|
|
foreach $detection (@{$chips_detected{$driver}}) {
|
|
# Tag adapters which host hardware monitoring chips we want to access
|
|
if (exists $detection->{i2c_devnr}
|
|
&& !exists $detection->{isa_addr}) {
|
|
$i2c_adapters[$detection->{i2c_devnr}]->{used}++;
|
|
}
|
|
|
|
# i2c-isa is loaded automatically (as a dependency)
|
|
# since 2.6.14, and will soon be gone.
|
|
if (exists $detection->{isa_addr}
|
|
&& !kernel_version_at_least(2, 6, 18)) {
|
|
$bus_modules{"i2c-isa"}++
|
|
}
|
|
}
|
|
if ($driver eq "ipmisensors") {
|
|
$bus_modules{"ipmi-si"}++;
|
|
}
|
|
}
|
|
|
|
# Handle aliases
|
|
# As of kernel 2.6.28, alias detection is handled by kernel drivers
|
|
# directly, so module options are no longer needed.
|
|
unless (kernel_version_at_least(2, 6, 28)) {
|
|
foreach $driver (keys %chips_detected) {
|
|
my @optionlist = ();
|
|
foreach $detection (@{$chips_detected{$driver}}) {
|
|
next unless exists $detection->{i2c_addr}
|
|
&& exists $detection->{isa_addr}
|
|
&& $i2c_adapters[$detection->{i2c_devnr}]->{used};
|
|
|
|
push @optionlist, sprintf("%d,0x%02x",
|
|
$detection->{i2c_devnr},
|
|
$detection->{i2c_addr});
|
|
}
|
|
|
|
next if not @optionlist;
|
|
$configfile = "# hwmon module options\n"
|
|
unless defined $configfile;
|
|
$configfile .= "options $driver ignore=".
|
|
(join ",", @optionlist)."\n";
|
|
}
|
|
}
|
|
|
|
# If we added any module option to handle aliases, we need to load all
|
|
# the adapter drivers so that the numbers will be the same. If not, then
|
|
# we only load the adapter drivers which are useful.
|
|
foreach $adap (@i2c_adapters) {
|
|
next if $adap->{autoload};
|
|
next if $adap->{driver} eq 'UNKNOWN';
|
|
next if not defined $configfile and not $adap->{used};
|
|
$bus_modules{$adap->{driver}}++;
|
|
}
|
|
|
|
# Now determine the chip probe lines
|
|
foreach $driver (keys %chips_detected) {
|
|
next if not @{$chips_detected{$driver}};
|
|
if ($driver eq "to-be-written") {
|
|
print "Note: there is no driver for ${$chips_detected{$driver}}[0]{chipname} yet.\n".
|
|
"Check http://www.lm-sensors.org/wiki/Devices for updates.\n\n";
|
|
} else {
|
|
open(local *INPUTFILE, "modprobe -l $driver 2>/dev/null |");
|
|
local $_;
|
|
my $modulefound = 0;
|
|
while (<INPUTFILE>) {
|
|
if (m@/@) {
|
|
$modulefound = 1;
|
|
last;
|
|
}
|
|
}
|
|
close(INPUTFILE);
|
|
# Check return value from modprobe in case modprobe -l
|
|
# isn't supported
|
|
if ((($? >> 8) == 0) && ! $modulefound) {
|
|
print "Warning: the required module $driver is not currently installed\n".
|
|
"on your system. If it is built into the kernel then it's OK.\n".
|
|
"Otherwise, check http://www.lm-sensors.org/wiki/Devices for\n".
|
|
"driver availability.\n\n";
|
|
} else {
|
|
$hwmon_modules{$driver}++
|
|
unless hwmon_is_autoloaded($driver);
|
|
}
|
|
}
|
|
}
|
|
|
|
my @bus_modules = sort keys %bus_modules;
|
|
my @hwmon_modules = sort keys %hwmon_modules;
|
|
return ($configfile, \@bus_modules, \@hwmon_modules);
|
|
}
|
|
|
|
sub write_config
|
|
{
|
|
my ($configfile, $bus_modules, $hwmon_modules) = @_;
|
|
|
|
if (defined $configfile) {
|
|
my $have_modprobe_d = -d '/etc/modprobe.d';
|
|
printf "Do you want to \%s /etc/modprobe.d/lm_sensors.conf? (\%s): ",
|
|
(-e '/etc/modprobe.d/lm_sensors.conf' ? 'overwrite' : 'generate'),
|
|
($have_modprobe_d ? 'YES/no' : 'yes/NO');
|
|
$_ = <STDIN>;
|
|
if (($have_modprobe_d and not m/^\s*n/i) or m/^\s*y/i) {
|
|
unless ($have_modprobe_d) {
|
|
mkdir('/etc/modprobe.d', 0777)
|
|
or die "Sorry, can't create /etc/modprobe.d ($!)";
|
|
}
|
|
open(local *MODPROBE_D, ">/etc/modprobe.d/lm_sensors.conf")
|
|
or die "Sorry, can't create /etc/modprobe.d/lm_sensors.conf ($!)";
|
|
print MODPROBE_D "# Generated by sensors-detect on " . scalar localtime() . "\n";
|
|
print MODPROBE_D $configfile;
|
|
close(MODPROBE_D);
|
|
} else {
|
|
print "To make the sensors modules behave correctly, add these lines to\n".
|
|
"/etc/modprobe.conf:\n\n";
|
|
print "#----cut here----\n".
|
|
$configfile.
|
|
"#----cut here----\n\n";
|
|
}
|
|
}
|
|
|
|
my $have_sysconfig = -d '/etc/sysconfig';
|
|
printf "Do you want to \%s /etc/sysconfig/lm_sensors? (\%s): ",
|
|
(-e '/etc/sysconfig/lm_sensors' ? 'overwrite' : 'generate'),
|
|
($have_sysconfig ? 'YES/no' : 'yes/NO');
|
|
$_ = <STDIN>;
|
|
if (($have_sysconfig and not m/^\s*n/i) or m/^\s*y/i) {
|
|
unless ($have_sysconfig) {
|
|
mkdir('/etc/sysconfig', 0777)
|
|
or die "Sorry, can't create /etc/sysconfig ($!)";
|
|
}
|
|
open(local *SYSCONFIG, ">/etc/sysconfig/lm_sensors")
|
|
or die "Sorry, can't create /etc/sysconfig/lm_sensors ($!)";
|
|
print SYSCONFIG "# Generated by sensors-detect on " . scalar localtime() . "\n";
|
|
print SYSCONFIG <<'EOT';
|
|
# This file is sourced by /etc/init.d/lm_sensors and defines the modules to
|
|
# be loaded/unloaded.
|
|
#
|
|
# The format of this file is a shell script that simply defines variables:
|
|
# HWMON_MODULES for hardware monitoring driver modules, and optionally
|
|
# BUS_MODULES for any required bus driver module (for example for I2C or SPI).
|
|
|
|
EOT
|
|
print SYSCONFIG "BUS_MODULES=\"", join(" ", @{$bus_modules}), "\"\n"
|
|
if @{$bus_modules};
|
|
print SYSCONFIG "HWMON_MODULES=\"", join(" ", @{$hwmon_modules}), "\"\n";
|
|
|
|
print SYSCONFIG <<'EOT';
|
|
|
|
# For compatibility reasons, modules are also listed individually as variables
|
|
# MODULE_0, MODULE_1, MODULE_2, etc.
|
|
# You should use BUS_MODULES and HWMON_MODULES instead if possible.
|
|
|
|
EOT
|
|
my $i = 0;
|
|
foreach (@{$bus_modules}, @{$hwmon_modules}) {
|
|
print SYSCONFIG "MODULE_$i=$_\n";
|
|
$i++;
|
|
}
|
|
close(SYSCONFIG);
|
|
|
|
print "Copy prog/init/lm_sensors.init to /etc/init.d/lm_sensors\n".
|
|
"for initialization at boot time.\n"
|
|
unless -f "/etc/init.d/lm_sensors";
|
|
|
|
if (-x "/sbin/insserv" && -f "/etc/init.d/lm_sensors") {
|
|
system("/sbin/insserv", "/etc/init.d/lm_sensors");
|
|
} elsif (-x "/sbin/chkconfig" && -f "/etc/init.d/lm_sensors") {
|
|
system("/sbin/chkconfig", "lm_sensors", "on");
|
|
if (-x "/sbin/service") {
|
|
system("/sbin/service", "lm_sensors", "start");
|
|
}
|
|
} else {
|
|
print "You should now start the lm_sensors service to load the required\n".
|
|
"kernel modules.\n\n";
|
|
}
|
|
} else {
|
|
print "To load everything that is needed, add this to one of the system\n".
|
|
"initialization scripts (e.g. /etc/rc.d/rc.local):\n\n";
|
|
print "#----cut here----\n";
|
|
if (@{$bus_modules}) {
|
|
print "# Adapter drivers\n";
|
|
print "modprobe $_\n" foreach (@{$bus_modules});
|
|
}
|
|
print "# Chip drivers\n";
|
|
print "modprobe $_\n" foreach (@{$hwmon_modules});
|
|
print((-e '/usr/bin/sensors' ?
|
|
"/usr/bin/sensors -s\n" :
|
|
"/usr/local/bin/sensors -s\n").
|
|
"#----cut here----\n\n");
|
|
|
|
print "If you have some drivers built into your kernel, the list above will\n".
|
|
"contain too many modules. Skip the appropriate ones! You really\n".
|
|
"should try these commands right now to make sure everything is\n".
|
|
"working properly. Monitoring programs won't work until the needed\n".
|
|
"modules are loaded.\n\n";
|
|
}
|
|
|
|
}
|
|
|
|
sub main
|
|
{
|
|
my ($input, $superio_features);
|
|
|
|
# Handle special command line cases first
|
|
if (defined $ARGV[0] && $ARGV[0] eq "--stat") {
|
|
show_i2c_stats();
|
|
exit 0;
|
|
}
|
|
|
|
# We won't go very far if not root
|
|
unless ($> == 0) {
|
|
print "You need to be root to run this script.\n";
|
|
exit -1;
|
|
}
|
|
|
|
if (-x "/sbin/service" && -f "/etc/init.d/lm_sensors" &&
|
|
-f "/var/lock/subsys/lm_sensors") {
|
|
system("/sbin/service", "lm_sensors", "stop");
|
|
}
|
|
|
|
initialize_kernel_version();
|
|
initialize_conf();
|
|
initialize_pci();
|
|
initialize_modules_list();
|
|
# Make sure any special case chips are added to the chip_ids list
|
|
# before making the support modules list
|
|
chip_special_cases();
|
|
initialize_modules_supported();
|
|
initialize_cpu_list();
|
|
|
|
print "# sensors-detect revision $revision\n";
|
|
initialize_dmi_data();
|
|
print_dmi_summary();
|
|
print "\n";
|
|
print "This program will help you determine which kernel modules you need\n",
|
|
"to load to use lm_sensors most effectively. It is generally safe\n",
|
|
"and recommended to accept the default answers to all questions,\n",
|
|
"unless you know what you're doing.\n\n";
|
|
|
|
print "Some south bridges, CPUs or memory controllers contain embedded sensors.\n".
|
|
"Do you want to scan for them? This is totally safe. (YES/no): ";
|
|
unless (<STDIN> =~ /^\s*n/i) {
|
|
$| = 1;
|
|
foreach my $entry (@cpu_ids) {
|
|
scan_cpu($entry);
|
|
}
|
|
$| = 0;
|
|
}
|
|
print "\n";
|
|
|
|
$superio_features = 0;
|
|
# Skip "random" I/O port probing on PPC
|
|
if ($kernel_arch ne 'ppc'
|
|
&& $kernel_arch ne 'ppc64') {
|
|
print "Some Super I/O chips contain embedded sensors. We have to write to\n".
|
|
"standard I/O ports to probe them. This is usually safe.\n";
|
|
print "Do you want to scan for Super I/O sensors? (YES/no): ";
|
|
unless (<STDIN> =~ /^\s*n/i) {
|
|
initialize_ioports();
|
|
$superio_features |= scan_superio(0x2e, 0x2f);
|
|
$superio_features |= scan_superio(0x4e, 0x4f);
|
|
close_ioports();
|
|
}
|
|
print "\n";
|
|
|
|
unless (is_laptop()) {
|
|
print "Some systems (mainly servers) implement IPMI, a set of common interfaces\n".
|
|
"through which system health data may be retrieved, amongst other things.\n".
|
|
"We first try to get the information from SMBIOS. If we don't find it\n".
|
|
"there, we have to read from arbitrary I/O ports to probe for such\n".
|
|
"interfaces. This is normally safe. Do you want to scan for IPMI\n".
|
|
"interfaces? (YES/no): ";
|
|
unless (<STDIN> =~ /^\s*n/i) {
|
|
if (!ipmi_from_smbios()) {
|
|
initialize_ioports();
|
|
scan_isa_bus(\@ipmi_ifs);
|
|
close_ioports();
|
|
}
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
printf "Some hardware monitoring chips are accessible through the ISA I/O ports.\n".
|
|
"We have to write to arbitrary I/O ports to probe them. This is usually\n".
|
|
"safe though. Yes, you do have ISA I/O ports even if you do not have any\n".
|
|
"ISA slots! Do you want to scan the ISA I/O ports? (\%s): ",
|
|
$superio_features ? "yes/NO" : "YES/no";
|
|
$input = <STDIN>;
|
|
unless ($input =~ /^\s*n/i
|
|
|| ($superio_features && $input !~ /^\s*y/i)) {
|
|
initialize_ioports();
|
|
scan_isa_bus(\@chip_ids);
|
|
close_ioports();
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
print "Lastly, we can probe the I2C/SMBus adapters for connected hardware\n".
|
|
"monitoring devices. This is the most risky part, and while it works\n".
|
|
"reasonably well on most systems, it has been reported to cause trouble\n".
|
|
"on some systems.\n".
|
|
"Do you want to probe the I2C/SMBus adapters now? (YES/no): ";
|
|
|
|
unless (<STDIN> =~ /^\s*n/i) {
|
|
adapter_pci_detection();
|
|
load_module("i2c-dev") unless -e "$sysfs_root/class/i2c-dev";
|
|
initialize_i2c_adapters_list();
|
|
$i2c_addresses_to_scan = i2c_addresses_to_scan();
|
|
print "\n";
|
|
|
|
# Skip SMBus probing by default if Super-I/O has all the features
|
|
my $by_default = ~$superio_features & (FEAT_IN | FEAT_FAN | FEAT_TEMP);
|
|
# Except on Asus and Tyan boards which often have more than
|
|
# one hardware monitoring chip
|
|
$by_default = 1 if dmi_match('board_vendor', 'asustek', 'tyan',
|
|
'supermicro');
|
|
|
|
for (my $dev_nr = 0; $dev_nr < @i2c_adapters; $dev_nr++) {
|
|
next unless exists $i2c_adapters[$dev_nr];
|
|
scan_i2c_adapter($dev_nr, $by_default);
|
|
}
|
|
}
|
|
|
|
if (!keys %chips_detected) {
|
|
print "Sorry, no sensors were detected.\n";
|
|
if (is_laptop() && -d "$sysfs_root/firmware/acpi") {
|
|
print "This is relatively common on laptops, where thermal management is\n".
|
|
"handled by ACPI rather than the OS.\n";
|
|
} else {
|
|
print "Either your system has no sensors, or they are not supported, or\n".
|
|
"they are connected to an I2C or SMBus adapter that is not\n".
|
|
"supported. If you find out what chips are on your board, check\n".
|
|
"http://www.lm-sensors.org/wiki/Devices for driver status.\n";
|
|
}
|
|
exit;
|
|
}
|
|
|
|
print "Now follows a summary of the probes I have just done.\n".
|
|
"Just press ENTER to continue: ";
|
|
<STDIN>;
|
|
|
|
initialize_hwmon_autoloaded();
|
|
foreach my $driver (keys %chips_detected) {
|
|
next unless @{$chips_detected{$driver}};
|
|
find_aliases($chips_detected{$driver});
|
|
print "\nDriver `$driver'";
|
|
print " (autoloaded)" if hwmon_is_autoloaded($driver);
|
|
print ":\n";
|
|
print_chips_report($chips_detected{$driver});
|
|
}
|
|
print "\n";
|
|
|
|
my ($configfile, $bus_modules, $hwmon_modules) = generate_modprobes();
|
|
|
|
if (@{$hwmon_modules}) {
|
|
write_config($configfile, $bus_modules, $hwmon_modules);
|
|
} else {
|
|
print "No modules to load, skipping modules configuration.\n\n";
|
|
}
|
|
|
|
unload_modules();
|
|
}
|
|
|
|
sub cleanup_on_int
|
|
{
|
|
print "\n";
|
|
unload_modules();
|
|
exit;
|
|
}
|
|
|
|
$SIG{INT} = \&cleanup_on_int;
|
|
|
|
main;
|