#!/usr/bin/perl

#
#    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 .= "$adap->{driver}\n"
          unless $modprobes =~ /$adap->{driver}\n/;
      }
      last;
    }
  }
  # i2c-isa is loaded automatically (as a dependency) since 2.6.14,
  # and will soon be gone.
  $modprobes .= "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 .= "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 .= "$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".
         "/etc/modules:\n\n";
        "$modules_conf:\n\n";
  print "#----cut here----\n".
        $modprobes.
        "#----cut here----\n\n";
   if ($modprobes =~ /i2c-viapro/ && $modprobes =~ /via686a/ && kernel_version_at_least(2,6,0)) {
     print("\nWARNING: Please note that i2c-viapro and via686a may conflict which\n",
           "each other. If you get 'sensors not found', try loading only one module\n",
 	  "at the same time.\n");
   }
   print "\nDo you want to add these lines to /etc/modules automatically? (yes/NO)";
   $_ = <STDIN>;
   if (m/^\s*[Yy]/) {
     open(MODULES, ">>/etc/modules")
       or die "Sorry, can't create /etc/modules ($!)?!?";
       print MODULES
        "\n# Generated by sensors-detect on " . scalar localtime() . "\n";
       print MODULES $modprobes;
       close(MODULES);
  }
}

main;
