#!/usr/bin/perl -w # # sensors-detect - Detect hardware monitoring chips # Copyright (C) 1998 - 2002 Frodo Looijaard # Copyright (C) 2004 - 2015 Jean Delvare # # 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 qw(:DEFAULT :seek); 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 constant LM_VERSION => '3.6.0+git'; use vars qw(@pci_adapters @chip_ids @ipmi_ifs @non_hwmon_chip_ids $i2c_addresses_to_scan @i2c_byte_cache %opt); # 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-written", }, { 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 => 0x3B30, procid => "Intel 3400/5 Series (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1C22, procid => "Intel Cougar Point (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1D22, procid => "Intel Patsburg (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1D70, procid => "Intel Patsburg (PCH) IDF", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1D71, procid => "Intel Patsburg (PCH) IDF", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1D72, procid => "Intel Patsburg (PCH) IDF", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x2330, procid => "Intel DH89xxCC (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1E22, procid => "Intel Panther Point (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8C22, procid => "Intel Lynx Point (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x9C22, procid => "Intel Lynx Point-LP (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x1F3C, procid => "Avoton (SOC)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8D22, procid => "Wellsburg (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8D7D, procid => "Wellsburg (PCH) MS", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8D7E, procid => "Wellsburg (PCH) MS", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8D7F, procid => "Wellsburg (PCH) MS", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x23B0, procid => "Coleto Creek (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x8CA2, procid => "Wildcat Point (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x9CA2, procid => "Wildcat Point-LP (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x0F12, procid => "BayTrail (SOC)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0xA123, procid => "Sunrise Point-H (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x9D23, procid => "Sunrise Point-LP (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x19DF, procid => "DNV (SOC)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x5AD4, procid => "Broxton (SOC)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0xA1A3, procid => "Lewisburg (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0xA223, procid => "Lewisburg Supersku (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0xA2A3, procid => "Kaby Lake (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x31D4, procid => "Gemini Lake (SOC)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0xA323, procid => "Cannon Lake-H (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x9DA3, procid => "Cannon Lake-LP (PCH)", driver => "i2c-i801", }, { vendid => 0x8086, devid => 0x18DF, procid => "Cedar Fork (PCH)", 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 => 0x1022, devid => 0x790b, procid => "AMD KERNCZ 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 => "National Semiconductor LM75A", driver => "lm75", i2c_addrs => [0x48..0x4f], i2c_detect => sub { lm75_detect(@_, 2); }, }, { 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(@_, 0); }, }, { name => "National Semiconductor LM96080", driver => "lm80", i2c_addrs => [0x28..0x2f], i2c_detect => sub { lm80_detect(@_, 1); }, }, { name => "TI / National Semiconductor ADC128D818", driver => "adc128d818", i2c_addrs => [0x1d, 0x1e, 0x1f, 0x2d, 0x2e, 0x2f], i2c_detect => sub { adc128d818_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 => "SMSC EMC6D103S or EMC2300", driver => "lm85", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { lm85_detect(@_, 8); }, }, { name => "SMSC EMC6W201", driver => "emc6w201", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { emc6w201_detect(@_); }, }, { 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 => "lm85", 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 => sub { kernel_version_at_least(2, 6, 33) ? "adt7475" : "adt7473" }, i2c_addrs => [0x2c..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 => "adt7475", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { adt7467_detect(@_, 1); }, }, { name => "Analog Devices ADT7490", driver => "adt7475", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { adt7490_detect(@_); }, }, { name => "Analog Devices ADT7410/ADT7420", driver => "adt7410", i2c_addrs => [0x48..0x4b], i2c_detect => sub { adt7410_detect(@_); }, }, { name => "Analog Devices ADT7411", driver => "adt7411", i2c_addrs => [0x48, 0x4a, 0x4b], i2c_detect => sub { adt7411_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 => "asc7621", 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(@_, 0); }, }, { name => "National Semiconductor LM94 or LM96194", driver => "lm93", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { lm93_detect(@_, 1); }, }, { 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 => "Nuvoton NCT7802Y", driver => "nct7802", i2c_addrs => [0x28..0x2f], i2c_detect => sub { nct7802_detect(@_); }, }, { name => "Nuvoton NCT7904D", driver => "nct7904", i2c_addrs => [0x2d..0x2e], i2c_detect => sub { nct7904_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 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 => "Global Mixed-mode Technology G781", driver => "lm90", i2c_addrs => [0x4c, 0x4d], i2c_detect => sub { lm90_detect(@_, 15); }, }, { 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 MAX6639", driver => "max6639", i2c_addrs => [0x2c, 0x2e, 0x2f], i2c_detect => sub { max6639_detect(@_); }, }, { name => "Maxim MAX6642", driver => "max6642", i2c_addrs => [0x48..0x4f], i2c_detect => sub { max6642_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", driver => "to-be-written", # probably lm90 i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e], i2c_detect => sub { lm90_detect(@_, 4); }, }, { name => "Maxim MAX6690", driver => "to-be-written", # probably lm90 i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e], i2c_detect => sub { lm90_detect(@_, 12); }, }, { 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 { max6680_95_detect(@_, 0); }, }, { name => "Maxim MAX6695/MAX6696", driver => "lm90", i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e], i2c_detect => sub { max6680_95_detect(@_, 1); }, }, { name => "Winbond W83L771W/G", driver => "lm90", i2c_addrs => [0x4c], i2c_detect => sub { lm90_detect(@_, 8); }, }, { name => "Winbond W83L771AWG/ASG", driver => "lm90", i2c_addrs => [0x4c], i2c_detect => sub { lm90_detect(@_, 11); }, }, { name => "Texas Instruments TMP400", driver => "to-be-written", # tmp401 i2c_addrs => [0x18..0x1a, 0x29..0x2b, 0x4c..0x4e], i2c_detect => sub { tmp401_detect(@_, 0); }, }, { name => "Texas Instruments TMP401", driver => "tmp401", i2c_addrs => [0x4c], i2c_detect => sub { tmp401_detect(@_, 1); }, }, { name => "Texas Instruments TMP411A", driver => "tmp401", i2c_addrs => [0x4c], i2c_detect => sub { tmp401_detect(@_, 2); }, }, { name => "Texas Instruments TMP411B", driver => "tmp401", i2c_addrs => [0x4d], i2c_detect => sub { tmp401_detect(@_, 3); }, }, { name => "Texas Instruments TMP411C", driver => "tmp401", i2c_addrs => [0x4e], i2c_detect => sub { tmp401_detect(@_, 4); }, }, { name => "Texas Instruments TMP421", driver => "tmp421", i2c_addrs => [0x1c..0x1f, 0x2a, 0x4c..0x4f], 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 => "Texas Instruments TMP431", driver => "tmp401", i2c_addrs => [0x4c, 0x4d], i2c_detect => sub { tmp401_detect(@_, 5); }, }, { name => "Texas Instruments TMP432", driver => "tmp401", i2c_addrs => [0x4c, 0x4d], i2c_detect => sub { tmp401_detect(@_, 6); }, }, { name => "Texas Instruments TMP435", driver => "tmp401", i2c_addrs => [0x48..0x4f], # 0x37 not probed i2c_detect => sub { tmp401_detect(@_, 7); }, }, { name => "Texas Instruments TMP441", driver => "tmp421", i2c_addrs => [0x1c..0x1f, 0x2a, 0x4c..0x4f], i2c_detect => sub { tmp42x_detect(@_, 3); }, }, { name => "Texas Instruments TMP442", driver => "tmp421", i2c_addrs => [0x4c, 0x4d], i2c_detect => sub { tmp42x_detect(@_, 4); }, }, { name => "Texas Instruments TMP451", driver => "lm90", i2c_addrs => [0x4c], i2c_detect => sub { tmp401_detect(@_, 8); }, }, { name => "Texas Instruments AMC6821", driver => "amc6821", i2c_addrs => [0x18..0x1a, 0x2c..0x2e, 0x4c..0x4e], i2c_detect => sub { amc6821_detect(@_); }, }, { name => "National Semiconductor LM95231", driver => "lm95241", i2c_addrs => [0x2b, 0x19, 0x2a], i2c_detect => sub { lm95231_detect(@_, 0); }, }, { name => "National Semiconductor LM95233", driver => "lm95234", i2c_addrs => [0x18, 0x2a, 0x2b], i2c_detect => sub { lm95231_detect(@_, 5); }, }, { name => "National Semiconductor LM95234", driver => "lm95234", i2c_addrs => [0x18, 0x4d, 0x4e], i2c_detect => sub { lm95231_detect(@_, 3); }, }, { name => "National Semiconductor LM95235", driver => "lm95245", i2c_addrs => [0x18, 0x29, 0x4c], i2c_detect => sub { lm95231_detect(@_, 4); }, }, { name => "National Semiconductor LM95241", driver => "lm95241", i2c_addrs => [0x2b, 0x19, 0x2a], i2c_detect => sub { lm95231_detect(@_, 1); }, }, { name => "National Semiconductor LM95245", driver => "lm95245", i2c_addrs => [0x18, 0x19, 0x29, 0x4c, 0x4d], i2c_detect => sub { lm95231_detect(@_, 2); }, }, { name => "National Semiconductor LM63", driver => "lm63", i2c_addrs => [0x4c], i2c_detect => sub { lm63_detect(@_, 1); }, }, { name => "National Semiconductor LM64", driver => "lm63", i2c_addrs => [0x18, 0x4e], i2c_detect => sub { lm63_detect(@_, 3); }, }, { name => "National Semiconductor LM96163", driver => "lm63", i2c_addrs => [0x4c], i2c_detect => sub { lm63_detect(@_, 4); }, }, { 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 ADT7461A, ON Semiconductor NCT1008", driver => "lm90", i2c_addrs => [0x4c..0x4d], i2c_detect => sub { lm90_detect(@_, 13); }, }, { name => "NXP/Philips SA56004", driver => "lm90", i2c_addrs => [0x48..0x4f], i2c_detect => sub { lm90_detect(@_, 14); }, }, { 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 => "FTS Teutates", driver => "ftsteutates", i2c_addrs => [0x73], i2c_detect => sub { fts_detect(@_); }, }, { 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 => "SMSC EMC2103", driver => "emc2103", i2c_addrs => [0x2e], i2c_detect => sub { emc1403_detect(@_, 2); }, }, { name => "SMSC EMC2104", driver => "to-be-written", i2c_addrs => [0x2f], i2c_detect => sub { emc1403_detect(@_, 13); }, }, { 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 => "f75375s", 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 => "SMSC EMC1002", driver => "to-be-written", i2c_addrs => [0x4c, 0x4d], # 0x3c, 0x3d not probed i2c_detect => sub { emc1403_detect(@_, 4); }, }, { name => "SMSC EMC1023", driver => "to-be-written", # emc1023 i2c_addrs => [0x48, 0x49, 0x4c, 0x4d], i2c_detect => sub { emc1023_detect(@_, 0); }, }, { name => "SMSC EMC1033", driver => "to-be-written", i2c_addrs => [0x4c, 0x4d], # 0x3c, 0x3d not probed i2c_detect => sub { emc1403_detect(@_, 5); }, }, { name => "SMSC EMC1043", driver => "to-be-written", # emc1023 i2c_addrs => [0x48, 0x49, 0x4c, 0x4d], i2c_detect => sub { emc1023_detect(@_, 1); }, }, { name => "SMSC EMC1046", driver => "to-be-written", i2c_addrs => [0x4c, 0x4d], i2c_detect => sub { emc1403_detect(@_, 6); }, }, { name => "SMSC EMC1047", driver => "to-be-written", i2c_addrs => [0x18], # 0x10 not probed i2c_detect => sub { emc1403_detect(@_, 7); }, }, { name => "SMSC EMC1053", driver => "to-be-written", # emc1023 i2c_addrs => [0x48, 0x49, 0x4c, 0x4d], i2c_detect => sub { emc1023_detect(@_, 2); }, }, { name => "SMSC EMC1063", driver => "to-be-written", # emc1023 i2c_addrs => [0x48, 0x49, 0x4c, 0x4d], i2c_detect => sub { emc1023_detect(@_, 3); }, }, { name => "SMSC EMC1072", driver => "to-be-written", i2c_addrs => [0x1c, 0x2c, 0x4c, 0x5c], # 0x3c, 0x6c, 0x7c not probed i2c_detect => sub { emc1403_detect(@_, 8); }, }, { name => "SMSC EMC1073", driver => "to-be-written", i2c_addrs => [0x1c, 0x2c, 0x4c, 0x5c], # 0x3c, 0x6c, 0x7c not probed i2c_detect => sub { emc1403_detect(@_, 9); }, }, { name => "SMSC EMC1074", driver => "to-be-written", i2c_addrs => [0x1c, 0x2c, 0x4c, 0x5c], # 0x3c, 0x6c, 0x7c not probed i2c_detect => sub { emc1403_detect(@_, 10); }, }, { name => "SMSC EMC1402", driver => "emc1403", i2c_addrs => [0x18, 0x29, 0x4c, 0x4d], i2c_detect => sub { emc1403_detect(@_, 11); }, }, { name => "SMSC EMC1403", driver => "emc1403", i2c_addrs => [0x18, 0x29, 0x4c, 0x4d], i2c_detect => sub { emc1403_detect(@_, 0); }, }, { name => "SMSC EMC1404", driver => "emc1403", i2c_addrs => [0x18, 0x29, 0x4c, 0x4d], i2c_detect => sub { emc1403_detect(@_, 1); }, }, { name => "SMSC EMC1422", driver => "emc1403", i2c_addrs => [0x4c], i2c_detect => sub { emc1403_detect(@_, 14); }, }, { name => "SMSC EMC1423", driver => "emc1403", i2c_addrs => [0x4c], i2c_detect => sub { emc1403_detect(@_, 3); }, }, { name => "SMSC EMC1424", driver => "emc1403", i2c_addrs => [0x4c], i2c_detect => sub { emc1403_detect(@_, 12); }, }, { name => "ST STTS424", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 0); }, }, { name => "ST STTS424E", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 10); }, }, { name => "ST STTS2002", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 11); }, }, { name => "ST STTS3000", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 12); }, }, { name => "NXP SE97/SE97B", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 1); }, }, { name => "NXP SE98", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 2); }, }, { name => "Analog Devices ADT7408", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 3); }, }, { name => "IDT TS3000/TSE2002", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 4); }, }, { name => "IDT TSE2004", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 16); }, }, { name => "IDT TS3001", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 17); }, }, { name => "Maxim MAX6604", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 5); }, }, { name => "Microchip MCP9804", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 13); }, }, { name => "Microchip MCP9808", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 18); }, },{ name => "Microchip MCP98242", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 6); }, }, { name => "Microchip MCP98243", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 7); }, }, { name => "Microchip MCP98244", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 15); }, }, { name => "Microchip MCP9843", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 8); }, }, { name => "ON CAT6095/CAT34TS02", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 9); }, }, { name => "ON CAT34TS02C", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 19); }, }, { name => "ON CAT34TS04", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 21); }, }, { name => "Atmel AT30TS00", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 14); }, }, { name => "Giantec GT30TS00", driver => "jc42", i2c_addrs => [0x18..0x1f], i2c_detect => sub { jedec_JC42_4_detect(@_, 20); }, } ); # IPMI interfaces have their own array now @ipmi_ifs = ( { name => "IPMI BMC KCS", driver => "to-be-written", # ipmisensors isa_addrs => [0x0ca0], isa_detect => sub { ipmi_detect(@_); }, }, { name => "IPMI BMC SMIC", driver => "to-be-written", # 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 => "Winbond WPCD377I", i2c_addrs => [0x2c..0x2e], i2c_detect => sub { lm85_detect(@_, 7); }, }, { 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 # regs (optional): Register definitions, where they differ from the standard. # 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, }, { name => "ITE IT8510E/TE/G Super IO", driver => "to-be-written", devid => 0x8510, features => FEAT_IN | FEAT_FAN, }, { name => "ITE IT8511E/TE/G Super IO", driver => "to-be-written", devid => 0x8511, features => FEAT_IN | FEAT_FAN, }, { name => "ITE IT8512E/F/G Super IO", driver => "to-be-written", devid => 0x8512, features => FEAT_IN | FEAT_FAN, }, { name => "ITE IT8513E/F/G Super IO", driver => "to-be-written", devid => 0x8513, features => FEAT_IN | FEAT_FAN, }, { name => "ITE IT8516E/F/G Super IO", driver => "to-be-written", devid => 0x8516, features => FEAT_IN | FEAT_FAN, }, { name => "ITE IT8518E Super IO", driver => "to-be-written", devid => 0x8518, features => FEAT_IN | FEAT_FAN, } ); @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 (Dell-specific part). Not compatible with # smsc47m1 nor dme1737 nor LPC47M233. No evidence that this # chip can do any hardware monitoring at all. driver => "not-a-sensor", 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 LPC47U32x Super IO Fan Sensors", driver => "to-be-written", devid => 0x58, 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, }, { name => "SMSC SCH5627 Super IO", driver => "sch5627", devid => 0xc6, regs => { basereg_lsb => 0x66, basereg_msb => 0x67, }, logdev => 0x0c, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "SMSC SCH5636 Super IO", driver => "sch5636", devid => 0xc7, regs => { basereg_lsb => 0x66, basereg_msb => 0x67, }, logdev => 0x0c, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, } ); # 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 LPC47N217 Super IO", driver => "not-a-sensor", devid => 0x7a, }, { name => "SMSC LPC47N227 Super IO", driver => "not-a-sensor", devid => 0x5a, }, { name => "SMSC LPC47N237 Super IO", driver => "not-a-sensor", devid => 0x13, }, { name => "SMSC SIO10N268 Notebook IO", driver => "not-a-sensor", devid => 0x5b, } ); @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", # Also SMSC LPC61W492 driver => "w83627hf", devid => 0x52, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Winbond W83627SF/GF Super IO", driver => "not-a-sensor", devid => 0x59, }, { 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/W83527HG Super IO Sensors", driver => "w83627ehf", devid => 0xB070, devid_mask => 0xFFF0, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Winbond W83627UHG/NCT6627UD Super IO Sensors", driver => "w83627ehf", 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 NCT6681D/NCT6682D eSIO", driver => "to-be-written", devid => 0xB270, devid_mask => 0xFFF0, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton W83667HG-B (NCT5571D) Super IO Sensors", driver => "w83627ehf", devid => 0xB350, devid_mask => 0xFFF0, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton W83677HG-I (NCT5572D/NCT6771F/NCT6772F/NCT6775F) Super IO Sensors", driver => sub { kernel_version_at_least(3, 10, 0) ? "nct6775" : "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 => "Nuvoton NCT6683D eSIO", driver => "nct6683", devid => 0xC730, devid_mask => 0xFFF0, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6687D eSIO", driver => "nct6683", devid => 0xD590, devid_mask => 0xFFF0, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6102D/NCT6104D/NCT6106D Super IO Sensors", driver => "nct6775", devid => 0xC450, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6112D/NCT6114D/NCT6116D Super IO Sensors", driver => "nct6775", devid => 0xD280, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT5573D/NCT5577D/NCT6776F Super IO Sensors", driver => "nct6775", devid => 0xC330, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT5532D/NCT6779D Super IO Sensors", driver => "nct6775", devid => 0xC560, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6791D Super IO Sensors", driver => "nct6775", devid => 0xC800, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6792D Super IO Sensors", driver => "nct6775", devid => 0xC910, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6793D Super IO Sensors", driver => "nct6775", devid => 0xD120, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6795D Super IO Sensors", driver => "nct6775", devid => 0xD350, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6796D Super IO Sensors", driver => "nct6775", devid => 0xD420, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6797D Super IO Sensors", driver => "nct6775", devid => 0xD450, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Nuvoton NCT6798D Super IO Sensors", driver => "nct6775", devid => 0xD428, devid_mask => 0xFFF8, logdev => 0x0b, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71805F/FG Super IO Sensors", driver => "f71805f", devid => 0x0406, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71808E Super IO Sensors", driver => "f71882fg", devid => 0x0901, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71808A Super IO Sensors", driver => "f71882fg", devid => 0x1001, 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 F71868A Super IO Sensors", driver => "f71882fg", devid => 0x1106, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71869F/E Super IO Sensors", driver => "f71882fg", devid => 0x0814, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71869A Super IO Sensors", driver => "f71882fg", devid => 0x1007, 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/F81801U Super IO Sensors", driver => "f71882fg", devid => 0x0723, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71889ED Super IO Sensors", driver => "f71882fg", devid => 0x0909, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F71889A Super IO Sensors", driver => "f71882fg", devid => 0x1005, 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 => "Fintek F81768D Super IO Sensors", driver => "f71882fg", devid => 0x1210, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F81865F Super IO Sensors", driver => "f71882fg", devid => 0x0704, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "Fintek F81866D Super IO Sensors", driver => "f71882fg", devid => 0x1010, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { 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 IT8603E Super IO Sensors", driver => "it87", devid => 0x8603, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8606E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8606, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8607E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8607, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8613E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8613, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8620E Super IO Sensors", driver => "it87", devid => 0x8620, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8622E Super IO Sensors", driver => "it87", devid => 0x8622, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8623E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8623, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8625E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8625, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8626E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8626, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8655E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8655, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8665E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8665, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8686E Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8686, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { 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 IT8721F/IT8758E Super IO Sensors", driver => "it87", devid => 0x8721, 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, }, { name => "ITE IT8728F Super IO Sensors", driver => "it87", devid => 0x8728, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8731F Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8731, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8732F Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8732, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8752F Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8752, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8771E Super IO Sensors", driver => "it87", devid => 0x8771, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8772E Super IO Sensors", driver => "it87", devid => 0x8772, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8780F Super IO Sensors", driver => "to-be-written", # it87 devid => 0x8780, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8781F Super IO Sensors", driver => "it87", devid => 0x8781, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8782F Super IO Sensors", driver => "it87", devid => 0x8782, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8783F Super IO Sensors", driver => "it87", devid => 0x8783, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8786E Super IO Sensors", driver => "it87", devid => 0x8786, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8790E Super IO Sensors", driver => "it87", devid => 0x8790, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8792E Super IO Sensors", driver => "it87", devid => 0x8733, logdev => 0x04, features => FEAT_IN | FEAT_FAN | FEAT_TEMP, }, { name => "ITE IT8987D Embedded Controller", driver => "to-be-written", devid => 0x8987, 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/ITE", 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 => sub { amd_pci_detect('1103') }, }, { name => "AMD Family 10h thermal sensors", driver => "k10temp", detect => \&fam10h_pci_detect, }, { name => "AMD Family 11h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1303') }, }, { name => "AMD Family 12h and 14h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1703') }, }, { name => "AMD Family 15h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1603', '1403', '141d', '1573', '15b3') }, }, { name => "AMD Family 16h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1533', '1583') }, }, { name => "AMD Family 17h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1463', '15d0', '1493', '1443') }, }, { name => "AMD Family 15h power sensors", driver => "fam15h_power", detect => sub { amd_pci_detect('1604', '141e', '1574', '15b4') }, }, { name => "AMD Family 16h power sensors", driver => "fam15h_power", detect => sub { amd_pci_detect('1534', '1584') }, }, { name => "Hygon Family 18h thermal sensors", driver => "k10temp", detect => sub { hygon_pci_detect('1463') }, }, { name => "AMD Family 19h thermal sensors", driver => "k10temp", detect => sub { amd_pci_detect('1653') }, }, { name => "Intel digital thermal sensor", driver => "coretemp", detect => \&coretemp_detect, }, { name => "Intel AMB FB-DIMM thermal sensor", driver => "i5k_amb", detect => \&intel_amb_detect, }, { name => "Intel 5500/5520/X58 thermal sensor", driver => "i5500_temp", detect => \&intel_5500_detect, }, { name => "VIA C7 thermal sensor", driver => "via-cputemp", detect => \&via_c7_detect, }, { name => "VIA Nano thermal sensor", driver => "via-cputemp", detect => \&via_nano_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; } # $_[0]: Reference to base hash # $_[1]: Reference to overlay hash # Result: Overlayed hash sub overlay_hash { my ($base, $overlay) = @_; my %result = %{$base}; foreach my $key (keys %{$overlay}) { $result{$key} = $overlay->{$key}; } return %result; } # Read answer from stdin or accept default answer automatically sub read_answer { if ($opt{auto}) { print "\n"; return ""; } return || ""; } ################### # I/O PORT ACCESS # ################### sub initialize_ioports { if (sysopen(IOPORTS, "/dev/port", O_RDWR)) { binmode(IOPORTS); return 1; } print STDERR "/dev/port: $!\n"; return 0; } 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 $systemd_systemctl $systemd_system_dir); sub initialize_conf { my $use_devfs = 0; open(local *INPUTFILE, "/proc/mounts") or die "Can't access /proc/mounts!"; local $_; while () { 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 () { 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' || -e '/run/udev') { $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; } } # Detect systemd presence and paths if (-x "/usr/bin/systemctl") { $systemd_systemctl = "/usr/bin/systemctl"; } elsif (-x "/bin/systemctl") { $systemd_systemctl = "/bin/systemctl"; } if (-d "/usr/lib/systemd/system") { $systemd_system_dir = "/usr/lib/systemd/system"; } elsif (-d "/lib/systemd/system") { $systemd_system_dir = "/lib/systemd/system"; } } # [0] -> VERSION # [1] -> PATCHLEVEL # [2] -> SUBLEVEL # [3] -> EXTRAVERSION # use vars qw($kernel_version @kernel_version $kernel_arch); sub initialize_kernel_version { chomp($kernel_version = `uname -r`); $kernel_version =~ /(\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 print_kernel_version { printf "# Kernel: \%d.\%d.\%d\%s \%s\n", @kernel_version, $kernel_arch; } 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 () { if (m/^processor\s*:\s*(\d+)/) { push @cpu, $entry if scalar keys(%{$entry}); # Previous entry $entry = { # New entry nr => $1, vendor_id => "undefined", }; next; } if (m/^(vendor_id|cpu family|model|model name|stepping|cpuid level|cpu|revision)\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 } sub print_cpu_info { my $cpu = $cpu[0]; if ($kernel_arch =~ m/^ppc(64(le)?)?$/) { print "# Processor: $cpu->{cpu} ($cpu->{revision})\n"; } elsif ($kernel_arch =~ m/^arm/) { print "# Processor: $cpu->{'model name'}\n"; } elsif (exists $cpu->{'model name'} && exists $cpu->{'cpu family'} && exists $cpu->{'model'} && exists $cpu->{'stepping'}) { print "# Processor: $cpu->{'model name'} ($cpu->{'cpu family'}/$cpu->{model}/$cpu->{stepping})\n"; } else { print "# Cannot show processor info on $kernel_arch architecture.\n"; } } # @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"; } } # This works for all recent kernels (with or without # CONFIG_I2C_COMPAT) if (!defined $entry->{path}) { my $link = readlink("${base_dir}/i2c-$nr"); $entry->{path} = "${base_dir}/i2c-$nr"; $link =~ s/(\/i2c-[0-9]+)+$//; # Handle multiplexers too $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_module($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_module("${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_builtin %modules_list %modules_supported @modules_we_loaded); sub initialize_modules_list { local ($_, *INPUTFILE); # Starting with kernel 2.6.33, a list of built-in modules is available if (open(*INPUTFILE, "/lib/modules/$kernel_version/modules.builtin")) { while () { tr/-/_/; $modules_builtin{$1} = 1 if m/\/([^\/]+)\.ko$/; } close(INPUTFILE); } # List all loaded modules open(*INPUTFILE, "/proc/modules") or return; while () { $modules_list{$1} = 1 if m/^(\S*)/; } } sub is_module_loaded { my $module = shift; $module =~ tr/-/_/; return exists $modules_list{$module} || exists $modules_builtin{$module}; } sub is_module_builtin { my $module = shift; $module =~ tr/-/_/; return exists $modules_builtin{$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; } # udev may take some time to create device nodes when loading modules sub udev_settle { if (!(-x "/sbin/udevadm" && system("/sbin/udevadm settle") == 0) && !(-x "/sbin/udevsettle" && system("/sbin/udevsettle") == 0)) { sleep(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 = ; 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 product name' => 1, 'system name' => 1, 'system version' => 1, 'to be filled by o.e.m.' => 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} = ; 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{lc($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 " [$dmi{product_version}]" if defined $dmi{product_version}; 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/(Portable|Laptop|Notebook|Hand Held)/i; return 1 if $dmi{chassis_type} =~ m/^(8|9|10|11|14)$/; return 0; } ################# # SYSFS HELPERS # ################# # From a sysfs device path, return the module name, or undef sub sysfs_device_module { my $device = shift; my $link = readlink("$device/driver/module"); return unless defined $link; return basename($link); } # From a sysfs device path, return the driver name, or undef sub sysfs_device_driver { my $device = shift; my $link = readlink("$device/driver"); 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 = ; 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 return \@pci_list; 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-written") { 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 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 # ). 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, $detected_ref, $detected_entry, $driver, $put_in_detected, @hash_addrs, @entry_addrs); # 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 $driver (keys %chips_detected) { $detected_ref = $chips_detected{$driver}; 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; delete $chips_detected{$driver} if (!@$detected_ref); } } } # Now add the new entry to detected $chips_detected{$chipdriver} = [] unless exists $chips_detected{$chipdriver}; push @{$chips_detected{$chipdriver}}, $datahash; } # Fake i2c drivers such as "not-a-sensor" or "use-isa-instead" have to be # inserted so that confidence comparison can be performed. But then we have # to filter them out so that the user doesn't get confused. sub filter_out_fake_i2c_drivers { delete $chips_detected{"not-a-sensor"}; delete $chips_detected{"use-isa-instead"}; } # 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; if (initialize_ioports()) { $alias_detect = $detected->[$isa]->{alias_detect}; $is_alias = &$alias_detect($detected->[$isa]->{isa_addr}, \*FILE, $detected->[$i2c]->{i2c_addr}); close_ioports(); } close(FILE); 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, $module, $new_hash); $device = sprintf("$sysfs_root/bus/i2c/devices/\%d-\%04x", $adapter_nr, $addr); $driver = sysfs_device_driver($device); $module = sysfs_device_module($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; if (defined($module)) { printf "Handled by driver `\%s' (already loaded), chip type `\%s'\n", $module, $new_hash->{chipname}; } else { printf "Handled by driver `\%s' (built-in), chip type `\%s'\n", $driver, $new_hash->{chipname}; # Let's hope that the driver name matches the module name $module = $driver; } # 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{$module}) { add_i2c_to_chips_detected($module, $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"; $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; return 0x0300 if $subsystem eq "drm"; # Graphics card return 0 unless $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}); # Do not probe adapters on multimedia and graphics cards by default if (($class & 0xff00) == 0x0400 || ($class & 0xff00) == 0x0300) { $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 = read_answer(); 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 = read_answer(); chomp($input); @not_to_scan = parse_not_to_scan(0x03, 0x77, $input); } elsif (($class & 0xff00) == 0x0300) { # Skip EDID and DDC/CI addresses by default on graphics # adapters. Also skip address 0x4f which was reported in a # display corruption case. @not_to_scan = parse_not_to_scan(0x03, 0x77, "0x37, 0x50-0x57, 0x4f"); } 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(%standard_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 registers within the logical device %standard_superio = ( devidreg => 0x20, logdevreg => 0x07, actreg => 0x30, actmask => 0x01, basereg_msb => 0x60, basereg_lsb => 0x61, ); 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, $standard_superio{logdevreg}); $oldldn = inb($datareg); for ($ldn = 0; $ldn < 16; $ldn++) { # Select logical device outb($addrreg, $standard_superio{logdevreg}); outb($datareg, $ldn); # Read base I/O address outb($addrreg, $standard_superio{basereg_msb}); $addr = inb($datareg) << 8; outb($addrreg, $standard_superio{basereg_lsb}); $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, $standard_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); my %superio; # Use chip-specific registers if provided if (exists $chip->{regs}) { %superio = overlay_hash(\%standard_superio, $chip->{regs}); } else { %superio = %standard_superio; } 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; } if (!exists $chip->{logdev}) { print "\n (no support yet)\n"; return 0; } # Switch to the sensor logical device outb($addrreg, $superio{logdevreg}); outb($datareg, $chip->{logdev}); # Get the IO base address outb($addrreg, $superio{basereg_msb}); $addr = inb($datareg); outb($addrreg, $superio{basereg_lsb}); $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 && $addr != 0xffff) { printf "\n (address 0x\%x, but not activated)\n", $addr; } else { print "\n (but not activated)\n"; } return 0; } if ($addr == 0 || $addr == 0xffff) { 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, $standard_superio{devidreg}); $val = inb($datareg); outb($addrreg, $standard_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, @superio_ids_natsemi, @superio_ids_smsc, @superio_ids_smsc_ns, @superio_ids_winbond, @superio_ids_ite) { 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, 2 = LM75A # Registers used: # 0x00: Temperature # 0x01: Configuration # 0x02: Hysteresis # 0x03: Overtemperature Shutdown # 0x04-0x07: No registers # 0x07: Device ID (LM75A only) # 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. # And the LM75A is again different, it cycles over 8-byte boundaries, but # registers 0x04-0x06 return 0xff. Thankfully it has a device ID register # at 0x07. # 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 ($maxreg, $hyst, $os, $dev_id); if ($chip == 2) { # LM75A $dev_id = i2c_smbus_read_byte_data($file, 0x07); return if $dev_id != 0xA1; $hyst = i2c_smbus_read_byte_data($file, 0x02); $os = i2c_smbus_read_byte_data($file, 0x03); for $i (0x04 .. 0x06) { return if i2c_smbus_read_byte_data($file, $i) != 0xff; } } else { # LM75 or DS75 $maxreg = $chip == 1 ? 0x0f : 0x07; $hyst = i2c_smbus_read_byte_data($file, 0x02, NO_CACHE); for $i (0x04 .. $maxreg) { return if i2c_smbus_read_byte_data($file, $i, NO_CACHE) != $hyst; } $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 != 1) { # LM75 or LM75A 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; return if $chip == 2 and i2c_smbus_read_byte_data($file, $i + 0x07) != $dev_id; } } # All registers hold the same value, obviously a misdetection return if $conf == $cur and $cur == $hyst and $cur == $os; # Unused bits return if $chip != 1 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); # Test with byte read first to avoid confusing other chips return if i2c_smbus_read_byte_data($file, 0x07) != 0x01 or 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; } # Chip to detect: 0 = LM80, 1 = LM96080 # Registers used: # 0x00: Configuration register # 0x02: Interrupt state register # 0x07: Conversion rate register (LM96080 only) # 0x2a-0x3d: Limits registers (LM80 only) # 0x3e: Manufacturer's ID register (LM96080 only) # 0x3f: Stepping/die revision ID register (LM96080 only) # The LM80 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, $chip) = @_; 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; if ($chip == 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> 1) - 4; # $confidence is now between -4 and 3 return unless $confidence > 0; return $confidence; } elsif ($chip == 1) { return if (i2c_smbus_read_byte_data($file, 0x07) & 0xfe) != 0; return if i2c_smbus_read_byte_data($file, 0x3e) != 0x01; return if i2c_smbus_read_byte_data($file, 0x3f) != 0x08; return 6; } } # Registers used: # 0x00: Configuration register # 0x07: Conversion rate register # 0x09: Oneshot register # 0x0a: Shutdown register # 0x0b: Advanced Configuration register # 0x0c: Busy Status register # 0x3e: Manufacturer's ID register # 0x3f: Stepping/die revision ID register sub adc128d818_detect { my ($file, $addr) = @_; return if i2c_smbus_read_byte_data($file, 0x3e) != 0x01; return if i2c_smbus_read_byte_data($file, 0x3f) != 0x09; return if (i2c_smbus_read_byte_data($file, 0x00) & 0xf4) != 0; return if (i2c_smbus_read_byte_data($file, 0x07) & 0xfe) != 0; return if (i2c_smbus_read_byte_data($file, 0x09) & 0xfe) != 0; return if (i2c_smbus_read_byte_data($file, 0x0a) & 0xfe) != 0; return if (i2c_smbus_read_byte_data($file, 0x0b) & 0xf8) != 0; return if (i2c_smbus_read_byte_data($file, 0x0c) & 0xfc) != 0; return 7; } # 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 = MAX6680/81, 1 = MAX6695/96 # Registers used: # 0x03: Configuration # 0x04: Conversion rate # 0x12: Status2 # 0x16: Overtemperature 2 # 0xfe: Manufacturer ID # 0xff: Chip ID / die revision sub max6680_95_detect { my ($file, $addr, $chip) = @_; my $cid = i2c_smbus_read_byte_data($file, 0xff); my $conf = i2c_smbus_read_byte_data($file, 0x03); my $mid = i2c_smbus_read_byte_data($file, 0xfe, NO_CACHE); my $emerg = i2c_smbus_read_byte_data($file, 0x16, NO_CACHE); my $rate = i2c_smbus_read_byte_data($file, 0x04, NO_CACHE); my $emerg2 = i2c_smbus_read_byte_data($file, 0x16, NO_CACHE); # Check common conditions return if $rate > 0x07; return if $mid != 0x4d; # Not Maxim return if $cid != 0x01; # None of the chips we are looking for if ($chip == 0) { return if ($conf & 0x03) != 0; return 8 if $emerg != $emerg2; # MAX6680/MAX6681 } if ($chip == 1) { my $status2 = i2c_smbus_read_byte_data($file, 0x12); return if ($conf & 0x10) != 0; return if ($status2 & 0x01) != 0; return 8 if $emerg == $emerg2; # MAX6695/MAX6696 } return; } # Chip to detect: 0 = LM90, 1 = LM89/LM99, 2 = LM86, 3 = ADM1032, # 4 = MAX6654, 5 = ADT7461, # 6 = MAX6646/MAX6647/MAX6648/MAX6649/MAX6692, # 8 = W83L771W/G, # 11 = W83L771AWG/ASG, 12 = MAX6690, # 13 = ADT7461A/NCT1008, 14 = SA56004, # 15 = G781 # Registers used: # 0x03: Configuration # 0x04: Conversion rate # 0xbf: Configuration 2 (National Semiconductor, Winbond, and Philips only) # 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); my $conf2 = i2c_smbus_read_byte_data($file, 0xbf); if ($chip == 0) { return if ($conf & 0x2a) != 0; return if ($conf2 & 0xf8) != 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 ($conf2 & 0xf8) != 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 ($conf2 & 0xf8) != 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 } 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 == 8) { return if ($conf & 0x2a) != 0; return if ($conf2 & 0xf8) != 0; return if $rate > 0x09; return if $mid != 0x5c; # Winbond return 6 if ($cid & 0xfe) == 0x00; # W83L771W/G } if ($chip == 11) { return if ($conf & 0x2a) != 0; return if ($conf2 & 0xf8) != 0; return if $rate > 0x08; return if $mid != 0x5c; # Winbond return 6 if ($cid & 0xfe) == 0x10; # W83L771AWG/ASG } if ($chip == 12) { return if ($conf & 0x07) != 0; return if $rate > 0x07; return if $mid != 0x4d; # Maxim return 8 if $cid == 0x09; # MAX6690 } if ($chip == 13) { return if ($conf & 0x1b) != 0; return if $rate > 0x0a; return if $mid != 0x41; # Analog Devices return 8 if $cid == 0x57; # ADT7461A, NCT1008 } if ($chip == 14) { return if ($conf & 0x2a) != 0; return if ($conf2 & 0xfe) != 0; return if $rate > 0x09; return if $mid != 0xa1; # NXP Semiconductor/Philips return 6 if $cid == 0x00; # SA56004 } if ($chip == 15) { return if ($conf & 0x3f) != 0; return if $rate > 0x08; return if $mid != 0x47; # GMT return 8 if $cid == 0x01; # G781 } return; } # Chip to detect: 0 = TMP400, 1 = TMP401, 2 = TMP411A, 3 = TMP411B, 4 = TMP411C, # 5 = TMP431, 6 = TMP432, 7 = TMP435, 8 = TMP451 # Registers used: # 0x03: Configuration (4 unused bits) # 0x04: Conversion rate (value 0x0f or lower, 0x09 or lower for TMP451) # 0x10: Remote temperature low byte (4 unused bits) # 0x13: Remote temperature low limit, low byte (4 unused bits) # 0x14: Remote temperature high limit, low byte (4 unused bits) # 0x22: Consecutive alert, 4 unused bits (TMP451 only) # 0x24: Digital filter control, 6 unused bits (TMP451 only) # 0xfe: Manufacturer ID # 0xff: Device ID sub tmp401_detect() { my ($file, $addr, $chip) = @_; my $mid = i2c_smbus_read_byte_data($file, 0xfe); return if ($mid != 0x55); 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); my $rtemp = i2c_smbus_read_byte_data($file, 0x10); my $rhigh = i2c_smbus_read_byte_data($file, 0x13); my $rlow = i2c_smbus_read_byte_data($file, 0x14); return if ($conf & 0x1b); return if ($rate > 0x0f); return if ($rtemp & 0x0f); return if ($rhigh & 0x0f); return if ($rlow & 0x0f); return 8 if ($chip == 0 && $cid == 0x01); # TMP400 return 8 if ($chip == 1 && $cid == 0x11); # TMP401 return 8 if ($chip == 2 && $cid == 0x12); # TMP411A return 8 if ($chip == 3 && $cid == 0x13); # TMP411B return 8 if ($chip == 4 && $cid == 0x10); # TMP411C return 8 if ($chip == 5 && $cid == 0x31); # TMP431 return 8 if ($chip == 6 && $cid == 0x32); # TMP432 return 8 if ($chip == 7 && $cid == 0x35); # TMP435 if ($chip == 8) { # TMP451 return if $cid != 0x00; return if $rate > 0x09; my $alert = i2c_smbus_read_byte_data($file, 0x22); return if ($alert & 0x71) != 0x01; my $filter = i2c_smbus_read_byte_data($file, 0x24); return if $filter & 0xfc; return 4; } return; } # Chip to detect: 0 = TMP421, 1 = TMP422, 2 = TMP423, 3 = TMP441, 4 = TMP442 # Registers used: # 0x08: Status (7 unused bits) # 0x0b: Conversion rate (value 0x07 or lower) # 0xfe: Manufacturer 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); my $status = i2c_smbus_read_byte_data($file, 0x08); my $rate = i2c_smbus_read_byte_data($file, 0x0b); return if ($mid != 0x55); return if ($status & 0x7f); return if ($rate > 0x07); 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 6 if ($chip == 3 && $cid == 0x41); # TMP441 return 6 if ($chip == 4 && $cid == 0x42); # TMP442 return; } # Registers used: # 0x3d: Device ID # 0x3e: Company ID sub amc6821_detect() { my ($file, $addr) = @_; my $dev_id = i2c_smbus_read_byte_data($file, 0x3d); my $comp_id = i2c_smbus_read_byte_data($file, 0x3e); return if ($comp_id != 0x49); # Texas Instruments # Bit 7 of register address is ignored return if i2c_smbus_read_byte_data($file, 0x80 | 0x3d) != $dev_id; return if i2c_smbus_read_byte_data($file, 0x80 | 0x3e) != $comp_id; return 6 if ($dev_id == 0x21); # AMC6821 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, 2 = LM95245, 3 = LM95234, # 4 = LM95235, 5 = LM95233 # Registers used: # 0x02: Status (3 unused bits) # 0x03: Configuration (3 unused bits) # 0x04: Conversion rate (6 unused bits, LM95234) # 0x06: Remote diode filter control (6 unused bits, LM95231 and LM95241) # 0x30: Remote diode model type select (6 unused bits, LM95231 and LM95241) # 0x30: Local Temperature LSB (5 unused bits, LM95245) # 0x30: Diode model (4 unused bits, LM95234) # 0x33: Status register 2 (6 unused bits, LM95245) # 0x38: Diode model status (4 unused bits, LM95234) # 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 if ($chip == 0 || $chip == 1) { return if $chip == 0 && $cid != 0xa1; # LM95231 return if $chip == 1 && $cid != 0xa4; # LM95241 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; } elsif ($chip == 2 || $chip == 4) { return if $chip == 4 && $cid != 0xb1; # LM95235 return if $chip == 2 && $cid != 0xb3; # LM95245 return if i2c_smbus_read_byte_data($file, 0x02) & 0x68; return if i2c_smbus_read_byte_data($file, 0x03) & 0xa1; return if i2c_smbus_read_byte_data($file, 0x30) & 0x1f; return if i2c_smbus_read_byte_data($file, 0x33) & 0x3f; } elsif ($chip == 3) { return if $cid != 0x79; # LM95234 return if i2c_smbus_read_byte_data($file, 0x02) & 0x30; return if i2c_smbus_read_byte_data($file, 0x03) & 0xbc; return if i2c_smbus_read_byte_data($file, 0x04) & 0xfc; return if i2c_smbus_read_byte_data($file, 0x30) & 0xe1; return if i2c_smbus_read_byte_data($file, 0x38) & 0xe1; } elsif ($chip == 5) { return if $cid != 0x89; # LM95233 return if i2c_smbus_read_byte_data($file, 0x02) & 0x30; return if i2c_smbus_read_byte_data($file, 0x03) & 0xbf; return if i2c_smbus_read_byte_data($file, 0x04) & 0xfc; return if i2c_smbus_read_byte_data($file, 0x30) & 0xf9; return if i2c_smbus_read_byte_data($file, 0x38) & 0xf9; } 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, 4 = LM96163 # 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; } elsif ($chip == 4) { return if $mid != 0x01 # National Semiconductor || $cid != 0x49; # LM96163 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/ADT7468 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 && $drev <= 0x6b; 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) = @_; 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; } # Registers used: # 0x02: Status # 0x0a: Thyst # 0x0b: ID # We also rely on the fact that only the 5 LSB of the address pointer # are considered, so registers cycle over 32 byte boundaries. sub adt7410_detect { my ($file, $addr) = @_; my $status = i2c_smbus_read_byte_data($file, 0x02); my $thyst = i2c_smbus_read_byte_data($file, 0x0a); my $id = i2c_smbus_read_byte_data($file, 0x0b); # Unused bits return if ($status & 0x0f); return if ($thyst & 0xf0); # ID register return if $id != 0xcb; # Cycling registers for (my $i = 2; $i < 16; $i *= 2) { return if i2c_smbus_read_byte_data($file, 0x0a + $i * 16) != $thyst; return if i2c_smbus_read_byte_data($file, 0x0b + $i * 16) != $id; } # Non-existent registers (other devices tend to have ID registers there) return if i2c_smbus_read_byte_data($file, 0x03e) != 0; return if i2c_smbus_read_byte_data($file, 0x03f) != 0; return if i2c_smbus_read_byte_data($file, 0x0fe) != 0; return if i2c_smbus_read_byte_data($file, 0x0ff) != 0; return 3; } # Registers used: # 0x4d: Device ID # 0x4e: Manufacturer ID # 0x4e: Silicon revision sub adt7411_detect { my ($file, $addr) = @_; my $dev_id = i2c_smbus_read_byte_data($file, 0x4d); my $man_id = i2c_smbus_read_byte_data($file, 0x4e); my $revision = i2c_smbus_read_byte_data($file, 0x4f); return if $man_id != 0x41; # Analog Devices return if $dev_id != 0x02; # ADT7411 # The datasheet suggests that the version is in the high nibble, but # a dump from a real ADT7411 chip shows that it is in the low nibble. return if ($revision & 0x0f) != 0x04; # ADT7411 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), 8 = EMC6D103S/EMC2300 # 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 A0 && $verstep != 0x69; # EMC6D103 A1 } elsif ($chip == 8) { return if $vendor != 0x5c; # SMSC return if $verstep != 0x6a; # EMC6D103S/EMC2300 } 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; } # Registers used: # 0x3e: Vendor register # 0x3f: Version/Stepping register # 0x40: Configuration register (reserved bits + ready) sub emc6w201_detect { my ($file, $addr) = @_; my $vendor = i2c_smbus_read_byte_data($file, 0x3e); my $verstep = i2c_smbus_read_byte_data($file, 0x3f); my $conf = i2c_smbus_read_byte_data($file, 0x40); my $stepping; return if $vendor != 0x5c; # SMSC return if ($verstep & 0xf0) != 0xb0; # EMC6W201 return if ($conf & 0xf4) != 0x04; $stepping = $verstep & 0x0f; return if $stepping > 3; # So far we've only seen stepping 1 chips return $stepping <= 1 ? 6 : 3; } # 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: # 0x00: Bank selection (Bank 0, 1) # 0x05: Temperature readout register LSB (Bank 0) # 0x08: PECI temperature readout register LSB (Bank 0) # 0x0f: Voltage readout register LSB (Bank 0) # 0xfd: Vendor ID (Bank 0) # 0xfe: Device ID (Bank 0) # 0xff: Device Revision (Bank 0) # # Note that identification registers are not accessible in bank 1, # and there is no usable other means to identify the chip if bank 1 # is selected. Only detect chip if bank 0 is selected. sub nct7802_detect { my ($bank, $reg); my ($file, $addr) = @_; $bank = i2c_smbus_read_byte_data($file, 0x00); return unless $bank == 0x00; $reg = i2c_smbus_read_byte_data($file, 0xfd); return unless $reg == 0x50; $reg = i2c_smbus_read_byte_data($file, 0xfe); return unless $reg == 0xc3; $reg = i2c_smbus_read_byte_data($file, 0xff); return unless ($reg & 0xf0) == 0x20; $reg = i2c_smbus_read_byte_data($file, 0x05); return unless ($reg & 0x1f) == 0x00; $reg = i2c_smbus_read_byte_data($file, 0x08); return unless ($reg & 0x3f) == 0x00; $reg = i2c_smbus_read_byte_data($file, 0x0f); return unless ($reg & 0x3f) == 0x00; return 8; } # Registers used: # 0x7a: Vendor ID # 0x7b: Chip ID # 0x7c: Device ID # 0xff: Bank Select # sub nct7904_detect { my ($reg); my ($file, $addr) = @_; $reg = i2c_smbus_read_byte_data($file, 0x7a); return unless $reg == 0x50; $reg = i2c_smbus_read_byte_data($file, 0x7b); return unless $reg == 0xc5; $reg = i2c_smbus_read_byte_data($file, 0x7c); return unless ($reg & 0xf0) == 0x50; $reg = i2c_smbus_read_byte_data($file, 0xff); return unless ($reg & 0xf8) == 0x00; 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. 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 (up to DDR2) # 0-127: SPD data and CRC (DDR3) sub eeprom_detect { my ($file, $addr) = @_; my $device_type = i2c_smbus_read_byte_data($file, 2); my $checksum = 0; # Check the checksum or CRC16 for validity if ($device_type >= 1 and $device_type <= 8) { for (my $i = 0; $i < 63; $i++) { $checksum += i2c_smbus_read_byte_data($file, $i); } $checksum &= 0xff; return 8 if $checksum == i2c_smbus_read_byte_data($file, 63); } elsif ($device_type >= 9 && $device_type <= 12) { # see JEDEC 21-C 4.1.2.11 2.4 my $crc_coverage = i2c_smbus_read_byte_data($file, 0); $crc_coverage = ($crc_coverage & 0x80) ? 117 : 126; for (my $i = 0; $i < $crc_coverage; $i++) { $checksum ^= i2c_smbus_read_byte_data($file, $i) << 8; for (my $bit = 0; $bit < 8; $bit++) { if ($checksum & 0x8000) { $checksum = ($checksum << 1) ^ 0x1021; } else { $checksum <<= 1; } } } $checksum &= 0xffff; return 8 if ($checksum & 0xff) == i2c_smbus_read_byte_data($file, 126) and ($checksum >> 8) == i2c_smbus_read_byte_data($file, 127); # note: if bit 7 of byte 32 is set, a jc42 sensor is at $addr-0x38 } 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; } # Chip to detect: FTS Teutates # Registers used: # 0x00: Device ID # 0x01: Revision # 0x0C: Device Detection Register 1 # 0x0D: Device Detection Register 2 # 0x0E: Device Detection Register 3 sub fts_detect { my ($file) = @_; # Device ID must be 0x11 # 0x10 == Baseboard Management Controller # 0x01 == Teutates return unless i2c_smbus_read_byte_data($file, 0x00) == 0x11; # Revision must be greater or equal 0x2b to use device detection return unless i2c_smbus_read_byte_data($file, 0x01) >= 0x2b; # Device Detection Registers must be 0x17, 0x34, 0x54 return unless i2c_smbus_read_byte_data($file, 0x0C) == 0x17 and i2c_smbus_read_byte_data($file, 0x0D) == 0x34 and i2c_smbus_read_byte_data($file, 0x0E) == 0x54; return 8; } # Chip to detect: 0 = LM93, 1 = LM94 # Registers used: # 0x3E: Manufacturer ID # 0x3F: Version/Stepping sub lm93_detect { my ($file, $addr, $chip) = @_; my ($man_id, $dev_id); $man_id = i2c_smbus_read_byte_data($file, 0x3E); $dev_id = i2c_smbus_read_byte_data($file, 0x3F); if ($chip == 0) { return unless $man_id == 0x01 # National Semiconductor and $dev_id == 0x73; # LM93 } if ($chip == 1) { return unless $man_id == 0x01 # National Semiconductor and $dev_id >= 0x79 and $dev_id <= 0x7A; # LM94 } 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; } # Chips to detect: 0 = EMC1023, 1 = EMC1043, 2 = EMC1053, 3 = EMC1063 # Registers used: # 0xed: Device ID register # 0xfe: Vendor ID register # 0xff: Revision register sub emc1023_detect { my ($file, $addr, $chip) = @_; my $dev_id = i2c_smbus_read_byte_data($file, 0xed); my $man_id = i2c_smbus_read_byte_data($file, 0xfe); my $rev = i2c_smbus_read_byte_data($file, 0xff); return unless $man_id == 0x5d; # SMSC return unless $rev <= 1; if ($chip == 0) { return if ($addr == 0x4c) && ($dev_id != 0x04); # EMC1023-1 return if ($addr == 0x4d) && ($dev_id != 0x05); # EMC1023-2 return if ($addr == 0x48) && ($dev_id != 0x06); # EMC1023-3 return if ($addr == 0x49) && ($dev_id != 0x07); # EMC1023-4 } elsif ($chip == 1) { if ($addr == 0x4c) { # EMC1043-1, EMC1043-5 return unless ($dev_id == 0x0c) || ($dev_id == 0x2c); } return if ($addr == 0x4d) && ($dev_id != 0x0d); # EMC1043-2 return if ($addr == 0x48) && ($dev_id != 0x0e); # EMC1043-3 return if ($addr == 0x49) && ($dev_id != 0x0f); # EMC1043-4 } elsif ($chip == 2) { return if ($addr == 0x4c) && ($dev_id != 0x3c); # EMC1053-1 return if ($addr == 0x4d) && ($dev_id != 0x3d); # EMC1053-2 return if ($addr == 0x48) && ($dev_id != 0x3e); # EMC1053-3 return if ($addr == 0x49) && ($dev_id != 0x3f); # EMC1053-4 } elsif ($chip == 3) { return if ($addr == 0x4c) && ($dev_id != 0x30); # EMC1063-1 return if ($addr == 0x4d) && ($dev_id != 0x31); # EMC1063-2 return if ($addr == 0x48) && ($dev_id != 0x32); # EMC1063-3 return if ($addr == 0x49) && ($dev_id != 0x33); # EMC1063-4 } return 7; } # Chip to detect: 0 = EMC1403, 1 = EMC1404, 2 = EMC2103, 3 = EMC1423, # 4 = EMC1002, 5 = EMC1033, 6 = EMC1046, 7 = EMC1047, 8 = EMC1072, # 9 = EMC1073, 10 = EMC1074, 11 = EMC1402, 12 = EMC1424, # 13 = EMC2104, 14 = EMC1422 # Registers used: # 0xfd: Device ID register # 0xfe: Vendor ID register # 0xff: Revision register sub emc1403_detect { my ($file, $addr, $chip) = @_; my $dev_id = i2c_smbus_read_byte_data($file, 0xfd); my $man_id = i2c_smbus_read_byte_data($file, 0xfe); my $rev = i2c_smbus_read_byte_data($file, 0xff); return unless $man_id == 0x5d; # SMSC if ($chip == 0) { # EMC1403 return unless $dev_id == 0x21; return unless $rev == 0x01 || $rev == 0x04; } elsif ($chip == 1) { # EMC1404 return unless $dev_id == 0x25; return unless $rev == 0x01 || $rev == 0x04; } elsif ($chip == 2) { # EMC2103 return unless ($dev_id == 0x24) || ($dev_id == 0x26); return unless $rev == 0x01; } elsif ($chip == 3) { # EMC1423 return unless $dev_id == 0x23; return unless $rev == 0x01 || $rev == 0x04; } elsif ($chip == 4) { # EMC1002 return unless ($dev_id == 0x02) || ($dev_id == 0x03); return unless $rev == 0x01; } elsif ($chip == 5) { # EMC1033 return unless ($dev_id == 0x0a) || ($dev_id == 0x0b); return unless $rev == 0x01; } elsif ($chip == 6) { # EMC1046 return unless $dev_id == 0x1a; return unless $rev == 0x01; } elsif ($chip == 7) { # EMC1047 return unless $dev_id == 0x1c; return unless $rev == 0x01; } elsif ($chip == 8) { # EMC1072 return unless $dev_id == 0x20; return unless $rev == 0x03; } elsif ($chip == 9) { # EMC1073 return unless $dev_id == 0x21; return unless $rev == 0x03; } elsif ($chip == 10) { # EMC1074 return unless $dev_id == 0x25; return unless $rev == 0x03; } elsif ($chip == 11) { # EMC1402 return unless $dev_id == 0x20; return unless $rev == 0x01 || $rev == 0x04; } elsif ($chip == 12) { # EMC1424 return unless $dev_id == 0x27; return unless $rev == 0x01 || $rev == 0x04; } elsif ($chip == 13) { # EMC2104 return unless $dev_id == 0x1d; return unless $rev == 0x02; } elsif ($chip == 14) { # EMC1422 return unless $dev_id == 0x22; return unless $rev == 0x01 || $rev == 0x04; } return 6; } # 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; } # Chip to detect: MAX6639 # Registers used: # 0x3d: Device ID # 0x3e: Manufacturer ID # 0x3f: Chip revision sub max6639_detect { my ($file, $addr) = @_; my ($man_id, $dev_id, $rev); $dev_id = i2c_smbus_read_byte_data($file, 0x3d); $man_id = i2c_smbus_read_byte_data($file, 0x3e); $rev = i2c_smbus_read_byte_data($file, 0x3f); return unless $man_id == 0x4d; # Maxim return unless $dev_id == 0x58; # MAX6639 return unless $rev == 0x00; return 6; } # Chip to detect: MAX6642 # Registers used: # 0x02: Status register # 0x03: Configuration register # 0xfe: Manufacturer ID # 0x04, 0x06, 0xff: No registers # We use the 0x04, 0x06 and 0xff addresses (unused) to improve reliability. # These are not real registers and will always return the last returned value. # This isn't documented. sub max6642_detect { my ($file, $addr) = @_; my ($man_id, $conf, $status); $man_id = i2c_smbus_read_byte_data($file, 0xfe); return unless $man_id == 0x4d; # Maxim return if i2c_smbus_read_byte_data($file, 0x04, NO_CACHE) != $man_id; return if i2c_smbus_read_byte_data($file, 0x06, NO_CACHE) != $man_id; return if i2c_smbus_read_byte_data($file, 0xff, NO_CACHE) != $man_id; $status = i2c_smbus_read_byte_data($file, 0x02); # Bit 5, 3, 1 and 0 should be zero return unless ($status & 0x2b) == 0x00; return if i2c_smbus_read_byte_data($file, 0x04, NO_CACHE) != $status; return if i2c_smbus_read_byte_data($file, 0x06, NO_CACHE) != $status; return if i2c_smbus_read_byte_data($file, 0xff, NO_CACHE) != $status; $conf = i2c_smbus_read_byte_data($file, 0x03); # The 4 lower bits should be zero return unless ($conf & 0x0f) == 0x00; return 5; } 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; } # Chip to detect: 0 = STTS424, 1 = SE97/SE97B, 2 = SE98, 3 = ADT7408, # 4 = TS3000/TSE2002, 5 = MAX6604, 6 = MCP98242, # 7 = MCP98243, 8 = MCP9843, 9 = CAT6095 / CAT34TS02, # 10 = STTS424E, 11 = STTS2002, 12 = STTS3000 # 13 = MCP9804, 14 = AT30TS00, 15 = MCP98244, # 16 = TSE2004, 17 = TS3001, 18 = MCP9808, # 19 = CAT34TS02C, 20 = Giantec GT30TS00, # 21 = CAT34TS04 # Registers used: # 0x00: Capabilities # 0x01: Configuration # 0x06: Manufacturer ID # 0x07: Device ID sub jedec_JC42_4_detect { my ($file, $addr, $chip) = @_; my ($reg, $manid, $devid); # We test the MSB only at first, because not all chips enjoy # word access. # Check for unused bits $reg = i2c_smbus_read_byte_data($file, 0x00); return if $reg & 0xff; $reg = i2c_smbus_read_byte_data($file, 0x01); return if $reg & 0xf8; # Check for manufacturer and device ID $manid = i2c_smbus_read_byte_data($file, 0x06); $devid = i2c_smbus_read_byte_data($file, 0x07); if ($chip == 0) { return unless $manid == 0x10; # STMicrolectronics return unless $devid == 0x01; # STTS424 } elsif ($chip == 1) { return unless $manid == 0x11; # NXP return unless $devid == 0xa2; # SE97 } elsif ($chip == 2) { return unless $manid == 0x11; # NXP return unless $devid == 0xa1; # SE98 } elsif ($chip == 3) { return unless $manid == 0x11; # ADT return unless $devid == 0x08; # ADT7408 } elsif ($chip == 4) { return unless $manid == 0x00; # IDT return unless $devid == 0x29; # TS3000/TSE2002 } elsif ($chip == 5) { return unless $manid == 0x00; # MAXIM return unless $devid == 0x3e; # MAX6604 } elsif ($chip == 6) { return unless $manid == 0x00; # MCP return unless $devid == 0x20; # MCP98242 } elsif ($chip == 7) { return unless $manid == 0x00; # MCP return unless $devid == 0x21; # MCP98243 } elsif ($chip == 8) { return unless $manid == 0x00; # MCP return unless $devid == 0x00; # MCP9843 } elsif ($chip == 9) { return unless $manid == 0x1b; # ONS return unless $devid == 0x08; # CAT6095 / CAT34TS02 } elsif ($chip == 10) { return unless $manid == 0x10; # STMicrolectronics return unless $devid == 0x00; # STTS424E02 } elsif ($chip == 11) { return unless $manid == 0x10; # STMicrolectronics return unless $devid == 0x03; # STTS2002 } elsif ($chip == 12) { return unless $manid == 0x10; # STMicrolectronics return unless $devid == 0x02; # STTS3000 } elsif ($chip == 13) { return unless $manid == 0x00; # MCP return unless $devid == 02; # MCP9804 } elsif ($chip == 14) { return unless $manid == 0x00; # Atmel return unless $devid == 0x82; # AT30TS00 } elsif ($chip == 15) { return unless $manid == 0x00; # MCP return unless $devid == 0x22; # MCP98244 } elsif ($chip == 18) { return unless $manid == 0x00; # MCP return unless $devid == 0x04; # MCP9808 } elsif ($chip == 19) { return unless $manid == 0x1b; # ON return unless $devid == 0x0a; # CAT34TS02C } elsif ($chip == 20) { return unless $manid == 0x1c || $manid == 0x13; # Giantec return unless $devid == 0x22; # GT30TS00 } elsif ($chip == 21) { return unless $manid == 0x1b; # ONS return unless $devid == 0x22; # CAT34TS04 } # Now, do it all again with words. Note that we get # the MSB first, so all value checks are byte-swapped. # Check for unused bits $reg = i2c_smbus_read_word_data($file, 0x00); return if $reg < 0 || $reg & 0x00ff; $manid = i2c_smbus_read_word_data($file, 0x06); $devid = i2c_smbus_read_word_data($file, 0x07); return if $manid < 0 || $devid < 0; if ($chip == 0) { return unless $manid == 0x4a10; # STMicrolectronics return unless ($devid & 0xfeff) == 0x0001; # STTS424 } elsif ($chip == 1) { return unless $manid == 0x3111; # NXP return unless ($devid & 0xfcff) == 0x00a2; # SE97 } elsif ($chip == 2) { return unless $manid == 0x3111; # NXP return unless ($devid & 0xfcff) == 0x00a1; # SE98 } elsif ($chip == 3) { return unless $manid == 0xd411; # ADT return unless $devid == 0x0108; # ADT7408 } elsif ($chip == 4) { return unless $manid == 0xb300; # IDT return unless ($devid & 0x00ff) == 0x0029; # TS3000/TSE2002 } elsif ($chip == 5) { return unless $manid == 0x4d00; # MAXIM return unless $devid == 0x003e; # MAX6604 } elsif ($chip == 6) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0020; # MCP98242 } elsif ($chip == 7) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0021; # MCP98243 } elsif ($chip == 8) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0000; # MCP9843 } elsif ($chip == 9) { return unless $manid == 0x091b; # ONS return unless ($devid & 0xe0ff) == 0x0008; # CAT6095 / CAT34TS02 } elsif ($chip == 10) { return unless $manid == 0x4a10; # STMicrolectronics return unless ($devid & 0xfeff) == 0x0000; # STTS424E02 } elsif ($chip == 11) { return unless $manid == 0x4a10; # STMicrolectronics return unless ($devid & 0xfeff) == 0x0003; # STTS2002 } elsif ($chip == 12) { return unless $manid == 0x4a10; # STMicrolectronics return unless ($devid & 0xfeff) == 0x0002; # STTS3000 } elsif ($chip == 13) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0002; # MCP9804 } elsif ($chip == 14) { return unless $manid == 0x1f00; # Atmel return unless ($devid & 0xfeff) == 0x0082; # AT30TS00 } elsif ($chip == 15) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0022; # MCP98244 } elsif ($chip == 16) { return unless $manid == 0xb300; # IDT return unless ($devid & 0x00ff) == 0x0022; # TSE2004 } elsif ($chip == 17) { return unless $manid == 0xb300; # IDT return unless ($devid & 0x00ff) == 0x0030; # TS3001 } elsif ($chip == 18) { return unless $manid == 0x5400; # MCP return unless ($devid & 0xfcff) == 0x0004; # MCP9808 } elsif ($chip == 19) { return unless $manid == 0x091b; # ONS return unless ($devid & 0xf0ff) == 0x000a; # CAT34TS02C } elsif ($chip == 20) { return unless $manid == 0x681c || $manid == 0x2d13; # Giantec return unless ($devid & 0x00ff) == 0x0022; # GT30TS00 } elsif ($chip == 21) { return unless $manid == 0x091b; # ONS return unless ($devid & 0xf0ff) == 0x0022; # CAT34TS04 } return 5; } # 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); my $ipmi_driver = "to-be-written"; # ipmisensors 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 () { 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 `$ipmi_driver')\n"; my $new_hash = { conf => 8, isa_addr => $if->{addr} || 0, chipname => $if->{type}, }; add_isa_to_chips_detected($ipmi_driver, $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 amd_pci_detect { my $dev_id; while (defined($dev_id = shift)) { return 9 if exists $pci_list{"1022:$dev_id"}; } return undef; } sub hygon_pci_detect { my $dev_id; while (defined($dev_id = shift)) { return 9 if exists $pci_list{"1d94:$dev_id"}; } return undef; } sub fam10h_pci_detect { return unless exists $pci_list{'1022:1203'}; # Errata 319 (Inaccurate Temperature Measurement) affects # revisions DR-BA, DR-B2 and DR-B3, all have model number = 2. # Revisions RB-C2 and HY-D0 are also affected. my $probecpu; foreach $probecpu (@cpu) { next unless $probecpu->{vendor_id} eq 'AuthenticAMD' && $probecpu->{'cpu family'} == 0x10; next if $probecpu->{model} < 4; # DR-B* next if $probecpu->{model} == 8; # HY-D0 if ($probecpu->{model} == 4 && $probecpu->{stepping} <= 2) { # RB-C2 my @dram_cfg = split /\n/, `setpci -d 1022:1202 94.L 2>/dev/null`; next unless @dram_cfg >= 1; next unless hex($dram_cfg[0]) & 0x00000100; # DDR3 } return 9; } return; } sub intel_amb_detect { if ((exists $pci_list{'8086:25f0'}) || # Intel 5000 (exists $pci_list{'8086:4030'})) { # Intel 5400 return 9; } return; } sub intel_5500_detect { return unless exists $pci_list{'8086:3438'}; return 5; } sub cpuid { my ($cpu_nr, $eax) = @_; sysopen(CPUID, "/dev/cpu/$cpu_nr/cpuid", O_RDONLY) or return; binmode CPUID; sysseek(CPUID, $eax, SEEK_SET) or die "Cannot seek /dev/cpu/$cpu_nr/cpuid"; sysread(CPUID, my $data, 16) or die "Cannot read /dev/cpu/$cpu_nr/cpuid"; close CPUID; return unpack("L4", $data); } sub coretemp_detect { my $probecpu; foreach $probecpu (@cpu) { next unless $probecpu->{vendor_id} eq 'GenuineIntel' && $probecpu->{'cpuid level'} >= 6; # Now we check for the DTS flag my @regs = cpuid($probecpu->{nr}, 6); return unless @regs == 4; return 9 if ($regs[0] & (1 << 0)); # eax, bit 0 } return; } sub via_c7_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; } sub via_nano_detect { my $probecpu; foreach $probecpu (@cpu) { if ($probecpu->{vendor_id} eq 'CentaurHauls' && $probecpu->{'cpu family'} == 6 && $probecpu->{model} == 0xf) { 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 unless defined $adap; 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 https://hwmon.wiki.kernel.org/device_support_status for updates.\n\n"; } elsif (!is_module_builtin($driver)) { open(local *INPUTFILE, "modprobe -l $driver 2>/dev/null |"); local $_; my $modulefound = 0; while () { 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. Check https://hwmon.wiki.kernel.org/device_support_status 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'); $_ = read_answer(); 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'); $_ = read_answer(); 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"; close(SYSCONFIG); if ($systemd_systemctl && $systemd_system_dir) { if (-f "$systemd_system_dir/lm_sensors.service") { system($systemd_systemctl, "enable", "lm_sensors.service"); system($systemd_systemctl, "start", "lm_sensors.service"); # All done, don't check for /etc/init.d/lm_sensors } else { print "Copy prog/init/lm_sensors.service to $systemd_system_dir\n". "and run 'systemctl enable lm_sensors.service'\n". "for initialization at boot time.\n"; } return; } 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 "You really should try these commands right now to make sure everything\n". "is working properly. Monitoring programs won't work until the needed\n". "modules are loaded.\n\n"; } } sub main { my ($input, $superio_features); # Parse command line options while (defined $ARGV[0]) { if ($ARGV[0] eq "--stat") { $opt{stat} = 1; } elsif ($ARGV[0] eq "--auto") { $opt{auto} = 1; } else { print STDERR "Error: unknown option $ARGV[0]\n"; exit 1; } shift @ARGV; } if ($opt{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; } 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(); if ($systemd_systemctl && $systemd_system_dir && -f "$systemd_system_dir/lm_sensors.service") { system("$systemd_systemctl", "stop", "lm_sensors.service"); } elsif (-x "/sbin/service" && -f "/etc/init.d/lm_sensors" && -f "/var/lock/subsys/lm_sensors") { system("/sbin/service", "lm_sensors", "stop"); } print "# sensors-detect version ".LM_VERSION."\n"; initialize_dmi_data(); print_dmi_summary(); print_kernel_version(); print_cpu_info(); print "\n"; if ($opt{auto}) { print "Running in automatic mode, default answers to all questions\n", "are assumed.\n\n"; } else { 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): "; $input = read_answer(); unless ($input =~ /^\s*n/i) { # Load the cpuid driver if needed unless (-e "$sysfs_root/class/cpuid") { load_module("cpuid"); udev_settle(); } $| = 1; foreach my $entry (@cpu_ids) { scan_cpu($entry); } $| = 0; } print "\n"; $superio_features = 0; # Skip "random" I/O port probing on non-x86 machines if ($kernel_arch =~ m/^i[3456]86$/ || $kernel_arch eq 'x86_64') { 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): "; $input = read_answer(); unless ($input =~ /^\s*n/i) { if (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): "; $input = read_answer(); unless ($input =~ /^\s*n/i) { if (!ipmi_from_smbios()) { if (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 = read_answer(); unless ($input =~ /^\s*n/i || ($superio_features && $input !~ /^\s*y/i)) { if (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): "; $input = read_answer(); unless ($input =~ /^\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'); udev_settle(); 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); } filter_out_fake_i2c_drivers(); } 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". "https://hwmon.wiki.kernel.org/device_support_status for driver status.\n"; } exit; } print "\nNow follows a summary of the probes I have just done.\n"; unless ($opt{auto}) { print "Just press ENTER to continue: "; ; } 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 " (built-in)" if is_module_builtin($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(); # Check if running non-interactively without --auto if (!$opt{auto} && ! -t STDIN) { print "***************************************************************\n". "Warning: the preferred way to run this script non-interactively\n". "is with option --auto. Other methods are discouraged and may\n". "stop working at some point in the future.\n". "***************************************************************\n\n"; } } sub cleanup_on_int { print "\n"; unload_modules(); exit; } $SIG{INT} = \&cleanup_on_int; main;