#!/usr/bin/perl -w

#
#    sensors-detect - Detect PCI bus and chips
#    Copyright (C) 1998 - 2002  Frodo Looijaard <frodol@dds.nl>
#    Copyright (C) 2000 - 2004  The lm_sensors team
#    Copyright (C) 2005 - 2006  Jean Delvare <khali@linux-fr.org>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

# TODO: Better handling of chips with several addresses

# A Perl wizard really ought to look upon this; the PCI and I2C stuff should
# each be put in a separate file, using modules and packages. That is beyond
# me.

require 5.004;

use strict;
use Fcntl;
use POSIX;
use File::Basename;

# Just in case a root user doesn't have /sbin in his/her path for some reason
# (was seen once)
$ENV{PATH} = '/sbin:'.$ENV{PATH}
	unless $ENV{PATH} =~ m,(^|:)/sbin/?(:|$),;
# Same for /usr/local/sbin since we need i2cdetect which is installed there
# by default (reported by Lennard Klein)
$ENV{PATH} = '/usr/local/sbin:'.$ENV{PATH}
	unless $ENV{PATH} =~ m,(^|:)/usr/local/sbin/?(:|$),;

#########################
# CONSTANT DECLARATIONS #
#########################

use vars qw(@pci_adapters @chip_ids @superio_ids $revision);

$revision = '$Revision: 4171 $ ($Date: 2006-09-24 03:37:01 -0700 (Sun, 24 Sep 2006) $)';
$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 (string as appears in /proc/pci; see linux/driver/pci,
# either pci.c or oldproc.c). If no driver is written yet, set the 
# driver (Driver Name) field to "to-be-written".
# The match (Match Description) field should contain a regular expression
# matching the adapter name as it would appear in /proc/bus/i2c or /sys.
@pci_adapters = ( 
     { 
       vendid => 0x8086,
       devid  => 0x7113,
       procid => "Intel 82371AB PIIX4 ACPI",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x719b,
       procid => "Intel 82443MX Mobile",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2413,
       procid => "Intel 82801AA ICH",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2423,
       procid => "Intel 82801AB ICH0",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2443,
       procid => "Intel 82801BA ICH2",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2483,
       procid => "Intel 82801CA/CAM ICH3",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x24C3,
       procid => "Intel 82801DB ICH4",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x24D3,
       procid => "Intel 82801EB ICH5",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x25A4,
       procid => "Intel 6300ESB",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x269B,
       procid => "Intel Enterprise Southbridge - ESB2",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     },
     {
       vendid => 0x8086,
       devid  => 0x266A,
       procid => "Intel 82801FB ICH6",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x27DA,
       procid => "Intel ICH7",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x283E,
       procid => "Intel ICH8",
       driver => "i2c-i801",
       match => qr/^SMBus I801 adapter at [0-9a-f]{4}/,
     }, 
     { 
       vendid => 0x1106,
       devid  => 0x3040,
       procid => "VIA Technologies VT82C586B Apollo ACPI",
       driver => "i2c-via",
       match => qr/^VIA i2c/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3050,
       procid => "VIA Technologies VT82C596 Apollo ACPI",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3051,
       procid => "VIA Technologies VT82C596B ACPI",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3057,
       procid => "VIA Technologies VT82C686 Apollo ACPI",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3074,
       procid => "VIA Technologies VT8233 VLink South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3147,
       procid => "VIA Technologies VT8233A South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3177,
       procid => "VIA Technologies VT8233A/8235 South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     {
       vendid => 0x1106,
       devid  => 0x3227,
       procid => "VIA Technologies VT8237 South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     {
       vendid => 0x1106,
       devid  => 0x3337,
       procid => "VIA Technologies VT8237A South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x8235,
       procid => "VIA Technologies VT8231 South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3287,
       procid => "VIA Technologies VT8251 South Bridge",
       driver => "i2c-viapro",
       match => qr/^SMBus V(IA|ia) Pro adapter at/,
     } ,
     {
       vendid => 0x1039,
       devid  => 0x5597,
       procid => "Silicon Integrated Systems SIS5581/5582/5597/5598 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
     } ,
     {
       vendid => 0x1039,
       devid  => 0x5598,
       procid => "Silicon Integrated Systems SIS5598 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0540,
       procid => "Silicon Integrated Systems SIS540 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0630,
       procid => "Silicon Integrated Systems SIS630",
       driver => "i2c-sis630",
       match => qr/^SMBus SIS630 adapter at [0-9a-f]{4}/,
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0730,
       procid => "Silicon Integrated Systems SIS730",
       driver => "i2c-sis630",
       match => qr/^SMBus SIS630 adapter at [0-9a-f]{4}/,
     } ,
#
# 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",
       match => qr/^SMBus ALI15X3 adapter at/,
     },
     {
       vendid => 0x10b9,
       devid => 0x7101,
       procid => "Acer Labs 1535",
       driver => "i2c-ali1535",
       match => qr/^SMBus ALI1535 adapter at/,
     },
     {
       vendid => 0x10b9,
       devid => 0x1563,
       procid => "Acer Labs 1563",
       driver => "i2c-ali1563",
       match => qr/^SMBus ALi 1563 Adapter @/,
     },
     { 
       vendid => 0x106b,
       devid  => 0x000e,
       procid => "Apple Computer Inc. Hydra Mac I/O",
       driver => "i2c-hydra",
       match => qr/^Hydra i2c/,
     },
     { 
       vendid => 0x1022,
       devid  => 0x740b,
       procid => "AMD-756 Athlon ACPI",
       driver => "i2c-amd756",
       match => qr/^SMBus AMD756 adapter at [0-9a-f]{4}/,
     },
     { 
       vendid => 0x1022,
       devid  => 0x7413,
       procid => "AMD-766 Athlon ACPI",
       driver => "i2c-amd756",
       match => qr/^SMBus AMD766 adapter at [0-9a-f]{4}/,
     },
     { 
       vendid => 0x1022,
       devid  => 0x7443,
       procid => "AMD-768 System Management",
       driver => "i2c-amd756",
       match => qr/^SMBus AMD768 adapter at [0-9a-f]{4}/,
     },
     { 
       vendid => 0x1022,
       devid  => 0x746b,
       procid => "AMD-8111 ACPI",
       driver => "i2c-amd756",
       match => qr/^SMBus AMD8111 adapter at [0-9a-f]{4}/,
     },
     { 
       vendid => 0x1022,
       devid  => 0x746a,
       procid => "AMD-8111 SMBus 2.0",
       driver => "i2c-amd8111",
       match => qr/^SMBus2 AMD8111 adapter at [0-9a-f]{4}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x0519,
       procid => "MGA 2064W [Millennium]",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x051a,
       procid => "MGA 1064SG [Mystique]",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x051b,
       procid => "MGA 2164W [Millennium II]",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x051e,
       procid => "MGA 1064SG [Mystique] AGP",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x051f,
       procid => "MGA 2164W [Millennium II] AGP",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x1000,
       procid => "MGA G100 [Productiva]",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x1001,
       procid => "MGA G100 [Productiva] AGP",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x0520,
       procid => "MGA G200",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x0521,
       procid => "MGA G200 AGP",
       driver => "i2c-matroxfb",
       match  => qr/^DDC:fb[0-9]{1,2}/,
     },
     {
       vendid => 0x102b,
       devid  => 0x0525,
       procid => "MGA G400 AGP",
       driver => "i2c-matroxfb",
       match  => qr/^(DDC,MAVEN):fb[0-9]{1,2}/,
     },
     {
       vendid => 0x121a,
       devid  => 0x0005,
       procid => "3Dfx Voodoo3",
       driver => "i2c-voodoo3",
       match  => qr/^(I2C|DDC) Voodoo3\/Banshee adapter/,
     },
     {
       vendid => 0x121a,
       devid  => 0x0003,
       procid => "3Dfx Voodoo Banshee",
       driver => "i2c-voodoo3",
       match  => qr/^(I2C|DDC) Voodoo3\/Banshee adapter/,
     },
     { 
       vendid => 0x8086,
       devid  => 0x7121,
       procid => "Intel 82810 GMCH",
       driver => "i2c-i810",
       match => qr/^I810/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x7123,
       procid => "Intel 82810-DC100 GMCH",
       driver => "i2c-i810",
       match => qr/^I810/ ,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x7125,
       procid => "Intel 82810E GMCH",
       driver => "i2c-i810",
       match => qr/^I810/,
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x1132,
       procid => "Intel 82815 GMCH",
       driver => "i2c-i810",
       match => qr/^I810/,
     } , 
     {
       vendid => 0x8086,
       devid  => 0x2562,
       procid => "Intel 82845G GMCH",
       driver => "i2c-i810",
       match => qr/^I810/,
     },
     {
       vendid => 0x10de,
       devid  => 0x01b4,
       procid => "nVidia nForce SMBus",
       driver => "i2c-amd756",
       match => qr/^SMBus nVidia nForce adapter at [0-9a-f]{4}/,
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0064,
       procid => "nVidia Corporation nForce2 SMBus (MCP)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     }, 
     {
       vendid => 0x10de,
       devid  => 0x0084,
       procid => "nVidia Corporation nForce2 Ultra 400 SMBus (MCP)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     }, 
     {
       vendid => 0x10de,
       devid  => 0x00D4,
       procid => "nVidia Corporation nForce3 Pro150 SMBus (MCP)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     }, 
     {
       vendid => 0x10de,
       devid  => 0x00E4,
       procid => "nVidia Corporation nForce3 250Gb SMBus (MCP)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     }, 
     {
       vendid => 0x10de,
       devid  => 0x0052,
       procid => "nVidia Corporation nForce4 SMBus (MCP)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     }, 
     {
       vendid => 0x10de,
       devid => 0x0034,
       procid => "nVidia Corporation nForce4 SMBus (MCP-04)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     },
     {
       vendid => 0x10de,
       devid => 0x0264,
       procid => "nVidia Corporation nForce4 SMBus (MCP51)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     },
     {
       vendid => 0x10de,
       devid => 0x0368,
       procid => "nVidia Corporation nForce4 SMBus (MCP55)",
       driver => "i2c-nforce2",
       match => qr/^SMBus nForce2 adapter at /,
     },
     {
       vendid => 0x1166,
       devid  => 0x0200,
       procid => "ServerWorks OSB4 South Bridge",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x1055,
       devid  => 0x9463,
       procid => "SMSC Victory66 South Bridge",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x1166,
       devid  => 0x0201,
       procid => "ServerWorks CSB5 South Bridge",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x1166,
       devid  => 0x0203,
       procid => "ServerWorks CSB6 South Bridge",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     {
       vendid => 0x1166,
       devid  => 0x0205,
       procid => "ServerWorks HT-1000 South Bridge",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     },
     { 
       vendid => 0x1283,
       devid  => 0x8172,
       procid => "ITE 8172G MIPS/SH4 Support Chip",
       driver => "i2c-adap-ite",
       match => qr/^ITE IIC adapter/,
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A20,
       procid => "S3 Savage 3D",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A21,
       procid => "S3 Savage 3D MV",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A22,
       procid => "S3 Savage 4",
       driver => "i2c-savage4",
       match => qr/^I2C Savage4 adapter/,
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x9102,
       procid => "S3 Savage 2000",
       driver => "i2c-savage4",
       match => qr/^I2C Savage4 adapter/,
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A25,
       procid => "S3 ProSavage PM",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A26,
       procid => "S3 ProSavage KM",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C10,
       procid => "S3 Savage MX MV",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C11,
       procid => "S3 Savage MX",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C12,
       procid => "S3 Savage IX MV",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C13,
       procid => "S3 Savage IX",
       driver => "to-be-written",
     } , 
     { 
       vendid => 0x1002,
       devid  => 0x4353,
       procid => "ATI Technologies Inc ATI SMBus",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x1002,
       devid  => 0x4363,
       procid => "ATI Technologies Inc ATI SMBus",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     { 
       vendid => 0x1002,
       devid  => 0x4372,
       procid => "ATI Technologies Inc ATI SMBus",
       driver => "i2c-piix4",
       match => qr/^SMBus PIIX4 adapter at /,
     } , 
     {
       vendid => 0x100B,
       devid => 0x0500,
       procid => "SCx200 Bridge",
       driver => "scx200_acb",
       match => qr/^(NatSemi SCx200 ACCESS\.bus|SCx200 ACB\d+) /,
     },
     {
       vendid => 0x100B,
       devid => 0x0510,
       procid => "SC1100 Bridge",
       driver => "scx200_acb",
       match => qr/^(NatSemi SCx200 ACCESS\.bus|SCx200 ACB\d+) /,
     },
     {
       vendid => 0x100B,
       devid => 0x002B,
       procid => "CS5535 ISA bridge",
       driver => "scx200_acb",
       match => qr/^CS5535 ACB\d+ /,
     },
     {
       vendid => 0x1022,
       devid => 0x2090,
       procid => "CS5536 [Geode companion] ISA",
       driver => "scx200_acb",
       match => qr/^CS553[56] ACB\d+ /,
     },
);

# The following entries used to appear directly in @pci_adapters.
# Because of the tendency of SiS chipsets to have their real PCI
# IDs obscured, we have to qualify these with a custom detection
# routine before we add them to the @pci_adapters list.
#
use vars qw(@pci_adapters_sis5595 @pci_adapters_sis645 @pci_adapters_sis96x);
@pci_adapters_sis5595 = (
     {
       vendid => 0x1039,
       devid  => 0x0008,
       procid => "Silicon Integrated Systems SIS5595",
       driver => "i2c-sis5595",
       match => qr/^SMBus SIS5595 adapter at [0-9a-f]{4}/,
     } ,
);

@pci_adapters_sis645 = (
     {
       vendid => 0x1039,
       devid  => 0x0008,
       procid => "Silicon Integrated Systems SIS5595",
       driver => "i2c-sis645",
       match => qr/^SiS645 SMBus adapter at [0-9a-f]{4}/,
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0016,
       procid => "Silicon Integrated Systems SMBus Controller",
       driver => "i2c-sis645",
       match => qr/^SiS645 SMBus adapter at 0x[0-9a-f]{4}/,
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0018,
       procid => "Silicon Integrated Systems 85C503/5513 (LPC Bridge)",
       driver => "i2c-sis645",
       match => qr/^SiS645 SMBus adapter at 0x[0-9a-f]{4}/,
     } ,
);

@pci_adapters_sis96x = (
     {
       vendid => 0x1039,
       devid  => 0x0016,
       procid => "Silicon Integrated Systems SMBus Controller",
       driver => "i2c-sis96x",
       match => qr/^SiS96x SMBus adapter at 0x[0-9a-f]{4}/,
     } ,
);

# This is a list of all recognized chips. 
# Each entry must have the following fields:
#  name: The full chip name
#  driver: The driver name (without .o extension). Put in exactly
#      "to-be-written" if it is not yet available. Put in exactly
#      "not-a-sensor" if it is not a hardware monitoring chip and
#      there is no known driver for it.
#      Put in exactly "use-isa-instead" if no i2c driver will be written.
#  i2c_addrs (optional): For I2C chips, the range of valid I2C addresses to
#      probe. Recommend avoiding 0x69 because of clock chips.
#  i2c_detect (optional): For I2C chips, the function to call to detect
#      this chip. The function should take two parameters: an open file
#      descriptor to access the bus, and the I2C address to probe.
#  isa_addrs (optional): For ISA chips, the range of valid port addresses to
#      probe.
#  isa_detect (optional): For ISA chips, the function to call to detect
#      this chip. The function should take 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 detectes whether two entries are the same.
#      The function should take three parameters: The ISA address, the
#      I2C bus number, 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 => [0x20..0x2f], 
       i2c_detect => sub { lm78_detect(0, @_); },
       isa_addrs => [0x290],
       isa_detect => sub { lm78_isa_detect(0, @_); },
       alias_detect => sub { lm78_alias_detect(0, @_); },
     } ,
     {
       name => "National Semiconductor LM78-J",
       driver => "lm78",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { lm78_detect(1, @_); },
       isa_addrs => [0x290],
       isa_detect => sub { lm78_isa_detect(1, @_); },
       alias_detect => sub { lm78_alias_detect(1, @_); },
     } ,
     {
       name => "National Semiconductor LM79",
       driver => "lm78",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { lm78_detect(2, @_); },
       isa_addrs => [0x290],
       isa_detect => sub { lm78_isa_detect(2, @_); },
       alias_detect => sub { lm78_alias_detect(2, @_); },
     } ,
     {
       name => "National Semiconductor LM75",
       driver => "lm75",
       i2c_addrs => [0x48..0x4f],
       i2c_detect => sub { lm75_detect(@_); },
     } ,
     {
       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 or LM96000",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect(0x01, @_); },
     },
     {
       name => "Analog Devices ADM1027, ADT7460 or ADT7463",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect(0x41, @_); },
     },
     {
       name => "SMSC EMC6D100, EMC6D101 or EMC6D102",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect(0x5c, @_); },
     },
     {
       name => "Analog Devices ADT7462",
       driver => "to-be-written",
       # The datasheet says addresses 0x5C and 0x58, but I guess these are
       # left-aligned values
       i2c_addrs => [0x2c, 0x2e],
       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 => "to-be-written",
       i2c_addrs => [0x2c, 0x2e, 0x2f],
       i2c_detect => sub { adt7467_detect(4, @_); },
     },
     {
       name => "Analog Devices ADT7473",
       driver => "to-be-written",
       i2c_addrs => [0x2e],
       i2c_detect => sub { adt7473_detect(0, @_); },
     },
     {
       name => "Analog Devices ADT7475",
       driver => "to-be-written",
       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 => "National Semiconductor LM87",
       driver => "lm87",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm87_detect(@_); },
     },
     {
       name => "National Semiconductor LM93",
       driver => "lm93",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm93_detect(@_); },
     },
     {
       name => "Winbond W83781D",
       driver => "w83781d",
       i2c_detect => sub { w83781d_detect(0, @_); },
       i2c_addrs => [0x20..0x2f], 
       isa_addrs => [0x290],
       isa_detect => sub { w83781d_isa_detect(0, @_); },
       alias_detect => sub { w83781d_alias_detect(0, @_); },
     } ,
     {
       name => "Winbond W83782D",
       driver => "w83781d",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect(1, @_); },
       isa_addrs => [0x290],
       isa_detect => sub { w83781d_isa_detect(1, @_); },
       alias_detect => sub { w83781d_alias_detect(1, @_); },
     } ,
     {
       name => "Winbond W83783S",
       driver => "w83781d",
       i2c_addrs => [0x2d],
       i2c_detect => sub { w83781d_detect(2, @_); },
     } ,
     {
       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(0, @_); },
     },
     {
       name => "Winbond W83791SD",
       driver => "not-a-sensor",
       i2c_addrs => [0x2c..0x2f],
       i2c_detect => sub { w83791sd_detect(@_); },
     },
     {
       name => "Winbond W83627HF",
       driver => "w83781d",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect(3, @_); },
       isa_addrs => [0x290],
       isa_detect => sub { w83781d_isa_detect(3, @_); },
       alias_detect => sub { w83781d_alias_detect(3, @_); },
     } ,
     {
       name => "Winbond W83627EHF",
       driver => "use-isa-instead",
       i2c_addrs => [0x28..0x2f], 
       i2c_detect => sub { w83781d_detect(9, @_); },
     },
     {
       name => "Winbond W83627DHG",
       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 ASM58 Mozart-2",
       driver => "to-be-written",
       i2c_addrs => [0x77],
       i2c_detect => sub { mozart_detect(0, @_); },
     } ,
     {
       name => "Asus AS2K129R Mozart-2",
       driver => "to-be-written",
       i2c_addrs => [0x77],
       i2c_detect => sub { mozart_detect(1, @_); },
     } ,
     {
       name => "Asus Mozart-2",
       driver => "to-be-written",
       i2c_addrs => [0x77],
       i2c_detect => sub { mozart_detect(2, @_); },
     } ,
     {
       name => "Winbond W83L784R/AR",
       driver => "to-be-written",
       i2c_addrs => [0x2d],
       i2c_detect => sub { w83l784r_detect(0, @_); },
     } ,
     {
       name => "Winbond W83L785R",
       driver => "to-be-written",
       i2c_addrs => [0x2d],
       i2c_detect => sub { w83l784r_detect(1, @_); },
     } ,
     {
       name => "Winbond W83L785TS-S",
       driver => "w83l785ts",
       i2c_addrs => [0x2e], 
       i2c_detect => sub { w83l785ts_detect(0, @_); },
     } ,
     {
       name => "Genesys Logic GL518SM Revision 0x00",
       driver => "gl518sm",
       i2c_addrs => [0x2c, 0x2d],
       i2c_detect => sub { gl518sm_detect(0, @_); },
     },
     {
       name => "Genesys Logic GL518SM Revision 0x80",
       driver => "gl518sm",
       i2c_addrs => [0x2c, 0x2d],
       i2c_detect => sub { gl518sm_detect(1, @_); },
     },
     {
       name => "Genesys Logic GL520SM",
       driver => "gl520sm",
       i2c_addrs => [0x2c, 0x2d],
       i2c_detect => sub { gl520sm_detect(@_); },
     },
     {
       name => "Genesys Logic GL525SM",
       driver => "Unwritten (GL525SM)",
       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",
       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,0x2d,0x2e],
       i2c_detect => sub { adm1026_detect(0, @_); },
     },
     {
       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 ADM1024",
       driver => "adm1024",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { adm1024_detect(0, @_); },
     },
     {
       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 MAX6650/MAX6651",
       driver => "max6650",
       i2c_addrs => [0x1b,0x1f,0x48,0x4b],
       i2c_detect => sub { max6650_detect(0, @_); },
     },
     {
       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 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(0, @_); },
     },
     {
       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 MAX6657/MAX6658/MAX6659",
       driver => "lm90",
       i2c_addrs => [0x4c],
       i2c_detect => sub { lm90_detect(4, @_); },
     },
     {
       name => "Maxim MAX6659",
       driver => "lm90",
       i2c_addrs => [0x4d..0x4e], # 0x4c is handled above
       i2c_detect => sub { lm90_detect(4, @_); },
     },
     {
       name => "National Semiconductor LM63",
       driver => "lm63",
       i2c_addrs => [0x4c],
       i2c_detect => sub { lm63_detect(1, @_); },
     },
     {
       name => "Fintek F75363SG",
       driver => "lm63", # Not yet
       i2c_addrs => [0x4c],
       i2c_detect => sub { lm63_detect(2, @_); },
     },
     {
       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 => [0x40..0x4f],
       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 ADM1029",
       driver => "to-be-written",
       i2c_addrs => [0x28..0x2f],
       i2c_detect => sub { adm1029_detect(0, @_); },
     },
     {
       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 => "Silicon Integrated Systems SIS5595",
       driver => "sis5595",
       isa_addrs => [ 0 ],
       isa_detect => sub { sis5595_pci_detect(); },
     },
     {
       name => "VIA VT82C686 Integrated Sensors",
       driver => "via686a",
       isa_addrs => [ 0 ],
       isa_detect => sub { via686a_pci_detect(); },
     },
     {
       name => "VIA VT8231 Integrated Sensors",
       driver => "vt8231",
       isa_addrs => [ 0 ],
       isa_detect => sub { via8231_pci_detect(); },
     },
     {
       name => "VIA VT1211 (I2C)",
       driver => "use-isa-instead",
       i2c_addrs => [0x2d],
       i2c_detect => sub { vt1211_i2c_detect(0, @_); },
     },
     {
       name => "ITE IT8712F",
       driver => "it87",
       i2c_addrs => [0x28..0x2f],
       i2c_detect => sub { ite_detect(0, @_); },
     },
     {
       name => "ITE IT8201R/IT8203R/IT8206R/IT8266R",
       driver => "not-a-sensor",
       i2c_addrs => [0x4e],
       i2c_detect => sub { ite_overclock_detect(@_); },
     },
     {
       name => "SPD EEPROM",
       driver => "eeprom",
       i2c_addrs => [0x50..0x57],
       i2c_detect => sub { eeprom_detect(0, @_); },
     },
     {
       name => "Sony Vaio EEPROM",
       driver => "eeprom",
       i2c_addrs => [0x57],
       i2c_detect => sub { eeprom_detect(1, @_); },
     },
# Disabled by default (potentially dangerous)
#     {
#       name => "SPD EEPROM with Software Write-Protect",
#       driver => "eeprom",
#       i2c_addrs => [0x50..0x57],
#       i2c_detect => sub { eeprom_detect(2, @_); },
#     },
     {
       name => "EDID EEPROM",
       driver => "eeprom",
       i2c_addrs => [0x50],
       i2c_detect => sub { ddcmonitor_detect(@_); },
     },
     {
       name => "FSC Poseidon",
       driver => "fscpos",
       i2c_addrs => [0x73],
       i2c_detect => sub { fscpos_detect(@_); },
     },
     {
       name => "FSC Scylla",
       driver => "fscscy",
       i2c_addrs => [0x73],
       i2c_detect => sub { fscscy_detect(@_); },
     },
     {
       name => "FSC Hermes",
       driver => "fscher",
       i2c_addrs => [0x73],
       i2c_detect => sub { fscher_detect(@_); },
     },
     {
       name => "ALi M5879",
       driver => "to-be-written",
       i2c_addrs => [0x2c..0x2d],
       i2c_detect => sub { m5879_detect(@_); },
     },
     {
       name => "SMSC LPC47M15x, LPC47M192 or LPC47M997",
       driver => "smsc47m192",
       i2c_addrs => [0x2c..0x2d],
       i2c_detect => sub { smsc47m192_detect(@_); },
     },
     {
       name => "Fintek F75111R/RG/N (GPIO)",
       driver => "to-be-written",
       i2c_addrs => [0x4e], # 0x37 not probed
       i2c_detect => sub { fintek_detect(1, @_); },
     },
     {
       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 => "to-be-written",
       i2c_addrs => [0x2d..0x2e],
       i2c_detect => sub { fintek_detect(3, @_); },
     },
     {
       name => "Fintek F75375S/SP",
       driver => "to-be-written",
       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 => "AMD K8 thermal sensors",
       driver => "k8temp",
       isa_addrs => [ 0 ],
       isa_detect => sub { k8temp_pci_detect(); },
     },
     {
       name => "Philips Semiconductors SAA1064",
       driver => "saa1064",
       i2c_addrs => [0x38..0x3b],
       i2c_detect => sub { saa1064_detect(@_); },
     },
     {
       name => "Philips Semiconductors PCA9540",
       driver => "pca9540",
       i2c_addrs => [0x70],
       i2c_detect => sub { pca9540_detect(@_); },
     },
     {
       name => "Philips Semiconductors PCA9556",
       driver => "to-be-written",
       i2c_addrs => [0x18..0x1f],
       i2c_detect => sub { pca9556_detect(@_); },
     },
     {
       name => "Maxim MAX6900",
       driver => "to-be-written",
       i2c_addrs => [0x50],
       i2c_detect => sub { max6900_detect(@_); },
     },
     {
       name => "SMBus 2.0 ARP-Capable Device",
       driver => "not-a-sensor",
       i2c_addrs => [0x61],
       i2c_detect => sub { arp_detect(@_); },
     },
     {
       name => "IPMI BMC KCS",
       driver => "bmcsensors",
       isa_addrs => [ 0x0ca0 ],
       isa_detect => sub { ipmi_kcs_detect(@_); },
     },
     {
       name => "IPMI BMC SMIC",
       driver => "bmcsensors",
       isa_addrs => [ 0x0ca8 ],
       isa_detect => sub { ipmi_smic_detect(@_); },
     },
     {
       name => "Smart Battery Charger",
       driver => "to-be-written",
       i2c_addrs => [0x09],
       i2c_detect => sub { smartbatt_chgr_detect(@_); },
     },
     {
       name => "Smart Battery Manager/Selector",
       driver => "to-be-written",
       i2c_addrs => [0x0a],
       i2c_detect => sub { smartbatt_mgr_detect(@_); },
     },
     {
       name => "Smart Battery",
       driver => "smartbatt",
       i2c_addrs => [0x0b],
       i2c_detect => sub { smartbatt_detect(@_); },
     },
);

# Special case chip information goes here and would be included in
# the chip_special_cases routine below
use vars qw($chip_kern24_w83791d $chip_kern26_w83791d);
$chip_kern24_w83791d = {
	name => "Winbond W83791D",
	driver => "w83781d",
	i2c_addrs => [0x2c..0x2f],
	i2c_detect => sub { w83781d_detect(7, @_); },
};

$chip_kern26_w83791d = {
	name => "Winbond W83791D",
	driver => "w83791d",
	i2c_addrs => [0x2c..0x2f],
	i2c_detect => sub { w83781d_detect(7, @_); },
};

# 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 (without .o extension). Put in
#      "to-be-written" if it is not yet available.
#      Put in "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).
#  devid: The device ID(s) we have to match (base device)
#  devid_mask (optional): Bitmask to apply before checking the device ID
#  logdev: The logical device containing the sensors
#  alias_detect (optional): For chips which can be both on the ISA and the
#      I2C bus, a function which detectes whether two entries are the same.
#      The function should take three parameters: The ISA address, the
#      I2C bus number, and the I2C address.
# 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
@superio_ids = (
  {
    family => "ITE",
    guess => 0x290,
    enter =>
    {
      0x2e => [0x87, 0x01, 0x55, 0x55],
      0x4e => [0x87, 0x01, 0x55, 0xaa],
    },
    chips =>
    [
      {
	name => "ITE IT8702F Super IO Sensors",
	driver => "to-be-written",
	devid => 0x8702,
	logdev => 0x04,
      },
      {
	name => "ITE IT8705F Super IO Sensors",
	driver => "it87",
	devid => 0x8705,
	logdev => 0x04,
      },
      {
	name => "ITE IT8712F Super IO Sensors",
	driver => "it87",
	devid => 0x8712,
	logdev => 0x04,
	alias_detect => sub { ite_alias_detect(0, @_); },
      },
      {
	name => "ITE IT8716F Super IO Sensors",
	driver => "it87",
	devid => 0x8716,
	logdev => 0x04,
      },
      {
	name => "ITE IT8718F Super IO Sensors",
	driver => "it87",
	devid => 0x8718,
	logdev => 0x04,
      },
    ],
  },
  {
    family => "National Semiconductor",
    enter =>
    {
      0x2e => [],
      0x4e => [],
    },
    chips =>
    [
      {
	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,
      },
      {
	name => "Nat. Semi. PC87363 Super IO Fan Sensors",
	driver => "pc87360",
	devid => 0xe8,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87364 Super IO Fan Sensors",
	driver => "pc87360",
	devid => 0xe4,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87365 Super IO Fan Sensors",
	driver => "pc87360",
	devid => 0xe5,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87365 Super IO Voltage Sensors",
	driver => "pc87360",
	devid => 0xe5,
	logdev => 0x0d,
      },
      {
	name => "Nat. Semi. PC87365 Super IO Thermal Sensors",
	driver => "pc87360",
	devid => 0xe5,
	logdev => 0x0e,
      },
      {
	name => "Nat. Semi. PC87366 Super IO Fan Sensors",
	driver => "pc87360",
	devid => 0xe9,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87366 Super IO Voltage Sensors",
	driver => "pc87360",
	devid => 0xe9,
	logdev => 0x0d,
      },
      {
	name => "Nat. Semi. PC87366 Super IO Thermal Sensors",
	driver => "pc87360",
	devid => 0xe9,
	logdev => 0x0e,
      },
      {
	name => "Nat. Semi. PC87372 Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0xf0,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87373 Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0xf3,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87591 Super IO",
	driver => "to-be-written",
	devid => 0xec,
	logdev => 0x0f,
      },
      {
	name => "Nat. Semi. PC87371 Super IO",
	driver => "not-a-sensor",
	devid => 0xd0,
      },
      {
	name => "Nat. Semi. PC97371 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 => "to-be-written",
	devid => 0xf2,
	logdev => 0x09,
      },
      {
	name => "Nat. Semi. PC87427 Super IO Health Sensors",
	driver => "to-be-written",
	devid => 0xf2,
	logdev => 0x14,
      },
    ],
  },
  {
    family => "SMSC",
    enter =>
    {
      0x2e => [0x55],
      0x4e => [0x55],
    },
    chips =>
    [
      {
	name => "SMSC LPC47B27x Super IO Fan Sensors",
	driver => "smsc47m1",
	devid => 0x51,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47M10x/112/13x Super IO Fan Sensors",
	driver => "smsc47m1",
	devid => 0x59,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47M14x Super IO Fan Sensors",
	driver => "smsc47m1",
	devid => 0x5f,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47M15x/192/997 Super IO Fan Sensors",
	driver => "smsc47m1",
	devid => 0x60,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47S42x Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0x57,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47S45x Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0x62,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47M172 Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0x14,
	logdev => 0x0a,
      },
      {
	name => "SMSC LPC47M182 Super IO Fan Sensors",
	driver => "to-be-written",
	devid => 0x74,
	logdev => 0x0a,
      },
      {
        name => "SMSC LPC47B397-NC Super IO",
        driver => "smsc47b397",
        devid => 0x6f,
        logdev => 0x08,
      },
      {
        name => "SMSC SCH5307-NS Super IO",
        driver => "smsc47b397",
        devid => 0x81,
        logdev => 0x08,
      },
      {
        name => "SMSC LPC47M584-NC Super IO",
        # No datasheet
        devid => 0x76,
      },
    ],
  },
  {
    family => "VIA/Winbond/Fintek",
    guess => 0x290,
    enter =>
    {
      0x2e => [0x87, 0x87],
      0x4e => [0x87, 0x87],
    },
    chips =>
    [
      {
	name => "VIA VT1211 Super IO Sensors",
	driver => "vt1211",
	devid => 0x3c,
	logdev => 0x0b,
	alias_detect => sub { vt1211_alias_detect(0, @_); },
      },
      {
	name => "Winbond W83627HF Super IO Sensors",
	driver => "w83627hf",
	devid => 0x52,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83627THF Super IO Sensors",
	driver => "w83627hf",
	devid => 0x82,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83637HF Super IO Sensors",
	driver => "w83627hf",
	devid => 0x70,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83687THF Super IO Sensors",
	driver => "w83627hf",
	devid => 0x85,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83697HF Super IO Sensors",
	driver => "w83627hf",
	devid => 0x60,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83697SF/UF Super IO PWM",
	driver => "to-be-written",
	devid => 0x68,
	logdev => 0x0b,
      },
      {
	name => "Winbond W83627EHF/EHG 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,
	alias_detect => sub { w83781d_alias_detect(9, @_); },
      },
      {
	name => "Winbond W83627DHG Super IO Sensors",
	driver => "w83627ehf",
	devid => 0xA020,
	devid_mask => 0xFFF0,
	logdev => 0x0b,
	alias_detect => sub { w83781d_alias_detect(10, @_); },
      },
      {
	name => "Winbond W83L517D Super IO",
	driver => "not-a-sensor",
	devid => 0x61,
      },
      {
	name => "Fintek F71805F/FG Super IO Sensors",
	driver => "f71805f",
	devid => 0x0406,
	logdev => 0x04,
      },
      {
	name => "Fintek F71872F/FG Super IO Sensors",
	driver => "f71805f",
	devid => 0x0341,
	logdev => 0x04,
      },
      {
	name => "Fintek F81218D Super IO",
	driver => "not-a-sensor",
	devid => 0x0206,
      },
    ],
  },
);

#######################
# AUXILIARY FUNCTIONS #
#######################

sub swap_bytes
{
  return (($_[0] & 0xff00) >> 8) + (($_[0] & 0x00ff) << 8)
}

# $_[0] is the sought value
# @_[1..] is the list to seek in
# Returns: 0 on failure, 1 if found.
# Note: Every use of this sub probably indicates the use of the wrong
#       datastructure
sub contains
{
  my $sought = shift;
  foreach (@_) {
    return 1 if $sought eq $_;
  }
  return 0;
}

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 = oct $start if $start =~ /^0/;
    if (defined $end) {
      $end = oct $end if $end =~ /^0/;
      $start = $min if $start < $min;
      $end = $max if $end > $max;
      push @res, ($start+0..$end+0);
    } else {
      push @res, $start+0 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)
    or print "Warning: $!\n";
}

# $_[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
# Returns: -1 on failure, 0 on success.
sub outb
{
  if ($_[1] > 0xff)
  {
    my ($package, $filename, $line, $sub) = caller(1);
    print "\n*** Called outb with value=$_[1] from line $line\n",
          "*** (in $sub). PLEASE REPORT!\n",
          "*** Terminating.\n";
    exit(-1);
  }
  my $towrite = pack "C", $_[1];
  sysseek IOPORTS, $_[0], 0 or return -1;
  my $nrchars = syswrite IOPORTS, $towrite, 1;
  return -1 if not defined $nrchars or $nrchars != 1;
  return 0;
}

# $_[0]: Address register
# $_[1]: Data register
# $_[2]: Register to read
# Returns: read value
sub isa_read_byte
{
  outb $_[0],$_[2];
  return inb $_[1];
}

# $_[0]: Address register
# $_[1]: Data register
# $_[2]: Register to write
# $_[3}: Value to write
# Returns: nothing
sub isa_write_byte
{
  outb $_[0],$_[2];
  outb $_[1],$_[3];
}

#################
# AUTODETECTION #
#################

use vars qw($modules_conf $dev_i2c $sysfs_root);

sub initialize_conf
{
  my $use_devfs = 0;
  open(local *INPUTFILE, "/proc/mounts") or die "Can't access /proc/mounts!";
  local $_;
  while (<INPUTFILE>) {
    if (m@^\w+ /dev devfs @) {
      $use_devfs = 1;
      $dev_i2c = '/dev/i2c/';
    }
    if (m@^\S+ (/\w+) sysfs @) {
      $sysfs_root = $1;
    }
  }
  close INPUTFILE;

  my $use_udev = 0;
  if (open(*INPUTFILE, '/etc/udev/udev.conf')) {
    while (<INPUTFILE>) {
      if (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 (-f '/etc/modules.conf') {
    $modules_conf = '/etc/modules.conf';
  } elsif (-f '/etc/conf.modules') {
    $modules_conf = '/etc/conf.modules';
  } else { # default
    $modules_conf = '/etc/modules.conf';
  }

  if (!($use_devfs || $use_udev)) {
    if (-c '/dev/i2c-0') {
      $dev_i2c = '/dev/i2c-';
    } else { # default
      print "No i2c device files found. Use prog/mkdev/mkdev.sh to create them.\n";
      exit -1;
    }
  }
}

# [0] -> VERSION
# [1] -> PATCHLEVEL
# [2] -> SUBLEVEL
# [3] -> EXTRAVERSION
#
use vars qw(@kernel_version);

sub initialize_kernel_version
{
  `uname -r` =~ /(\d+)\.(\d+)\.(\d+)(.*)/;
  @kernel_version = ($1, $2, $3, $4);
}

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;
}

###########
# MODULES #
###########

use vars qw(%modules_list %modules_supported);

sub initialize_modules_list
{
  open(local *INPUTFILE, "/proc/modules") or return;
  local $_;
  while (<INPUTFILE>) {
    tr/_/-/;
    $modules_list{$1} = 1 if m/^(\S*)/;
  }
}

sub initialize_modules_supported
{
  foreach my $chip (@chip_ids) {
    $modules_supported{$chip->{driver}}++;
  }
}

#################
# SYSFS HELPERS #
#################

# From a sysfs device path, return the driver name, or undef
sub sysfs_device_driver($)
{
  my $device = shift;
  
  my $link = readlink("$device/driver");
  return unless defined $link;
  return basename($link);
}

# From a sysfs device path 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;
  return unless defined($value = <FILE>);
  close(FILE);

  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.
# This function is used when sysfs is available (Linux 2.6).
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;
}

# This function returns a list of hashes. Each hash has some PCI information:
# 'bus', 'slot' and 'func' uniquely identify a PCI device in a computer;
# 'vendid' and 'devid' uniquely identify a type of device.
# This function is used when sysfs is not available (Linux 2.4).
sub read_proc_dev_pci
{
  my ($dfn, $vend, @pci_list);
  open(local *INPUTFILE, "/proc/bus/pci/devices")
    or die "/proc/bus/pci/devices: $!";
  local $_;
  while (<INPUTFILE>) {
    my %record;
    ($dfn, $vend) = map { hex } (split) [0..1];
    $record{bus} = $dfn >> 8;
    $record{slot} = ($dfn & 0xf8) >> 3;
    $record{func} = $dfn & 0x07;
    $record{vendid} = $vend >> 16;
    $record{devid} = $vend & 0xffff;
    
    push @pci_list, \%record;
  }
  return \@pci_list;
}

sub initialize_proc_pci
{
  my $pci_list;

  if (defined $sysfs_root) {
    $pci_list = read_sys_dev_pci("$sysfs_root/bus/pci/devices");
  } else {
    $pci_list = read_proc_dev_pci();
  }

  # 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 #
#####################

sub adapter_pci_detection_sis_96x
{
  my $driver="";

  # first, determine which driver if any...
  if (kernel_version_at_least(2,6,0)) {
    if (exists $pci_list{"1039:0016"}) {
      $driver = "i2c-sis96x";
    } elsif (exists $pci_list{"1039:0008"}) {
      $driver = "i2c-sis5595";
    }
  } elsif (kernel_version_at_least(2,4,0)) {
    if (exists $pci_list{"1039:0008"}) {
      if ((exists $pci_list{"1039:0645"}) ||
          (exists $pci_list{"1039:0646"}) ||
          (exists $pci_list{"1039:0648"}) ||
          (exists $pci_list{"1039:0650"}) ||
          (exists $pci_list{"1039:0651"}) ||
          (exists $pci_list{"1039:0655"}) ||
          (exists $pci_list{"1039:0661"}) ||
          (exists $pci_list{"1039:0735"}) ||
          (exists $pci_list{"1039:0745"}) ||
          (exists $pci_list{"1039:0746"})) {
        $driver = "i2c-sis645";
      } else {
        $driver = "i2c-sis5595";
      }
    } elsif ((exists $pci_list{"1039:0016"}) ||
             (exists $pci_list{"1039:0018"})) {
      $driver = "i2c-sis645";
    }
  }

  # then, add the appropriate entries to @pci_adapters
  if ($driver eq "i2c-sis5595") {
    push @pci_adapters, @pci_adapters_sis5595;
  } elsif ($driver eq "i2c-sis645") {
    push @pci_adapters, @pci_adapters_sis645;
  } elsif ($driver eq "i2c-sis96x") {
    push @pci_adapters, @pci_adapters_sis96x;
  }
}

# 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, @res, %smbus);
  print "Probing for PCI bus adapters...\n";

  # Custom detection routine for some SiS chipsets
  adapter_pci_detection_sis_96x();

  # Build a list of detected SMBus devices
  foreach $key (keys %pci_list) {
    $device = $pci_list{$key};
    $smbus{$key}++
      if exists $device->{class} && $device->{class} == 0x0c05; # SMBus
  }

  # Loop over the known I2C/SMBus adapters
  foreach $try (@pci_adapters) {
    $key = sprintf("%04x:%04x", $try->{vendid}, $try->{devid});
    if (exists $pci_list{$key}) {
        $device = $pci_list{$key};
        printf "Use driver `\%s' for device \%s: \%s\n",
               $try->{driver}, pci_busid($device), $try->{procid};
        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";
          print "Continue... ";
          <STDIN>;
          print "\n";
        }
        push @res,$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);
  }
  
  if (! @res) {
    print "Sorry, no known PCI bus adapters found.\n";
  }
  return @res;
}

# $_[0]: Adapter description as found in /proc/bus/i2c
sub find_adapter_driver
{
  my $adapter;
  for $adapter (@pci_adapters) {
    return $adapter->{driver}
      if (exists $adapter->{match} && $_[0] =~ $adapter->{match});
  }
  return "UNKNOWN";
}

#############################
# I2C AND SMBUS /DEV ACCESS #
#############################

# This should really go into a separate module/package.

# These are copied from <linux/i2c-dev.h>

use constant IOCTL_I2C_SLAVE    => 0x0703;
use constant IOCTL_I2C_FUNCS    => 0x0705;
use constant IOCTL_I2C_SMBUS    => 0x0720;

use constant SMBUS_READ         => 1;
use constant SMBUS_WRITE        => 0;

use constant SMBUS_QUICK        => 0;
use constant SMBUS_BYTE         => 1;
use constant SMBUS_BYTE_DATA    => 2;
use constant SMBUS_WORD_DATA    => 3;

use constant I2C_FUNC_SMBUS_QUICK	=> 0x00010000;
use constant I2C_FUNC_SMBUS_READ_BYTE	=> 0x00020000;

# 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;

  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) = @_;
  ioctl $file, IOCTL_I2C_SLAVE, $addr or return 0;
  return 1;
}

# i2c_smbus_access is based upon the corresponding C function (see 
# <linux/i2c-dev.h>). You should not need to call this directly.
# Exact calling conventions are intricate; read i2c-dev.c if you really need
# to know.
# $_[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
# $_[1]: Either 0 or 1
# Returns: -1 on failure, the 0 on success.
sub i2c_smbus_write_quick
{
  my ($file,$value) = @_;
  my @data;
  i2c_smbus_access $file, $value, 0, SMBUS_QUICK, \@data
         or return -1;
  return 0;
}

# $_[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.
sub i2c_smbus_read_byte_data
{
  my ($file,$command) = @_;
  my @data;
  i2c_smbus_access $file, SMBUS_READ, $command, SMBUS_BYTE_DATA, \@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 word on success.
# Note: some devices use the wrong endiannes; use swap_bytes to correct for 
# this.
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) = @_;
  my $data = [];
  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, $data);
  } else {
    return 0 unless ($funcs & I2C_FUNC_SMBUS_QUICK);
    return i2c_smbus_access($file, SMBUS_WRITE, 0, SMBUS_QUICK, $data);
  }
}

####################
# ADAPTER SCANNING #
####################

use vars qw(@chips_detected);

# We will build a complicated structure @chips_detected here, being:
# A list of
#  references to hashes
#    with field 'driver', being a string with the driver name for this chip;
#    with field 'detected'
#      being a reference to a list of
#        references to hashes of type 'detect_data';
#    with field 'misdetected'
#      being a reference to a list of
#        references to hashes of type 'detect_data'

# Type detect_data:
# A hash
#   with field 'i2c_adap' containing an adapter string as appearing
#        in /proc/bus/i2c (if this is an I2C detection)
#  with field 'i2c_devnr', contianing the /dev/i2c-* number of this
#       adapter (if this is an I2C detection)
#  with field 'i2c_driver', containing the driver name for 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

# This adds a detection to the above structure. We do no alias detection
# here; so you should do ISA detections *after* all I2C detections.
# 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,$new_misdetected_ref,$detected_ref,$misdetected_ref,
      $main_entry,$detected_entry,$put_in_detected,@hash_addrs,@entry_addrs,
      $do_not_add);

  # First determine where the hash has to be added.
  for ($i = 0; $i < @chips_detected; $i++) {
    last if ($chips_detected[$i]->{driver} eq $chipdriver);
  }
  if ($i == @chips_detected) {
    push @chips_detected, { driver => $chipdriver,
                            detected => [],
                            misdetected => [] };
  }
  $new_detected_ref = $chips_detected[$i]->{detected};
  $new_misdetected_ref = $chips_detected[$i]->{misdetected};

  # Find out whether our new entry should go into the detected or the
  # misdetected list. We compare all i2c addresses; if at least one matches,
  # but our conf value is lower, we assume this is a misdetect.
  @hash_addrs = ($datahash->{i2c_addr});
  push @hash_addrs, @{$datahash->{i2c_sub_addrs}}
       if exists $datahash->{i2c_sub_addrs};
  $put_in_detected = 1;
  $do_not_add = 0;
  FIND_LOOP:
  foreach $main_entry (@chips_detected) {
    foreach $detected_entry (@{$main_entry->{detected}}) {
      @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} and
          any_list_match \@entry_addrs, \@hash_addrs) {
        if ($detected_entry->{conf} >= $datahash->{conf}) {
          $put_in_detected = 0;
        }
        if ($chipdriver eq $main_entry->{driver}) {
          $do_not_add = 1;
        }
        last FIND_LOOP;
      }
    }
  }

  if ($put_in_detected) {
    # Here, we move all entries from detected to misdetected 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.
    # (Khali 2003-09-13) If the driver is the same, the "misdetected"
    # entry is simply deleted; failing to do so cause the configuration
    # lines generated later to look very confusing (the driver will
    # be told to ignore valid addresses).
    @hash_addrs = ($datahash->{i2c_addr});
    push @hash_addrs, @{$datahash->{i2c_sub_addrs}} 
         if exists $datahash->{i2c_sub_addrs};
    foreach $main_entry (@chips_detected) {
      $detected_ref = $main_entry->{detected};
      $misdetected_ref = $main_entry->{misdetected};
      for ($i = @$detected_ref-1; $i >=0; $i--) {
        @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} and
            any_list_match \@entry_addrs, \@hash_addrs) {
          push @$misdetected_ref,$detected_ref->[$i]
            unless $chipdriver eq $main_entry->{driver};
          splice @$detected_ref, $i, 1;
        }
      }
    }

    # Now add the new entry to detected
    push @$new_detected_ref, $datahash;
  } else {
    # No hard work here 
    push @$new_misdetected_ref, $datahash
      unless $do_not_add;
  }
}

# This adds a detection to the above structure. We also do alias detection
# here; so you should do ISA detections *after* all I2C detections.
# $_[0]: alias detection function
# $_[1]: chip driver
# $_[2]: reference to data hash
# Returns: 0 if it is not an alias, datahash reference if it is.
sub add_isa_to_chips_detected
{
  my ($alias_detect,$chipdriver,$datahash) = @_;
  my ($i,$new_detected_ref,$new_misdetected_ref,$detected_ref,$misdetected_ref,
      $main_entry,$isalias);

  # First determine where the hash has to be added.
  $isalias=0;
  for ($i = 0; $i < @chips_detected; $i++) {
    last if ($chips_detected[$i]->{driver} eq $chipdriver);
  }
  if ($i == @chips_detected) {
    push @chips_detected, { driver => $chipdriver,
                            detected => [],
                            misdetected => [] };
  }
  $new_detected_ref = $chips_detected[$i]->{detected};
  $new_misdetected_ref = $chips_detected[$i]->{misdetected};

  # Now, we are looking for aliases. An alias can only be the same chiptype.
  # If an alias is found in the misdetected list, we add the new information
  # and terminate this function. If it is found in the detected list, we
  # still have to check whether another chip has claimed this ISA address.
  # So we remove the old entry from the detected list and put it in datahash.

  # Misdetected alias detection:
  for ($i = 0; $i < @$new_misdetected_ref; $i++) {
    if (exists $new_misdetected_ref->[$i]->{i2c_addr} and
        not exists $new_misdetected_ref->[$i]->{isa_addr} and
        defined $alias_detect and
        $new_misdetected_ref->[$i]->{chipname} eq $datahash->{chipname}) {
      open(local *FILE, "$dev_i2c$new_misdetected_ref->[$i]->{i2c_devnr}") or
        print("Can't open $dev_i2c$new_misdetected_ref->[$i]->{i2c_devnr}?!?\n"),
        next;
      binmode FILE;
      i2c_set_slave_addr \*FILE,$new_misdetected_ref->[$i]->{i2c_addr} or
           print("Can't set I2C address for ",
                 "$dev_i2c$new_misdetected_ref->[$i]->{i2c_devnr}?!?\n"),
           next;
      if (&$alias_detect ($datahash->{isa_addr},\*FILE,
                          $new_misdetected_ref->[$i]->{i2c_addr})) {
        $new_misdetected_ref->[$i]->{isa_addr} = $datahash->{isa_addr};
        return $new_misdetected_ref->[$i]; 
      }
    }
  }

  # Detected alias detection:
  for ($i = 0; $i < @$new_detected_ref; $i++) {
    if (exists $new_detected_ref->[$i]->{i2c_addr} and
        not exists $new_detected_ref->[$i]->{isa_addr} and
        defined $alias_detect and
        $new_detected_ref->[$i]->{chipname} eq $datahash->{chipname}) {
      open(local *FILE, "$dev_i2c$new_detected_ref->[$i]->{i2c_devnr}") or
        print("Can't open $dev_i2c$new_detected_ref->[$i]->{i2c_devnr}?!?\n"),
        next;
      binmode FILE;
      i2c_set_slave_addr \*FILE,$new_detected_ref->[$i]->{i2c_addr} or
           print("Can't set I2C address for ",
                 "$dev_i2c$new_detected_ref->[$i]->{i2c_devnr}?!?\n"),
           next;
      if (&$alias_detect ($datahash->{isa_addr},\*FILE,
                          $new_detected_ref->[$i]->{i2c_addr})) {
        $new_detected_ref->[$i]->{isa_addr} = $datahash->{isa_addr};
        ($datahash) = splice (@$new_detected_ref, $i, 1);
        $isalias=1;
        last;
      }
    }
  }


  # Find out whether our new entry should go into the detected or the
  # misdetected list. We only compare main isa_addr here, of course.
  # (Khali 2004-05-12) If the driver is the same, the "misdetected"
  # entry is simply deleted; same we do for I2C chips.
  foreach $main_entry (@chips_detected) {
    $detected_ref = $main_entry->{detected};
    $misdetected_ref = $main_entry->{misdetected};
    for ($i = 0; $i < @{$main_entry->{detected}}; $i++) {
      if (exists $detected_ref->[$i]->{isa_addr} and
          $detected_ref->[$i]->{isa_addr} == $datahash->{isa_addr}) {
        if ($detected_ref->[$i]->{conf} >= $datahash->{conf}) {
          push @$new_misdetected_ref, $datahash
            unless $main_entry->{driver} eq $chipdriver;
        } else {
          push @$misdetected_ref,$detected_ref->[$i]
            unless $main_entry->{driver} eq $chipdriver;
          splice @$detected_ref, $i,1;
          push @$new_detected_ref, $datahash;
        }
        if ($isalias) {
          return $datahash;
        } else {
          return 0;
        }
      }
    }
  }

  # Not found? OK, put it in the detected list
  push @$new_detected_ref, $datahash;
  if ($isalias) {
    return $datahash;
  } else {
    return 0;
  }
}

# $_[0]: The number of the adapter to scan
# $_[1]: The name of the adapter, as appearing in /proc/bus/i2c
# $_[2]: The driver of the adapter
# @_[3]: Addresses not to scan (array reference)
sub scan_adapter
{
  my ($adapter_nr, $adapter_name, $adapter_driver, $not_to_scan) = @_;
  my ($funcs, $chip, $addr, $conf, @chips, $new_hash, $other_addr);

  # As we modify it, we need a copy
  my @not_to_scan = @$not_to_scan;

  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))) {
    print "Adapter cannot be probed, skipping.\n";
    return;
  }
  if (~$funcs & (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_READ_BYTE)) {
    print "Adapter doesn't support all probing functions.\n",
          "Some addresses won't be probed.\n";
  }

  # Now scan each address in turn
  foreach $addr (0x03..0x77) {
    # As the not_to_scan list is sorted, we can check it fast
    if (@not_to_scan and $not_to_scan[0] == $addr) {
      shift @not_to_scan;
      next;
    }

    if (!i2c_set_slave_addr(\*FILE, $addr)) {
      # If the address is busy, in Linux 2.6 we can find out which driver
      # is using it, and we assume it is the right one. In Linux 2.4 we
      # just give up and warn the user.
      my ($device, $driver);
      if (defined($sysfs_root)) {
        $device = sprintf("$sysfs_root/bus/i2c/devices/\%d-\%04x",
                             $adapter_nr, $addr);
        $driver = sysfs_device_driver($device);
      }
      if (defined($driver)) {
        $new_hash = {
          conf => 6, # Arbitrary confidence
          i2c_addr => $addr,
          chipname => sysfs_device_attribute($device, "name") || "unknown",
          i2c_adap => $adapter_name,
          i2c_driver => $adapter_driver,
          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";
        }    
      } else {
        printf("Client at address 0x%02x can not be probed - ".
               "unload all client drivers first!\n", $addr);
      }
      next;
    }

    next unless i2c_probe(\*FILE, $addr, $funcs);
    printf "Client found at address 0x%02x\n",$addr;

    foreach $chip (@chip_ids) {
      if (exists $chip->{i2c_addrs} and contains $addr, @{$chip->{i2c_addrs}}) {
        printf("\%-60s", sprintf("Probing for `\%s'... ", $chip->{name}));
        if (($conf,@chips) = &{$chip->{i2c_detect}} (\*FILE ,$addr)) {
          print "Success!\n",
                "    (confidence $conf, driver `$chip->{driver}')";
          if (@chips) {
            print ", other addresses:";
            @chips = sort @chips;
            foreach $other_addr (sort @chips) {
              printf(" 0x%02x",$other_addr);
            }
          }
          printf "\n";
          $new_hash = { conf => $conf,
                        i2c_addr => $addr,
                        chipname => $chip->{name},
                        i2c_adap => $adapter_name,
                        i2c_driver => $adapter_driver,
                        i2c_devnr => $adapter_nr,
                      };
          if (@chips) {
            my @chips_copy = @chips;
            $new_hash->{i2c_sub_addrs} = \@chips_copy;
          }
          add_i2c_to_chips_detected $chip->{driver}, $new_hash;
        } else {
          print "No\n";
        }
      }
    }
  }
}

sub scan_isa_bus
{
  my ($chip,$addr,$conf);
  foreach $chip (@chip_ids) {
    next if not exists $chip->{isa_addrs} or not exists $chip->{isa_detect};
    foreach $addr (@{$chip->{isa_addrs}}) {
      printf("\%-60s", sprintf("Probing for `\%s'\%s... ", $chip->{name},
                               $addr ? sprintf(" at 0x\%x", $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}
                     };
      $new_hash = add_isa_to_chips_detected $chip->{alias_detect},$chip->{driver},
                                            $new_hash;
      if ($new_hash) {
        printf "    Alias of the chip on I2C bus `%s', address 0x%04x\n",
                        $new_hash->{i2c_adap},$new_hash->{i2c_addr};
      }
    }
  }
}

use vars qw(%superio);

%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);
}

sub probe_superio($$$)
{
  my ($addrreg, $datareg, $chip) = @_;
  my ($val, $addr);

  printf "\%-60s",  "Found `$chip->{name}'";

  # Does it have hardware monitoring capabilities?
  if (!exists $chip->{driver}) {
    print "\n    (no information available)\n";
    return;
  }
  if ($chip->{driver} eq "not-a-sensor") {
    print "\n    (no hardware monitoring capabilities)\n";
    return;
  }

  # Switch to the sensor logical device
  outb($addrreg, $superio{logdevreg});
  outb($datareg, $chip->{logdev});

  # Check the activation register
  outb($addrreg, $superio{actreg});
  $val = inb($datareg);
  if (!($val & $superio{actmask})) {
    print "\n    (but not activated)\n";
    return;
  }

  # Get the IO base register
  outb($addrreg, $superio{basereg});
  $addr = inb($datareg);
  outb($addrreg, $superio{basereg} + 1);
  $addr = ($addr << 8) | inb($datareg);
  if ($addr == 0) {
    print "\n    (but no address specified)\n";
    return;
  }
  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->{alias_detect},$chip->{driver},
                                        $new_hash;
}

# The following are taken from the PNP ISA spec (so it's supposed
# to be common to all Super I/0 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
sub scan_superio
{
  my ($addrreg, $datareg) = @_;
  my ($val, $found);

  printf("Probing for Super-I/O at 0x\%x/0x\%x\n", $addrreg, $datareg);

  FAMILY: 
  foreach my $family (@superio_ids) {
    printf("\%-60s", "Trying family `$family->{family}'... ");
# write the password
    foreach $val (@{$family->{enter}->{$addrreg}}) {
      outb($addrreg, $val);
    }
# 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 FAMILY;
    }
    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})) {
        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};
    }

    exit_superio($addrreg, $datareg);
  }
}


##################
# CHIP DETECTION #
##################

# This routine allows you to select which chips are optionally added to the
# chip detection list. The most common use is to allow for different chip
# detection/drivers based on different linux kernels
# This routine follows the pattern of the SiS adapter special cases
sub chip_special_cases
{
	# Based on the kernel, add the appropriate chip structure to the
	# chip_ids detection list
	if (kernel_version_at_least(2, 6, 0)) {
		push @chip_ids, $chip_kern26_w83791d;
	} else {
		push @chip_ids, $chip_kern24_w83791d;
	}
}

# Each function returns a confidence value. The higher this value, the more
# sure we are about this chip. A Winbond W83781D, for example, will be
# detected as a LM78 too; but as the Winbond detection has a higher confidence 
# factor, you should identify it as a Winbond.

# 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.

# If there are devices which get confused if they are only read from, then
# this program will surely confuse them. But we guarantee never to write to
# any of these devices.


# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (7) if detected.
# Registers used: 0x58
sub mtp008_detect
{
  my ($file,$addr) = @_;
  return if (i2c_smbus_read_byte_data($file,0x58)) != 0xac;
  return (8);
}
  
# $_[0]: Chip to detect (0 = LM78, 1 = LM78-J, 2 = LM79)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (6) if detected.
# Registers used:
#   0x40: Configuration
#   0x48: Full I2C Address
#   0x49: Device ID
# Note that this function is always called through a closure, so the
# arguments are shifted by one place.
sub lm78_detect
{
  my $reg;
  my ($chip,$file,$addr) = @_;
  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 unless ($chip == 0 and ($reg == 0x00 or $reg == 0x20)) or
                    ($chip == 1 and $reg == 0x40) or
                    ($chip == 2 and ($reg & 0xfe) == 0xc0);
  return (6);
}

# $_[0]: Chip to detect (0 = LM78, 1 = LM78-J, 2 = LM79)
# $_[1]: Address
# Returns: undef if not detected, 6 if detected.
# Note: Only address 0x290 is scanned at this moment.
sub lm78_isa_detect
{
  my ($chip,$addr) = @_ ;
  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) & 0x7f;
  outb($addr + 5, ~$val & 0xff);
  if ((inb ($addr+5) & 0x7f) != (~ $val & 0x7f)) {
    outb($addr+5,$val);
    return;
  }
  my $readproc = sub { isa_read_byte $addr + 5, $addr + 6, @_ };
  return unless (&$readproc(0x40) & 0x80) == 0x00;
  my $reg = &$readproc(0x49);
  return unless ($chip == 0 and ($reg == 0x00 or $reg == 0x20)) or
                ($chip == 1 and $reg == 0x40) or
                ($chip == 2 and ($reg & 0xfe) == 0xc0);

  # Explicitely prevent misdetection of Winbond chips
  $reg = &$readproc(0x4f);
  return if $reg == 0xa3 || $reg == 0x5c;

  # Explicitely prevent misdetection of ITE chips
  $reg = &$readproc(0x58);
  return if $reg == 0x90;

  return 6;
}


# $_[0]: Chip to detect (0 = LM78, 1 = LM78-J, 2 = LM79)
# $_[1]: ISA address
# $_[2]: I2C file handle
# $_[3]: I2C address
sub lm78_alias_detect
{
  my ($chip,$isa_addr,$file,$i2c_addr) = @_;
  my $i;
  my $readproc = sub { isa_read_byte $isa_addr + 5, $isa_addr + 6, @_ };
  return 0 unless &$readproc(0x48) == $i2c_addr;
  for ($i = 0x2b; $i <= 0x3d; $i ++) {
    return 0 unless &$readproc($i) == i2c_smbus_read_byte_data($file,$i);
  }
  return 1;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 3 or 6 if detected;
#   6 means that the temperatures make sense;
#   3 means that the temperatures look strange;
# 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.
sub lm75_detect
{
  my $i;
  my ($file,$addr) = @_;
  my $cur = i2c_smbus_read_word_data($file,0x00);
  my $cur_varies = 0;
  my $conf = i2c_smbus_read_byte_data($file,0x01);

  my $hyst = i2c_smbus_read_word_data($file,0x02);
  return if i2c_smbus_read_word_data($file,0x04) != $hyst;
  return if i2c_smbus_read_word_data($file,0x05) != $hyst;
  return if i2c_smbus_read_word_data($file,0x06) != $hyst;
  return if i2c_smbus_read_word_data($file,0x07) != $hyst;

  my $os = i2c_smbus_read_word_data($file,0x03);
  return if i2c_smbus_read_word_data($file,0x04) != $os;
  return if i2c_smbus_read_word_data($file,0x05) != $os;
  return if i2c_smbus_read_word_data($file,0x06) != $os;
  return if i2c_smbus_read_word_data($file,0x07) != $os;

  for ($i = 0x00; $i < 0xff; $i += 8) {
    return if i2c_smbus_read_byte_data($file, $i + 0x01) != $conf;
    return if i2c_smbus_read_word_data($file, $i + 0x02) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x04) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x05) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x06) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x07) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x03) != $os;
    return if i2c_smbus_read_word_data($file, $i + 0x04) != $os;
    return if i2c_smbus_read_word_data($file, $i + 0x05) != $os;
    return if i2c_smbus_read_word_data($file, $i + 0x06) != $os;
    return if i2c_smbus_read_word_data($file, $i + 0x07) != $os;
	$cur_varies = 1
      if (! $cur_varies) and
        i2c_smbus_read_word_data($file, $i) != $cur;
  }

  # All registers hold the same value, obviously a misdetection
  return if (! $cur_varies) and $conf == ($cur & 0xff) and $cur == $hyst
    and $cur == $os;

  $cur = swap_bytes($cur);
  $hyst = swap_bytes($hyst);
  $os = swap_bytes($os);
  # Unused bits
  return if ($conf & 0xe0);

  $cur = $cur >> 8;
  $hyst = $hyst >> 8;
  $os = $os >> 8;
  # Most probable value ranges
  return 6 if $cur <= 100 and ($hyst >= 10 && $hyst <= 125)
    and ($os >= 20 && $os <= 127) and $hyst < $os;
  return 3;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 3 or 6 if detected;
#   6 means that the temperatures make sense;
#   3 means that the temperatures look strange;
# Registers used:
#   0x00: Temperature
#   0x01: Configuration
#   0x02: Hysteresis
#   0x03: Overtemperature Shutdown
#   0x04: Low limit
#   0x05: High limit
#   0x04-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.
sub lm77_detect
{
  my $i;
  my ($file,$addr) = @_;
  my $cur = i2c_smbus_read_word_data($file,0x00);
  my $cur_varies = 0;
  my $conf = i2c_smbus_read_byte_data($file,0x01);
  my $hyst = i2c_smbus_read_word_data($file,0x02);
  my $os = i2c_smbus_read_word_data($file,0x03);

  my $low = i2c_smbus_read_word_data($file,0x04);
  return if i2c_smbus_read_word_data($file,0x06) != $low;
  return if i2c_smbus_read_word_data($file,0x07) != $low;

  my $high = i2c_smbus_read_word_data($file,0x05);
  return if i2c_smbus_read_word_data($file,0x06) != $high;
  return if i2c_smbus_read_word_data($file,0x07) != $high;

  for ($i = 0x00; $i < 0xff; $i += 8) {
    return if i2c_smbus_read_byte_data($file, $i + 0x01) != $conf;
    return if i2c_smbus_read_word_data($file, $i + 0x02) != $hyst;
    return if i2c_smbus_read_word_data($file, $i + 0x03) != $os;
    return if i2c_smbus_read_word_data($file, $i + 0x04) != $low;
    return if i2c_smbus_read_word_data($file, $i + 0x06) != $low;
    return if i2c_smbus_read_word_data($file, $i + 0x07) != $low;
    return if i2c_smbus_read_word_data($file, $i + 0x05) != $high;
    return if i2c_smbus_read_word_data($file, $i + 0x06) != $high;
    return if i2c_smbus_read_word_data($file, $i + 0x07) != $high;
    $cur_varies = 1
      if (! $cur_varies) and
        i2c_smbus_read_word_data($file, $i) != $cur;
  }

  # All registers hold the same value, obviously a misdetection
  return if (! $cur_varies) and $conf == ($cur & 0xff) and $cur == $hyst
    and $cur == $os and $cur == $low and $cur == $high;

  $cur = swap_bytes($cur);
  $os = swap_bytes($os);
  $hyst = swap_bytes($hyst);
  $low = swap_bytes($low);
  $high = swap_bytes($high);
  # Unused bits
  return if ($conf & 0xe0)
    or (($cur >> 12) != 0 && ($cur >> 12) != 0xf)
    or (($hyst >> 12) != 0 && ($hyst >> 12) != 0xf)
    or (($os >> 12) != 0 && ($os >> 12) != 0xf)
    or (($low >> 12) != 0 && ($low >> 12) != 0xf)
    or (($high >> 12) != 0 && ($high >> 12) != 0xf);

  $cur /= 16;
  $hyst /= 16;
  $os /= 16;
  $high /= 16;
  $low /= 16;

  # Most probable value ranges
  return 6 if $cur <= 100 and $hyst <= 40
    and ($os >= 20 && $os <= 127) and ($high >= 20 && $high <= 127);
  return 3;
}

# $_[0]: Chip to detect (0 = LM92, 1 = LM76, 2 = MAX6633/MAX6634/MAX6635)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 2 or 4 if detected;
# 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.
sub lm92_detect
{
  my ($chip, $file, $addr) = @_;

  my $conf = i2c_smbus_read_byte_data($file, 0x01);
  my $hyst = i2c_smbus_read_word_data($file, 0x02);
  my $crit = i2c_smbus_read_word_data($file, 0x03);
  my $low = i2c_smbus_read_word_data($file, 0x04);
  my $high = i2c_smbus_read_word_data($file, 0x05);

  return if $chip == 0
        and i2c_smbus_read_word_data($file, 0x07) != 0x0180;
  
  return if ($chip == 0 || $chip == 1)
        and ($conf & 0xE0);

  for (my $i = 0; $i < 8; $i++) {
    return if i2c_smbus_read_byte_data($file, $i*16+0x01) != $conf;
    return if i2c_smbus_read_word_data($file, $i*16+0x02) != $hyst;
    return if i2c_smbus_read_word_data($file, $i*16+0x03) != $crit;
    return if i2c_smbus_read_word_data($file, $i*16+0x04) != $low;
    return if i2c_smbus_read_word_data($file, $i*16+0x05) != $high;
  }
  
  foreach my $temp ($hyst, $crit, $low, $high) {
    return if $chip == 2 and ($temp & 0x7F00);
    return if $chip != 2 and ($temp & 0x0700);
  }

  return 4 if $chip == 0;
  return 2;
}
  
# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 3 if detected
# Registers used:
#   0xAA: Temperature
#   0xA1: High limit
#   0xA2: Low limit
#   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).
# Temperature checkings will hopefully prevent LM75 or other chips from
# being detected as a DS1621.
sub ds1621_detect
{
  my $i;
  my ($file,$addr) = @_;
  my $temp = i2c_smbus_read_word_data($file,0xAA);
  my $high = i2c_smbus_read_word_data($file,0xA1);
  my $low = i2c_smbus_read_word_data($file,0xA2);
  return if ($temp | $high | $low) & 0x7F00;
  my $conf = i2c_smbus_read_byte_data($file,0xAC);
  return if ($temp == 0 && $high == 0 && $low == 0 && $conf == 0);
  return 3 if ($conf & 0x10) == 0x00;
  return;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 1 to 3 if detected.
# 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 ($i,$reg);
  my ($file,$addr) = @_;

  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 wether limits are in the correct order
  # (min<max for voltages, hyst<max for temperature). Since it is still
  # possible that the chip is an LM80 with limits not properly set,
  # a few "errors" are tolerated.
  my $confidence = 0;
  for ($i = 0x2a; $i <= 0x3a; $i++) {
    $confidence++
      if i2c_smbus_read_byte_data($file,$i) < i2c_smbus_read_byte_data($file,$i+1);
  }
  # hot temp<OS temp
  $confidence++
    if i2c_smbus_read_byte_data($file,0x38) < i2c_smbus_read_byte_data($file,0x3a);

  # Negative temperature limits are unlikely.
  for ($i = 0x3a; $i <= 0x3d; $i++) {
    $confidence++ if (i2c_smbus_read_byte_data($file,$i) & 0x80) == 0;
  }

  # $confidence is between 0 and 14
  $confidence = ($confidence >> 1) - 4;
  # $confidence is now between -4 and 3

  return unless $confidence > 0;

  return $confidence;
}

# $_[0]: Chip to detect
#   (0 = LM82/LM83)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 4 to 8 if detected.
# 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 ($chip, $file) = @_;
  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;
}

# $_[0]: Chip to detect
#   (0 = LM90, 1=LM89/LM99, 2=LM86, 3=ADM1032, 4=MAX6657/MAX6658/MAX6659,
#    5 = ADT7461)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 4, 6 or 8 if detected.
#   The Maxim chips have a low confidence value (4)
#   because they don't have a die revision register.
# Registers used:
#   0x03: Configuration
#   0x04: Conversion rate
#   0xfe: Manufacturer ID
#   0xff: Chip ID / die revision
sub lm90_detect
{
  my ($chip, $file, $addr) = @_;
  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 8 if ($cid & 0xf0) == 0x40; # ADM1032
  }
  if ($chip == 4) {
    return if ($conf & 0x1f) != ($mid & 0x0f); # No low nibble,
                                               # returns previous low nibble
    return if $rate > 0x09;
    return if $mid != 0x4d;     # Maxim
    return if $cid != 0x4d;     # No register, returns previous value
    return 4;
  }
  if ($chip == 5) {
    return if ($conf & 0x1b) != 0;
    return if $rate > 0x0a;
    return if $mid != 0x41;     # Analog Devices
    return 8 if $cid == 0x61;   # ADT7461
  }
  return;
}

# $_[0]: Chip to detect
#   (1 = LM63, 2 = F75363SG)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address (unused)
# Returns: undef if not detected, 8 if detected.
# 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)
#   0x03-0x0e: Mirrored registers (five pairs)
sub lm63_detect
{
  my ($chip, $file, $addr) = @_;

  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;  
  }

  # For compatibility with the LM86, some registers are mirrored
  # to alternative locations
  return if $conf != i2c_smbus_read_byte_data($file, 0x09);
  foreach my $i (0x04, 0x05, 0x07, 0x08) {
    return if i2c_smbus_read_byte_data($file, $i)
           != i2c_smbus_read_byte_data($file, $i+6);
  }

  return 8;
}

# $_[0]: Chip to detect
#   (0 = ADM1029)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 3 to 9 if detected.
# Registers used:
#   0x02, 0x03: Fan support
#   0x05: GPIO config
#   0x07, 0x08, 0x09: Fan config
#   0x0d: Manufacturer ID
#   0x0e: Chip ID / die revision
sub adm1029_detect
{
  my ($chip, $file, $addr) = @_;
  my $mid = i2c_smbus_read_byte_data($file, 0x0d);
  my $cid = i2c_smbus_read_byte_data($file, 0x0e);
  my $fansc = i2c_smbus_read_byte_data($file, 0x02);
  my $fanss = i2c_smbus_read_byte_data($file, 0x03);
  my $gpio = i2c_smbus_read_byte_data($file, 0x05);
  my $fanas = i2c_smbus_read_byte_data($file, 0x07);
  my $fanhps = i2c_smbus_read_byte_data($file, 0x08);
  my $fanfs = i2c_smbus_read_byte_data($file, 0x09);
  my $confidence = 3;

  if ($chip == 0) {
    return if $mid != 0x41;     # Analog Devices
    $confidence++ if ($cid & 0xF0) == 0x00; # ADM1029
    $confidence+=2 if ($fansc & 0xFC) == 0x00
                   && ($fanss & 0xFC) == 0x00;
    $confidence+=2 if ($fanas & 0xFC) == 0x00
                   && ($fanhps & 0xFC) == 0x00
                   && ($fanfs & 0xFC) == 0x00;
    $confidence++ if ($gpio & 0x80) == 0x00;
    return $confidence;
  }
  return;
}

# $_[0]: Chip to detect
#   (0 = ADM1030, 1=ADM1031)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 3 to 7 (ADM1031) or 9 (ADM1030)
#          if detected.
# 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 ($chip, $file, $addr) = @_;
  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;
  }
  return;
}

# $_[0]: Chip to detect
#   (0 = ADM1033, 1 = ADM1034)
# $_[1]: A reference to the file descriptor to access this chip.
# $_[2]: Address (unused)
# Returns: undef if not detected, 4 or 6 if detected.
# Registers used:
#   0x3d: Chip ID
#   0x3e: Manufacturer ID
#   0x3f: Die revision
sub adm1034_detect
{
  my ($chip, $file, $addr) = @_;
  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;
  }
  return
}

# $_[0]: Chip to detect
#   (0 = ADT7467/ADT7468, 1 = ADT7476, 2 = ADT7462, 3 = ADT7466,
#    4 = ADT7470)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 5 or 7 if detected.
# Registers used:
#   0x3d: Chip ID
#   0x3e: Manufacturer ID
#   0x3f: Die revision
sub adt7467_detect
{
  my ($chip, $file, $addr) = @_;
  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 != 0x68;     # ADT7467
    return if ($drev & 0xf0) != 0x70;
    return 7 if ($drev == 0x71 || $drev == 0x72);
    return 5;
  }
  if ($chip == 1) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x76;     # ADT7476
    return if ($drev & 0xf0) != 0x60;
    return 7 if ($drev == 0x69);
    return 5;
  }
  if ($chip == 2) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x62;     # ADT7462
    return if ($drev & 0xf0) != 0x00;
    return 7 if ($drev == 0x04);
    return 5;
  }
  if ($chip == 3) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x66;     # ADT7466
    return if ($drev & 0xf0) != 0x00;
    return 7 if ($drev == 0x02);
    return 5;
  }
  if ($chip == 4) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x70;     # ADT7470
    return if ($drev & 0xf0) != 0x00;
    return 7 if ($drev == 0x00);
    return 5;
  }
  return
}

# $_[0]: Chip to detect
#   (0 = ADT7473, 1 = ADT7475)
# $_[1]: A reference to the file descriptor to access this chip.
# $_[2]: Address (unused)
# Returns: undef if not detected, 5 if detected.
# Registers used:
#   0x3d: Chip ID
#   0x3e: Manufacturer ID
sub adt7473_detect
{
  my ($chip, $file, $addr) = @_;
  my $mid = i2c_smbus_read_byte_data($file, 0x3e);
  my $cid = i2c_smbus_read_byte_data($file, 0x3d);

  if ($chip == 0) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x73;     # ADT7473
    return 5;
  }
  if ($chip == 1) {
    return if $mid != 0x41;     # Analog Devices
    return if $cid != 0x75;     # ADT7475
    return 5;
  }
  return
}

# $_[0]: Vendor to check for
#   (0x01 = National Semi, 0x41 = Analog Dev, 0x5c = SMSC)
# $_[1]: A reference to the file descriptor to access this chip.
# #_[2]: Base address.
# Returns: undef if not detected, (7) or (8) if detected.
# Registers used: 0x3e == Vendor register.
#                 0x3d == Device ID register (Analog Devices only).
#		  0x3f == Version/Stepping register.
# Constants used: 0x01 == National Semiconductor Vendor Id.
#                 0x41 == Analog Devices Vendor Id.
#                 0x5c == SMSC Vendor Id.
#		  0x60 == Version number. The lower 4 stepping
#			  bits are masked and ignored.
sub lm85_detect
{
  my ($vendor,$file,$addr) = @_;
  return if (i2c_smbus_read_byte_data($file,0x3e)) != $vendor ;
  return if (i2c_smbus_read_byte_data($file,0x3f) & 0xf0) != 0x60;

  if ($vendor == 0x41) # Analog Devices
  {
    return if i2c_smbus_read_byte_data($file, 0x3d) != 0x27;
    return (8);
  }

  return (7);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (7) if detected.
# Registers used: 0x3E, 0x3F
#        Assume lower 2 bits of reg 0x3F are for revisions.
sub lm87_detect
{
  my ($file,$addr) = @_;
  return if (i2c_smbus_read_byte_data($file,0x3e)) != 0x02;
  return if (i2c_smbus_read_byte_data($file,0x3f) & 0xfc) != 0x04;
  return (7);
}
  
# $_[0]: 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)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (8,addr1,addr2) if detected, but only
#          if the LM75 chip emulation is enabled.
# 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: Detection overrules a previous LM78 detection
# 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 ($reg1,$reg2,@res);
  my ($chip,$file,$addr) = @_;

  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 != 0xa1;
  return if $chip == 10 and  $reg1 != 0xa2;
  $reg1 = i2c_smbus_read_byte_data($file,0x4a);
  # Default address is 0x2d
  @res = ($addr != 0x2d) ? (7) : (8);
  return @res if $chip == 9; # No subclients
  push @res, ($reg1 & 0x07) + 0x48 unless $reg1 & 0x08;
  push @res, (($reg1 & 0x70) >> 4) + 0x48 unless ($reg1 & 0x80 or $chip == 2);
  return @res;
}
  
# $_[0]: Chip to detect (0 = W83793)
# $_[1]: A reference to the file descriptor to access this chip.
# $_[2]: Address
# Returns: undef if not detected
#          6 if detected and bank different from 0
#          (8,addr1,addr2) if detected, band is 0 and LM75 chip emulation
#          is enabled
# 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 ($bank, $reg, @res);
  my ($chip, $file, $addr) = @_;

  $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 $chip == 0 and $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;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 3 if detected
# 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;
}

# $_[0]: Chip to detect (0 = ASM58, 1 = AS2K129R, 2 = ???)
# $_[1]: A reference to the file descriptor to access this chip
# $_[2]: Address (unused)
# Returns: undef if not detected, 5 if detected
# 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 ($vid,$dev);
  my ($chip,$file,$addr) = @_;

  $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 if ($chip == 0) and ($dev != 0x56 || $vid != 0x9436);
  return if ($chip == 1) and ($dev != 0x56 || $vid != 0x9406);
  return if ($chip == 2) and ($dev != 0x10 || $vid != 0x5ca3);

  return 5;
}

# $_[0]: Chip to detect (0 = W83781D, 1 = W83782D, 3 = W83627HF,
#                        9 = W83627EHF 10 = W83627DHG)
# $_[1]: ISA address
# $_[2]: I2C file handle
# $_[3]: I2C address
sub w83781d_alias_detect
{
  my ($chip,$isa_addr,$file,$i2c_addr) = @_;
  my $i;
  my $readproc = sub { isa_read_byte $isa_addr + 5, $isa_addr + 6, @_ };
  return 0 unless &$readproc(0x48) == $i2c_addr;
  for ($i = 0x2b; $i <= 0x3d; $i ++) {
    return 0 unless &$readproc($i) == i2c_smbus_read_byte_data($file,$i);
  }
  return 1;
}

# $_[0]: Chip to detect (0 = W83781D, 1 = W83782D, 3 = W83627HF)
# $_[1]: Address
# Returns: undef if not detected, (8) if detected.
sub w83781d_isa_detect
{
  my ($chip,$addr) = @_ ;
  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) & 0x7f;
  outb($addr+5, ~$val & 0xff);
  if ((inb ($addr+5) & 0x7f) != (~ $val & 0x7f)) {
    outb($addr+5,$val);
    return;
  }

  my $read_proc = sub { isa_read_byte $addr + 5, $addr + 6, @_ };
  $reg1 = &$read_proc(0x4e);
  $reg2 = &$read_proc(0x4f);
  return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0xa3) or 
                (($reg1 & 0x80) == 0x80 and $reg2 == 0x5c);
  return unless ($reg1 & 0x07) == 0x00;
  $reg1 = &$read_proc(0x58);
  return if $chip == 0 and  ($reg1 & 0xfe) != 0x10;
  return if $chip == 1 and  ($reg1 & 0xfe) != 0x30;
  return if $chip == 3 and  ($reg1 & 0xfe) != 0x20;

  return 8;
}

# $_[0]: Chip to detect (0 = Revision 0x00, 1 = Revision 0x80)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (6) if detected.
# Registers used:
#   0x00: Device ID
#   0x01: Revision ID
#   0x03: Configuration 
# Mediocre detection
sub gl518sm_detect
{
  my $reg;
  my ($chip,$file,$addr) = @_;
  return unless i2c_smbus_read_byte_data($file,0x00) == 0x80;
  return unless (i2c_smbus_read_byte_data($file,0x03) & 0x80) == 0x00;
  $reg = i2c_smbus_read_byte_data($file,0x01);
  return unless ($chip == 0 and $reg == 0x00) or
                ($chip == 1 and $reg == 0x80);
  return (6);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (5) if detected.
# Registers used:
#   0x00: Device ID
#   0x01: Revision ID
#   0x03: Configuration 
# Mediocre detection
sub gl520sm_detect
{
  my ($file,$addr) = @_;
  return unless i2c_smbus_read_byte_data($file,0x00) == 0x20;
  return unless (i2c_smbus_read_byte_data($file,0x03) & 0x80) == 0x00;
  # The line below must be better checked before I dare to use it.
  # return unless i2c_smbus_read_byte_data($file,0x01) == 0x00;
  return (5);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (5) if detected.
# Registers used:
#   0x00: Device ID
# Mediocre detection
sub gl525sm_detect
{
  my ($file,$addr) = @_;
  return unless i2c_smbus_read_byte_data($file,0x00) == 0x25;
  return (5);
}

# $_[0]: Chip to detect (0 = ADM9240, 1 = DS1780, 2 = LM81)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (7) if detected.
# Registers used:
#   0x3e: Company ID
#   0x40: Configuration
#   0x48: Full I2C Address
# Note: Detection overrules a previous LM78 detection
sub adm9240_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;
  $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);
}

# $_[0]: Chip to detect (0 = ADM1022, 1 = THMC50, 2 = ADM1028)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x3e: Company ID
#   0x3f: Revision
#   0x40: Configuration
# Note: Detection overrules a previous LM78 or ADM9240 detection
sub adm1022_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;
  $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);
  return unless (i2c_smbus_read_byte_data($file,0x40) & 0x80) == 0x00;
  $reg = i2c_smbus_read_byte_data($file, 0x3f);
  return unless ($reg & 0xc0) == 0xc0;
  return if $chip == 0 and ($reg & 0xc0) != 0xc0;
  return if $chip == 2 and ($reg & 0xc0) == 0xc0;
  return (8);
}

# $_[0]: Chip to detect (0 = ADM1025, 1 = NE1619)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x3e: Company ID
#   0x3f: Revision
#   0x40: Configuration
#   0x41: Status 1
#   0x42: Status 2
# Note: Detection overrules a previous LM78 or ADM9240 detection
sub adm1025_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;

  $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);
}

# $_[0]: Chip to detect (0 = ADM1026)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x16: Company ID
#   0x17: Revision
sub adm1026_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;
  $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);
}

# $_[0]: Chip to detect (0 = ADM1024)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x3e: Company ID
#   0x3f: Revision
#   0x40: Configuration
sub adm1024_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;
  $reg = i2c_smbus_read_byte_data($file,0x3e);
  return unless ($reg == 0x41);
  return unless (i2c_smbus_read_byte_data($file,0x40) & 0x80) == 0x00;
  return unless (i2c_smbus_read_byte_data($file,0x3f) & 0xf0) == 0x10;
  return (8);
}

# $_[0]: Chip to detect
#   (0 = ADM1021, 1 = ADM1021A/ADM1023, 2 = MAX1617, 3 = MAX1617A, 4 = THMC10,
#    5 = LM84, 6 = GL523, 7 = MC1066)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 3 if simply detected, 5 if detected and
#          manufacturer ID matches, 7 if detected and manufacturer ID and
#          revision match
# 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 ($chip, $file, $addr) = @_;
  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 != 5) # LM84 doesn't have low limits
    {
      $llo-=256 if ($llo & 0x80);
      $rlo-=256 if ($rlo & 0x80);
      return if ($llo > $lhi) or ($rlo > $rhi);
    }
  }

  return 3 if ($chip == 2) or ($chip == 5);
  return 7 if $chip <= 3;
  return 5;
}

# $_[0]: Chip to detect
#   (0 = MAX1619)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 7 if detected
# Registers used:
#   0xfe: Company ID
#   0xff: Device ID
#   0x02: Status
#   0x03: Configuration
#   0x04: Conversion rate
sub max1619_detect
{
  my ($chip, $file, $addr) = @_;
  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
    or $dev_id != 0x04
    or ($conf & 0x03)
    or ($status & 0x61)
    or $convrate >= 8;

  return 7;
}

# $_[0]: A reference to the file descriptor to access this chip.
# $_[1]: Address (unused)
# Returns: undef if not detected, 6 if detected.
# Registers used:
#   0x28: User ID
#   0x29: User ID2
#   0x2A: Version ID

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;
}

# $_[0]: Chip to detect (0 = IT8712F)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 7 or 8 if detected (tops LM78).
# Registers used:
#   0x00: Configuration
#   0x48: Full I2C Address
#   0x58: Mfr ID
#   0x5b: Device ID (not on IT8705)
# Note that this function is always called through a closure, so the
# arguments are shifted by one place.
sub ite_detect
{
  my $reg;
  my ($chip,$file,$addr) = @_;
  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 $chip == 0 and i2c_smbus_read_byte_data($file,0x5b) != 0x12;
  return (7 + ($addr == 0x2d));
}


# $_[0]: Chip to detect (0 = IT8712F)
# $_[1]: ISA address
# $_[2]: I2C file handle
# $_[3]: I2C address
sub ite_alias_detect
{
  my ($chip,$isa_addr,$file,$i2c_addr) = @_;
  my $i;
  my $readproc = sub { isa_read_byte $isa_addr + 5, $isa_addr + 6, @_ };
  return 0 unless &$readproc(0x48) == $i2c_addr;
  for ($i = 0x30; $i <= 0x45; $i++) {
    return 0 unless &$readproc($i) == i2c_smbus_read_byte_data($file,$i);
  }
  return 1;
}

# $_[0]: Chip to detect (0 = SPD EEPROM, 1 = Sony Vaio EEPROM,
#                        2 = SPD EEPROM with Software Write Protect)
# $_[1]: A reference to the file descriptor to access this chip
# $_[2]: Address
# Returns: 8 for a memory eeprom (9 if write-protect register found),
#          4 to 9 for a Sony Vaio eeprom,
#          1 for an unknown eeprom (2 if write-protect register found)
# Registers used:
#   0-63: SPD Data and Checksum
#   0x80-0x83: Sony Vaio Data ("PCG-")
#   0xe2, 0xe5, 0xe8, 0xeb, Oxee: Sony Vaio Timestamp constant bytes.
#   0x1a-0x1c: Sony Vaio MAC address
# This detection function is a bit tricky; this is to workaround
# wrong misdetection messages that would else arise.
sub eeprom_detect
{
  my ($chip,$file,$addr) = @_;
  my $checksum = 0;

  # Check the checksum for validity (works for most DIMMs and RIMMs)
  if ($chip != 1) {
	  for (my $i = 0; $i <= 62; $i ++) {
	    $checksum += i2c_smbus_read_byte_data($file,$i);
	  }
	  $checksum &= 255;
	  $checksum -= i2c_smbus_read_byte_data($file,63);
  }     
  if ($chip == 0) {
	if($checksum == 0) {
		return 8;
	} else {
		return 1;
	}
  }	
  if ($chip == 2) {
	# check for 'shadow' write-protect register at 0x30-0x37
	# could be dangerous
	i2c_set_slave_addr($file,$addr - 0x20);
	if(i2c_smbus_write_quick($file, SMBUS_WRITE) >= 0 &&
	   i2c_smbus_read_byte_data($file,0x80) == -1) {
		i2c_set_slave_addr($file,$addr);
		if($checksum == 0) {
			return (9, $addr - 0x20);
		} else {
			return (2, $addr - 0x20);
		}
	}
	i2c_set_slave_addr($file,$addr);
	return;
  }

  # Look for a Sony Vaio EEPROM ($chip == 1)
  my $vaioconf = 1;
  $vaioconf += 4
    if i2c_smbus_read_byte_data($file,0x80) == 0x50
    && i2c_smbus_read_byte_data($file,0x81) == 0x43
    && i2c_smbus_read_byte_data($file,0x82) == 0x47
    && i2c_smbus_read_byte_data($file,0x83) == 0x2d;
  $vaioconf += 5
    if i2c_smbus_read_byte_data($file,0xe2) == 0x2f
    && i2c_smbus_read_byte_data($file,0xe5) == 0x2f
    && i2c_smbus_read_byte_data($file,0xe8) == 0x20
    && i2c_smbus_read_byte_data($file,0xeb) == 0x3a
    && i2c_smbus_read_byte_data($file,0xee) == 0x3a;
  $vaioconf += 3
    if i2c_smbus_read_byte_data($file,0x1a) == 0x08
    && i2c_smbus_read_byte_data($file,0x1b) == 0x00
    && i2c_smbus_read_byte_data($file,0x1c) == 0x46;
  $vaioconf = 9
    if $vaioconf > 9;

  if ($vaioconf > 1) {
    return $vaioconf;
  }
  return;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (1) if detected.
# Registers used:
#   0x00..0x07: DDC signature
#   0x08..0x7E: checksumed area
#   0x7F:       checksum
sub ddcmonitor_detect
{
  my ($file,$addr) = @_;
  my $i;

  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;

  # Check the checksum for validity.
  my $checksum = 0;
  for ($i = 0; $i <= 127; $i = $i + 1) {
    $checksum = $checksum + i2c_smbus_read_byte_data($file,$i);
  }
  $checksum=$checksum & 255;
  if ($checksum != 0) {
    # I have one such monitor...
    return (2,$addr+1,$addr+2,$addr+3,$addr+4,$addr+5,$addr+6,$addr+7);
  }
  return (8,$addr+1,$addr+2,$addr+3,$addr+4,$addr+5,$addr+6,$addr+7);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x00-0x02: Identification ('P','E','G' -> Pegasus ? :-)
sub fscpos_detect
{
  my ($file,$addr) = @_;
  # check the first 3 registers
  if (i2c_smbus_read_byte_data($file,0x00) != 0x50) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x01) != 0x45) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x02) != 0x47) {
  	return;
  }
  return (8);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x00-0x02: Identification ('S','C','Y')
sub fscscy_detect
{
  my ($file,$addr) = @_;
  # check the first 3 registers
  if (i2c_smbus_read_byte_data($file,0x00) != 0x53) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x01) != 0x43) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x02) != 0x59) {
  	return;
  }
  return (8);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (8) if detected.
# Registers used:
#   0x00-0x02: Identification ('H','E','R')
sub fscher_detect
{
  my ($file,$addr) = @_;
  # check the first 3 registers
  if (i2c_smbus_read_byte_data($file,0x00) != 0x48) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x01) != 0x45) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x02) != 0x52) {
  	return;
  }
  return (8);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address (unused)
# Returns: undef if not detected, 5 if detected.
# Registers used:
#   0x3E: Manufacturer ID
#   0x3F: Version/Stepping
sub lm93_detect
{
  my $file = shift;
  return unless i2c_smbus_read_byte_data($file, 0x3E) == 0x01
            and i2c_smbus_read_byte_data($file, 0x3F) == 0x73;
  return 5;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (7) if detected.
# 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);
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 5 or 6 if detected.
# 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);
}

# $_[0]: 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)
# $_[1]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[2]: Address (unused)
# Returns: undef if not detected, 7 if detected.
# Registers used:
#   0x5A-0x5B: Chip ID
#   0x5D-0x5E: Vendor ID
sub fintek_detect
{
  my ($chip, $file, $addr) = @_;
  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;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 4 or 7 if detected
# Detection is based on the fact that the SAA1064 has only one readable
# register, and thus ignores the read address. This register can have value
# 0x80 (first read since power-up) or 0x00.
sub saa1064_detect
{
	my ($file,$addr) = @_;
	my $status = i2c_smbus_read_byte_data ($file, 0x00);

	return if ($status & 0x7f) != 0x00;

	for (my $i=0 ; $i<256; $i++) {
		return if i2c_smbus_read_byte_data ($file, $i) != 0x00;
	}

	return 7
		if $status == 0x80;
	return 4;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 1 if detected
# Detection is rather difficult, since the PCA9540 has a single register.
# Fortunately, no other device is known to live at this address.
sub pca9540_detect
{
	my ($file, $addr) = @_;
	my $reg = i2c_smbus_read_byte($file);

	return if ($reg & 0xfa);
	return if $reg != i2c_smbus_read_byte($file);
	return if $reg != i2c_smbus_read_byte($file);
	return if $reg != i2c_smbus_read_byte($file);

	return 1;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We assume an i2c_set_slave_addr was already done.
# $_[1]: Address (unused)
# Returns: undef if not detected, 1 if detected
# Detection is rather difficult, since the PCA9556 only has 4 registers
# and no unused bit. We use the fact that the registers cycle over
# 4 addresses boundaries, and the logic rules between registers.
sub pca9556_detect
{
	my ($file, $addr) = @_;
	my $input = i2c_smbus_read_byte_data($file, 0x00);
	my $output = i2c_smbus_read_byte_data($file, 0x01);
	my $invert = i2c_smbus_read_byte_data($file, 0x02);
	my $config = i2c_smbus_read_byte_data($file, 0x03);

	# Pins configured for output (config = 0) must obey the following
	# rule: input = output ^ invert

	return unless ($input & ~$config) == (($output ^ $invert) & ~$config);

	for (my $i = 5; $i < 254 ; $i+=4) {
		return unless i2c_smbus_read_byte_data($file, $i) == $output;
		return unless i2c_smbus_read_byte_data($file, $i+1) == $invert;
		return unless i2c_smbus_read_byte_data($file, $i+2) == $config;
	}

	return 1;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, 3 if detected
sub max6900_detect
{
	my ($file,$addr) = @_;
	my $reg;
	
	# SEC
	$reg = i2c_smbus_read_byte_data ($file, 0x81);
	return if
		($reg & 0xF0) > 0x50 or
		($reg & 0x0F) > 9;

	# MIN
	$reg = i2c_smbus_read_byte_data ($file, 0x83);
	return if
		($reg & 0xF0) > 0x50 or
		($reg & 0x0F) > 9;

	# HR
	$reg = i2c_smbus_read_byte_data ($file, 0x85);
	return if
		($reg & 0x40) != 0x00 or
		($reg & 0x0F) > 9;

	# DATE
	$reg = i2c_smbus_read_byte_data ($file, 0x87);
	return if
		$reg == 0x00 or
		($reg & 0xF0) > 0x30 or
		($reg & 0x0F) > 9;

	# MONTH
	$reg = i2c_smbus_read_byte_data ($file, 0x89);
	return if
		$reg == 0x00 or
		($reg & 0xF0) > 0x10 or
		($reg & 0x0F) > 9;

	# DAY
	$reg = i2c_smbus_read_byte_data ($file, 0x8B);
	return if
		$reg == 0 or
		$reg > 7;

	# YEAR
	$reg = i2c_smbus_read_byte_data ($file, 0x8D);
	return if
		($reg & 0xF0) > 0x90 or
		($reg & 0x0F) > 9;

	# CONTROL
	$reg = i2c_smbus_read_byte_data ($file, 0x8F);
	return if
		($reg & 0x7F) != 0x00;

	# CENTURY
	$reg = i2c_smbus_read_byte_data ($file, 0x93);
	return if
		($reg & 0xF0) > 0x90 or
		($reg & 0x0F) > 9;

	return 3;
}

# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: 1
# This is a placeholder so we get a report if any device responds
# to the SMBus Device Default Address (0x61), which is used for
# ARP in SMBus 2.0.
sub arp_detect
{
  return (1);
}

# This checks for non-FFFF values for SpecInfo and Status.
# The address (0x09) is specified by the SMBus standard so it's likely
# that this really is a smart battery charger.
# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: 5
sub smartbatt_chgr_detect
{
  my ($file,$addr) = @_;
  # check some registers
  if (i2c_smbus_read_word_data($file,0x11) == 0xffff) {
  	return;
  }
  if (i2c_smbus_read_word_data($file,0x13) == 0xffff) {
  	return;
  }
  return (5);
}

# This checks for non-FFFF values for State and Info.
# The address (0x0a) is specified by the SMBus standard so it's likely
# that this really is a smart battery manager/selector.
# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: 5
sub smartbatt_mgr_detect
{
  my ($file,$addr) = @_;
  # check some registers
  if (i2c_smbus_read_word_data($file,0x01) == 0xffff) {
  	return;
  }
  if (i2c_smbus_read_word_data($file,0x04) == 0xffff) {
  	return;
  }
  return (5);
}

# 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.
# $_[0]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: 5
sub smartbatt_detect
{
  my ($file,$addr) = @_;
  # check some registers
  if (i2c_smbus_read_word_data($file,0x08) == 0xffff) {
  	return;
  }
  if (i2c_smbus_read_word_data($file,0x09) == 0xffff) {
  	return;
  }
  if (i2c_smbus_read_word_data($file,0x0a) == 0xffff) {
  	return;
  }
  return (5);
}

# Returns: 4
# These are simple detectors that only look for a register at the
# standard location. No writes are performed.
# For KCS, use the STATUS register. For SMIC, use the FLAGS register.
sub ipmi_kcs_detect
{
  return if inb (0x0ca3) == 0xff;
  return (4);
}

sub ipmi_smic_detect
{
  return if inb (0x0cab) == 0xff;
  return (4);
}

# $_[0]: Chip to detect (0 = W83L784R/AR, 1 = W83L785R)
# $_[1]: A reference to the file descriptor to access this chip.
# $_[2]: Address
# Returns: undef if not detected, 6 or 8 if detected
# Registers used:
#   0x40: Configuration
#   0x4a: Full I2C Address (not W83L785R)
#   0x4b: I2C addresses of emulated LM75 chips (not W83L785R)
#   0x4c: Winbond Vendor ID (Low Byte)
#   0x4d: Winbond Vendor ID (High Byte)
#   0x4e: Chip ID
# Note that this function is always called through a closure, so the
# arguments are shifted by one place.
sub w83l784r_detect
{
  my ($reg,@res);
  my ($chip,$file,$addr) = @_;

  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;
  return if $chip == 0
    and i2c_smbus_read_byte_data($file,0x4e) != 0x50;
  return if $chip == 1
    and i2c_smbus_read_byte_data($file,0x4e) != 0x60;

  $reg = i2c_smbus_read_byte_data($file,0x4b);

  return 6 if $chip == 1; # W83L785R doesn't have subclients
  
  @res = (8);
  push @res, ($reg & 0x07) + 0x48 unless $reg & 0x08 ;
  push @res, (($reg & 0x70) >> 4) + 0x48 unless $reg & 0x80;
  return @res;
}

# $_[0]: Chip to detect (0 = W83L785TS-S)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 8 if detected
# Registers used:
#   0x4C-4E: Mfr and Chip ID
# Note that this function is always called through a closure, so the
# arguments are shifted by one place.
sub w83l785ts_detect
{
  my ($chip,$file,$addr) = @_;
  return unless i2c_smbus_read_byte_data($file,0x4c) == 0xa3;
  return unless i2c_smbus_read_byte_data($file,0x4d) == 0x5c;
  return unless i2c_smbus_read_byte_data($file,0x4e) == 0x70;
  return (8);
}

# $_[0]: Chip to detect. Always zero for now, but available for future use
#        if somebody finds a way to distinguish MAX6650 and MAX6651.
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
# Returns: undef if not detected, 4 if detected.
#
# 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 ($chip, $file) = @_;

  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 4;
}

# $_[0]: Chip to detect (0 = VT1211)
# $_[1]: A reference to the file descriptor to access this chip.
#        We may assume an i2c_set_slave_addr was already done.
# $_[2]: Address
#
# 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 ($chip,$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;
}

# $_[0]: Chip to detect (0 = VT1211)
# $_[1]: ISA address
# $_[2]: I2C file handle
# $_[3]: I2C address
sub vt1211_alias_detect
{
  my ($chip,$isa_addr,$file,$i2c_addr) = @_;
  my $i;
  return 0 unless (inb($isa_addr + 0x48) & 0x7f) == $i2c_addr;
  return 0 unless (i2c_smbus_read_byte_data($file,0x48) & 0x7f) == $i2c_addr;
  for ($i = 0x2b; $i <= 0x3d; $i ++) {
    return 0 unless inb($isa_addr + $i) == i2c_smbus_read_byte_data($file,$i);
  }
  return 1;
}


######################
# PCI CHIP DETECTION #
######################

# Returns: undef if not detected, (7) or (9) if detected.
# The address is encoded in PCI space. We could decode it and print it.
# For Linux 2.4 we should probably check for invalid matches (SiS645).
sub sis5595_pci_detect
{
	return unless exists $pci_list{'1039:0008'};
	return (kernel_version_at_least(2, 6, 0) ? 9 : 7);
}

# Returns: undef if not detected, (9) if detected.
# The address is encoded in PCI space. We could decode it and print it.
sub via686a_pci_detect
{
	return unless exists $pci_list{'1106:3057'};
	return 9;
}

# Returns: undef if not detected, (9) if detected.
# The address is encoded in PCI space. We could decode it and print it.
sub via8231_pci_detect
{
	return unless exists $pci_list{'1106:8235'};
	return 9;
}

# Returns: undef if not detected, (9) if detected.
sub k8temp_pci_detect
{
	return unless exists $pci_list{'1022:1103'};
	return 9;
}


################
# 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", $data->{i2c_adap};
      printf "    Busdriver `%s', I2C address 0x%02x", 
             $data->{i2c_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 "    " if  $is_i2c;
      if ($data->{isa_addr}) {
        printf "ISA bus address 0x%04x (Busdriver `i2c-isa')\n", 
               $data->{isa_addr};
      } else {
        print "ISA bus, undetermined address (Busdriver `i2c-isa')\n";
      }
    }
    printf "    Chip `%s' (confidence: %d)\n",
           $data->{chipname},  $data->{conf};
  }
}

# $_[0]: 1 if ISA bus is prefered, 0 for SMBus
# We build here an array adapters, indexed on the number the adapter has
# at this moment (we assume only loaded adapters are interesting at all;
# everything that got scanned also got loaded). Each entry is a reference
# to a hash containing:
#  driver: Name of the adapter driver
#  nr_now: Number of the bus now
#  nr_later: Number of the bus when the modprobes are done (not included if the
#        driver should not be loaded)
# A second array, called 
sub generate_modprobes
{
  my ($prefer_isa) = @_;

  my ($chip,$detection,$nr,$i,@optionlist,@probelist,$driver,$isa,$adap);
  my $bmcsensors = 0;
  my @adapters;
  my $modprobes = "";
  my $configfile = "";

  # These are always needed
  $configfile .= "# I2C module options\n";
  $configfile .= "alias char-major-89 i2c-dev\n";

  # Collect all loaded adapters
  # i2cdetect -l either cats /proc/bus/i2c or scans sysfs for the same information
  open(local *INPUTFILE, "i2cdetect -l |") or die "Couldn't find i2cdetect program!!";
  local $_;
  while (<INPUTFILE>) {
    my ($dev_nr, $type, $adap) = /^i2c-(\d+)\s+(\S+)\s+(.*?) *(\t|$)/;
    next if ($type eq "dummy" || $type eq "isa");
    $adapters[$dev_nr]->{driver} = find_adapter_driver($adap);
    $adapters[$dev_nr]->{adapname} = $adap;
  }
  close INPUTFILE;

  # Collect all adapters used
  $nr = 0;
  $isa = 0;
  $modprobes .= "# I2C adapter drivers\n";
  foreach $chip (@chips_detected) {
    foreach $detection (@{$chip->{detected}}) {
      # If there is more than one bus detected by a driver, they are
      # still all added. So we number them in the correct order
      if (exists $detection->{i2c_driver} and
          not exists $adapters[$detection->{i2c_devnr}]->{nr_later} and 
          not (exists $detection->{isa_addr} and $prefer_isa)) {
         foreach $adap (@adapters) {
           next unless exists $adap->{driver};
           $adap->{nr_later} = $nr++ if $adap->{driver} eq $detection->{i2c_driver};
         }
      }
      if (exists $detection->{isa_addr} and
          not (exists $detection->{i2c_driver} and not $prefer_isa)) {
           $isa=1;
      }
      if ($chip->{driver} eq "bmcsensors") {
           $bmcsensors=1;
      }
    }
  }

  for ($i = 0; $i < $nr; $i++) {
    foreach $adap (@adapters) {
      next unless exists $adap->{nr_later} and $adap->{nr_later} == $i;
      if ($adap->{driver} eq "UNKNOWN") {
        $modprobes .= "# modprobe unknown adapter ".$adap->{adapname}."\n";
      } elsif ($adap->{driver} eq "DISABLED") {
        $modprobes .= "# modprobe disabled adapter ".$adap->{adapname}."\n";
      } elsif ($adap->{driver} eq "to-be-written") {
        $modprobes .= "# no driver available for adapter ".$adap->{adapname}."\n";
      } else {
        $modprobes .= "modprobe $adap->{driver}\n"
          unless $modprobes =~ /modprobe $adap->{driver}\n/;
      }
      last;
    }
  }
  # i2c-isa is loaded automatically (as a dependency) since 2.6.14,
  # and will soon be gone.
  $modprobes .= "modprobe i2c-isa\n" if ($isa && !kernel_version_at_least(2, 6, 18));
  if ($bmcsensors) {
    $modprobes .= "# You must also install and load the IPMI modules\n";
    $modprobes .= "modprobe i2c-ipmi\n";
  }

  # Now determine the chip probe lines
  $modprobes .= "# Chip drivers\n";
  foreach $chip (@chips_detected) {
    next if not @{$chip->{detected}};
    next if $chip->{driver} eq "not-a-sensor";    
    next if $chip->{driver} eq "use-isa-instead";
    if ($chip->{driver} eq "to-be-written") {
      $modprobes .= "# no driver for $chip->{detected}[0]{chipname} yet\n";
    } else {
       # need the * for 2.4 kernels, won't necessarily be an exact match
       open(local *INPUTFILE, "modprobe -l $chip->{driver}\\* 2>/dev/null |");
       local $_;
       my $modulefound = 0;
       while (<INPUTFILE>) {
         if(m@/@) {
           $modulefound = 1;
           last;
         }
       }
       close INPUTFILE;
       #check return value from modprobe in case modprobe -l isn't supported
       if((($? >> 8) == 0) && ! $modulefound) {
         $modprobes .= "# Warning: the required module $chip->{driver} is not currently installed\n".
                       "# on your system. For status of 2.6 kernel ports check\n".
                       "# http://www.lm-sensors.org/wiki/Devices. If driver is built\n".
                       "# into the kernel, or unavailable, comment out the following line.\n";
       }
       $modprobes .= "modprobe $chip->{driver}\n";
    }

    # Handle misdetects
    foreach $detection (@{$chip->{misdetected}}) {
      push @optionlist, $adapters[$detection->{i2c_devnr}]->{nr_later},
                       $detection->{i2c_addr}
           if exists $detection->{i2c_addr} and
              exists $adapters[$detection->{i2c_devnr}]->{nr_later};
      push @optionlist, -1, $detection->{isa_addr}
           if exists $detection->{isa_addr} and $isa;
    }

    # Handle aliases
    foreach $detection (@{$chip->{detected}}) {
      if (exists $detection->{i2c_driver} and 
          exists $detection->{isa_addr} and
          exists $adapters[$detection->{i2c_devnr}]->{nr_later} and
          $isa) {
        if ($prefer_isa) {
          push @optionlist,$adapters[$detection->{i2c_devnr}]->{nr_later},
                           $detection->{i2c_addr};
        } else {
          push @optionlist, -1, $detection->{isa_addr}
        }
      }
    }

    next if not (@probelist or @optionlist);
    $configfile .= "options $chip->{driver}";
    $configfile .= sprintf " ignore=%d,0x%02x",shift @optionlist, 
                                               shift @optionlist
                  if @optionlist;
    $configfile .= sprintf ",%d,0x%02x",shift @optionlist, shift @optionlist
                  while @optionlist;
    $configfile .= sprintf " probe=%d,0x%02x",shift @probelist,
                                              shift @probelist
                  if @probelist;
    $configfile .= sprintf ",%d,0x%02x",shift @probelist, shift @probelist
                  while @probelist;
    $configfile .= "\n";
  }

  return ($modprobes,$configfile);
  
}

sub main
{
  my (@adapters,$res,$did_adapter_detection,$adapter);

  # We won't go very far if not root
  unless ($> == 0) {
    print "You need to be root to run this script.\n";
    exit -1;
  }

  initialize_conf;
  initialize_proc_pci;
  initialize_modules_list;
  initialize_modules_supported;
  initialize_kernel_version;

  print "# sensors-detect revision $revision\n\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";
  print "You need to have i2c and lm_sensors installed before running this\n",
        "program.\n"
    unless kernel_version_at_least(2, 6, 0);
  print "\n";

  print "We can start with probing for (PCI) I2C or SMBus adapters.\n";
  print "Do you want to probe now? (YES/no): ";
  @adapters = adapter_pci_detection 
                        if ($did_adapter_detection = not <STDIN> =~ /\s*[Nn]/);
  print "\n";

  if (not $did_adapter_detection) {
    print "As you skipped adapter detection, we will only scan already loaded\n".
          "adapter modules.\n";
  } else {
    print "We will now try to load each adapter module in turn.\n";
    foreach $adapter (@adapters) {
      next if $adapter eq "DISABLED";
      next if $adapter eq "to-be-tested";
      if (exists($modules_list{$adapter})) {
        print "Module `$adapter' already loaded.\n";
      } else {
        print "Load `$adapter' (say NO if built into your kernel)? (YES/no): ";
        unless (<STDIN> =~ /^\s*[Nn]/) {
          if (system ("modprobe", $adapter)) {
            print "Loading failed... skipping.\n";
          } else {
            print "Module loaded successfully.\n";
          }
        }
      }
    }
  }

  print "If you have undetectable or unsupported adapters, you can have them\n".
        "scanned by manually loading the modules before running this script.\n\n";

  if (!exists($modules_list{"i2c-dev"})
   && !(defined $sysfs_root && -e "$sysfs_root/class/i2c-dev")) {
    print "To continue, we need module `i2c-dev' to be loaded.\n";
    print "If it is built-in into your kernel, you can safely skip this.\n"
      unless kernel_version_at_least(2, 6, 0);
    print "Do you want to load `i2c-dev' now? (YES/no): ";
    if (<STDIN> =~ /^\s*n/i) {
      print "Well, you will know best.\n";
    } elsif (system "modprobe", "i2c-dev") {
      print "Loading failed, expect problems later on.\n";
    } else {
      print "Module loaded successfully.\n";
    }
    print "\n";
  }

  # Before looking for chips, make sure any special case chips are
  # added to the chip_ids list
  chip_special_cases();

  print "We are now going to do the I2C/SMBus adapter probings. Some chips may\n",
        "be double detected; we choose the one with the highest confidence\n",
        "value in that case.\n",
        "If you found that the adapter hung after probing a certain address,\n",
        "you can specify that address to remain unprobed.\n";

  my ($inp,@not_to_scan,$inp2);
  # i2cdetect -l either cats /proc/bus/i2c or scans sysfs for the same information
  open(local *INPUTFILE, "i2cdetect -l |") or die "Couldn't find i2cdetect program!!";
  local $_;
  while (<INPUTFILE>) {
    my ($dev_nr, $type, $adap) = /^i2c-(\d+)\s+(\S+)\s+(.*?) *(\t|$)/;
    next if ($type eq "dummy" || $type eq "isa");
    print "\n";
    print "Next adapter: $adap\n";
    print "Do you want to scan it? (YES/no/selectively): ";
    
    $inp = <STDIN>;
    if ($inp =~ /^\s*[Ss]/) {
      print "Please enter one or more addresses not to scan. Separate them ",
            "with comma's.\n",
            "You can specify a range by using dashes. Addresses may be ",
            "decimal (like 54)\n",
            "or hexadecimal (like 0x33).\n",
            "Addresses: ";
      $inp2 = <STDIN>;
      chop $inp2;
      @not_to_scan = parse_not_to_scan 0,0x7f,$inp2;
    }
    scan_adapter $dev_nr, $adap, find_adapter_driver($adap),
                 \@not_to_scan   unless $inp =~ /^\s*[Nn]/;
  }
  print "\n";

  print "Some chips are also accessible through the ISA I/O ports. We have to\n".
        "write to arbitrary I/O ports to probe them. This is usually safe though.\n".
        "Yes, you do have ISA I/O ports even if you do not have any ISA slots!\n";
  print "Do you want to scan the ISA I/O ports? (YES/no): ";
  unless (<STDIN> =~ /^\s*n/i) {
    initialize_ioports();
    scan_isa_bus();
    close_ioports();
  }
  print "\n";

  print "Some Super I/O chips may also contain sensors. We have to write to\n".
        "standard I/O ports to probe them. This is usually safe.\n";
  print "Do you want to scan for Super I/O sensors? (YES/no): ";
  unless (<STDIN> =~ /^\s*n/i) {
    initialize_ioports();
    scan_superio(0x2e, 0x2f);
    scan_superio(0x4e, 0x4f);
    close_ioports();
  }
  print "\n";

  if(! @chips_detected) {
    print "Sorry, no sensors were detected.\n",
          "Either your sensors are not supported, or they are connected to an\n",
          "I2C or SMBus adapter that is not supported. See doc/FAQ,\n",
          "doc/lm_sensors-FAQ.html or http://www.lm-sensors.org/wiki/FAQ\n",
          "(FAQ #4.24.3) for further information.\n",
          "If you find out what chips are on your board, check\n",
          "http://www.lm-sensors.org/wiki/Devices for driver status.\n";
    exit;
  }

  print "Now follows a summary of the probes I have just done.\n".
        "Just press ENTER to continue: ";
  <STDIN>;

  my ($chip,$data);
  foreach $chip (@chips_detected) {
    next if $chip->{driver} eq "not-a-sensor";
    next if $chip->{driver} eq "use-isa-instead";
    print "\nDriver `$chip->{driver}' ";
    if (@{$chip->{detected}}) {
      if (@{$chip->{misdetected}}) {
        print "(should be inserted but causes problems):\n";
      } else {
        print "(should be inserted):\n";
      }
    } else {
      if (@{$chip->{misdetected}}) {
        print "(may not be inserted):\n";
      } else {
        print "(should not be inserted, but is harmless):\n";
      }
    }
    if (@{$chip->{detected}}) {
      print "  Detects correctly:\n";
      print_chips_report $chip->{detected};
    }
    if (@{$chip->{misdetected}}) {
      print "  Misdetects:\n";
      print_chips_report $chip->{misdetected};
    }

    # People are easily confused
    if ($chip->{driver} eq "eeprom") {
      print "\n  EEPROMs are *NOT* sensors! They are data storage chips commonly\n",
            "  found on memory modules (SPD), in monitors (EDID), or in some\n",
            "  laptops, for example.\n";
    }
  }
  print "\n";

  print "I will now generate the commands needed to load the required modules.\n".
        "Just press ENTER to continue: ";
  <STDIN>;
     
  print "\n";
  my ($modprobes, $configfile) = generate_modprobes 1;	# 1 == prefer ISA
  print "To make the sensors modules behave correctly, add these lines to\n".
        "$modules_conf:\n\n";
  print "#----cut here----\n".
        $configfile.
        "#----cut here----\n\n";

  print "To load everything that is needed, add this to some /etc/rc* file:\n\n";
  print "#----cut here----\n".
        $modprobes.
        "# sleep 2 # optional\n".
        "/usr/local/bin/sensors -s # recommended\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";
  
  my $have_sysconfig = -d '/etc/sysconfig';
  printf "Do you want to \%s /etc/sysconfig/lm_sensors? (\%s): ",
         (-e '/etc/sysconfig/lm_sensors' ? 'overwrite' : 'generate'),
         ($have_sysconfig ? 'YES/no' : 'yes/NO');
  $_ = <STDIN>;
  if (($have_sysconfig and not m/^\s*n/i) or m/^\s*y/i) {
    unless ($have_sysconfig) {
      mkdir '/etc/sysconfig', 0777
        or die "Sorry, can't create /etc/sysconfig ($!)";
    }
    open(local *SYSCONFIG, ">/etc/sysconfig/lm_sensors")
      or die "Sorry, can't create /etc/sysconfig/lm_sensors ($!)";
    print SYSCONFIG <<'EOT';
#    /etc/sysconfig/lm_sensors - Defines modules loaded by
#                                /etc/init.d/lm_sensors
#    Copyright (c) 1998 - 2001  Frodo Looijaard <frodol@dds.nl>
#
#    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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# See also the lm_sensors homepage at:
#     http://www.lm-sensors.org/
#
# This file is used by /etc/init.d/lm_sensors and defines the modules to
# be loaded/unloaded. This file is sourced into /etc/init.d/lm_sensors.
#
# The format of this file is a shell script that simply defines the modules
# in order as normal variables with the special names:
#    MODULE_0, MODULE_1, MODULE_2, etc.
#
# List the modules that are to be loaded for your system
#
EOT
    print SYSCONFIG
      "# Generated by sensors-detect on " . scalar localtime() . "\n";
    my @modules = grep /^modprobe /, split "\n", $modprobes;
    my $i = 0;
    my $sysconfig = "";
    foreach (@modules) {
      s/^modprobe //;
      $sysconfig .= "MODULE_$i=$_\n";
      $i++;
    }
    print SYSCONFIG $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";
  }
}

main;
