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